Quantcast
Channel: Hacker News
Viewing all 25817 articles
Browse latest View live

Artificial data give the same results as real data

$
0
0

Although data scientists can gain great insights from large data sets — and can ultimately use these insights to tackle major challenges — accomplishing this is much easier said than done. Many such efforts are stymied from the outset, as privacy concerns make it difficult for scientists to access the data they would like to work with.

In a paper presented at the IEEE International Conference on Data Science and Advanced Analytics, members of the Data to AI Lab at the MIT Laboratory for Information and Decision Systems (LIDS) Kalyan Veeramachaneni, principal research scientist in LIDS and the Institute for Data, Systems, and Society (IDSS) and co-authors Neha Patki and Roy Wedge describe a machine learning system that automatically creates synthetic data — with the goal of enabling data science efforts that, due to a lack of access to real data, may have otherwise not left the ground. While the use of authentic data can cause significant privacy concerns, this synthetic data is completely different from that produced by real users — but can still be used to develop and test data science algorithms and models.

“Once we model an entire database, we can sample and recreate a synthetic version of the data that very much looks like the original database, statistically speaking,” says Veeramachaneni. “If the original database has some missing values and some noise in it, we also embed that noise in the synthetic version… In a way, we are using machine learning to enable machine learning.”

The paper describes the Synthetic Data Vault (SDV), a system that builds machine learning models out of real databases in order to create artificial, or synthetic, data. The algorithm, called "recursive conditional parameter aggregation," exploits the hierarchical organization of data common to all databases. For example, it can take a customer-transactions table and form a multivariate model for each customer based on his or her transactions.

This model captures correlations between multiple fields within those transactions — for example, the purchase amount and type, the time at which the transaction took place, and so on. After the algorithm has modeled and assembled parameters for each customer, it can then form a multivariate model of the these parameters themselves, and recursively model the entire database. Once a model is learned, it can synthesize an entire database, filled with artificial data.

Outcome and impact

After building the SDV, the team used it to generate synthetic data for five different publicly available datasets. They then hired 39 freelance data scientists, working in four groups, to develop predictive models as part of a crowd-sourced experiment. The question they wanted to answer was: “Is there any difference between the work of data scientists given synthesized data, and those with access to real data?" To test this, one group was given the original data sets, while the other three were given the synthetic versions. Each group used their data to solve a predictive modeling problem, eventually conducting 15 tests across 5 datasets. In the end, when their solutions were compared, those generated by the group using real data and those generated by the groups using synthetic data displayed no significant performance difference in 11 out of the 15 tests (70 percent of the time). 

These results suggest that synthetic data can successfully replace real data in software writing and testing — meaning that data scientists can use it to overcome a massive barrier to entry. “Using synthetic data gets rid of the ‘privacy bottleneck’ — so work can get started,” says Veeramachaneni.

This has implications for data science across a spectrum of industries. Besides enabling work to begin, synthetic data will allow data scientists to continue ongoing work without involving real, potentially sensitive data.

“Companies can now take their data warehouses or databases and create synthetic versions of them,” says Veeramachaneni. “So they can circumvent the problems currently faced by companies like Uber, and enable their data scientists to continue to design and test approaches without breaching the privacy of the real people — including their friends and family — who are using their services.”

In addition, the machine-learning model from Veeramachaneni and his team can be easily scaled to create very small or very large synthetic data sets, facilitating rapid development cycles or stress tests for big data systems. Artificial data is also a valuable tool for educating students — although real data is often too sensitive for them to work with, synthetic data can be effectively used in its place. This innovation can allow the next generation of data scientists to enjoy all the benefits of big data, without any of the liabilities.

The project was funded, in part, by Accenture and the National Science Foundation.


Brass and lead toxicity in midrange espresso machines

$
0
0
AuthorMessages
Chaff
Senior Member


Joined: 5 Feb 2006
Posts: 4
Location: nyc
Expertise: I love coffee
Posted Mon Feb 6, 2006, 5:00pm
Subject: Brass and lead toxicity
 

back to top
itinerant
Senior Member


Joined: 1 Sep 2005
Posts: 901
Location: Vancouver
Expertise: I live coffee
Posted Mon Feb 6, 2006, 6:14pm
Subject: Re: Brass and lead toxicity
 

back to top
Kristi
Senior Member
Kristi
Joined: 6 Oct 2005
Posts: 2,286
Location: Boston
Expertise: I love coffee

Espresso: Gaggia Evo w silvia wand
Grinder: Macap M4 mod to d&s-less
Roaster: (Jeff at Redbirdcoffee)

Posted Mon Feb 6, 2006, 6:49pm
Subject: Re: Brass and lead toxicity
 

back to top
Chaff
Senior Member


Joined: 5 Feb 2006
Posts: 4
Location: nyc
Expertise: I love coffee
Posted Mon Feb 6, 2006, 8:55pm
Subject: Re: Brass and lead toxicity
 

back to top
itinerant
Senior Member


Joined: 1 Sep 2005
Posts: 901
Location: Vancouver
Expertise: I live coffee
Posted Mon Feb 6, 2006, 10:17pm
Subject: Re: Brass and lead toxicity
 

back to top
ItalianWay
Senior Member
ItalianWay
Joined: 18 Jan 2006
Posts: 151
Location: Saudi Arabia
Expertise: I like coffee

Espresso: GAGGIA Baby, ISOMAC...
Grinder: GAGGIA MDF
Vac Pot: None
Drip: None
Roaster: Gene Cafe

Posted Mon Feb 6, 2006, 10:33pm
Subject: Re: Brass and lead toxicity
 

back to top
super_pasty_white_guy
Senior Member


Joined: 3 Jan 2006
Posts: 16
Location: Washington, DC
Expertise: I live coffee

Espresso: Gaggia Coffee w/ PID
Grinder: Iberital
Vac Pot: bodum santos
Drip: Vietnamese Coffee Maker
Roaster: ancient poppery

Posted Tue Feb 7, 2006, 7:35am
Subject: Re: Brass and lead toxicity
 

back to top
Chaff
Senior Member


Joined: 5 Feb 2006
Posts: 4
Location: nyc
Expertise: I love coffee
Posted Tue Feb 7, 2006, 8:06am
Subject: Re: Brass and lead toxicity
 

back to top
heart_throb_rob
Senior Member
heart_throb_rob
Joined: 3 Mar 2017
Posts: 4
Location: San Diego
Expertise: I love coffee

Grinder: Rancilio Rocky doserless

Posted Fri Mar 3, 2017, 5:41pm
Subject: Re: Brass and lead toxicity
 

back to top
u2
Junior Member


Joined: 27 Jan 2018
Posts: 1
Location: Texas
Expertise: I love coffee
Posted Sun Jan 28, 2018, 3:03pm
Subject: Re: Brass and lead toxicity
 

back to top
Forum Rules:
No profanity, illegal acts or personal attacks will be tolerated in these discussion boards.
No commercial posting of any nature will be tolerated; only private sales by private individuals, in the "Buy and Sell" forum.
No SEO style postings will be tolerated. SEO related posts will result in immediate ban from CoffeeGeek.
No cross posting allowed - do not post your topic to more than one forum, nor repost a topic to the same forum.
Who Can Read The Forum? Anyone can read posts in these discussion boards.
Who Can Post New Topics? Any registered CoffeeGeek member can post new topics.
Who Can Post Replies? Any registered CoffeeGeek member can post replies.
Can Photos be posted? Anyone can post photos in their new topics or replies.
Who can change or delete posts? Any CoffeeGeek member can edit their own posts. Only moderators can delete posts.
Probationary Period: If you are a new signup for CoffeeGeek, you cannot promote, endorse, criticise or otherwise post an unsolicited endorsement for any company, product or service in your first five postings.

Issues of “Counterspy” Are Now Online

$
0
0
Photo: Philip Agee, one of CounterSpy’s most famous contributors. (AP)

Long before Wikileaks was promoting “radical transparency” in the digital age, CounterSpy was publishing a magazine that named CIA station chiefs and exposed covert operations. Now, 23 issues from its 32-issue run have been pulled from the CIA’s own archives and digitized for your perusal. We can neither confirm nor deny that the agency is happy about this.

CounterSpy started publishing from its headquarters in Washington, DC in 1973. Its staff and contributors were made up of journalists and former intelligence agents who wanted to expose the CIA and other intelligence apparatuses as corrupt organizations. It ran stories about subjects like the CIA’s efforts to undermine labor movements around the world and psychological warfare conducted under COINTELPRO. The spooks at Langley weren’t fans but were certainly readers.

The article that made CounterSpy infamous ran in the Winter 1975 issue. “Chiefs of Station: Who They Are & What They Do”advocated for revealing the names and assignments of CIA station chiefs. It also named about 100 chiefs in the process, including Richard Welch, who was later murdered by Marxist terrorists in Athens in December of 1975. The article became a touch point for CIA officials to criticize Philip Agee, a former agent who wrote another piece in that same issue.

Both Agee and Counterspy were blamed for Welch’s death, despite the fact that Agee didn’t write the article and Welch was already exposed as an agent in 1968. Long after he stepped down as director of the CIA, George H.W. Bush continued to hold Agee responsible for Welch’s murder. Barbara Bush did the same in her 1994 memoir—until Agee sued her for libel. The claim was removed from later printings of the book. More recently, current CIA Director Mike Pompeo repeated this account while denouncing WikiLeaks and comparing their practices to Agee’s. In the process, Pompeo incorrectly claimed that Welch was named by CounterSpy as the CIA’s Athens station chief (the magazine listed him as Lima station chief).

CounterSpy shut down in 1984 following infighting and a staff exodus. Before it collapsed, Agee took some of the CounterSpy team and started CovertAction Information Bulletin, which published in one form or another until 1985. In the first issue, Agee attributed the split, in part, to pressures brought on by CIA harassment of CounterSpy staff.

It’s almost impossible to find old issues of CounterSpy, but the CIA likes to collect publications that are critical of its activities. And thanks to a series of executive orders modifying how classified information is handled, the agency is also required to release non-exempt historically valuable records after 25 years. Try searching the CIA’s CREST archive for CounterSpy and you’ll find about 20,000 items. Dr. Susan Maret, an academic and intelligence researcher, combed through that mess for everyone and found 23 issues which she provided to AltGov2. They’ve all been digitized for searchability and are almost entirely intact, although that most infamous issue from 1975 has been heavily redacted. Check them out here.

[AltGov2]

Linux Raw Sockets

$
0
0
Linux Raw Sockets

2018-03-19

Recently I did a userspace implementation of theHost Identity Protokoll (HIPv2, RFC 7401) with the upcomingDiet Exchange (HIP DEX, IETF draft 6). Doing so, I've learn a lot about raw socktet programing under Linux and here I want to share a few things with you.

So, I assume you have already worked with network sockets before – if not, don't fear, it's not that hard and there are plenty of nice introductions out there. I can for example recommend Beej's Guide to Network Programming. For this article I'll start with a normal UDP/TCP based socket and work my way down the layers. So we open a traditional socket by:

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

This will open a UDP based datagram socket via IPv4. The first argument ofsocket() specifies the domain of your socket in our case that's Internet Protokoll. Sometimes you will see here AF… and sometimes PF…, this doesn't matter, they are the same. While PF stands for protocol family, AF is short for address family. Historically it was thought that in the future there might be multiple protocol families sharing the same address family – but this never happend. So the correct way would be to use PF_INET in the socket call andAF_INET in your struct sockaddr_in, but most people nowadays use the address family everywhere. With the second argument type we specify if we want to use a connection-based protocol like TCP (SOCK_STREAM) or a protocol without connections like UDP (SOCK_DGRAM). The third argument protocol specifies which protocol we actually want to use – we could set UDP or TCP here (IPPROTO_UDP, IPPROTO_TCP) but setting 0 works too: this sets the protocol to the default protocol for the combination of the domain and type field – for AF_INET and SOCK_DGRAM the default is UDP and for SOCK_STREAM it's TCP. You might also see IPPROTO_IP as protocol which is simply by definition 0. But the above variant seems to be the most common one.

But hey, we have the year 2018 – why the heck should be limit us to IPv4? Luckily it's easy enough to support IPv6: just replace AF_INET by AF_INET6 and it will work with both IPv4 and IPv6! So don't you dare to ever use AF_INET anymore without a good excuse. By the way: if you want IPv6 only you can set the socket option IPV6_V6ONLY.

But we don't want to talk about ordinary TCP/UDP sockets here! So lets dig down in the mysterious world of raw sockets.

The first thing I want to note is: you'll need super user rights for creating a raw socket or more precisely the CAP_NET_RAWcapability otherwise you'll get the error ”Operation not permitted.” (EPERM).

sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
sockfd = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP);

The first kind of Raw-Socket we look at is what you get by setting type toSOCK_RAW but still set protocol to TCP or UDP. You will still only receive the type of packet specified (here UDP), but this time you will not only receive the data but also the layer 4 (TCP/UDP) header and you're also responsible to set the layer 4 header yourself.

Contrary to above, here the choice of domain does matter a lot. First of all here AF_INET6 will only receive IPv6 and not both! Second what you get if you read from the socket differs: if you read from the first variant with AF_INET you will get the IPv4 header, the UDP/TCP header and the data; in the second variant your read will instead result in only the UDP/TCP header and data but not the IPv6-Header!

The third important difference between AF_INET and AF_INET6 for raw sockets is the endianness: unlike IPv4 raw sockets, all data sent via IPv6 raw sockets must be in the network byte order and all data received via raw sockets will be in the network byte order.

If you want to send something through the socket, your packet has to include the Layer 4-Header but not the IP-Header. (Note: this is unspecified in POSIX, but I focus on Linux here.) So but what if we want to change something in the IP-Header? For IPv4 there are two options: you can set the desired field(s) via calls to setsockopt or if you want to do the full header on your own, you can use the socket option IP_HDRINCL to tell that you will construct the header and write both header and payload to the socket:

sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
int on = 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

Even if you use this you won't have to deal with Source Address and Packet ID – the kernel will fill them in for you if you leave them all zero. The fields for the IP checksum and the length field will be set by the kernel if you want or not.

What's important here: IPv6 doesn't have IP_HDRINCL or a direct equivalent, as per RFC 3542 section 3. You can, however, also set various parameters viasetsocketopt. Alternatively the IPv6 advanced socket API employs another framework called “ancillary data”. For outgoing packages one can set the majority of the fields in the header as well as supported header extensions via ancillary data and for received packages the majority of the fields and header extensions can be read with the same framework. A description of ancillary data is out of the scope of this article but the basic idea is you specify which values you want to set via a call of setsockopt then you write the value for the header fields and the actual data into a struct msghdr and send this viasendmsg().

If you want to send data with a transport protocol which has no user interface you can set the protocol field to raw too:

sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

This will automatically set IP_HDRINCL and allow you to send your data with arbitrary layer 4 protocols. Most commons use: sending ICMP packets. Receiving of data is however not possible with this type of socket!

So far we got full control over layer 4 and partial control over layer 3. It's time to step down one further level into the dungeon.

sockfd = socket(AF_PACKET, SOCK_DGRAM, htons(ETHERTYPE_IPV6));

This is called a packet socket, it allows you to receive and send raw packets at the device driver level (layer 2). In the above version we used the protocol to specify that we only want to receive IPv6 packets. We can drop this requirement to receive all packets no matter if it's IPv4, IPv6 or something else:

sockfd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));

By default, a packet socket will receive all packets matching the protocol. You can use bind() to bind the packet socket to an interface.

The field type set to SOCK_DGRAM results in the cooked mode: when reading from the socket you will read the packet without MAC-header but you can get the MAC-addresses comfortable by using recvfrom() and likewise you can use thesendto() to specify the destination by the sockaddr_ll struct. Alternatively we can set type to SOCK_RAW:

sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

This is the lowest we can get: this way ethernet frames are passed from the device driver without any changes to your application, including the full level 2 header. Likewise, when writing to the socket the user-supplied buffer hast to contain all the headers of layer 2 to 4.

This is the deepest we can go in userspace – at this point we have full control of the complete ethernet frame. I hope you enjoyed our journey into the rabbit hole.


Sources and further readings:

  • Beej's Guide to Network Programming
  • socket(7)
  • raw(7)
  • packet(7)
  • sendto(2), recvfrom(2)
  • UNIX Network Programming, Volume 1 by W. Richard Stevens
  • IPv6 Core Protocols Implementation by Qing Li Tatuya Jinmei Keiichi Shima
  • IPv6 Socket API Extensions: Programmer's Guide by Qing Li Tatuya Jinmei Keiichi Shima
  • Linux Kernel source code

linuxcnetwork

Saas CTO Security Checklist

$
0
0

This is built-in if you are using a cloud service and all your machines are registered or spawned through it. Otherwise, you will need to create and maintain a list of your assets (servers, network devices, etc.). Review it on a regular basis to determine if you still need them, keep them up to date, and ensure that they benefit from your latest deployments.

“Pinterest needs to be removed from Google IMO”

$
0
0

Even after logging into Pinterest, the image does not show. You have to search for it again in Pinterest. So, I'm with you.

I know someone that works for pinterest. Its 100% the thing that gets complained about the most, and theyre aware of it. Its a tactic to get more people to sign up for the site or download the app

Edit: please stop telling me to tell him this feature sucks, this information came up from telling him its an annoying feature

My frustration with them sabotaging my Google searches is the main reason why I have no more interest whatsoever to sign up for Pinterest.

Yeah – I like will literally never use Pinterest. If we enter in age of humanity where everybody uses Pinterest all the time, I’m going to be one of those weird people that never adopts it, ever.

But it's not just Pinterest now. Since Google removed the 'view image' button many sites you visit now have said image somewhere but you don't even know what resolution. Google is ass.

Google had to remove the view image button as a result of a legal dispute with Getty Images.

Edit: Source: https://arstechnica.com/gadgets/2018/02/internet-rages-after-google-removes-view-image-button-bowing-to-getty/

They should've just removed Getty from the results instead.

They can't because getty image are used on many different sites. It's impossible to match every single one back to getty.

Removing Getty would be removing like 95% of images.

Is there any add-on that brings it back?

Edit: for all the smart-asses, no, I can't right click and download it because that would only give you a shitty resolution thumbnail compared to the original view image button

Here's the extension without having to go through a shitty blog article first:https://chrome.google.com/webstore/detail/view-image/jpcmhcelnjdmblfmjabdeclccemkghjk

Thank you, this will help me immensely.

I use imagus, it's a neat extension. Just hover over images/webm/gifs and it they enlarge.

It's a life changer, you won't even have to click anything on reddit anymore, just put your cursor on the thumbnail.

I am a fan of https://duckduckgo.com/

It is pretty good for images.

Right click -> Copy Image Address

Works for me.

Or just Right Click -> Open Image in New Tab.

When I do this it only opens the image in the previews resolution instead of the full source resolution. Quite frustrating

That's a good question.

Edit: Why yes there is, on desktop at least.

https://mashable.com/2018/02/19/google-view-image-extension/

Got one for Firefox?

That's dumb because Getty watermarks everything. I never bothered with their images because of that. Visiting sites I don't want to now for images I probably won't want anyway for one reason or another.

Someone else will just do it better, having learned from Google's mistake here.

Someone else will just do it better, having learned from Google's mistake here.

I kind of doubt it.

Google image search has always been aggressively toeing the line of what's acceptable from a copyright perspective. Honestly I'm kind of surprised it still exists at all in its current form.

The main reason they are allowed to display images they don't own on their site in the first place is that courts have found thumbnails to be "sufficiently transformative" to qualify as fair use.

Honestly I'm kind of surprised the "enlarge" feature when you click on the image hasn't been legally challenged either. I suspect that a proper lawsuit by a major organization could take out that functionality too.

This is one of those situations where everyone just assumes what Google is doing is normal and fine because they're used to in and like the functionality. But that doesn't necessarily line up with the law.

I honestly don't understand the courts here and the stupidity of law making. If the image is available on a certain page without any access control then it is meant to be viewed. If someone doesn't like the image to be viewed then he has to put it behind a access control.

If I don't want Google to index my content, I tell this using robots.txt or I implement a access restriction.

People are just stupid as fuck... Wait no they're greedy as fuck and that's sick and destroys the whole idea of freely available knowledge. IMO

I think the idea is that if you can download it straight from google search, you don't actually go to the page that is hosting it. From a basic perspective, it's like providing access to somebody else's product (the image) without "paying" for it (with pageviews + user data).

Google's Search Liaison, Danny Sullivan, announced the change on Twitter yesterday, saying it would "help connect users and useful websites." Later Sullivan admitted that "these changes came about in part due to our settlement with Getty Images this week" and that "they are designed to strike a balance between serving user needs and publisher concerns, both stakeholders we value."

OMFG who would believe that is would help connect users and useful websites? It removes functionality, and it purely removes functionality used by people who wanted to look at that one particular image!

90% of the time when you click "view site", it presents a page that doesn't even have the image due to the way dynamic pages work.

Now if Google links me to a page that doesn't even have the image, or it's really hard to find, then I'm not likely to visit that site again.

Google should show Getty images last.

they could have just removed getty images from the results and saved the world a lot of hassle.

Or the picture just isn't there, just like half the words I include in my search, and I'm left to wonder why the hell that result came up if it didn't have all the words.

The minute I realised they made that changed I instantly checked for an add-on that brought it back.

Lo and behold, the internet does not disappoint.

I would LOOOOOOOOVE a chrome extension that filters out anything related to pinterest.

right now I just type "-pinterest" when i search from something and get a ton of pinterest results.

Here's a Greasemonkey script for blocking sites from google results: https://greasyfork.org/en/scripts/1682-google-hit-hider-by-domain-search-filter-block-sites

No interest in Pinterest

This reminds me of the TV commercial dilemma, where most stations increase the volume of the commercials with the simple idea that you’ll hear it... more? All it does is make me irritated and press mute. I always thought if they lowered the volume on commercials, everyone would be interested as to why their TV’s just got quieter, and would actually be forced to look at their tv and listen to the sound.

We stopped watching anything with commercials years ago and this was a big part of the reason. We'd buy the boxed set dvds of a show if it wasn't stupidly priced or just not watch the show.

When streaming became reasonably useful we started using that, but still with no commercials. We'd pay for Amazon Prime and watch things there, then added Netflix. We don't watch on Amazon anymore since they added commercials. The Amazon ones are at least for other shows and we did give it a try, but they are still louder than the volume of what we are trying to watch and the shows in the ads are always completely unrelated to the types of things we would be interested in watching.

We've been moving back to DVD/BR sets for many things so we don't have to worry about making it through a series before it gets removed from the site or having our ISP decide we only need usable internet some days.

I would've used it. I follow tons of design tumblr blogs where people aggregate images they liked within a common theme. But Pinterest has managed to annoy me so much that I would never use it in my life. As far as I can tell having never signed up, it's a piece of crap worthless website, that might as well be the most frustrating thing on the internet. I honestly hate it with a passion.

It's not just that Pinterest is spamming google searches, and that you have to search again once you get to Pinterest (assuming you have an account and are logged in). It's also the fact that the majority of Pins are BROKEN- they don't lead back to the original source. (Whether because the pins are old, content has been moved, etc.) So as opposed to a search engine like Google that continuously updates its index, the Pinterest model will only lead to more and more broken links as time goes by. It is a broken model, and incredibly frustrating.

Speaking of the original source, I've seen people and even had friends who's art and other creative works have been reposted to Pinterest without credit

I mean...artists are never respected anyways. It kind of sucks. But I know a lot people who do art commissions and Holy shit people don't pay at all. Oh its just art lol lol lol

exposure

God... The amount of times I see that as an excuse to not pay when it's a fucking commission.

you know what's worse? When someone grabs your shit, puts it on pinterest, then a blogger gets your shit from pinterest and puts it in their blog. Then someone else grabs it from the blog, puts it in pinterest, and credits the blogger with your work. Now, it's in pinterest, with high credibility as someone else's work and you're the thief.

It is probably broken on purpose, as you'll click around a few times before you give up.

I 100% boycott Pinterest specifically because of this kind of BS and actively try to dissuade family/friends from using the site as well. There's not much I can do as a standalone user, and your post confirms that they know about this and don't care, so I'll just keep on making my little stand.

I used to have an active Pinterest account but now I never click on their links or log in because of their tactics.

You know if I could actually view the image I came to pinterest to view, I might actually stay on the site..

I can never find a god damned THING on pinterest. I fucking hate them and everything about their entire design and layout.

Its a tactic to get more people to sign up for the site or download the app

* pretends to be shocked *

This is why I never sign up for Pinterest.

Jokes on them I fucking hate that bullshit so much I'd rather just get an addon and never visit their site ever again.

I might have considered getting an account if their shit wasn't locked off behind walls. Oh, I have to give you my information before I can see how awesome your website is? It must be so fucking groundbreaking. /s

I signed up once to view an image, then deleted it after and never went back since

they won't let me delete my account.

Worked on me, until I started getting alerts that my account was getting hacked about a week after signing up. Deleted my account. Fuck pinterest.

Yeah we know that’s what it is. That’s why it pisses us off

And even if you do find it, half the time it’s just an image. Doesn’t lead to anything about the image. No other information. It’s really frustrating.

I started adding "-pinterest" to my searches when they became polluted.

-site:https://www.pinterest.com is better. Yours would remove links that link to pinterest.

You can simply do -site:pinterest.com (just the domain is needed). One thing that sucks about this is that you'll still get Pinterest from UK and other countries.

Try -site:pinterest.*

I absolutely hate them. They've ruined searching for several categories, particularly when trying to get ideas for house design.

For me, it's worksheets for students. Wtf am I going to do with this 150x240 thumbnail? Is this a school for ants?!

Hadn't thought about it much but basically if you have a hobby then they've ruined it. Heck in your case not even a hobby just your job.

Hijacking the top comment to get this seen. I bypass pintrests login bullshit with Tampermonkey it runs scripts that stop website scripts from doing all kinds of things, like asking you to log in/create an account to view their services. This also works on a fair number of media/news outlets.

I just hate when I look for “how to ___” and pinterest shows up with something completely unrelated and useless to what I’m looking for

I'd probably let it slide occasionally if it actually identified the product and where to buy it. Currently it only serves to derail a quick search

Yes, I gave up and created Pinterest account to proceed with google search results, but it didn't help at all. Pinterest is just scam.

This extension can block domains from Google search and it's made by Google. https://chrome.google.com/webstore/detail/personal-blocklist-by-goo/nolijncfnkgaikbjbdaogikpmpbdcdef?hl=en

Oh god, thank you!

(Not by Google) hmmm....

Google's not the only source of good software.

Source code is linked from the page, it is just a port of the Google one minus the phone-home part that reports to Google which domains you blocked.

Doesn't seem to work for google image search.

Yes, I've been using this for years! Pinterest doesn't even show in my google searches.

You can just add "-domain.com" to the query

It used to be great, but it's been broken for months now, lots of people don't have the ability to block domains from the google search page any longer - look at the reviews. You have to visit the offending domain you want blocked, then use the block extension, which is really annoying.

Also, it never worked for Google Image Search, which is also highly annoying.

I still use it, it's just a PITA to use now.

ya doesn't work with google images for some reason.

Doesn't work nearly as well as I would hope

Doesn't work for Google Images which is where most of Pinterest's abuse occurs.

Damn they did Matt Cutts dirty

Everybody report them as "deceptive search results"

Every time.

How do you do that?

No time to explain, just do.

Grab a cactus...

Didn't realize my physics professor was a Reddit user.

The send feedback button.

I mean, ok, I guess you had to search for SOME image to show that, just not something I would expect for demonstration purposes.

Obviously he is that kid and is trying to get Google to deindex that image for him.

Where the fuck does Pinterest get off putting up a signup wall in the first place? Its a content aggregator, its not even their content to protect.

Even without the wall, Pinterest is NEVER a valuable search result. Its just a slew of images that miiiight be relevant, but usually not. Hardly any text, nevermind useful text.

Agreed. I used to use Pinterest for recipes but now most recipe search results don't even link to a recipe. I think people are gaming Pinterest to drive traffic to their site which is usually not related to the image on Pinterest. As a result I never use Pinterest anymore... It's just garbage now.

I'd been using them ever since ~2011 when they first started up. I stopped using it immediately when they started the sign-in prompts. Not to mention the poor designing where you're never actually taken to the specific pin you're looking for on a page.

Why in the everloving fuck did they do that?? It's an asshole design and surely public opinion has suffered? Make your site good enough that people WANT to sign up, don't shove it down their throats when they're just browsing. I always make a fake account whenever I need to log in anyway, so it's not like they're collecting useful data.

Who the fuck even uses Pinterest anymore

White girls in the suburbs. me

Yes!!! Now when I look for an image and see the word Pinterest, I skip over it. I wish I could filter it out completely. I'm not signing up for Pinterest and it makes me hate that brand even more.

You CAN filter it: -site:Pinterest.com

I constantly and searching images and would be annoyed to have to type that out every time on my phone. I wish it was just a simple filter or something.

You can also write "-pinterest" which is slightly shorter and easier to remember

Except that also filters out any site that mentions pinterest which may not be what you want because those sites may have useful results.

Aah true. Makes sense that it would remove mentions as well

yup, bin it.

i will never sign up for that pos

Pinterest is a fucking dumpster fire of a website.

I've had people show me a dessert on Pinterest and ask if I could make it. I just look at the picture and guess what's in it, I'll be damned if I sign up for that people trap.

That's because its not its intended use. That site is magic for collecting ideas for renovations, styles, colors, web design etc... "I want something that looks like theses, I like these colors" It wont tell you how to achieve these things.

even if you sign4d up, it would just be a fuckign picutre of the dessert with no other ingformation because pinterest is pure fucking trash

I'm someone who actively likes and uses pinterest for the work I do (it is a surprisingly good resource for tutorials and references for digital 3D art and animation) as well as collecting general inspiration material for said work.

I absolutely hate what it's done to google, though. I wish there was an alternative, but part of pinterest being useful for me is how much stuff is on there...

I use tumblr for that. Most people I follow are just reposting images from other tumblrs and you can follow the link to the original poster discovering even more awesome blogs. All of this with nearly no text or fluff (I use an rss reader). What's different about pinterest?

Wait so you make desserts? That's neat. I was wondering if you could make this for me.

If you want to exclude a website from the search, just add -site:pinterest.com. As you can see, there are no more results from pinterest.com

Edit: as others have replied, -pinterest might yield better results, although it will also remove any website wmwhere it finds the word Pinterest, which might be a bad idea.

Also gold ?! Thank you !

I do this. But is there anyway to remove Pinterest from all searches without having to type this out in every search? Since Google discontinued the blocked website feature.

I think there's a chrome add-on for it

For the paranoid (since this extension has no website / source to examine) I just installed this this morning and browsed the source locally. It is not obfuscated, doesn't appear to do anything shady, and does seem to actually have been written by a googler named Manuel Holtz.

It does send the domains that you have blocked back to google which they mention on the install page.

Good. Then they can see how many of us explicitly block Pinterest.

It's such a shame too because a lot of the things I search for that have Pinterest results pop up seem to be interesting. But I just refuse to use that site so those results just get ignored.

Ye I seen that, but I use my phone 99% of the time.

Text replacement shortcut

elaborate please

When you type a certain piece of text on your phone, it gets replaced with something else. For example when I type @@ it gets replaced by my email address. You could do the same thing for the - site:Pinterest

It’s in the keyboard settings on iPhone, not sure on Android where.

For funsies you could also do something like “a+s+d+f”= ¯_(ツ)/¯ (or ¯\(ツ)_/¯ specifically for Reddit) in case you’ve ever wondered how people type special characters so quickly.

That guy from the war lizard gaming forums taught me this, sort of

I love you for this.

iOS: Settings/General/Keyboards/Text Replacement

I'm an android user. :(

Personal dictionary allows you to add shortcuts for words.

If you use Android, FireFox for Android allows you to use real extensions. It is a real browser. So if that extension exists for FireFox you can probably use it on mobile. Sadly FireFox for iOS is not a real browser. It is just a Safari wrapper because that is all Apple allows, because they fear competition.

I noticed that the other day, that there was no longer a 'block this website' line in the search results. When did this occur? I can't find anything about it in a search.

Years ago. They quietly dropped the feature.

Wouldn't you still see results from the other pinterest domains? Like pinterest.fr or pinterest.ca, for instance?

Instead of what they recommended, you should do

-site:pinterest.*

Yep. Just tested it and still got a bunch of Pinterest crap from other domains.

when you do ~20-100 searches a day, that'd get tiring. Pinterest just needs to go away like myspace

Not completely true- you WILL still get results from pinterest's country domains like pinterest.co.uk Extremely annoying and difficult to add every country domain to that site exclusion filter.

Just append -pinterest.*

But that would presumably cover pinterest.example.com

I so agree. I stopped using google for my primary image search engine due to this. Pinterest just clutters it up and I can't find the page I want whether I have an account or not.

Have you noticed how they've redone image searches now too? Half the time it gives me links for YouTube videos.

And searching for videos just gives you articles.

Not to mention the image search being recently jacked so we are unable to go directly to the image page.

And the tendency for google searches to turn up thousands of useless pages of machine generated non-sensical text with any and every pseudo-related marketable product placed in those pages trying to make it look like a blog by a real person.

Now even exact phrase search is broken. Using quotes has virtually no effect anymore.

I even rekindled an old support topic bc google didn't address a single thing about https://support.google.com/websearch/forum/AAAAgtjJeM4iM87-bhd6-Y/?hl=en

Bing and DuckDuckGo both still have the view source image option, thankfully!

Duckduckgo is extremely slow at loading the image thumbnails and I have to click "show more" every few pictures. After I click it five times or so, it's gone. Is there something broken on my end or is it the duckduckgo method?

I despise Google's search since long but duckduckgo has even worse comfort.

There's an open-source extension to re-enable the direct image link button: https://github.com/devunt/make-gis-great-again

I keep hitting "send feedback" and asking why they even bother having a search text field if they completely ignore anything I type in it. I could mash my face against the keyboard and get the same mix of irrelevant spam sites. It's like they're intentionally trying to kill their search site, which may actually be the case.

I don't expect them to ever fix their junk, but hopefully my feedback is annoying to whatever algorithm sorts those things into fine gradients of subject before they're automatically discarded.

Yeah, I feel like it's someone's job to answer these support topics so we could just keep bugging him, since it's obvious they're not trying to fix the problems, so it'll go on for-ev-er

[removed]

looking into doing the same, do you use anything else than bing ?

Bing image and video search is very good for porn. I ain't even joking

The best by far.

Anyone who says otherwise hasn't tried it IMO.

I think they’re aware that that is all it’s used for.

I had this video I used to jerk it to on a DVD when I was like 13. I’d been searching for it for literally 15 years online, hoping to relive that classic wank.

Anyway, I had been looking forever and never could find it. Then a year or two ago I read on Reddit about how great Bing is for porn.

Got on Bing and found the video within 10 minutes. Every bit as good as I remembered. What a night.

link pls

What other engine do you use instead? Bing? I've been thinking about flipping the finger to Google because the new image searching is bullshit.

Use duckduckgo. Its clean and gives you decent results.

I use it daily. Can recomend. I can't belive google hasn't stolen !bangs yet

It's seriously awful.

Bing still has the view source image button so sometimes I'll go through there, but DuckDuckGo has my favourite one so far.

Not just that, when you do sign up it takes you away from the page so you don't see what you want anyway.

Every Google search I do starts with site-pinterest.com

and then the UK version started showing up, then the Chinese version, then the...

Change com to *

Cool, appreciate that.

I agree. Its nothing more than a spam-site.

Seriously though, is there a possibility this will be done?

I'm so fucking sick of that site, I just want it to die. They can fix their shit and make it the best image upload/search site ever, I still won't use it. I really have never been this mad with a website before. Not even when I got like 50 ads while trying to watch anime on my ipad. Pinterest needs to fucking die!

I was told that in order to hide my virtual footprint (compromising pictures on Facebook or whatever you may have out there) the easiest and cheapest solution was to create a Pinterest account and start pinning shit.

So... Makes sense.

I don't actually have fb pictures on my Google search for some reason, even for the handful of photos I put as "public"--I used to, but it doesn't show up anymore. I've noticed this when searching for people too, just the profile pic shows up.

But yeah, I used to have a pinterest like 7 years ago and was a prolific pinner, so I'd say a good 80% of stuff that comes up with my name is random pins.

That's not even a little bit true. That will in no way stop someone even remotely talented at using the internet for information gathering.

I never claimed that this would stop someone remotely talented using the internet for information gathering... It's just a cheap and simple alternative. I was given this advise in a seminar in lawschool as they prepared us to deal with potential employers so... I'm assuming this works better with your boss not finding that Facebook picture of you drunk and shirtless, rather than some sort of meticulous background check.

I hate Pinterest. They locked me out of my account because of inactivity and they wont let me log in again. I tried to contact support but no help.

Hopefully Google will sort this out.

Pinterest needs to be removed from the internet IMO.

Tattoo artist here! Clients come to me with Pinterest images every day and tell me the keywords to find it etc. Unfortunately for them I refuse to sign up for yet another bloody website so Pinterest can go fuck itself.

If it was a back tattoo, I'd be tempted to stick them with this.

and tell me the keywords to find it etc.

Oh yeah, don't bring it in on a flash drive or print it out or anything. Gotta love people.

And that's why they all leave with tattoos of Pintrest's sign-up page.

Almost like they want to make your work harder. They should just give you the damn image they want on their skin.

Former wedding photographer here. "i want this image I saw on pinterest" was quite common. Quite annoying, too.

Preach.

It's gotten to where Pinterest is essentially scraping the entire internet and becoming an intermediary between Google and the content Google shows in search results. If anyone else did that, Google would adjust their algorithm to exclude it. WTF.

It's seriously devaluing Google itself. Why do they allow it?

isn't Quora similar in someways? hide some of their answers

I think now you can view answers from search results.

Quora is a shit pile these days and ohh if you use an adblocker they block the answers hahah. Die Quora Die

"But how can anyone who speaks German be evil?" - The Simpsons

This needs to be higher. Who the hell goes to Quora and asks the same question I'm asking? Everyone knows Google is the place to ask these questions.

Add ?share=1 to a Quora url.

Yep, quora is also on my blocklist.

I think you could find the View Image extension useful.

While it isn't perfect, I have saved a bookmark to my menu bar that uses the custom search criteria:

-site:pinterest.* -site:etsy.* -site:ebay.*

I experienced this today! My first reaction was "Wow, that didn't match what I wanted at all", and then "Why is pinterest on my first 3 hits?". Very strange.

Same with LinkedIn

I'm a teacher and have the problem when I search for things like "list of synonyms for walk" or "blank US map." All I get are a million Pinterest posts.

Duck duck Go all the way...

the search algorithm is super bad thou. hope they can make it better.

How can it be worse than showing a million results from Pinterest?

agreed

Get Personal Blocklist (chrome.google.com). It's an official google extension. Firefox no doubt has something similar.

Go to pinterest.com

Click extenson icon.

Block current domain.

Done.

Not to say pinterest shouldn't be blocked, but until they are this is 4 seconds work. Also helpful for other garbage sites that hook search results.

Edit: I'm aware you can do it in the search results, but that functionality apparently doesn't work for everyone.

If we could get rid of Yelp too, that would be great.

I started using Bing. They give rewards the more you search. Their videos are better. Yes, sometimes when I search stuff I don't get what I need and have to switch back to Google for a minute or two, but usually it's no issue at all. I've been getting better results for the majority of stuff, and no Pinterest!

Honestly, it's so annoying to get that site. IDK if they should completely remove it, but maybe have something where you can ban sites when you search. Unless they already have that? Wish they did.

It’s really fucking bad. So many of my searches now include -Pinterest

https://support.google.com/customsearch/answer/2631038?hl=en

To exclude sites from your search engine

  1. On the Custom Search home page, click the search engine you want.

  2. Click Setup, and then click the Basics tab.

  3. In the Sites to search section, click Advanced.

  4. In the Sites to exclude section, click Add, and then do one of the following:

  5. To exclude a single URL, type the URL and specify the options you want.

  6. To exclude multiple URLs, click Exclude sites in bulk and then type or paste the URLs you want to exclude.

  7. Click Save.

100% agree, this is super obnoxious

Google or "Alphabet" would have been split up by regulators already, if politicinas weren't corrupt and stupid.

We're fucked anyway.

Florida bridge collapse shows how Accelerated Bridge Construction can go wrong

$
0
0

':""},t.getDefinedParams=function(e,t){return t.filter((function(t){return e[t]})).reduce((function(t,n){return g(t,i({},n,e[n]))}),{})},t.isValidMediaTypes=function(e){var t=["banner","native","video"],n=["instream","outstream"];return!!Object.keys(e).every((function(e){return(0,m.default)(t,e)}))&&(!e.video||!e.video.context||(0,m.default)(n,e.video.context))},t.getBidderRequest=function(e,t,n){return(0,b.default)(e,(function(e){return e.bids.filter((function(e){return e.bidder===t&&e.adUnitCode===n})).length>0}))||{start:null,auctionId:null}},t.getOrigin=function(){return window.location.origin?window.location.origin:window.location.protocol+"//"+window.location.hostname+(window.location.port?":"+window.location.port:"")},t.getDNT=function(){return"1"===navigator.doNotTrack||"1"===window.doNotTrack||"1"===navigator.msDoNotTrack||"yes"===navigator.doNotTrack},t.isAdUnitCodeMatchingSlot=function(e){return function(t){return C(e,t)}},t.isSlotMatchingAdUnitCode=function(e){return function(t){return C(t,e)}},t.unsupportedBidderMessage=function(e,t){var n=Object.keys(e.mediaTypes||{banner:"banner"}).join(", ");return"\n "+e.code+" is a "+n+" ad unit\n containing bidders that don't support "+n+": "+t+".\n This bidder won't fetch demand.\n "},t.deletePropertyFromObject=function(e,t){var n=g({},e);return delete n[t],n},t.removeRequestId=function(e){return t.deletePropertyFromObject(e,"requestId")},t.isInteger=function(e){return Number.isInteger?Number.isInteger(e):"number"==typeof e&&isFinite(e)&&Math.floor(e)===e};var v=n(2),y=r(n(61)),b=r(n(11)),m=r(n(8)),h=n(3),S=!1,E=Object.prototype.toString,T=null;try{T=console.info.bind(window.console)}catch(e){}t.replaceTokenInString=function(e,t,n){return this._each(t,(function(t,r){t=void 0===t?"":t;var i=n+r.toUpperCase()+n,o=new RegExp(i,"g");e=e.replace(o,t)})),e};var A=(function(){var e=0;return function(){return++e}})();t.getUniqueIdentifierStr=o,t.generateUUID=function e(t){return t?(t^16*Math.random()>>t/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e)},t.getBidIdParameter=function(e,t){return t&&t[e]?t[e]:""},t.tryAppendQueryString=function(e,t,n){return n?e+=t+"="+encodeURIComponent(n)+"&":e},t.parseQueryStringParameters=function(e){var t="";for(var n in e)e.hasOwnProperty(n)&&(t+=n+"="+encodeURIComponent(e[n])+"&");return t},t.transformAdServerTargetingObj=function(e){return e&&Object.getOwnPropertyNames(e).length>0?f(e).map((function(t){return t+"="+encodeURIComponent(l(e,t))})).join("&"):""},t.getTopWindowLocation=function(){var e=void 0;try{window.top.location.toString(),e=window.top.location}catch(t){e=window.location}return e},t.getTopWindowUrl=function(){var e=void 0;try{e=this.getTopWindowLocation().href}catch(t){e=""}return e},t.getTopWindowReferrer=function(){try{return window.top.document.referrer}catch(e){return document.referrer}},t.logWarn=function(e){I()&&console.warn&&console.warn("WARNING: "+e)},t.logInfo=function(e,t){I()&&u()&&T&&(t&&0!==t.length||(t=""),T("INFO: "+e+(""===t?"":" : params : "),t))},t.logMessage=function(e){I()&&u()&&console.log("MESSAGE: "+e)},t.hasConsoleLogger=u;var I=function(){if(!1===v.config.getConfig("debug")&&!1===S){var e="TRUE"===_(h.DEBUG_MODE).toUpperCase();v.config.setConfig({debug:e}),S=!0}return!!v.config.getConfig("debug")};t.debugTurnedOn=I,t.logError=function(){I()&&d()&&console.error.apply(console,arguments)},t.createInvisibleIframe=function(){var e=document.createElement("iframe");return e.id=o(),e.height=0,e.width=0,e.border="0px",e.hspace="0",e.vspace="0",e.marginWidth="0",e.marginHeight="0",e.style.border="0",e.scrolling="no",e.frameBorder="0",e.src="about:blank",e.style.display="none",e};var _=function(e){var t="[\\?&]"+e+"=([^]*)",n=new RegExp(t).exec(window.location.search);return null===n?"":decodeURIComponent(n[1].replace(/\+/g," "))};t.getParameterByName=_,t.hasValidBidRequest=function(e,t,n){for(var r=!1,i=0;i0);for(var n in e)if(hasOwnProperty.call(e,n))return!1;return!0},t.isEmptyStr=function(e){return this.isStr(e)&&(!e||0===e.length)},t._each=function(e,t){if(!this.isEmpty(e)){if(this.isFn(e.forEach))return e.forEach(t,this);var n=0,r=e.length;if(r>0)for(;n'},t.createTrackPixelIframeHtml=function(e){var n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return e?(n&&(e=encodeURI(e)),r&&(r='sandbox="'+r+'"'),"'):""},t.getIframeDocument=function(e){if(e){var t=void 0;try{t=e.contentWindow?e.contentWindow.document:e.contentDocument.document?e.contentDocument.document:e.contentDocument}catch(e){this.logError("Cannot get iframe document",e)}return t}},t.getValueString=function(e,t,n){return void 0===t||null===t?n:this.isStr(t)?t:this.isNumber(t)?t.toString():void this.logWarn("Unsuported type for param: "+e+" required type: String")};var C=function(e,t){return e.getAdUnitPath()===t||e.getSlotElementId()===t}},1:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e){function t(t){if(e.getUserSyncs){var n=e.getUserSyncs({iframeEnabled:l.config.getConfig("userSync.iframeEnabled"),pixelEnabled:l.config.getConfig("userSync.pixelEnabled")},t);n&&(Array.isArray(n)||(n=[n]),n.forEach((function(t){v.userSync.registerSync(t.type,e.code,t.url)})))}}function n(t){return!!e.isBidRequestValid(t)||((0,h.logWarn)("Invalid bid sent to bidder "+e.code+": "+JSON.stringify(t)),!1)}return s(new c.default(e.code),{getSpec:function(){return Object.freeze(e)},registerSyncs:t,callBids:function(r,i,o,u){function c(e,t){y[e]=!0,a(e,t,[r])&&i(e,t)}function f(e){var n=e&&e[0]&&e[0].mediaType&&"video"===e[0].mediaType,r=l.config.getConfig("cache.url");n&&r||o(),t(b)}function v(e){return e?"?"+("object"===(void 0===e?"undefined":d(e))?(0,h.parseQueryStringParameters)(e):e):""}if(Array.isArray(r.bids)){var y={},b=[],m=r.bids.filter(n);if(0!==m.length){var S={};m.forEach((function(e){S[e.bidId]=e,e.adUnitCode||(e.adUnitCode=e.placementCode)}));var E=e.buildRequests(m,r);if(E&&0!==E.length){Array.isArray(E)||(E=[E]);var T=(0,h.delayExecution)(f,E.length);E.forEach((function(t){function n(n,r){function i(t){var n=S[t.requestId];if(n){var r=s(g.default.createBid(p.STATUS.GOOD,n),t);c(n.adUnitCode,r)}else(0,h.logWarn)("Bidder "+e.code+" made bid for unknown request ID: "+t.requestId+". Ignoring.")}try{n=JSON.parse(n)}catch(e){}n={body:n,headers:{get:r.getResponseHeader.bind(r)}},b.push(n);var o=void 0;try{o=e.interpretResponse(n,t)}catch(t){return(0,h.logError)("Bidder "+e.code+" failed to interpret the server's response. Continuing without bids",null,t),void T()}o&&(o.forEach?o.forEach(i):i(o)),T(o)}function r(t){(0,h.logError)("Server call for "+e.code+" failed: "+t+". Continuing without bids."),T()}switch(t.method){case"GET":u(""+t.url+v(t.data),{success:n,error:r},void 0,s({method:"GET",withCredentials:!0},t.options));break;case"POST":u(t.url,{success:n,error:r},"string"==typeof t.data?t.data:JSON.stringify(t.data),s({method:"POST",contentType:"text/plain",withCredentials:!0},t.options));break;default:(0,h.logWarn)("Skipping invalid request from "+e.code+". Request type "+t.type+" must be GET or POST"),T()}}))}else f()}else f()}}})}function o(e,t,n){if((t.width||0===t.width)&&(t.height||0===t.height))return!0;var r=(0,h.getBidderRequest)(n,t.bidderCode,e),i=r&&r.bids&&r.bids[0]&&r.bids[0].sizes,o=(0,h.parseSizesInput)(i);if(1===o.length){var a=o[0].split("x"),d=u(a,2),s=d[0],c=d[1];return t.width=s,t.height=c,!0}return!1}function a(e,t,n){function r(e){return"Invalid bid from "+t.bidderCode+". Ignoring bid: "+e}return e?t?(function(){var e=Object.keys(t);return S.every((function(t){return(0,m.default)(e,t)}))})()?"native"!==t.mediaType||(0,y.nativeBidIsValid)(t,n)?"video"!==t.mediaType||(0,b.isValidVideoBid)(t,n)?!("banner"===t.mediaType&&!o(e,t,n))||((0,h.logError)(r("Banner bids require a width and height")),!1):((0,h.logError)(r("Video bid does not have required vastUrl or renderer property")),!1):((0,h.logError)(r("Native bid missing some required properties.")),!1):((0,h.logError)(r("Bidder "+t.bidderCode+" is missing required params. Check http://prebid.org/dev-docs/bidder-adapter-1.html for list of params.")),!1):((0,h.logWarn)("Some adapter tried to add an undefined bid for "+e+"."),!1):((0,h.logWarn)("No adUnitCode was supplied to addBidResponse."),!1)}Object.defineProperty(t,"__esModule",{value:!0});var u=(function(){function e(e,t){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=e[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(e){i=!0,o=e}finally{try{!r&&u.return&&u.return()}finally{if(i)throw o}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},s=Object.assign||function(e){for(var t=1;t=p.syncsPerBidder?a.logWarn('Number of user syncs exceeded for "{$bidder}"'):p.enabledBidders&&p.enabledBidders.length&&p.enabledBidders.indexOf(t)0&&void 0!==arguments[0]?arguments[0]:0;if(e)return setTimeout(n,Number(e));n()},c.triggerUserSyncs=function(){p.enableOverride&&c.syncUsers()},c}Object.defineProperty(t,"__esModule",{value:!0}),t.userSync=void 0;var i=(function(){function e(e,t){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=e[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(e){i=!0,o=e}finally{try{!r&&u.return&&u.return()}finally{if(i)throw o}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),o=Object.assign||function(e){for(var t=1;te.getTimeout()+S.config.getConfig("timeoutBuffer")&&e.executeCallback(!0)}function o(e,t){O.emit(B.EVENTS.BID_RESPONSE,t),e.addBidReceived(t),i(e,t)}function a(e,t,n){var r=!0;S.config.getConfig("cache.url")&&(t.videoCacheKey?t.vastUrl||(w.logError("videoCacheKey specified but not required vastUrl for video bid"),r=!1):(r=!1,(0,m.store)([t],(function(r,a){r?(w.logWarn("Failed to save to the video cache: "+r+". Video bid must be discarded."),i(e,t)):(t.videoCacheKey=a[0].uuid,t.vastUrl||(t.vastUrl=(0,m.getCacheUrl)(t.videoCacheKey)),n.doneCbCallCount+=1,o(e,t),e.bidsBackAll())})))),r&&o(e,t)}function u(e){var t=e.adUnitCode,n=e.bid,r=e.bidRequest,i=e.auctionId,o=r.start,a=p({},n,{auctionId:i,responseTimestamp:(0,v.timestamp)(),requestTimestamp:o,cpm:parseFloat(n.cpm)||0,bidder:n.bidderCode,adUnitCode:t});a.timeToRespond=a.responseTimestamp-a.requestTimestamp,O.emit(B.EVENTS.BID_ADJUSTMENT,a);var u=r.bids&&r.bids[0]&&r.bids[0].renderer;u&&u.url&&(a.renderer=h.Renderer.install({url:u.url}),a.renderer.setRender(u.render));var d=(0,y.getPriceBucketString)(a.cpm,S.config.getConfig("customPriceBucket"),S.config.getConfig("currency.granularityMultiplier"));a.pbLg=d.low,a.pbMg=d.med,a.pbHg=d.high,a.pbAg=d.auto,a.pbDg=d.dense,a.pbCg=d.custom;var c;return a.bidderCode&&(a.cpm>0||a.dealId)&&(c=s(a.bidderCode,a)),a.adserverTargeting=p(a.adserverTargeting||{},c),a}function d(){var e=S.config.getConfig("priceGranularity"),t=pbjs.bidderSettings;return t[B.JSON_MAPPING.BD_SETTING_STANDARD]||(t[B.JSON_MAPPING.BD_SETTING_STANDARD]={}),t[B.JSON_MAPPING.BD_SETTING_STANDARD][B.JSON_MAPPING.ADSERVER_TARGETING]||(t[B.JSON_MAPPING.BD_SETTING_STANDARD][B.JSON_MAPPING.ADSERVER_TARGETING]=[{key:"hb_bidder",val:function(e){return e.bidderCode}},{key:"hb_adid",val:function(e){return e.adId}},{key:"hb_pb",val:function(t){return e===B.GRANULARITY_OPTIONS.AUTO?t.pbAg:e===B.GRANULARITY_OPTIONS.DENSE?t.pbDg:e===B.GRANULARITY_OPTIONS.LOW?t.pbLg:e===B.GRANULARITY_OPTIONS.MEDIUM?t.pbMg:e===B.GRANULARITY_OPTIONS.HIGH?t.pbHg:e===B.GRANULARITY_OPTIONS.CUSTOM?t.pbCg:void 0}},{key:"hb_size",val:function(e){return e.size}},{key:"hb_deal",val:function(e){return e.dealId}},{key:"hb_source",val:function(e){return e.source}},{key:"hb_format",val:function(e){return e.mediaType}}]),t[B.JSON_MAPPING.BD_SETTING_STANDARD]}function s(e,t){var n={},r=pbjs.bidderSettings;return t&&r&&c(n,d(),t),e&&t&&r&&r[e]&&r[e][B.JSON_MAPPING.ADSERVER_TARGETING]&&(c(n,r[e],t),t.sendStandardTargeting=r[e].sendStandardTargeting),t.native&&(n=p({},n,(0,b.getNativeTargeting)(t))),n}function c(e,t,n){var r=t[B.JSON_MAPPING.ADSERVER_TARGETING];return n.size=n.getSize(),w._each(r,(function(r){var i=r.key,o=r.val;if(e[i]&&w.logWarn("The key: "+i+" is getting ovewritten"),w.isFn(o))try{o=o(n)}catch(e){w.logError("bidmanager","ERROR",e)}(void 0===t.suppressEmptyKeys||!0!==t.suppressEmptyKeys)&&"hb_deal"!==i||!w.isEmptyStr(o)&&null!==o&&void 0!==o?e[i]=o:w.logInfo("suppressing empty key '"+i+"' from adserver targeting")})),e}function f(e){var t=e.bidderCode,n=e.cpm,r=void 0;if(pbjs.bidderSettings&&(t&&pbjs.bidderSettings[t]&&"function"==typeof pbjs.bidderSettings[t].bidCpmAdjustment?r=pbjs.bidderSettings[t].bidCpmAdjustment:pbjs.bidderSettings[B.JSON_MAPPING.BD_SETTING_STANDARD]&&"function"==typeof pbjs.bidderSettings[B.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment&&(r=pbjs.bidderSettings[B.JSON_MAPPING.BD_SETTING_STANDARD].bidCpmAdjustment),r))try{n=r(e.cpm,p({},e))}catch(e){w.logError("Error during bid adjustment","bidmanager.js",e)}n>=0&&(e.cpm=n)}function l(e,t){return e[t.adUnitCode]||(e[t.adUnitCode]={bids:[]}),e[t.adUnitCode].bids.push(t),e}function g(e,t){var n=e.filter((function(e){return!e.doneCbCallCount})).map((function(e){return e.bidderCode})).filter(v.uniques),r=t.map((function(e){return e.bidder})).filter(v.uniques),i=n.filter((function(e){return!(0,I.default)(r,e)}));return e.map((function(e){return(e.bids||[]).filter((function(e){return(0,I.default)(i,e.bidder)}))})).reduce(v.flatten,[]).map((function(e){return{bidId:e.bidId,bidder:e.bidder,adUnitCode:e.adUnitCode,auctionId:e.auctionId}}))}Object.defineProperty(t,"__esModule",{value:!0}),t.addBidResponse=t.AUCTION_COMPLETED=t.AUCTION_IN_PROGRESS=t.AUCTION_STARTED=void 0;var p=Object.assign||function(e){for(var t=1;t=1}))&&(w.logInfo("Bids Received for Auction with id: "+h,b),E=R,r(!1,!0))}var a=e.adUnits,u=e.adUnitCodes,d=e.callback,s=e.cbTimeout,c=a,f=e.labels,p=u,y=[],b=[],m=void 0,h=w.generateUUID(),E=void 0,T=d,I=void 0,P=s,k=void 0;return{addBidReceived:function(e){b=b.concat(e)},executeCallback:r,callBids:function(){n(),E=U;var e={timestamp:m=Date.now(),auctionId:h,timeout:P};O.emit(B.EVENTS.AUCTION_INIT,e);var r=C.makeBidRequests(c,m,h,P,f);w.logInfo("Bids Requested for Auction with id: "+h,r),r.forEach((function(e){t(e)})),E=N,C.callBids(c,r,j.bind(this),i.bind(this))},bidsBackAll:o,setWinningBid:function(e){k=e},getWinningBid:function(){return k},getTimeout:function(){return P},getAuctionId:function(){return h},getAuctionStatus:function(){return E},getAdUnits:function(){return c},getAdUnitCodes:function(){return p},getBidRequests:function(){return y},getBidsReceived:function(){return b}}},t.getStandardBidderSettings=d,t.getKeyValueTargetingPairs=s,t.adjustBids=f;var v=n(0),y=n(28),b=n(14),m=n(148),h=n(20),S=n(2),E=n(13),T=n(19),A=r(n(11)),I=r(n(8)),_=E.userSync.syncUsers,w=n(0),C=n(5),O=n(9),B=n(3),U=t.AUCTION_STARTED="started",N=t.AUCTION_IN_PROGRESS="inProgress",R=t.AUCTION_COMPLETED="completed";O.on(B.EVENTS.BID_ADJUSTMENT,(function(e){f(e)}));var j=t.addBidResponse=(0,T.createHook)("asyncSeries",(function(e,t){var n=this,r=n.getBidRequests(),i=n.getAuctionId(),d=(0,v.getBidderRequest)(r,t.bidderCode,e),s=u({adUnitCode:e,bid:t,bidRequest:d,auctionId:i});"video"===s.mediaType?a(n,s,d):o(n,s)}),"addBidResponse")},148:function(e,t,n){"use strict";function r(e){return'\n \n \n prebid.org wrapper\n \n \n \n \n \n "}function i(e){return{type:"xml",value:e.vastXml?e.vastXml:r(e.vastUrl)}}function o(e){return{success:function(t){var n=void 0;try{n=JSON.parse(t).responses}catch(t){return void e(t,[])}n?e(null,n):e(new Error("The cache server didn't respond with a responses property."),[])},error:function(t,n){e(new Error("Error storing video ad in the cache: "+t+": "+JSON.stringify(n)),[])}}}Object.defineProperty(t,"__esModule",{value:!0}),t.store=function(e,t){var n={puts:e.map(i)};(0,a.ajax)(u.config.getConfig("cache.url"),o(t),JSON.stringify(n),{contentType:"text/plain",withCredentials:!0})},t.getCacheUrl=function(e){return u.config.getConfig("cache.url")+"?uuid="+e};var a=n(6),u=n(2)},15:function(e,t,n){"use strict";function r(e,t){var n=t&&t.bidId||i.getUniqueIdentifierStr(),r=t&&t.src||"client",o=e||0;this.bidderCode=t&&t.bidder||"",this.width=0,this.height=0,this.statusMessage=(function(){switch(o){case 0:return"Pending";case 1:return"Bid available";case 2:return"Bid returned empty or error response";case 3:return"Bid timed out"}})(),this.adId=n,this.mediaType="banner",this.source=r,this.getStatusCode=function(){return o},this.getSize=function(){return this.width+"x"+this.height}}var i=n(0);t.createBid=function(e,t){return new r(e,t)}},16:function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},17:function(e,t){var n=e.exports={version:"2.5.1"};"number"==typeof __e&&(__e=n)},18:function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},19:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:10;"function"==typeof e&&(a.push({fn:e,priority:t}),a.sort((function(e,t){return t.priority-e.priority})))},removeHook:function(e){a=a.filter((function(n){return n.fn===t||n.fn!==e}))}};return"string"==typeof n&&(o[n]=d),r((function(){for(var n=arguments.length,r=Array(n),i=0;i0;)try{this.cmd.shift().call()}catch(e){o.logError("Error processing Renderer command: ",e)}}},21:function(e,t,n){var r=n(16),i=n(17),o=n(29),a=n(43),u=function(e,t,n){var d,s,c,f=e&u.F,l=e&u.G,g=e&u.S,p=e&u.P,v=e&u.B,y=e&u.W,b=l?i:i[t]||(i[t]={}),m=b.prototype,h=l?r:g?r[t]:(r[t]||{}).prototype;l&&(n=t);for(d in n)(s=!f&&h&&void 0!==h[d])&&d in b||(c=s?h[d]:n[d],b[d]=l&&"function"!=typeof h[d]?n[d]:v&&s?o(c,r):y&&h[d]==c?(function(e){var t=function(t,n,r){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,n)}return new e(t,n,r)}return e.apply(this,arguments)};return t.prototype=e.prototype,t})(c):p&&"function"==typeof c?o(Function.call,c):c,p&&((b.virtual||(b.virtual={}))[d]=c,e&u.R&&m&&!m[d]&&a(m,d,c)))};u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,e.exports=u},22:function(e,t,n){e.exports=!n(30)((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},23:function(e,t){e.exports=function(){}},24:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=e;return{callBids:function(){},setBidderCode:function(e){t=e},getBidderCode:function(){return t}}}},25:function(e,t,n){n(94),e.exports=n(17).Array.findIndex},26:function(e,t){var n;n=(function(){return this})();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},27:function(e,t,n){"use strict";function r(){function e(e){t.push(e)}var t=[],n={};return n.addWinningBid=function(e){var n=(0,a.default)(t,(function(t){return t.getAuctionId()===e.auctionId}));n?n.setWinningBid(e):utils.logWarn("Auction not found when adding winning bid")},n.getAllWinningBids=function(){return t.map((function(e){return e.getWinningBid()})).reduce(i.flatten,[])},n.getBidsRequested=function(){return t.map((function(e){return e.getBidRequests()})).reduce(i.flatten,[])},n.getBidsReceived=function(){return t.map((function(e){if(e.getAuctionStatus()===o.AUCTION_COMPLETED)return e.getBidsReceived()})).reduce(i.flatten,[]).filter((function(e){return e}))},n.getAdUnits=function(){return t.map((function(e){return e.getAdUnits()})).reduce(i.flatten,[])},n.getAdUnitCodes=function(){return t.map((function(e){return e.getAdUnitCodes()})).reduce(i.flatten,[]).filter(i.uniques)},n.createAuction=function(t){var n=t.adUnits,r=t.adUnitCodes,i=t.callback,a=t.cbTimeout,u=t.labels,d=(0,o.newAuction)({adUnits:n,adUnitCodes:r,callback:i,cbTimeout:a,labels:u});return e(d),d},n.findBidByAdId=function(e){return(0,a.default)(t.map((function(e){return e.getBidsReceived()})).reduce(i.flatten,[]),(function(t){return t.adId===e}))},n.getStandardBidderAdServerTargeting=function(){return(0,o.getStandardBidderSettings)()[u.JSON_MAPPING.ADSERVER_TARGETING]},n}Object.defineProperty(t,"__esModule",{value:!0}),t.auctionManager=void 0,t.newAuctionManager=r;var i=n(0),o=n(147),a=(function(e){return e&&e.__esModule?e:{default:e}})(n(11)),u=n(3);t.auctionManager=r()},28:function(e,t,n){"use strict";function r(e,t,n){var r="";if(!i(t))return r;var u=t.buckets.reduce((function(e,t){return e.max>t.max?e:t}),{max:0}),s=(0,a.default)(t.buckets,(function(t){if(e>u.max*n){var i=t.precision;void 0===i&&(i=d),r=(t.max*n).toFixed(i)}else if(e=t.min*n)return t}));return s&&(r=o(e,s.increment,s.precision,n)),r}function i(e){if(u.isEmpty(e)||!e.buckets||!Array.isArray(e.buckets))return!1;var t=!0;return e.buckets.forEach((function(e){void 0!==e.min&&e.max&&e.increment||(t=!1)})),t}function o(e,t,n,r){void 0===n&&(n=d);var i=1/(t*r);return(Math.floor(e*i)/i).toFixed(n)}Object.defineProperty(t,"__esModule",{value:!0}),t.isValidPriceConfig=t.getPriceBucketString=void 0;var a=(function(e){return e&&e.__esModule?e:{default:e}})(n(11)),u=n(0),d=2,s={buckets:[{min:0,max:5,increment:.5}]},c={buckets:[{min:0,max:20,increment:.1}]},f={buckets:[{min:0,max:20,increment:.01}]},l={buckets:[{min:0,max:3,increment:.01},{min:3,max:8,increment:.05},{min:8,max:20,increment:.5}]},g={buckets:[{min:0,max:5,increment:.05},{min:5,max:10,increment:.1},{min:10,max:20,increment:.5}]};t.getPriceBucketString=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,i=parseFloat(e);return isNaN(i)&&(i=""),{low:""===i?"":r(e,s,n),med:""===i?"":r(e,c,n),high:""===i?"":r(e,f,n),auto:""===i?"":r(e,g,n),dense:""===i?"":r(e,l,n),custom:""===i?"":r(e,t,n)}},t.isValidPriceConfig=i},29:function(e,t,n){var r=n(42);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},3:function(e,t){e.exports={JSON_MAPPING:{PL_CODE:"code",PL_SIZE:"sizes",PL_BIDS:"bids",BD_BIDDER:"bidder",BD_ID:"paramsd",BD_PL_ID:"placementId",ADSERVER_TARGETING:"adserverTargeting",BD_SETTING_STANDARD:"standard"},REPO_AND_VERSION:"prebid_prebid_1.5.0-pre",DEBUG_MODE:"pbjs_debug",STATUS:{GOOD:1,NO_BID:2},CB:{TYPE:{ALL_BIDS_BACK:"allRequestedBidsBack",AD_UNIT_BIDS_BACK:"adUnitBidsBack",BID_WON:"bidWon",REQUEST_BIDS:"requestBids"}},EVENTS:{AUCTION_INIT:"auctionInit",AUCTION_END:"auctionEnd",BID_ADJUSTMENT:"bidAdjustment",BID_TIMEOUT:"bidTimeout",BID_REQUESTED:"bidRequested",BID_RESPONSE:"bidResponse",BID_WON:"bidWon",SET_TARGETING:"setTargeting",REQUEST_BIDS:"requestBids",ADD_AD_UNITS:"addAdUnits"},EVENT_ID_PATHS:{bidWon:"adUnitCode"},GRANULARITY_OPTIONS:{LOW:"low",MEDIUM:"medium",HIGH:"high",AUTO:"auto",DENSE:"dense",CUSTOM:"custom"},TARGETING_KEYS:["hb_bidder","hb_adid","hb_pb","hb_size","hb_deal","hb_source","hb_format"],S2S:{SRC:"s2s",SYNCED_BIDDERS_KEY:"pbjsSyncs"}}},30:function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},31:function(e,t,n){var r=n(29),i=n(32),o=n(50),a=n(35),u=n(51);e.exports=function(e,t){var n=1==e,d=2==e,s=3==e,c=4==e,f=6==e,l=5==e||f,g=t||u;return function(t,u,p){for(var v,y,b=o(t),m=i(b),h=r(u,p,3),S=a(m.length),E=0,T=n?g(t,S):d?g(t,0):void 0;S>E;E++)if((l||E in m)&&(v=m[E],y=h(v,E,b),e))if(n)T[E]=y;else if(y)switch(e){case 3:return!0;case 5:return v;case 6:return E;case 2:T.push(v)}else if(c)return!1;return f?-1:s||c?c:T}}},32:function(e,t,n){var r=n(33);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==r(e)?e.split(""):Object(e)}},33:function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},34:function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},35:function(e,t,n){var r=n(36),i=Math.min;e.exports=function(e){return e>0?i(r(e),9007199254740991):0}},351:function(e,t,n){e.exports=n(352)},352:function(e,t,n){"use strict";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t,n){e.defaultView&&e.defaultView.frameElement&&(e.defaultView.frameElement.width=t,e.defaultView.frameElement.height=n)}function o(e){e.forEach((function(e){if(void 0===e.called)try{e.call(),e.called=!0}catch(e){S.logError("Error processing command :","prebid.js",e)}}))}var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},u=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},t=e.bidsBackHandler,n=e.timeout,r=e.adUnits,i=e.adUnitCodes,o=e.labels;A.emit(O);var a=n||g.config.getConfig("bidderTimeout");if(r=r||m.adUnits,S.logInfo("Invoking pbjs.requestBids",arguments),i&&i.length?r=r.filter((function(e){return(0,b.default)(i,e.code)})):i=r&&r.map((function(e){return e.code})),r.forEach((function(e){var t=Object.keys(e.mediaTypes||{banner:"banner"}),n=e.bids.map((function(e){return e.bidder})),r=E.bidderRegistry;n.forEach((function(n){var i=r[n],o=i&&i.getSpec&&i.getSpec(),a=o&&o.supportedMediaTypes||["banner"];t.some((function(e){return(0,b.default)(a,e)}))||(S.logWarn(S.unsupportedBidderMessage(e,n)),e.bids=e.bids.filter((function(e){return e.bidder!==n})))}))})),r&&0!==r.length){var u=p.auctionManager.createAuction({adUnits:r,adUnitCodes:i,callback:t,cbTimeout:a,labels:o});return u.callBids(),u}if(S.logMessage("No adUnits configured. No bids requested."),"function"==typeof t)try{t()}catch(e){S.logError("Error executing bidsBackHandler",null,e)}})),m.addAdUnits=function(e){S.logInfo("Invoking pbjs.addAdUnits",arguments),S.isArray(e)?(e.forEach((function(e){return e.transactionId=S.generateUUID()})),m.adUnits.push.apply(m.adUnits,e)):"object"===(void 0===e?"undefined":a(e))&&(e.transactionId=S.generateUUID(),m.adUnits.push(e)),A.emit(w)},m.onEvent=function(e,t,n){S.logInfo("Invoking pbjs.onEvent",arguments),S.isFn(t)?!n||U[e].call(null,n)?A.on(e,t,n):S.logError('The id provided is not valid for event "'+e+'" and no handler was set.'):S.logError('The event handler provided is not a function and was not set on event "'+e+'".')},m.offEvent=function(e,t,n){S.logInfo("Invoking pbjs.offEvent",arguments),n&&!U[e].call(null,n)||A.off(e,t,n)},m.registerBidAdapter=function(e,t){S.logInfo("Invoking pbjs.registerBidAdapter",arguments);try{E.registerBidAdapter(e(),t)}catch(e){S.logError("Error registering bidder adapter : "+e.message)}},m.registerAnalyticsAdapter=function(e){S.logInfo("Invoking pbjs.registerAnalyticsAdapter",arguments);try{E.registerAnalyticsAdapter(e)}catch(e){S.logError("Error registering analytics adapter : "+e.message)}},m.createBid=function(e){return S.logInfo("Invoking pbjs.createBid",arguments),T.createBid(e)},m.loadScript=function(e,t,n){S.logInfo("Invoking pbjs.loadScript",arguments),(0,l.loadScript)(e,t,n)},m.enableAnalytics=function(e){e&&!S.isEmpty(e)?(S.logInfo("Invoking pbjs.enableAnalytics for: ",e),E.enableAnalytics(e)):S.logError("pbjs.enableAnalytics should be called with option {}")},m.aliasBidder=function(e,t){S.logInfo("Invoking pbjs.aliasBidder",arguments),e&&t?E.aliasBidAdapter(e,t):S.logError("bidderCode and alias must be passed as arguments","pbjs.aliasBidder")},m.getAllWinningBids=function(){return p.auctionManager.getAllWinningBids().map(s.removeRequestId)},m.getHighestCpmBids=function(e){return v.targeting.getWinningBids(e,p.auctionManager.getBidsReceived()).map(s.removeRequestId)},m.getConfig=g.config.getConfig,m.setConfig=g.config.setConfig,m.que.push((function(){return(0,c.listenMessagesFromCreative)()})),m.cmd.push=function(e){if("function"==typeof e)try{e.call()}catch(e){S.logError("Error processing command :",e.message,e.stack)}else S.logError("Commands written into pbjs.cmd.push must be wrapped in a function")},m.que.push=m.cmd.push,m.processQueue=function(){o(m.que),o(m.cmd)}},353:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=e.message?"message":"data",n={};try{n=JSON.parse(e[t])}catch(e){return}if(n.adId){var r=(0,l.default)(f.auctionManager.getBidsReceived(),(function(e){return e.adId===n.adId}));"Prebid Request"===n.message&&(o(r,n.adServerDomain,e.source),f.auctionManager.addWinningBid(r),u.default.emit(g,r)),"Prebid Native"===n.message&&((0,d.fireNativeTrackers)(n,r),f.auctionManager.addWinningBid(r),u.default.emit(g,r))}}function o(e,t,n){var r=e.adId,i=e.ad,o=e.adUrl,u=e.width,d=e.height;r&&(a(e),n.postMessage(JSON.stringify({message:"Prebid Response",ad:i,adUrl:o,adId:r,width:u,height:d}),t))}function a(e){var t=e.adUnitCode,n=e.width,r=e.height,i=document.getElementById((0,l.default)(window.googletag.pubads().getSlots().filter((0,c.isSlotMatchingAdUnitCode)(t)),(function(e){return e})).getSlotElementId()).querySelector("iframe");i.width=""+n,i.height=""+r}Object.defineProperty(t,"__esModule",{value:!0}),t.listenMessagesFromCreative=function(){addEventListener("message",i,!1)};var u=r(n(9)),d=n(14),s=n(3),c=n(0),f=n(27),l=r(n(11)),g=s.EVENTS.BID_WON},36:function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},37:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getGlobal=function(){return window.pbjs},window.pbjs=window.pbjs||{},window.pbjs.cmd=window.pbjs.cmd||[],window.pbjs.que=window.pbjs.que||[]},38:function(e,t,n){"use strict";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e){function n(e){return e.map((function(e){return r({},Object.keys(e)[0],e[Object.keys(e)[0]].map((function(e){return r({},Object.keys(e)[0],e[Object.keys(e)[0]].join(", "))})).reduce((function(e,t){return o(t,e)}),{}))})).reduce((function(e,t){var n=Object.keys(t)[0];return e[n]=o({},e[n],t[n]),e}),{})}function i(t){return"string"==typeof t?[t]:f.isArray(t)?t:e.getAdUnitCodes()||[]}function s(){return e.getBidsReceived().filter(y).filter(t.isBidExpired)}function b(e,t){var n=w.getWinningBids(e,t);n.forEach((function(e){e.status=p}));var i=m();return n=n.map((function(e){return r({},e.adUnitCode,Object.keys(e.adserverTargeting).filter((function(t){return void 0===e.sendStandardTargeting||e.sendStandardTargeting||-1===i.indexOf(t)})).map((function(t){return r({},"hb_deal"===t?(t+"_"+e.bidderCode).substring(0,v):t.substring(0,v),[e.adserverTargeting[t]])})))}))}function m(){return e.getStandardBidderAdServerTargeting().map((function(e){return e.key})).concat(l.TARGETING_KEYS).filter(a.uniques)}function h(e,t,n,r){function i(e){return function(n){f.isArray(n.adserverTargeting[e])||(n.adserverTargeting[e]=[n.adserverTargeting[e]]),n.adserverTargeting[e]=n.adserverTargeting[e].concat(t.adserverTargeting[e]).filter(a.uniques),delete t.adserverTargeting[e]}}function o(e){return function(n){return n.adUnitCode===t.adUnitCode&&n.adserverTargeting[e]}}return Object.keys(t.adserverTargeting).filter(S()).forEach((function(t){e.length&&e.filter(o(t)).forEach(i(t))})),e.push(t),e}function S(){var e=m();return function(t){return-1===e.indexOf(t)}}function E(e){return r({},e.adUnitCode,Object.keys(e.adserverTargeting).filter(S()).map((function(t){return r({},t.substring(0,v),[e.adserverTargeting[t]])})))}function T(e,t){return t.filter((function(t){return(0,c.default)(e,t.adUnitCode)})).map((function(e){return o({},e)})).reduce(h,[]).map(E).filter((function(e){return e}))}function A(e,t){var n=l.TARGETING_KEYS.concat(d.NATIVE_TARGETING_KEYS),i=[],o=(0,a.groupBy)(t,"adUnitCode");return Object.keys(o).forEach((function(e){var t=(0,a.groupBy)(o[e],"bidderCode");Object.keys(t).forEach((function(e){return i.push(t[e].reduce(a.getHighestCpm,_()))}))})),i.map((function(e){if(e.adserverTargeting)return r({},e.adUnitCode,I(e,n.filter((function(t){return void 0!==e.adserverTargeting[t]}))))})).filter((function(e){return e}))}function I(e,t){return t.map((function(t){return r({},(t+"_"+e.bidderCode).substring(0,v),[e.adserverTargeting[t]])}))}function _(e){return{adUnitCode:e,cpm:0,adserverTargeting:{},timeToRespond:0}}var w={};return w.resetPresetTargeting=function(t){if((0,a.isGptPubadsDefined)()){var n=i(t),r=e.getAdUnits().filter((function(e){return(0,c.default)(n,e.code)}));window.googletag.pubads().getSlots().forEach((function(e){g.forEach((function(t){r.forEach((function(n){n.code!==e.getAdUnitPath()&&n.code!==e.getSlotElementId()||e.setTargeting(t,null)}))}))}))}},w.getAllTargeting=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:s(),r=i(e),o=b(r,t).concat(T(r,t)).concat(u.config.getConfig("enableSendAllBids")?A(0,t):[]);return o.map((function(e){Object.keys(e).map((function(t){e[t].map((function(e){-1===g.indexOf(Object.keys(e)[0])&&(g=Object.keys(e).concat(g))}))}))})),o=n(o)},w.setTargetingForGPT=function(e){window.googletag.pubads().getSlots().forEach((function(t){Object.keys(e).filter((0,a.isAdUnitCodeMatchingSlot)(t)).forEach((function(n){return Object.keys(e[n]).forEach((function(r){var i=e[n][r].split(",");(i=i.length>1?[i]:i).map((function(e){return f.logMessage("Attempting to set key value for slot: "+t.getSlotElementId()+" key: "+r+" value: "+e),e})).forEach((function(e){t.setTargeting(r,e)}))}))}))}))},w.getWinningBids=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:s(),n=i(e);return t.filter((function(e){return(0,c.default)(n,e.adUnitCode)})).filter((function(e){return e.cpm>0})).map((function(e){return e.adUnitCode})).filter(a.uniques).map((function(e){return t.filter((function(t){return t.adUnitCode===e?t:null})).reduce(a.getHighestCpm,_(e))}))},w.setTargetingForAst=function(){var e=w.getAllTargeting();Object.keys(e).forEach((function(t){return Object.keys(e[t]).forEach((function(n){if(f.logMessage("Attempting to set targeting for targetId: "+t+" key: "+n+" value: "+e[t][n]),f.isStr(e[t][n])||f.isArray(e[t][n])){var r={};r["hb_adid"===n.substring(0,"hb_adid".length)?n.toUpperCase():n]=e[t][n],window.apntag.setKeywords(t,r)}}))}))},w.isApntagDefined=function(){if(window.apntag&&f.isFn(window.apntag.setKeywords))return!0},w}Object.defineProperty(t,"__esModule",{value:!0}),t.targeting=t.isBidExpired=t.BID_TARGETING_SET=void 0;var o=Object.assign||function(e){for(var t=1;t1?arguments[1]:void 0)}}),n(23)("find")},42:function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},43:function(e,t,n){var r=n(44),i=n(49);e.exports=n(22)?function(e,t,n){return r.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},44:function(e,t,n){var r=n(45),i=n(46),o=n(48),a=Object.defineProperty;t.f=n(22)?Object.defineProperty:function(e,t,n){if(r(e),t=o(t,!0),r(n),i)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},45:function(e,t,n){var r=n(18);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},46:function(e,t,n){e.exports=!n(22)&&!n(30)((function(){return 7!=Object.defineProperty(n(47)("div"),"a",{get:function(){return 7}}).a}))},47:function(e,t,n){var r=n(18),i=n(16).document,o=r(i)&&r(i.createElement);e.exports=function(e){return o?i.createElement(e):{}}},48:function(e,t,n){var r=n(18);e.exports=function(e,t){if(!r(e))return e;var n,i;if(t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;if("function"==typeof(n=e.valueOf)&&!r(i=n.call(e)))return i;if(!t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;throw TypeError("Can't convert object to primitive value")}},49:function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},5:function(e,t,n){"use strict";function r(e,t){return e.labelAll?{labelAll:!0,labels:e.labelAll,activeLabels:t}:{labelAll:!1,labels:e.labelAny,activeLabels:t}}function i(e){var t=e.bidderCode,n=e.auctionId,i=e.bidderRequestId,o=e.adUnits,a=e.labels;return o.reduce((function(e,o){var u=(0,g.resolveStatus)(r(o,a),o.sizes),d=u.active,s=u.sizes;return d&&e.push(o.bids.filter((function(e){return e.bidder===t})).reduce((function(e,t){o.mediaTypes&&(h.isValidMediaTypes(o.mediaTypes)?t=f({},t,{mediaTypes:o.mediaTypes}):h.logError("mediaTypes is not correctly configured for adunit "+o.code));var u=o.nativeParams||h.deepAccess(o,"mediaTypes.native");u&&(t=f({},t,{nativeParams:(0,p.processNativeAdUnitParams)(u)})),t=f({},t,(0,l.getDefinedParams)(o,["mediaType","renderer"]));var d=(0,g.resolveStatus)(r(t,a),s),c=d.active,v=d.sizes;return c&&e.push(f({},t,{adUnitCode:o.code,transactionId:o.transactionId,sizes:v,bidId:t.bid_id||h.getUniqueIdentifierStr(),bidderRequestId:i,auctionId:n})),e}),[])),e}),[]).reduce(l.flatten,[]).filter((function(e){return""!==e}))}function o(e){var t=[];return h.parseSizesInput(e.sizes).forEach((function(e){var n=e.split("x"),r={w:parseInt(n[0]),h:parseInt(n[1])};t.push(r)})),t}function a(e){var t=I.bidders,n=h.deepClone(e);return n.forEach((function(e){e.sizes=o(e),e.bids=e.bids.filter((function(e){return(0,m.default)(t,e.bidder)&&(!d()||e.finalSource!==T.CLIENT)})).map((function(e){return e.bid_id=h.getUniqueIdentifierStr(),e}))})),n=n.filter((function(e){return 0!==e.bids.length}))}function u(e){var t=h.deepClone(e);return t.forEach((function(e){e.bids=e.bids.filter((function(e){return!d()||e.finalSource!==T.SERVER}))})),t=t.filter((function(e){return 0!==e.bids.length}))}function d(){return I&&I.enabled&&I.testing&&T}function s(e){var n=[];return(0,m.default)(t.videoAdapters,e)&&n.push("video"),(0,m.default)(p.nativeAdapters,e)&&n.push("native"),n}var c=(function(){function e(e,t){var n=[],r=!0,i=!1,o=void 0;try{for(var a,u=e[Symbol.iterator]();!(r=(a=u.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(e){i=!0,o=e}finally{try{!r&&u.return&&u.return()}finally{if(i)throw o}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),f=Object.assign||function(e){for(var t=1;t (eg mediaTypes.banner.sizes).");var t=e.mediaTypes;if(t&&t.banner){var n=t.banner;n.sizes?e.sizes=n.sizes:(h.logError("Detected a mediaTypes.banner object did not include sizes. This is a required field for the mediaTypes.banner object. Removing invalid mediaTypes.banner object from request."),delete e.mediaTypes.banner)}if(t&&t.video){var r=t.video;r.playerSize&&(Array.isArray(r.playerSize)&&2===r.playerSize.length&&h.isInteger(r.playerSize[0])&&h.isInteger(r.playerSize[1])?e.sizes=r.playerSize:(h.logError("Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [640, 480]. Removing invalid mediaTypes.video.playerSize property from request."),delete e.mediaTypes.video.playerSize))}if(t&&t.native){var i=t.native;i.image&&i.image.sizes&&!Array.isArray(i.image.sizes)&&(h.logError("Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request."),delete e.mediaTypes.native.image.sizes),i.image&&i.image.aspect_ratios&&!Array.isArray(i.image.aspect_ratios)&&(h.logError("Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request."),delete e.mediaTypes.native.image.aspect_ratios),i.icon&&i.icon.sizes&&!Array.isArray(i.icon.sizes)&&(h.logError("Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request."),delete e.mediaTypes.native.icon.sizes)}})),e},t.callBids=function(e,t,n,r){if(t.length){var i=(0,y.ajaxBuilder)(t[0].timeout),o=t.reduce((function(e,t){return e[Number(void 0!==t.src&&t.src===S.S2S.SRC)].push(t),e}),[[],[]]),a=c(o,2),u=a[0],d=a[1];if(d.length){var s=I.bidders,f=A[I.adapter],g=d[0].tid,p=d[0].adUnitsS2SCopy;if(f){var v={tid:g,ad_units:p};if(v.ad_units.length){var b=d.map((function(e){return e.start=(0,l.timestamp)(),e.doneCbCallCount=0,r(e.bidderRequestId)})),T=v.ad_units.reduce((function(e,t){return e.concat((t.bids||[]).reduce((function(e,t){return e.concat(t.bidder)}),[]))}),[]);h.logMessage("CALLING S2S HEADER BIDDERS ==== "+s.filter((function(e){return(0,m.default)(T,e)})).join(",")),d.forEach((function(e){E.emit(S.EVENTS.BID_REQUESTED,e)})),f.callBids(v,d,n,(function(){return b.forEach((function(e){return e()}))}),i)}}}u.forEach((function(e){e.start=(0,l.timestamp)();var t=A[e.bidderCode];if(t){h.logMessage("CALLING BIDDER ======= "+e.bidderCode),E.emit(S.EVENTS.BID_REQUESTED,e),e.doneCbCallCount=0;var o=r(e.bidderRequestId);t.callBids(e,n,o,i)}else h.logError("Adapter trying to be called which does not exist: "+e.bidderCode+" adaptermanager.callBids")}))}else h.logWarn("callBids executed with no bidRequests. Were they filtered by labels or sizing?")},t.videoAdapters=[],t.registerBidAdapter=function(e,n){var r=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).supportedMediaTypes,i=void 0===r?[]:r;e&&n?"function"==typeof e.callBids?(A[n]=e,(0,m.default)(i,"video")&&t.videoAdapters.push(n),(0,m.default)(i,"native")&&p.nativeAdapters.push(n)):h.logError("Bidder adaptor error for bidder code: "+n+"bidder must implement a callBids() function"):h.logError("bidAdaptor or bidderCode not specified")},t.aliasBidAdapter=function(e,t){if(void 0===A[t]){var n=A[e];if(void 0===n)h.logError('bidderCode "'+e+'" is not an existing bidder.',"adaptermanager.aliasBidAdapter");else try{var r=void 0,i=s(e);if(n.constructor.prototype!=Object.prototype)(r=new n.constructor).setBidderCode(t);else{var o=n.getSpec();r=(0,v.newBidder)(f({},o,{code:t}))}this.registerBidAdapter(r,t,{supportedMediaTypes:i})}catch(t){h.logError(e+" bidder does not currently support aliasing.","adaptermanager.aliasBidAdapter")}}else h.logMessage('alias name "'+t+'" has been already specified.')},t.registerAnalyticsAdapter=function(e){var t=e.adapter,n=e.code;t&&n?"function"==typeof t.enableAnalytics?(t.code=n,_[n]=t):h.logError('Prebid Error: Analytics adaptor error for analytics "'+n+'"\n analytics adapter must implement an enableAnalytics() function'):h.logError("Prebid Error: analyticsAdapter or analyticsCode not specified")},t.enableAnalytics=function(e){h.isArray(e)||(e=[e]),h._each(e,(function(e){var t=_[e.provider];t?t.enableAnalytics(e):h.logError("Prebid Error: no analytics adapter found in registry for\n "+e.provider+".")}))},t.getBidAdapter=function(e){return A[e]},t.setS2STestingModule=function(e){T=e}},50:function(e,t,n){var r=n(34);e.exports=function(e){return Object(r(e))}},51:function(e,t,n){var r=n(52);e.exports=function(e,t){return new(r(e))(t)}},52:function(e,t,n){var r=n(18),i=n(53),o=n(54)("species");e.exports=function(e){var t;return i(e)&&("function"!=typeof(t=e.constructor)||t!==Array&&!i(t.prototype)||(t=void 0),r(t)&&null===(t=t[o])&&(t=void 0)),void 0===t?Array:t}},53:function(e,t,n){var r=n(33);e.exports=Array.isArray||function(e){return"Array"==r(e)}},54:function(e,t,n){var r=n(55)("wks"),i=n(56),o=n(16).Symbol,a="function"==typeof o;(e.exports=function(e){return r[e]||(r[e]=a&&o[e]||(a?o:i)("Symbol."+e))}).store=r},55:function(e,t,n){var r=n(16),i=r["__core-js_shared__"]||(r["__core-js_shared__"]={});e.exports=function(e){return i[e]||(i[e]={})}},56:function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},57:function(e,t,n){"use strict";var r=n(21),i=n(58)(!0);r(r.P,"Array",{includes:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),n(23)("includes")},58:function(e,t,n){var r=n(59),i=n(35),o=n(60);e.exports=function(e){return function(t,n,a){var u,d=r(t),s=i(d.length),c=o(a,s);if(e&&n!=n){for(;s>c;)if((u=d[c++])!=u)return!0}else for(;s>c;c++)if((e||c in d)&&d[c]===n)return e||c||0;return!e&&-1}}},59:function(e,t,n){var r=n(32),i=n(34);e.exports=function(e){return r(i(e))}},6:function(e,t,n){"use strict";function r(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:3e3;return function(t,n,r){var s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};try{var c=void 0,f=!1,l=s.method||(r?"POST":"GET"),g="object"===(void 0===n?"undefined":o(n))?n:{success:function(){u.logMessage("xhr success")},error:function(e){u.logError("xhr error",null,e)}};if("function"==typeof n&&(g.success=n),window.XMLHttpRequest?void 0===(c=new window.XMLHttpRequest).responseType&&(f=!0):f=!0,f?((c=new window.XDomainRequest).onload=function(){g.success(c.responseText,c)},c.onerror=function(){g.error("error",c)},c.ontimeout=function(){g.error("timeout",c)},c.onprogress=function(){u.logMessage("xhr onprogress")}):(c.onreadystatechange=function(){if(c.readyState===d){var e=c.status;e>=200&&e0&&void 0!==arguments[0]?arguments[0]:{},t=e.labels,n=void 0===t?[]:t,r=e.labelAll,o=void 0!==r&&r,a=e.activeLabels,u=void 0===a?[]:a,c=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],f=i(arguments.length>2&&void 0!==arguments[2]?arguments[2]:s),l=void 0;return l=f.shouldFilter?c.filter((function(e){return f.sizesSupported[e]})):c,{active:l.length>0&&(0===n.length||!o&&(n.some((function(e){return f.labels[e]}))||n.some((function(e){return(0,d.default)(u,e)})))||o&&n.reduce((function(e,t){return e?f.labels[t]||(0,d.default)(u,t):e}),!0)),sizes:l}};var a=n(2),u=n(0),d=(function(e){return e&&e.__esModule?e:{default:e}})(n(8)),s=[];a.config.getConfig("sizeConfig",(function(e){return r(e.sizeConfig)}))},63:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.hasNonVideoBidder=t.videoBidder=t.videoAdUnit=void 0,t.isValidVideoBid=function(e,t){var n=(0,i.getBidRequest)(e.adId,t),r=n&&(0,i.deepAccess)(n,"mediaTypes.video"),a=r&&(0,i.deepAccess)(r,"context");return!n||r&&a!==u?o.config.getConfig("cache.url")||!e.vastXml||e.vastUrl?!(!e.vastUrl&&!e.vastXml):((0,i.logError)('\n This bid contains only vastXml and will not work when a prebid cache url is not specified.\n Try enabling prebid cache with pbjs.setConfig({ cache: {url: "..."} });\n '),!1):a!==u||!(!e.renderer&&!n.renderer)};var r=n(5),i=n(0),o=n(2),a=(function(e){return e&&e.__esModule?e:{default:e}})(n(8)),u="outstream",d=(t.videoAdUnit=function(e){var t="video"===e.mediaType,n=(0,i.deepAccess)(e,"mediaTypes.video");return t||n},t.videoBidder=function(e){return(0,a.default)(r.videoAdapters,e.bidder)});t.hasNonVideoBidder=function(e){return e.bids.filter((function(e){return!d(e)})).length}},8:function(e,t,n){n(57),e.exports=n(17).Array.includes},9:function(e,t,n){"use strict";var r=Object.assign||function(e){for(var t=1;t1?arguments[1]:void 0)}}),n(23)(o)}}); pbjsChunk([100],{103:function(n,e,t){n.exports=t(104)},104:function(n,e,t){"use strict";function o(){if(T&&"object"===l(window[m])&&"function"==typeof window[m].getInstance){for(var n=0;n0){var e=i(n);h++,window[m].logEvent("Prebid.js Bids",e)}})),o()}function u(n){E.push((function(){d._each(n,(function(n){var e=i(n);h++,window[m].logEvent("Prebid.js Timeouts",e)}))})),o()}function c(n){E.push((function(){var e=i(n);h++,window[m].logEvent("Prebid.js Wins",e)})),o()}var l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?"symbol":typeof n},r=t(9),d=t(0),f=t(3),p=t(5),b=f.EVENTS.BID_REQUESTED,y=f.EVENTS.BID_TIMEOUT,v=f.EVENTS.BID_RESPONSE,g=f.EVENTS.BID_WON,E=[],m=null,T=!0,h=0,w=!0,S=["bidder","bidderCode","size","cpm","statusMessage","timeToRespond","adUnitCode"];e.enableAnalytics=function(n){var e=n.provider,t=n.options;m=e||"amplitude",w=void 0===t||void 0===t.sampling||Math.random()2&&void 0!==arguments[2]?arguments[2]:{},t=g.Renderer.install({id:r.renderer_id,url:r.renderer_url,config:a,loaded:!1});try{t.setRender(f)}catch(e){_.logWarn("Prebid Error calling setRender on renderer",e)}return t.setEventHandlers({impression:function(){return _.logMessage("AppNexus outstream video impression event")},loaded:function(){return _.logMessage("AppNexus outstream video loaded event")},ended:function(){_.logMessage("AppNexus outstream renderer video event"),document.querySelector("#"+e).style.display="none"}}),t}function n(e){var r=[];return _._each(e,(function(e,a){if(_.isArray(e)){var t=[];_._each(e,(function(e){(e=_.getValueString("keywords."+a,e))&&t.push(e)})),e=t}else{if(e=_.getValueString("keywords."+a,e),!_.isStr(e))return;e=[e]}r.push({key:a,value:e})})),r}function d(e,r,a){var t={requestId:e.uuid,cpm:r.cpm,creativeId:r.creative_id,dealId:r.deal_id,currency:"USD",netRevenue:!0,ttl:300,appnexus:{buyerMemberId:r.buyer_member_id}};if(r.rtb.video){if(h(t,{width:r.rtb.video.player_width,height:r.rtb.video.player_height,vastUrl:r.rtb.video.asset_url,ttl:3600}),r.renderer_url){var i=_.deepAccess(a.bids[0],"renderer.options");h(t,{adResponse:e,renderer:s(t.adUnitCode,r,i)}),t.adResponse.ad=t.adResponse.ads[0],t.adResponse.ad.video=t.adResponse.ad.rtb.video}}else if(r.rtb[E.NATIVE]){var n=r.rtb[E.NATIVE];t[E.NATIVE]={title:n.title,body:n.desc,cta:n.ctatext,sponsoredBy:n.sponsored,clickUrl:n.link.url,clickTrackers:n.link.click_trackers,impressionTrackers:n.impression_trackers},n.main_img&&(t.native.image={url:n.main_img.url,height:n.main_img.height,width:n.main_img.width}),n.icon&&(t.native.icon={url:n.icon.url,height:n.icon.height,width:n.icon.width})}else{h(t,{width:r.rtb.banner.width,height:r.rtb.banner.height,ad:r.rtb.banner.content});try{var d=r.rtb.trackers[0].impression_urls[0],o=_.createTrackPixelHtml(d);t.ad+=o}catch(e){_.logError("Error appending tracking pixel",e)}}return t}function o(e){var r={};if(r.sizes=u(e.sizes),r.primary_size=r.sizes[0],r.ad_types=[],r.uuid=e.bidId,e.params.placementId?r.id=parseInt(e.params.placementId,10):r.code=e.params.invCode,r.allow_smaller_sizes=e.params.allowSmallerSizes||!1,r.use_pmt_rule=e.params.usePaymentRule||!1,r.prebid=!0,r.disable_psa=!0,e.params.reserve&&(r.reserve=e.params.reserve),e.params.position&&(r.position={above:1,below:2}[e.params.position]||0),e.params.trafficSourceCode&&(r.traffic_source_code=e.params.trafficSourceCode),e.params.privateSizes&&(r.private_sizes=u(e.params.privateSizes)),e.params.supplyType&&(r.supply_type=e.params.supplyType),e.params.pubClick&&(r.pubclick=e.params.pubClick),e.params.extInvCode&&(r.ext_inv_code=e.params.extInvCode),e.params.externalImpId&&(r.external_imp_id=e.params.externalImpId),_.isEmpty(e.params.keywords)||(r.keywords=n(e.params.keywords)),(e.mediaType===E.NATIVE||_.deepAccess(e,"mediaTypes."+E.NATIVE))&&(r.ad_types.push(E.NATIVE),e.nativeParams)){var a=l(e.nativeParams);r[E.NATIVE]={layouts:[a]}}var t=_.deepAccess(e,"mediaTypes."+E.VIDEO),i=_.deepAccess(e,"mediaTypes.video.context");return(e.mediaType===E.VIDEO||t)&&r.ad_types.push(E.VIDEO),(e.mediaType===E.VIDEO||t&&"outstream"!==i)&&(r.require_asset_url=!0),e.params.video&&(r.video={},Object.keys(e.params.video).filter((function(e){return(0,k.default)(T,e)})).forEach((function(a){return r.video[a]=e.params.video[a]}))),(_.isEmpty(e.mediaType)&&_.isEmpty(e.mediaTypes)||e.mediaType===E.BANNER||e.mediaTypes&&e.mediaTypes[E.BANNER])&&r.ad_types.push(E.BANNER),r}function u(e){var r=[],a={};if(_.isArray(e)&&2===e.length&&!_.isArray(e[0]))a.width=parseInt(e[0],10),a.height=parseInt(e[1],10),r.push(a);else if("object"===(void 0===e?"undefined":b(e)))for(var t=0;t0&&(u.member_id=d),{method:"POST",url:"//ib.adnxs.com/ut/v3/prebid",data:JSON.stringify(u),bidderRequest:r}},interpretResponse:function(e,r){var a=this,t=r.bidderRequest,i=[];if(!(e=e.body)||e.error){var s="in response for "+t.bidderCode+" adapter";return e&&e.error&&(s+=": "+e.error),_.logError(s),i}return e.tags&&e.tags.forEach((function(e){var r=m(e);if(r&&0!==r.cpm&&(0,k.default)(a.supportedMediaTypes,r.ad_type)){var s=d(e,r,t);s.mediaType=v(r),i.push(s)}})),i},getUserSyncs:function(e){if(e.iframeEnabled)return[{type:"iframe",url:"//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html"}]}};(0,I.registerBidder)(R)},112:function(e,r){}},[110]); pbjsChunk([99],{137:function(e,r,t){e.exports=t(138)},138:function(module,exports,__webpack_require__){"use strict";function _interopRequireWildcard(e){if(e&&e.__esModule)return e;var r={};if(null!=e)for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(r[t]=e[t]);return r.default=e,r}function publisherTagAvailable(){return"undefined"!=typeof Criteo&&Criteo.PubTag&&Criteo.PubTag.Adapters&&Criteo.PubTag.Adapters.Prebid}function buildContext(e){var r=utils.getTopWindowUrl(),t=(0,_url.parse)(r).search,i={url:r,debug:"1"===t.pbt_debug,noLog:"1"===t.pbt_nolog,integrationMode:void 0};return e.forEach((function(e){e.params.integrationMode&&(i.integrationMode=e.params.integrationMode)})),i}function buildCdbUrl(e){var r=CDB_ENDPOINT;return r+="?profileId="+PROFILE_ID,r+="&av="+String(ADAPTER_VERSION),r+="&cb="+String(Math.floor(99999999999*Math.random())),e.integrationMode in INTEGRATION_MODES&&(r+="&im="+INTEGRATION_MODES[e.integrationMode]),e.debug&&(r+="&debug=1"),e.noLog&&(r+="&nolog=1"),r}function buildCdbRequest(e,r){var t=void 0,i={publisher:{url:e.url},slots:r.map((function(e){t=e.params.networkId||t;var r={impid:e.adUnitCode,transactionid:e.transactionId,auctionId:e.auctionId,sizes:e.sizes.map((function(e){return e[0]+"x"+e[1]}))};return e.params.zoneId&&(r.zoneid=e.params.zoneId),e.params.publisherSubId&&(r.publishersubid=e.params.publisherSubId),e.params.nativeCallback&&(r.native=!0),r}))};return t&&(i.publisher.networkid=t),i}function createNativeAd(e,r,t){return window.criteo_prebid_native_slots=window.criteo_prebid_native_slots||{},window.criteo_prebid_native_slots[e]={callback:t,payload:r},'

"}function d(e){var r=e.params;if("video"===e.mediaType){var t=[];return r.video.playerWidth&&r.video.playerHeight?t=[r.video.playerWidth,r.video.playerHeight]:Array.isArray(e.sizes)&&e.sizes.length>0&&Array.isArray(e.sizes[0])&&e.sizes[0].length>1&&(t=e.sizes[0]),t}return c(Array.isArray(r.sizes)?r.sizes:u(e.sizes))}function u(e){return f.parseSizesInput(e).reduce((function(e,r){var t=parseInt(m[r],10);return t&&e.push(t),e}),[])}function c(e){var r=[15,2,9];return e.sort((function(e,t){var i=r.indexOf(e),n=r.indexOf(t);return i>-1||n>-1?-1===i?1:-1===n?-1:i-n:e-t}))}Object.defineProperty(r,"__esModule",{value:!0}),r.spec=void 0;var p=(function(){function e(e,r){var t=[],i=!0,n=!1,o=void 0;try{for(var a,s=e[Symbol.iterator]();!(i=(a=s.next()).done)&&(t.push(a.value),!r||t.length!==r);i=!0);}catch(e){n=!0,o=e}finally{try{!i&&s.return&&s.return()}finally{if(n)throw o}}return t}return function(r,t){if(Array.isArray(r))return r;if(Symbol.iterator in Object(r))return e(r,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};r.masSizeOrdering=c,r.resetUserSync=function(){h=!1};var f=(function(e){if(e&&e.__esModule)return e;var r={};if(null!=e)for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(r[t]=e[t]);return r.default=e,r})(t(0)),v=t(1),y=t(2),m={1:"468x60",2:"728x90",5:"120x90",8:"120x600",9:"160x600",10:"300x600",13:"200x200",14:"250x250",15:"300x250",16:"336x280",19:"300x100",31:"980x120",32:"250x360",33:"180x500",35:"980x150",37:"468x400",38:"930x180",43:"320x50",44:"300x50",48:"300x300",54:"300x1050",55:"970x90",57:"970x250",58:"1000x90",59:"320x80",60:"320x150",61:"1000x1000",65:"640x480",67:"320x480",68:"1800x1000",72:"320x320",73:"320x160",78:"980x240",79:"980x300",80:"980x400",83:"480x300",94:"970x310",96:"970x210",101:"480x320",102:"768x1024",103:"480x280",108:"320x240",113:"1000x300",117:"320x100",125:"800x250",126:"200x600",144:"980x600",195:"600x300",199:"640x200",213:"1030x590",214:"980x360"};f._each(m,(function(e,r){return m[e]=r}));var x=r.spec={code:"rubicon",aliases:["rubiconLite"],supportedMediaTypes:["banner","video"],isBidRequestValid:function(e){if("object"!==l(e.params))return!1;var r=e.params;return!!/^\d+$/.test(r.accountId)&&(!(d(e).length.01?n.floor:.01,element_id:e.adUnitCode,name:e.adUnitCode,language:n.video.language,width:s[0],height:s[1],size_id:n.video.size_id};return n.inventory&&"object"===l(n.inventory)&&(c.inventory=n.inventory),n.keywords&&Array.isArray(n.keywords)&&(c.keywords=n.keywords),n.visitor&&"object"===l(n.visitor)&&(c.visitor=n.visitor),u.slots.push(c),{method:"POST",url:"//fastlane-adv.rubiconproject.com/v1/auction/video",data:u,bidRequest:e}}var p=e.params,v=p.accountId,m=p.siteId,x=p.zoneId,h=p.position,_=p.floor,b=p.keywords,g=p.visitor,w=p.inventory,j=p.userId;_=(_=parseFloat(_))>.01?_:.01,h=h||"btf";var I=d(e),z=["account_id",v,"site_id",m,"zone_id",x,"size_id",I[0],"alt_size_ids",I.slice(1).join(",")||void 0,"p_pos",h,"rp_floor",_,"rp_secure",i()?"1":"0","tk_flint","pbjs_lite_v1.5.0-pre","x_source.tid",e.transactionId,"p_screen_res",o(),"kw",b,"tk_user_key",j];return null!==g&&"object"===(void 0===g?"undefined":l(g))&&f._each(g,(function(e,r){return z.push("tg_v."+r,e)})),null!==w&&"object"===(void 0===w?"undefined":l(w))&&f._each(w,(function(e,r){return z.push("tg_i."+r,e)})),z.push("rand",Math.random(),"rf",t),z=z.concat(a()),z=z.reduce((function(e,r,t){return t%2==0&&void 0!==z[t+1]?e+r+"="+encodeURIComponent(z[t+1])+"&":e}),"").slice(0,-1),{method:"GET",url:"//fastlane.rubiconproject.com/a/api/fastlane.json",data:z,bidRequest:e}}))},interpretResponse:function(e,r){var t=r.bidRequest,i=(e=e.body).ads;return"object"!==(void 0===e?"undefined":l(e))||"ok"!==e.status?[]:("object"===(void 0===t?"undefined":l(t))&&"video"===t.mediaType&&"object"===(void 0===i?"undefined":l(i))&&(i=i[t.adUnitCode]),!Array.isArray(i)||i.length'}Object.defineProperty(r,"__esModule",{value:!0}),r.spec=void 0;var u=Object.assign||function(e){for(var r=1;r -1; } /** * Checks if ad slot is in view and window is focused */ function adIsInView(slotElement) { const windowHasFocus = $window.document.hasFocus(); if (!windowHasFocus) { return false; } else { return $visibility.isElementInViewport(slotElement); } } /* * Create ad object */ this.createAd = function(el) { var type = el.getAttribute("data-type"), pageType = el.getAttribute("data-page-type"), prebid = el.getAttribute("data-prebid"), dataDfpSizes = el.getAttribute("data-sizes"), dataPrebidSizes = el.getAttribute("data-prebid-sizes"), rubiconZoneId = el.getAttribute("data-zone-id"), appnexusPlacementId = el.getAttribute("data-placement-id"), trustxId = el.getAttribute("data-trustx-id"), criteoZoneId = el.getAttribute("data-criteo-id"), sizes; if (!adIndex[type]) { adIndex[type] = 1; } el.id = type + "-" + adIndex[type]; adIndex[type]++; var ad = { id: el.id, type: type, pageType: pageType, prebid: prebid, dfpSizes: getSizeArray(dataDfpSizes), prebidSizes: getSizeArray(dataPrebidSizes), rubiconZoneId: rubiconZoneId, appnexusPlacementId: appnexusPlacementId, trustxId: trustxId, criteoZoneId: criteoZoneId }; return ad; }; /** * Returns sizes as an array * @param {string} sizes * @returns {array} */ function getSizeArray(sizes) { var sizesArray = []; if (sizes && sizes.length) { sizes = sizes.split(","); _.map(sizes, function(size) { size = size.split("x"); w = parseInt(size[0]); h = parseInt(size[1]); sizesArray.push([w, h]); }); } return sizesArray; } /** * Returns ad node * @returns {string} */ function getAdNode() { let adNodeElement = document.querySelector("div[data-adnode]"), adNode = ""; if (adNodeElement) { adNode = adNodeElement.getAttribute("data-adnode"); } return adNode; } /** * Returns DFP ad unit * @returns {string} */ function getAdUnit() { const networkCode = "91898098", platform = "slate.com", adNode = getAdNode(); return networkCode + "/" + platform + "/" + adNode; } /** * Whether or not to run prebid for this slot * Returns true if prebid is enabled for this slot * and prebid isn't requesting bids for other slot at this time. * @returns {boolean} */ function runPrebid(ad_data) { return ad_data.prebid === "true" && !pbjs.adserverRequestSent; } } ]); "use strict"; DS.service("ajax", [ "_", function(_) { var ajaxService = this; // helpful for testing. /** * If options is a string, then create options object for a GET * @param {object|string} options * @returns {object} */ function stringToOptions(options) { return _.isString(options) ? { method: "GET", url: options } : options; } /** * * @param {number} [status] * @param {object} [err] * @returns {object} */ function errorWithStatus(status, err) { return _.set(err || {}, "status", status); } /** * @callback errorXhrCallback * @param {object} error * @param {number} [error.status] * @param {XMLHttpRequest} xhr */ /** * Send an AJAX request. * @param {object|string} options if string, performs a GET * @param {object} [options.headers] * @param {object|string} [options.data] * @param {errorXhrCallback} callback (see definition above in `@callback errorXhrCallback`) */ function send(options, callback) { var xhr = new XMLHttpRequest(); options = stringToOptions(options); xhr.open(options.method, options.url, true); // always async _.each(options.headers, function(value, key) { xhr.setRequestHeader(key, value); }); if (_.isObject(options.data)) { options.data = JSON.stringify(options.data); } xhr.addEventListener( "load", function() { var error = xhr.readyState === 4 && xhr.status 969, backfillId; if (slotId === "#outstream-video-1") { backfillId = isDesktop ? "outstream-backfill-1" : "outstream-backfill-tablet-1"; } if (slotId === "#outstream-video-2") { backfillId = isDesktop ? "outstream-backfill-2" : "outstream-backfill-tablet-2"; } if (slotId === "#mobile-outstream-video-1") { backfillId = "outstream-backfill-mobile-1"; } if (slotId === "#mobile-outstream-video-2") { backfillId = "outstream-backfill-mobile-2"; } if (backfillId) { var el = document.getElementById(backfillId); el.style.display = "block"; } }; } ]); /* globals window: false, document: false, URL: false, location: false, history: false, DS: false */ /* eslint no-console: ["error", { allow: ["warn", "error"] }] */ DS.service("via", function() { "use strict"; // remove `via` from url, to be used after amplitude logs it to prevent users from sharing such urls function removeFromLocation() { const url = new URL(location.href); url.searchParams.delete("via"); history.replaceState(null, "", url.toString()); } // and add `via` param to any outbound links function addViaToUrl(href, via) { if (!href || href.substr(0, 1) === "#") { return href; // don't add to jumps on the current page, e.g. "Skip to main content" } const url = new URL(href); const apexDomain = new URL(location.href).hostname .split(".") .slice(-2) .join("."); if (url.hostname.indexOf(apexDomain) === -1) { return href; // don't add it to external links } url.searchParams.set("via", via); return url.toString(); } // keys correspond to "page_types" in editable_components.yml const PREFIXES = { article: "article", homepage: "homepage", "vertical front": "section", "rubric front": "rubric" }; let pageType; function setPageType(amplitudePageType) { pageType = PREFIXES[amplitudePageType]; } const DELIMITER = "_"; function concatVia(node, via) { const tag = node.dataset && node.dataset.via; if (tag) { via = (via.length ? tag + DELIMITER : tag) + via; } return via; } function addToClickedLinks() { document.documentElement.addEventListener("click", function(e) { let a; let via = ""; // detect link nodes and collect via directives to append to the href let node = e.target; while (node !== e.currentTarget) { if (node.tagName === "A") { a = node; } via = concatVia(node, via); node = node.parentNode; } if (a && via) { if (pageType) { via = pageType + DELIMITER + via; } a.href = addViaToUrl(a.href, via); } }); } function addToSubmittedForms() { document.documentElement.addEventListener("submit", function(e) { const form = e.target; // collect via directives let via = ""; let node = e.target; while (node !== e.currentTarget) { via = concatVia(node, via); node = node.parentNode; } if (via) { if (pageType) { via = pageType + DELIMITER + via; } // dynamically create a hidden input for the form url var input = document.createElement("input"); input.type = "hidden"; input.name = "via"; input.value = via; form.appendChild(input); } }); } // start listening only once, when first injected addToClickedLinks(); addToSubmittedForms(); return { setPageType: setPageType, removeFromLocation: removeFromLocation }; }); "use strict"; DS.service("$visibility", [ "$document", "$window", "_", "Eventify", function($document, $window, _, Eventify) { var list = [], Visible, VisibleEvent; /** * @param {number} a * @param {number} b * @returns {*} * @see http://jsperf.com/math-min-vs-if-condition-vs/8 */ function min(a, b) { return a b ? a : b; } /** * Fast loop through watched elements */ function onScroll() { list.forEach(updateVisibility); } /** * updates seen property * @param {Visble} item * @param {{}} evt * @fires Visible#shown * @fires Visible#hidden */ function updateSeen(item, evt) { var px = evt.visiblePx, percent = evt.visiblePercent; // if some pixels are visible and we're greater/equal to threshold if (px && percent >= item.shownThreshold && !item.seen) { item.seen = true; setTimeout(function() { item.trigger("shown", new VisibleEvent("shown", evt)); }, 15); // if no pixels or percent is less than threshold } else if ((!px || percent = 0 && rect.left >= 0 && rect.bottom 1) { result += getLinearSpacialHash( remainder, Math.floor(stepSize / base), optimalK - 1, base ); } return result; } /** * @param {ClientRect} rect * @param {number} innerHeight * @returns {number} */ function getVerticallyVisiblePixels(rect, innerHeight) { return ( min(innerHeight, max(rect.bottom, 0)) - min(max(rect.top, 0), innerHeight) ); } /** * Get offset of element relative to entire page * * @param {Element} el * @returns {{left: number, top: number}} * @see http://jsperf.com/offset-vs-getboundingclientrect/7 */ function getPageOffset(el) { var offsetLeft = el.offsetLeft, offsetTop = el.offsetTop; while ((el = el.offsetParent)) { offsetLeft += el.offsetLeft; offsetTop += el.offsetTop; } return { left: offsetLeft, top: offsetTop }; } /** * Execute function when any of the selectors become visible * * Safely stops watching all selectors after first 'shown' event. * * @param {string} selector * @param {function} fn * @returns {[Visible]} Array of elements that we're watching for visibility */ function watchForAny(selector, fn) { var el, visibleList; selector = selector.split(","); visibleList = _.filter( _.map(selector, function(selector) { el = $document.querySelector(selector); return ( el && new Visible(el).on("shown", function() { // stop watching for visibility _.invokeMap(visibleList, "destroy"); // let them proceed fn(); }) ); }) ); return visibleList; } /** * Create a new Visible class to observe when elements enter and leave the viewport * * Call destroy function to stop listening (this is until we have better support for watching for Node Removal) * @param {Element} el * @param {{shownThreshold: number, hiddenThreshold: number}} [options] * @class * @example this.visible = new $visibility.Visible(el); */ Visible = function(el, options) { options = options || {}; this.el = el; this.seen = false; this.preload = false; this.preloadThreshhold = (options && options.preloadThreshhold) || 0; this.shownThreshold = (options && options.shownThreshold) || 0; this.hiddenThreshold = (options && min(options.shownThreshold, options.hiddenThreshold)) || 0; list.push(this); updateVisibility(this); // set immediately to visible or not }; Visible.prototype = { /** * Stop triggering. */ destroy: function() { // remove from list list.splice(list.indexOf(this), 1); } /** * @name Visible#on * @function * @param {'shown'|'hidden'} e EventName * @param {function} cb Callback */ /** * @name Visible#trigger * @function * @param {'shown'|'hidden'} e * @param {{}} */ }; Eventify.enable(Visible.prototype); VisibleEvent = function(type, options) { this.type = type; _.assign(this, options); }; // listen for scroll events (throttled) $document.addEventListener("scroll", _.throttle(onScroll, 200)); // public this.getPageOffset = getPageOffset; this.getLinearSpacialHash = getLinearSpacialHash; this.getVerticallyVisiblePixels = getVerticallyVisiblePixels; this.getViewportHeight = getViewportHeight; this.getViewportWidth = getViewportWidth; this.isElementNotHidden = isElementNotHidden; this.isElementInViewport = isElementInViewport; this.watchForAny = watchForAny; this.Visible = Visible; } ]); !function e(t,n,o){function r(c,s){if(!n[c]){if(!t[c]){var a="function"==typeof require&&require;if(!s&&a)return a(c,!0);if(i)return i(c,!0);var u=new Error("Cannot find module '"+c+"'");throw u.code="MODULE_NOT_FOUND",u}var d=n[c]={exports:{}};t[c][0].call(d.exports,function(e){var n=t[c][1][e];return r(n?n:e)},d,d.exports,e,t,n,o)}return n[c].exports}for(var i="function"==typeof require&&require,c=0;c1){if(i=e({path:"/"},o.defaults,i),"number"==typeof i.expires){var s=new Date;s.setMilliseconds(s.getMilliseconds()+864e5*i.expires),i.expires=s}i.expires=i.expires?i.expires.toUTCString():"";try{c=JSON.stringify(r),/^[\{\[]/.test(c)&&(r=c)}catch(e){}r=n.write?n.write(r,t):encodeURIComponent(String(r)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),t=encodeURIComponent(String(t)),t=t.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),t=t.replace(/[\(\)]/g,escape);var a="";for(var u in i)i[u]&&(a+="; "+u,i[u]!==!0&&(a+="="+i[u]));return document.cookie=t+"="+r+a}t||(c={});for(var d=document.cookie?document.cookie.split("; "):[],l=/(%[0-9A-Z]{2})+/g,f=0;f1){if(i=e({path:"/"},r.defaults,i),"number"==typeof i.expires){var s=new Date;s.setMilliseconds(s.getMilliseconds()+864e5*i.expires),i.expires=s}i.expires=i.expires?i.expires.toUTCString():"";try{c=JSON.stringify(o),/^[\{\[]/.test(c)&&(o=c)}catch(e){}o=t.write?t.write(o,n):encodeURIComponent(String(o)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)),n=n.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),n=n.replace(/[\(\)]/g,escape);var u="";for(var f in i)i[f]&&(u+="; "+f,i[f]!==!0&&(u+="="+i[f]));return document.cookie=n+"="+o+u}n||(c={});for(var a=document.cookie?document.cookie.split("; "):[],p=/(%[0-9A-Z]{2})+/g,l=0;l1){if(i=e({path:"/"},o.defaults,i),"number"==typeof i.expires){var s=new Date;s.setMilliseconds(s.getMilliseconds()+864e5*i.expires),i.expires=s}i.expires=i.expires?i.expires.toUTCString():"";try{c=JSON.stringify(r),/^[\{\[]/.test(c)&&(r=c)}catch(e){}r=t.write?t.write(r,n):encodeURIComponent(String(r)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)),n=n.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),n=n.replace(/[\(\)]/g,escape);var u="";for(var d in i)i[d]&&(u+="; "+d,i[d]!==!0&&(u+="="+i[d]));return document.cookie=n+"="+r+u}n||(c={});for(var a=document.cookie?document.cookie.split("; "):[],l=/(%[0-9A-Z]{2})+/g,f=0;f10?void console.error("RETRY LIMIT EXCEEDED"):void setTimeout(function(){f(e,t+1)},u)};return s}])},{"../../services/client/analytics-js":3,"../../services/universal/membership":4}],2:[function(e,t,n){!function(e){var o=!1;if("function"==typeof define&&define.amd&&(define(e),o=!0),"object"==typeof n&&(t.exports=e(),o=!0),!o){var i=window.Cookies,r=window.Cookies=e();r.noConflict=function(){return window.Cookies=i,r}}}(function(){function e(){for(var e=0,t={};e1){if(r=e({path:"/"},o.defaults,r),"number"==typeof r.expires){var s=new Date;s.setMilliseconds(s.getMilliseconds()+864e5*r.expires),r.expires=s}r.expires=r.expires?r.expires.toUTCString():"";try{c=JSON.stringify(i),/^[\{\[]/.test(c)&&(i=c)}catch(e){}i=n.write?n.write(i,t):encodeURIComponent(String(i)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),t=encodeURIComponent(String(t)),t=t.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),t=t.replace(/[\(\)]/g,escape);var a="";for(var u in r)r[u]&&(a+="; "+u,r[u]!==!0&&(a+="="+r[u]));return document.cookie=t+"="+i+a}t||(c={});for(var f=document.cookie?document.cookie.split("; "):[],l=/(%[0-9A-Z]{2})+/g,p=0;p

A Look Back at the 1960s PLATO Computing System

$
0
0
A student using the touch panel during a genetics lesson
Photo: Paul Tenczar/University of Illinois

In the 1960s, researchers at the University of Illinois Urbana—Champaign developed a computer system that they hoped would expand access to education. They envisioned instructors using the system to build lessons, and students stationing themselves at machines—whose touchscreen plasma displays had a distinct orange glow—to complete coursework.

But something unexpected happened. As the system began to catch on, students quickly spun its best features—which included emoticons, chat rooms, and email—into an early social network of sorts.

“Many features we’d take for granted in AOL chat rooms many years later, such as instant messaging and opening chat rooms with multiple people—all of that was available in what was called Talk-o-Matic on PLATO,“ says Brian Dear, author of The Friendly Orange Glow: The Untold Story of the PLATO System and the Dawn of Cyberculture. 

On Sunday at the 2018 South by Southwest Interactive conference in Austin, Texas, Dear shared his research on the history of PLATO (which stands for Programmed Logic for Automatic Teaching Operations), and its strange evolution from educational tool to a platform for bustling online communities that predated the era of personal computing. Dear’s book is available at Amazon.com and Barnes & Noble.

PLATO, which was invented by electrical engineering professor Donald Bitzer, was originally housed at the University of Illinois’ Computer-based Education Research Laboratory (CERL). Eventually, universities around the United States and the rest of the world installed more than a thousand PLATO terminals. Many of them could connect to systems on other campuses through telephone lines.

Before long, this pioneering system boasted more users than the experimental computer network known as ARPANET. But PLATO has now been largely forgotten (a fate that also befell other early networks and time-sharing services such as Tymnet). Meanwhile, ARPANET went on to support the World Wide Web.

PLATO's slow fade into obscurity can't be explained away by technical glitches or poor user design. In fact, the system proved quite popular and far more flexible than even its founders could have predicted.

As Dear tells it, students were far more interested in using PLATO to chat with one another than to complete their lessons. They even created their own emoticons by taking advantage of a feature within PLATO’s operating system that allowed them to stack letters on top of each other instead of typing them side by side. Typing the word “WOBTAX” in a stack produced a smiley face.

“This was a great pastime of many undergraduates, many of whom would flunk out of school for stuff like this,” Dear says. He adds that those early communities were also rife with problems that still exist today—including phishing, catfishing (sharing false information online about one’s identity), and password theft.

PLATO also included a feature very familiar to modern app users: a typing awareness indicator akin to the three dots that pop up in Facebook Messenger, Google Chat, or in iMessage when someone is typing. But instead of using dots to indicate that a message was being typed, PLATO revealed every single character that a person typed, as they were typing it.

Students also spent a lot of time playing games on PLATO—a pastime that Dear says proved somewhat embarrassing to the University of Illinois, which had invested heavily in building a serious system for educational purposes.

“Out of the top 10 programs on PLATO running any day, most were games,” Dear says. “They used more CPU time than anything else.” In one popular game called Empire, players blast each other’s spaceships with phasers and torpedoes in order to take over planets.

Dear himself worked with PLATO in 1979 as a freshman at the University of Delaware, which had 450 terminals on its campus. Then in 1984, he was running a PLATO lab for the University of Maryland when he noticed that someone else in an online chat room was using a terminal at his former desk in another lab on campus. He sent a message to that user; she would later become his wife.

Dear hopes that sharing the story of PLATO can clear up a few misconceptions about the history of computing and online networks. Among them is the mistaken belief that online education is a new idea. “You could take, in the mid-60s, for full college credit, several courses through PLATO,” Dear says. And though many people assume social networks only sprung up after the first personal computers were sold, online communities existed long before most people had a computer at home. 

Dear also argues that with PLATO’s demise, the field of computing lost out on features and settings that could have been quite valuable today. For example, the entire PLATO system was designed with social sharing in mind, he says, whereas the modern Internet is more fundamentally suited to fetching websites and documents.

“Imagine if today, iOS or Linux had built-in libraries of code that allowed anyone to build a social application that didn’t require cutting a deal with Facebook or using their APIs,” Dear says. “[With PLATO], the API was in the operating system and it allowed any app to be social. That was kind of the assumption that the PLATO people had.”

Another helpful feature that no longer exists was called Term Comment. It allowed users to leave feedback for developers and programmers at any place within a program where they spotted a typo or had trouble completing a task.

To do this, the user would simply open a comment box and leave a note right there on the screen. Term Comment would append the comment to the user’s place in the program so that the recipient could easily navigate to it and clearly see the problem, instead of trying to recreate it from scratch on their own system.

“That was immensely useful for developers,” Dear says. “If you were doing QA on software, you could quickly comment, and it would track exactly where the user left this comment. We never really got this on the Web, and it’s such a shame that we didn’t.”

Sadly, few PLATO terminals still exist. As Dear describes in his book, PLATO’s gradual extinction came from a series of missteps. Its creators were so focused on education that they failed to consider other, potentially more lucrative uses for their system. And it was hard to get schools to buy in, because at the time, each terminal cost US $8,000, plus $12,000 per year in telephone bills to operate it.

There is, however, at least one working PLATO terminal at the Living Computer Museum in Seattle. You can also play with a software simulation at cyber1.org.


Booting Windows NT 4 on a DEC Multia

$
0
0

The Multia feels to me like Digital’s take on a high-end “legacy-free” (at introduction in late 1994) desktop. Its spec sheet includes features unheard of in its contemporaries:

  • a 64-bit RISC processor
  • ECC memory
  • Ethernet networking
  • SCSI2 hard drive
  • high-resolution (1280x1024) monitor support
  • PCMCIA expansion slots

A RISC processor, powerful networking, and PCMCIA slots nearly sounds like a description of a Network Computer, but the Multia pre-dates the NC by more than a year. It was also ahead of its time in terms of its original OS: the fully 32-bit Windows NT 3.5. There wouldn’t be a 32-bit capable consumer Windows for another year (Windows 95 came out in late 1995).

Having an advanced-but-unfamiliar OS, expensive ECC memory and SCSI2 disks, and a non-Intel architecture didn’t lead to consumer success. Its low-end Alpha processor was starved of on-chip cache, so it wasn’t particularly attractive as a technical workstation. The Multia sold so poorly that later in its life Digital discounted the price heavily and sold it without an OS, targeting the nacent Linux desktop market.

A little Alpha

My Multia is the lowest-end model: a VX40 with a non-upgradable 166 MHz CPU. When it first arrived, I didn’t have a PS/2 keyboard and mouse to test with, so I was only able to verify that it came with Windows NT 4.0 Workstation pre-installed:

It’s mostly in good physical shape - the sliding floppy drive door isn’t broken off, the case hasn’t been roughed up, and nothing seems missing. The back panel is a bit corroded:

I’ve seen similar corrosion on several Multias for sale on eBay - perhaps it’s a design flaw rather than the result of living a hard life. The Multia has more than its fair share of design flaws - chief among them is its utterly inadequate cooling. Most guides strongly recommend running the Multia standing vertically (rather than pizzabox traditional horizontal) to expose more surface area to airflow - the NetBSD FAQ has a lot more detail.

First login

A few months later (once I had aquired peripherals) I was ready to log in. Since it’s running Windows NT, I was expecting to have to use rainbow tables to hack the Administrator password - the seller didn’t note it down for me. My understanding of this procedure is that I would mount the hard drive on another computer and match the Administrator password hash to a known password. First, though, I looked to see if there was a default post-install password.

To my surprise, the default is to have no password, and that was how my Multia was configured! I was presented with a stock standard Windows NT 4.0 desktop - the previous owner must have done a fresh install before selling it to me.

default windows nt 4.0 desktop

If you’ve never used Windows NT 4.0 Workstation before, it’s a lot like Windows 95. Most of the pre-installed applications are the same, the GUI is the same, even the desktop background is the same. There are certainly some differences (actually caring at all about user accounts, bits for integration with AD domains, and NTFS), but in terms of “what is it like to use”, it’s nothing surprising.

Connecting to my network

Another telltale sign that the previous owner did a fresh re-install before selling it is that it was at a pretty old patchlevel: SP1. I downloaded the final Service Pack (SP6), put it on my NAS’s “public” directory, and connected the Multia to my network. Some things don’t change much - thankfully for my vintage pizzaboxes, among them are:

  • 10Base-T Ethernet
  • DHCP
  • unauthenticated SMB & NFS

Running the upgrade from a NAS more than 20 years newer than the Multia went smoothly. Once updated, the version of Internet Explorer is still elderly:

internet explorer splash page

I tried to load up my favorite classic web site, the Space Jam marketing page. Unfortunately, though the page’s contents haven’t changed since 1996, it now is served via Cloudflare and doesn’t work over plain HTTP (HTTPS would fail b/c of cipher mismatches, as well as no SNI support):

space jam website wont load

From here

Setting up the Multia was anticlimactic - it Just Worked™ out of the box and its OS feels rather pedestrian. Finding software written for Win32/Alpha, though, is likely to be a challenge. Most software I have heard of is linked to DEC’s (dead) website. My hope is to find an IDE, some database software, and maybe a solid Telnet client or X server so I can experience the ways folks actually used them at the time.

Did you use a Multia back in the 1990s? Or maybe have a lead on some Alpha software? Send me an email at sophie@pizzabox.computer!

AWS Lambda and .zip is a recipe for serverless success

$
0
0

I’ve been involved in numerous discussions in recent days and weeks mainly on Twitter, but elsewhere, around when and how serverless is going to be working with, and supporting, containers instead of just “code”.
 
It’s mainly container folks who are absolutely desperate to bring their container fu to the serverless world, and start to make use of event driven provisioning and having to avoid some of the issues around provisioning of their own containers and the management that comes with that.
 
The conversation often boils down to…
 
“Well you’re using containers behind the scenes, so why can’t I just give you my container and you run that instead?”
 
So let’s try and answer that by explaining why .zip files are awesome.

How AWS Lambda runs your code

It’s relatively simple. You create an AWS Lambda function and specify a .zip file with the code in it. Yes there are various different runtimes and you need to get the .zip file structure correct, but the package received is simply a .zip file. 
 
It’s just a .zip file. 
 
And .zip files are simply a directory structure of files and folders that has been compressed into a standard format.
 
And that’s been around for a long time (1989 believe it or not).
 
That .zip file with code in it gets put into S3 and is linked to the Lambda function and at some point after it’s been uploaded, the Lambda function is invoked, and a cold start happens.
 
Then the magic happens (well it’s not actual magic, but it’s pretty clever).
 
In the background, we run an optimised execution environment for the runtime and version your function has specified, and we load your code into from the .zip file.
 
Then we execute your code (invoke the function) with the data in the event payload that has been sent to the function.
 
 Simple isn’t it?

  1. Create function code
  2. Zip the function code up
  3. Create an AWS Lambda function

Now you have a function. What happens on an event?

  1. Download the function code
  2. Start an execution environment
  3. Execution environment gets function code (the data in the .zip file) and bootstraps the runtime
  4. Execute the code with the event data payload

That’s a cold start. More in-depth explanation can be found on the Become a Serverless Blackbelt video on youtube.
 
Note: slow cold starts are often due to overly complex frameworks and dependencies. See https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html#function-code for more info e.g. for Java prefer dependency injection like Dagger or Guice, over Spring Framework.
 
A warm start is even simpler. It’s basically step 4 as steps 1 to 3 have been done already. The optimised execution environment is kept around for a while to be invoked, and if the event is triggered for which that function is required then the function is sent a new event payload and is invoked.

So it’s a “container”

There’s an execution environment of some sort in there behind the scenes running your code and AWS talked about that in the very beginning when Lambda was in preview (back then it was called a container).

https://aws.amazon.com/blogs/compute/container-reuse-in-lambda

With AWS Fargate you can build an actual container instead of just providing code in a .zip file and run it yourself, but you need to provision it yourself.
 
On the surface it seems like you’d get a better deal by building the container for a serverless scenario as you can control more factors.
 
You get to choose the exact version of the operating system if you really want. (As an example, you can create ‘FROM scratch’ docker images https://docs.docker.com/develop/develop-images/baseimages/)
 
You get to choose the exact libraries you want.
 
You get to choose the exact build versions and security patches.
 
You get to choose the security inside it as well.
 
You can scale with AWS Fargate as well and while you’re not managing servers, you’re still managing containers. And when scaling you’ll still have to consider the problem of cold starts. How fast does your container start up?
 
Warm starts are less of an issue though with containers as they are already running, but then you are paying for idle, which doesn’t happen with AWS Lambda. You only pay for invocations at that point.
 
But you also need to be able to manage and maintain the containers themselves. If somebody decides to build a badly designed container (inadvertently or due to inexperience) then the cold start on scaling may be compromised.

You also have to take into account that every single time there is a security patch needed to a container, that everybody needs to patch their own containers.
 
Remember Meltdown?

Adrian Cockroft had it right. You had to patch your containers and your instances/servers, but you didn’t have to patch Lambda functions. 
 
Why?
 
Because you don’t control the AWS Lambda execution environment or server.
 
AWS controls it.
 
AWS patched it all for you.
 
Think about it for a moment. 
 
You only gave them code in a .zip file.
 
Now with AWS Lambda, you still have to worry about library code that you put into functions needing security patches, but that’s a whole lot easier than having to worry about what actually executes that code.
 
And if you were going to have the same code as in the Lambda function anyway, but run it in your own container, then letting AWS run it in a Lambda function is removing the ops burden of managing the container.

But why .zip files?

Because .zip files are easy to create, easy to read (programmatically) and easy to work with.
 
In fact, they are ridiculously simple. Every developer can write code and make a zip file in several different ways.
 
But not only that, they are quick to extract and use.
 
And thinking about the build and deploy process, it’s far far easier to have a build process that looks like:

  1. Write code
  2. Test code
  3. .zip code
  4. Deploy code

Comparing it to “creating a Docker container” you have to create your Dockerfile and essentially create a series of commands to generate a server to run your code. While you can boilerplate this to an extent, the Dockerfile becomes a part of the system that needs to be maintained and managed, introducing both security issues and likely errors and bugs (especially if deployed over a period of time).
 
In terms of .zip files, step 3 is trivial, well understood, and very stable (nearly 30 years old). It is a step that is automatable by junior developers, and that makes development processes easier to understand, easier to teach, and very difficult to get wrong. 
 
Assuming that the code is the same in both, why introduce the complexity of the container unless you have to?
 
Using .zip files as the unit of deployment allows developers to focus on what they should be focusing on: the business logic and the value it provides to the business.

Serverless needs containers, but we don’t need to worry about them

You know the joke about “Serverless — You know there are still servers right?”
 
There’s another one…
 
“Serverless — you know there are still containers right?”
 
You could say, we’re “containerless” just as much as we’re serverless.
 
Part of the purpose of serverless as a concept is that the whole team can stop worrying about the complexity and management of deployment and simply focus on development.
 
Containers definitely have their place and value, and don’t think that we in the serverless world are thinking that you’re all wrong and don’t know what you’re talking about. The serverless world wouldn’t exist without containers and it’s really important to understand that and realise that we know that too.
 
But the thing is that with AWS Lambda and serverless, we don’t even need to worry about what a container is, or why it matters. AWS does the creation, the optimisation, the securing, the provisioning, the scaling and the patching of those execution environments for us.
 
It also means that AWS could build and deploy improvements to that technology and give customers those improvements without having to make any changes.
 
Because it’s just a .zip file.
 
Serverless people simply don’t have to worry about what a container is, or how it really works. 
 
Containers just don’t matter in the serverless world.
 
Because we write code.
 
And it runs on demand.
 
And it scales.
 
And it’s quick.
 
And if we ever needed a container, we could figure it out and run it on AWS Fargate.

Disclaimer

I currently work for AWS as a Senior Developer Advocate for Serverless based in the UK and working in EMEA.

Opinions expressed in this blog are mine and may or may not reflect the opinions of my employer.

Show HN: LabNotebook, a tool to monitor and record Deep Learning experiments

$
0
0

README.md

A simple experiment manager for deep learning experiments

labnotebook allows you to:

All you need to do is to modify your code to include labnotebook.start_experiment() and labnotebook.stop_experiment() and pass the info you would like to save to the database as arguments. As an option, you can save information for each training step by using labnotebook.step_experiment().

You can see a very simple example notebook here.

Why labnotebook?

In the life sciences, scientists write everything in their lab notebooks. I wanted a similar permanent store for my PyTorch experiments that allowed me to:

  • asynchronously look at what was going on. Tensoboard obviously provides excellent functionality, albeit with an interface and storage system that I didn't especially like. It's very hard to keep track of all the indicators of old experiments and to compare them to newer experiments.

  • store everything forever in a queryable database. Sacred provides some of this functionality, but the interface is complex and inflexible. In addition, I think experimental data is relational data intermixed with nosql data, and postgres is better adapted to the type of queries for this kind of experimental data.

Set up a postgres database:

Follow the detailed installation guides, create your database, and make a note of your database's url. It's usually of the form postgres://<username>:<password>@localhost/<databasename>.

Install labnotebook:

Clone the repository:

git clone https://github.com/henripal/labnotebook.git

Enter the directory and install labnotebook locally:

cd labnotebook
pip install .

Start the API:

Once you've installed the package, you can run the following command on your database url to start the API:

start_backend <database_url>

Start the webapp:

Simply navigate to labnotebook/frontend and double click on index.html.

A simple example notebook is available here.

This is a very early alpha version of the tool that I'd thought some people might enjoy. I haven't tested it on older browsers or frameworks. For now I've tested this only on Ubuntu, with PyTorch-style experiments, using chromium. I'm happy to get any feedback of how this runs on other platforms!

The front-end of this project uses VueJS, Vuetify and Highcharts.

Ghana teacher drawing MS word on blackboard gets real computers

$
0
0
Written by Shreya Das | New Delhi | Published: March 18, 2018 5:32 pm
ghana, ghana teach ms word viral photo, ghana teacher draw ms word to teach student, ghana school lack of computer, ict ghana, educatio news, niit ghana, africa news, good news, viral news, indian express Netizens applauded the teacher for drawing MS Word on blackboard for teaching students despite all odds. (Source: NIIT Ghana/ Facebook)

Teachers are the greatest heroes in our lives and along with our parents, and we owe them a lot. Often they go out of their ways to help students — be it the professor who carried his student’s child on his lap throughout the class to help a single mom or the teacher from Ghana who drew MS Word on a blackboard to teach his pupils — such stories always melt our hearts. Remember, Owura Kwadwo Hottish, the computer teacher at the Betenase M-A Junior High School in Sekyedomase town of Ghana, drew the entire interface of the Microsoft software on board as they lacked resources. But that certainly did not stop this young teacher from going the extra mile to let his students know a little more about computers.

As Hottish’s Facebook post went viral last month, it seems it has not only garnered thousands of likes but has also managed to effect change. After the post viral, an Indian company took note of the excellent work being attempted in Ghana and sent over some computers so that the children could get a look at what the actual interface looks! Yes, if you had any doubts about the good that can come out of social media, then let this be a fine example.

teacher in ghana, teacher in ghana ms word, ms word on blackboard teacher in ghana, teacher in ghana teaches MS word on blackboard, Ghana teacher teaches computer on blackboard, Ghana teacher draws MS word on blackboard, Indian Express, Indian Express news For more than six years this computer teacher has been drawing interfaces on blackboard for his students as the school did not have the resources. (Source: Owura Kwadwo Hottish/Facebook)

The photos posted by him on Facebook inspired NIIT Ghana, an offshore wing of the Indian firm, to donate five computers, a laptop and ICT textbooks to the school.

Sharing the positive news on Facebook, NIIT Ghana also wrote, “The District Chief Executive for the area has promised to setup an ICT centre with the computers which will help the entire community.” Recognising Hottish’s industrious effort, the company presented a new laptop to him as well. Along with it they also announced to provide free IT training for him. “The teacher will also benefit from a training package at NIIT Kumasi,” the post added.

Director of NIIT Ghana, Yaw Amoateng with the support of Sanjeev Mishra, Senior Manager of the Kumasi office of the firm handed over the computers to the Municipal Chief Executive and officials of the Municipal Education Directorate.

For all the latest Trending News, download Indian Express App

© IE Online Media Services Pvt Ltd

Luxe: free, cross platform, open source, rapid development game engine

$
0
0

the luxe editors complement both a data driven or code based workflow

luxe is for solo developers as well as teams, so while it provides a great code based workflow, it is not limited to code only.

editors and tools enhance the luxe workflow, which empowers artists, designers and programmers in the expression of ideas, and rapid game development.
The editor is designed for game specifics, just like the rest of the engine, so custom/project editors are easy, and shareable via modules.

editors help to build worlds, animation, ui and more.


worlds, modifiers, entities and systems

Games are a diverse medium with unique challenges. luxe makes the distinction between tools and features explicit, and builds around tools as a core principle.

This frees the engine from being bloated by things a fraction of games want, and instead aims to empower games to be specific, adaptive and exact. At runtime, you only pay for what you use.

By combining systems both high and low level,

luxe is like a toolbox, and a game is the connection of these into a whole.

When configuring a type of project to reuse (like a 2D platformer or 3D first person project), luxe provides outlines, which jump start projects into predefined workflows. Outlines are where custom workflows are defined above the engine level.

luxe provides pieces that snap together,

ready to make games.


What to do about NFS and SIGBUS

$
0
0

A couple of days ago, Iwrote about how running programs from NFS mounts can lead to interesting crashes if the files change on the file server. That post elicited a question from an anonymous reader:

Hey, I liked the article! Would you be willing to write a little bit about the strategies you can use to prevent the SIGBUS? The only thing I could think of is creating a wrapper script and the CI server building versions that include the datestamp and never deleting them.

Do you have better ideas?

So hey, I definitely have ideas. I don't know how practical they would be for every situation, so you'll have to judge and discard the ones which don't fit in.

My first recommendation would be to kick NFS to the curb. It's one of those things that you wind up having to do eventually in any organization once it grows beyond a certain size. The sooner you kick it, the less time and energy it'll take to get rid of it down the road.

I'm not completely opposed to using it for home directories, but then, those tend to be mounted in just a handful of locations at most. What bugs me is when you have huge mounts that are accessible from everywhere. At that point, people do the natural thing and start relying on it.

Before long, you have a ridiculous single point of failure that has no version control, really nutty caching behavior on some clients, and worse. There are also brilliant failure modes when you run big filers with primary/secondary relationships, and manually flip the sense of which one is feeding which at the wrong time.

If you have the option to ditch NFS and go to some explicit method of distributing your binaries, I recommend it highly.

Of course, if you decide to solve this by building RPMs because you're on a Red Hat-derived system, you will eventually run into scaling issues with yum, reliability issues with db4(yes, even five years later) and worse.

But hey, it's probably easier to go from RPM to not-RPM than it is to go from NFS to not-NFS. So there's that.

If you really want to stay on NFS for some reason, then my recommendation would be to try having unique names for your builds, and use symlinks or some other method of pointing at them. If the files are treated as immutable, there shouldn't be any rug-pulling going on when the kernel starts paging things back in.

Of course, if you do that, now you have a roach motel: the binaries check in, but they never check out. Given enough time, you will fill up your filer, no matter how big it is. Now you'll have to come up with a garbage collection strategy, and that means being able to somehow track who's on what version, and what's in use. Then you have the problem of managing restarts so that hosts stay up to date and you don't have to retain as many builds as there are broken hosts.

The above is just another day in this line of work.

Artificial data give the same results as real data

$
0
0

Although data scientists can gain great insights from large data sets — and can ultimately use these insights to tackle major challenges — accomplishing this is much easier said than done. Many such efforts are stymied from the outset, as privacy concerns make it difficult for scientists to access the data they would like to work with.

In a paper presented at the IEEE International Conference on Data Science and Advanced Analytics, members of the Data to AI Lab at the MIT Laboratory for Information and Decision Systems (LIDS) Kalyan Veeramachaneni, principal research scientist in LIDS and the Institute for Data, Systems, and Society (IDSS) and co-authors Neha Patki and Roy Wedge describe a machine learning system that automatically creates synthetic data — with the goal of enabling data science efforts that, due to a lack of access to real data, may have otherwise not left the ground. While the use of authentic data can cause significant privacy concerns, this synthetic data is completely different from that produced by real users — but can still be used to develop and test data science algorithms and models.

“Once we model an entire database, we can sample and recreate a synthetic version of the data that very much looks like the original database, statistically speaking,” says Veeramachaneni. “If the original database has some missing values and some noise in it, we also embed that noise in the synthetic version… In a way, we are using machine learning to enable machine learning.”

The paper describes the Synthetic Data Vault (SDV), a system that builds machine learning models out of real databases in order to create artificial, or synthetic, data. The algorithm, called "recursive conditional parameter aggregation," exploits the hierarchical organization of data common to all databases. For example, it can take a customer-transactions table and form a multivariate model for each customer based on his or her transactions.

This model captures correlations between multiple fields within those transactions — for example, the purchase amount and type, the time at which the transaction took place, and so on. After the algorithm has modeled and assembled parameters for each customer, it can then form a multivariate model of the these parameters themselves, and recursively model the entire database. Once a model is learned, it can synthesize an entire database, filled with artificial data.

Outcome and impact

After building the SDV, the team used it to generate synthetic data for five different publicly available datasets. They then hired 39 freelance data scientists, working in four groups, to develop predictive models as part of a crowd-sourced experiment. The question they wanted to answer was: “Is there any difference between the work of data scientists given synthesized data, and those with access to real data?" To test this, one group was given the original data sets, while the other three were given the synthetic versions. Each group used their data to solve a predictive modeling problem, eventually conducting 15 tests across 5 datasets. In the end, when their solutions were compared, those generated by the group using real data and those generated by the groups using synthetic data displayed no significant performance difference in 11 out of the 15 tests (70 percent of the time). 

These results suggest that synthetic data can successfully replace real data in software writing and testing — meaning that data scientists can use it to overcome a massive barrier to entry. “Using synthetic data gets rid of the ‘privacy bottleneck’ — so work can get started,” says Veeramachaneni.

This has implications for data science across a spectrum of industries. Besides enabling work to begin, synthetic data will allow data scientists to continue ongoing work without involving real, potentially sensitive data.

“Companies can now take their data warehouses or databases and create synthetic versions of them,” says Veeramachaneni. “So they can circumvent the problems currently faced by companies like Uber, and enable their data scientists to continue to design and test approaches without breaching the privacy of the real people — including their friends and family — who are using their services.”

In addition, the machine-learning model from Veeramachaneni and his team can be easily scaled to create very small or very large synthetic data sets, facilitating rapid development cycles or stress tests for big data systems. Artificial data is also a valuable tool for educating students — although real data is often too sensitive for them to work with, synthetic data can be effectively used in its place. This innovation can allow the next generation of data scientists to enjoy all the benefits of big data, without any of the liabilities.

The project was funded, in part, by Accenture and the National Science Foundation.


Ask HN: How did you find your first hire?

$
0
0

First ever: I was that stereotypical someone's son who's good with computers. (During highschool, sysadmin job)

First full-time: ~200 applications sent to lots of companies. (Uni break, applying to a different country)


Ideas are great but taking the first step to develop the product is very tiring. People just won’t have time to join your time. That’s common in India!

We were Always on the hunt for someone who could believe in launching the minimum sellable product. We met a lot of people and eventually we landed up with 2 Freshman developers from Waterloo. I had known one of them before he joined Waterloo and he brought in his friend.


Just hired a boot camp/immersive graduate. Going well but it’s a bit early.

Boot camps have lists of students and you can post jobs on their sites.

Worked out well for me because I was looking for Somone junior.

Prototype. The big bro behind ES6 class

$
0
0

One of the great things that ES6 brought us are classes. We need to remember though, that all the work is done by prototypes under the hood. It means that ES6 classes act just as syntactic sugar. In this article, I will walk you through the basics of the prototypes, so that you can better grasp the concept.

Understanding prototypes

prototype
object that provides shared properties for other objects

JavaScript happens to have an inheritance model quite different from most Object Oriented Programming languages. Objects do not have a class or type that they get their properties from: they use prototypes for it. In other languages, a class can inherit from another class (and that class can inherit from another class), in JavaScript, there is a prototype chain. 

Objects here act as wrappers for properties, which are called own properties, meaning that the object directly contains them. When trying to access a property it does not own, the prototype chain is traversed. Interpreter looks for the property from the closest prototype to the furthest, until either the property is found, or the prototype is null, which means the end of the chain.

Almost all objects in JavaScript are instances of Object. There is an easy way to observe it:

The function was called, even though we didn’t assign a function named valueOf to our dog. What happened is that the function was looked for through the prototype chain and the Object happens to have this function.

This time, since the dog itself, had a valueOf() function, there was no reason to look through the prototype chain. The same logic applies to all properties (not only functions).

In case you’re wondering what does  ...this mean, check out the spread syntax.

Constructors

constructor
function object that creates and initializes objects

It might seem confusing at first but in JavaScript, all functions are also objects. One of their properties is called a prototype and refers to an object. It will serve as a prototype for a newly created object when a function is called as a constructor with a new operator.

The Object.setPrototypeOf() method sets the prototype (i.e., the internal [[Prototype]] property) of a specified object to another object or null.

In the following example, a few things happen:

  1. Animal and Dog constructors are created. Dog“inherits” from Animal
  2. A new object is created, inheriting from Dog.prototype
  3. The Dog constructor is called with this referring to the new object
  4. The Animal constructor is called, with this referring to the new object
  5. Because the constructor does not return anything (it can, though), the return value of the  newDog('Fluffy) is the newly created object

In general, you should avoid changing the prototype of an already created object. If you’d like to read more on that subject, I recommend the MDN docs.

There are a few ways in which you can set the properties that a newly created object will inherit.

As you can see, it can be done both by adding properties to this in the constructor and modifying the prototype itself. Once again, the prototype chain was traversed – fluffy didn’t have the isItADog function, nor the Dog.prototype. Finally, it was found in the Object.prototype. It is worth mentioning that this in the function refers to the fluffy.

The instanceof operator tests whether the prototype property of a constructor appears anywhere in the prototype chain of an object.

Also, notice that if you add more properties to the prototype, they will be accessible to the objects that are an instance of it but were declared before adding the new property.

Accessing prototypes

You can always get the prototype of an object through the Object.getPrototypeOf function. There is also a __proto__ property, but it was not standardized until ES6 (even if it was implemented by most browsers).

It is not a property of our dog though: it is a property of Object.prototype. It acts as a getterSince it is the last prototype in the prototype chain of a dog, it will be called (if not overshadowed). This piece of code illustrates how it works:

ES6 class

As I already mentioned, ES6 classes act just as syntactic sugar for prototypes.

As you can see, these are just prototypes in the end. It looks much cleaner. What issues does it solve, aside from that? Quite a few, actually!

  • We have a super function inside of a constructor – it calls a constructor of a class that is being extended.
  • There is no need to call Object.setPrototypeOf to extend another prototype
  • You can extend any class easily and in a very natural way – even those of built-in objects (like the Array)

There are some drawbacks though. One of them is that using class syntax implicates that there are actually some classes involved, but there aren’t any. It can make the process seem even more complicated.

Summary

Understanding prototypes is one of the crucial tasks for an aspiring JavaScript developer. Currently, with the ES6 classes syntax popularized, it might seem even harder. It doesn’t make it less important though.

Edward Snowden: Facebook is a surveillance company rebranded as 'social media'

$
0
0

This story was updated at 10:37 p.m.

Former National Security Agency contractor Edward Snowden ripped Facebook in a tweet Saturday after the social media giant suspended Cambridge Analytica, a data analytics firm which worked worked for President Trump’s campaign.

Facebook accused the firm on Friday of not deleting data it had improperly harvested from Facebook users, which number in the tens of millions, but Snowden pinned the blame squarely on Facebook and lumped in other social media companies for being just as reckless.

"Businesses that make money by collecting and selling detailed records of private lives were once plainly described as 'surveillance companies,'" Snowden said. "Their rebranding as 'social media' is the most successful deception since the Department of War became the Department of Defense."

"Facebook makes their money by exploiting and selling intimate details about the private lives of millions, far beyond the scant details you voluntarily post," Snowden said earlier in the day. "They are not victims. They are accomplices."

Cambridge Analytica on Saturday denied any wrongdoing, issuing a statement that said the firm "fully complies" with Facebook's terms of service,

The ensuing uproar has prompted at least one lawmaker, Sen. Amy Klobuchar, D-Minn., to call on Facebook CEO Mark Zuckerberg to testify before the Senate Judiciary Committee.

Special counsel Robert Mueller, who is investigating possible collusion between the Trump campaign and Russia, reportedly asked Cambridge Analytica last fall to surrender emails from any of its employees who worked for the Trump campaign. The firm complied to the request.

Facebook has already taken heat for spreading "fake news" during the election and promised changes.

Last year Facebook handed Mueller its findings regarding Russian Facebook ads, revealed when the company announced $100,000 was purchased for ads from June 2015 to May 2017 by a Russian "troll farm" called the Internet Research Agency, which has promoted pro-Russian propaganda. The money was connected to approximately 3,000 ads and 470 "inauthentic accounts and pages."

Mueller later indicted 13 Russian nationals and three Russian entities for meddling in the 2016 presidential election, those who were part of the Internet Research Agency.

Snowden was granted asylum in Russia back in 2013 after he leaked secret information from the National Security Agency's surveillance programs and has been there ever since.

She Was My World, but We Couldn’t Marry

$
0
0

We eventually bonded over hamburgers at a campus cafeteria. A week later I organized a group paintball outing, mostly to invite her. She came, though she hid in terror behind a barricade for much of the afternoon. Next we got together for a bike ride followed by Brie-stuffed French toast at a local breakfast spot — not quite as romantic as paintball.

Six years later, our refrigerator is decorated with souvenir magnets from our trips across America. She’s my girlfriend and partner in life, even in my sickness. I massage her knotted shoulder when it freezes up, and we laugh riotously alone together in the dark after turning off the lights.

We find pleasure in hashing out legal issues from the news or Dunia’s job as a corporate lawyer. When I’m well enough, I accompany Dunia to the dozens of weddings she is invited to every year. Apparently she still relishes having me as her tuxedoed guest, and that makes me happy.

“These watches keep time perfectly,” the salesman said.

Coming off a month of almost constant bed rest, I was antsy and overcome by a compulsion to do something — stomp my feet, scream, buy a wristwatch, anything.

I had managed to hold on to the hefty bonus I had earned three years earlier, before taking medical leave from my Wall Street law firm. Maybe a watch would be an important investment, a commitment to my future during this difficult time.

Just do it, I told myself. Do something.

Staring at the stainless-steel timekeeper on my wrist, I sensed an impending front coming on, a dueling mix of unease and elation that accompanies an extravagant purchase worth more than three months of rent. I stared into the salesman’s hopeful eyes and announced my intentions without quivering.

“I’ll buy it.”

The salesman, suddenly quite spirited, set into motion a string of fanfare. His assistant produced a miniature bottle of champagne from a back room (the mere sight of which increased my nausea). A young man in a slim suit peeled the protective wrapping from the sapphire crystal of my chronometer and began to polish feverishly.

He held the timepiece up to the light for examination after every few strokes of his cloth. A barrel-chested technician emerged from behind an elevator door and measured my wrist, saying, “Sir, we should remove one link.”

While the watch was being adjusted, I handed over my debit card and gulped. There was no turning back. Part of me felt as if I had purchased a distant star and named it after myself.

Chronic illness is a grind. Renowned neurologists have told me I have disorders I never knew existed — dysautonomia, autonomic failure, persistent visual snow. They have assured me these conditions aren’t terminal, but I am less concerned with death. Rather, I am consumed with how to live, how to maintain health care coverage, how to sustain disability insurance.

As my roommate and sometimes caregiver, Dunia has witnessed my struggles, replete with ambulance rides and crying fits. I imagine she would rather be elsewhere during these distressing episodes, but she stands by me nonetheless.

Perhaps it’s no coincidence that Dunia means “the world” in Arabic, because having her with me is everything. We would be married but for my condition, which has placed a question mark at the end of nearly everything. Will experimental treatments eventually fix me? Can I contribute to a family? Is it fair to ask that Dunia sustain a lifetime of my poor health?

I left the vast Tourneau showroom that day lighter in the wallet but heavier on the wrist. The watch felt dense, luxurious. It delighted me. And yet, as I loitered in front of the store, I was struck by how unchanged the city appeared, how unchanged I felt. The cabbies honked; the clip of the pedestrian stream was as swift as ever; the summer humidity was thick and filthy; and I still felt dizzy and disoriented.

I shuffled at my sluggish pace toward the subway and wondered what Dunia would make of the small fortune dangling from my arm.

Our shared bedroom is a cloud of freshly painted white walls, soft sheets and warm bedside lighting. Above the bed hang sunny oversize photographs shot from inside the canopy of a California date palm tree. This is our hide-out, a treehouse floating above the implausible pandemonium of New York.

“You’re still the smartest and most handsome man I know,” Dunia says to me in our hide-out. “I’m proud of you, proud of how you’re handling this.”

Then, at other times, she is lost to me, constructing an invisible wall of uncertainty and fear as we talk about the future.

After we voice these fears, I fall asleep awash with shame, fantasizing that I’ll awake cured, competent to practice law, capable of helping Dunia with her plans and aspirations. But for now these are just dreams. A load of laundry overtires me. I can’t imagine the marathon of work, parenthood and homeownership.

Lately I have concluded that the best thing for Dunia would be for me to leave her, free her from the burdens of a disabled partner, provide her the opportunity to find someone new and healthy with whom to build her vision.

I said nothing of the shimmering fixture on my wrist when she arrived home from work.

“Wait,” she said, having caught it in the light. “What’s that on your wrist?”

I held it up.

“It’s so beautiful,” she said. “I fully support this. You deserve it.” She asked to try it on and became fixated with it.

I observed Dunia as she studied the Roman numerals on the dial, harking back to a distant, ancient time. As I watched her, my uncertainty about the future gave way to the certainty of the present, and I was overcome by the notion that she and I were moving through time together, like musicians playing from the same sheet music. A duet not yet completed, partially composed but largely improvised.

Our future may be unsure, but such is the case for everyone and everything. As the second hand on the Datejust skated forward on Dunia’s wrist, I saw that it portended nothing other than the now, the togetherness of Dunia and me alive and undeniable, good and right.

In this sense, my timekeeper is a shield, an armor of steel defending against the gnawing fear of the unknown. I peered into the sapphire crystal of my wristwatch and saw that the future is merely an accumulation of present moments — moments I can choose to savor. A watch to keep time perfectly.

I thought about the grandfather clock from my law school days, ticking by my nook in the reading room. Another hungry law student sits there now, reading doggedly about fairness and rightness, Lady Justice keeping vigil.

I can no longer say whether time is kind or just, if I ever could. Dunia and I may adapt fully to this life, maybe not. It may be true that time heals all wounds; I can’t say. Who am I to judge time?

Continue reading the main story

TCP as an underspecified two-node consensus algorithm and your proxies

$
0
0

18 March 2018

I recently found myself dealing with TCP load balancing for a project and I've come to think that generic TCP proxies can't be implemented without substantial pain and how it makes TLS terminating proxies a bad idea.

That might seem like a pretty bold statement right out of the gate, so let's drill down a bit.

When people talk about TCP, it's easy to fall into the trap of thinking of it as a connection, with a bi-directional stream of bytes. That is the abstraction that TCP provides, but it's not what TCP is. TCP is an agreement between 2 nodes to run a simple consensus algorithm. The data that is agreed on is (roughly) how much of what I have sent have you seen and how much have I seen of what you've sent. Since there are only 2 nodes, the algorithm is much simpler than what you would see in Raft or Paxos, but like a lot of consensus algorithms, it's based on nodes agreeing on what the current highest number is.

Throughout this post, I'll be using "connection" as a shorthand for this agreement, but keep in mind that we're talking about 2 nodes communicating over a lossy connection, not a property of the network itself.

Besides the streams being sent, there's another important bit of information: the state of the connection itself. Annoyingly, some of this information is not transmitted over the network. The state of the connection is based largely on heuristics of the individual TCP implementations and to make matters worse, we allow programs to change this behavior depending on the application protocol. If you have a box in the middle of the network that is able to read the entirety of a TCP session, it would not be able to guess at what the state of a TCP connection is. It would end up in the position of having to guess at what is meant by a certain series of TCP/IP packets.

So, what impact does this have for TCP proxies? Let's set up a simple hypothetical with 3 nodes, a client, a proxy and a server. Whenever the client establishes a TCP connection to the proxy, it in turn establishes a TCP connection to the server. Whatever the client sends to the proxy gets forwarded to the server and whatever the server sends to the proxy gets forwarded to the client.

On the application layer, The client sends a request that takes the server a long time to reply to. Since the client expects that the request will take a long time, it enables TCP keepalive to periodically inform the server that it is still alive and able to receive the response.

Since the proxy will be the recipient of the keepalive packets, the server will not see them. It might think that the client has gone away, stop processing the request and close the TCP connection. We can have the proxy guess at what timeout might be appropriate, but those values are very protocol-specific and we end up either having the proxy terminate still viable sessions or taking up resources on the proxy machine.

Having a proxy in the middle shields the server from the specifics of the clients TCP behavior, even in cases where it would want to know it. The usual case where people might want that information is the IP address and there are ways of having it be transmitted, but TCP is a large spec. There are a myriad of features in TCP like TCP Fast Open or alternativecongestion control or the client might be using archaicfeatures and the proxy will either negate their advantages, or outright break the connection.

This serves as an example of the end-to-end principle in action and we don't have to stray to far from TCP to see more examples of it. On the IP layer, datagrams can be split into multiple parts for when the underlying physical transport cannot support a packet of a given size. The idea was that IP datagrams would be split and then recombined by the routers in the middle of the network when the physical layer would support a packet of that size again. This turned out to be disastrous in practice. Hosts would often get partial datagrams that would never be able to recombine and they would also have no way to tell the host on the other end that a packet was lost (the packet acknowledgement is in the TCP layer). Because of this issue and many more, we have largely scrapped the idea of IP fragmentation and came up with better solutions.

If you're building an application that does use TCP, you need to be prepared for the possibility that your application will end up being proxied through a host that might not particularly care for whatever TCP tricks you're doing or what the state of the protocol is at any given moment. You can guard against these issues by constructing your protocol in a resilient manner. Note that while these safeguards will help with proxies, they're in general a good idea, since they will also guard against lower-level network issues on the IP layer.

Application-level pings

Since you can't rely on the proxy to pass through the behavior of the TCP connection, techniques like keepalive packets can no longer be used to ensure liveness of a connection. Instead, you'll have to implement a ping on the application level. Since a proxy must pass through the data if it is to be useful in any way, these pings will have to poke through the proxy.

End-of-file is not the end of things

A lot of TCP proxies will turn connection errors into a clean termination. If you're using the closing of the TCP connection as a way to signal no more data (looking at you, HTTP 1.0), you cannot determine whether you have read the entire response, or there might be more data if the operation was retried.

If you're in the position of having to implement a proxy, for load balancing or inspection reasons, there are a couple of things you can do to make it less invasive.

Implement the protocol

The only way a TCP proxy might know when it is safe to terminate a connection, is when it knows the protocol state. A great example of this is an HTTP load balancer. It can see if there is an outstanding request and keep the connection open. More importantly, if the proxy needs to go down for maintenance, it can terminate connections cleanly and let the client re-establish.

Be a NAT

If the mismatch between a proxy's TCP implementation and the server's TCP implementation is an issue, another solution is for the proxy to not even implement TCP at all. Instead of acting as a TCP proxy, act like a smart IP forwarder. Since the proxy is not constructing packets, only modifying and forwarding them, the mismatch in implementation goes away. Examples of software that does this are Linux Virtual Server or Google's Maglev.

A special case of the TCP proxy is the TLS proxy. They take a TCP connection containing a TLS session and turn them into unencrypted TCP within a trusted network. These proxies are useful because they offload the responsibilities of implementing complex cryptography code away from the application servers. Additionally, they are useful for key management, since your backends no longer have to have the keys stored on the servers.

But since they're a TCP proxy, they have the same issues as any other TCP proxy. Additionally, they cannot function as a NAT, since they have to respond with their own data to negotiate the TLS handshake.

So, we're stuck in a dilemma. Either use a TLS proxy, gain the ease of cryptographic deployment and lose nuances in TCP handling, or push TLS termination into the servers, creating key management issues and increased responsibility for cryptography code.

I have not yet figured out what can be done to solve the specific TLS issue. Using HSMs or something like CloudFlare's Keyless SSL and terminating at the edges might help with key management, but it still pushes large burden onto your application servers. Since the edges have to terminate, you lose the ability to route based on the information inside the TLS session. SNI would usually allow you to route to different backends and have key management be in one location, but that is no longer possible. This poses a large problem for cloud providers who want to run their TLS terminators as multi-tenant machines.

I think the issue also serves to illustrate a layering violation in the design of TLS itself. While TLS uses the TCP protocol to simplify its key negotiation handshake, the reliable delivery of TCP is completely orthogonal to the goals of privacy and integrity that TLS provides. If we pull the cryptography into TCP, we might be able to simplify things further. If a given TCP segment was always guaranteed to contain enough data to decrypt and authenticate it, then a pass-through NAT-like TLS proxy becomes trivial, without having to do significant connection tracking or creating acknowledgement packets on the end servers behalf. QUIC is an example of this kind of construction, although they chose to encrypt the transport layer for different reasons related to middleboxes.

For now, I'm going to grit my teeth, implement the protocols fully in my TLS terminators and hope that the mismatch never gets too bad, but there is an interesting issue to be solved here. If you meet me in person, buy me a beer and I'll tell you all about my plans to put a TLS terminator inside a hypervisor.

Viewing all 25817 articles
Browse latest View live


Latest Images

<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>