How Wordle kept your streak alive when it migrated to the NYT's website
Tags: javascript, webdev, html
Published: 2022-02-14 17:53:17

If you've been online even just one day in the past few weeks, you've probably heard of [Wordle](https://www.nytimes.com/games/wordle/), a word-game that caught the world, and Twitter in particular, by surprise with cryptic grids of colored square emoji.

It got so popular that after a few weeks, [the New York Times bought the game](https://www.nytimes.com/2022/01/31/business/media/new-york-times-wordle.html) to add it to their suite of daily word games.

[Josh Wardle](https://powerlanguage.co.uk/), Wordle's creator, had announced that he "was working with [the NYT] to make sure wins and streaks will be preserved".

Last week, the game migrated to the NYTimes' domain and I was curious how they migrated the statistics. It turns out that it's pretty simple. :)

The original game lived at https://www.powerlanguage.co.uk/wordle/

If you curl that URL today, you get this:
```html
$ curl -D /dev/stdout https://www.powerlanguage.co.uk/wordle/
HTTP/2 200
date: Mon, 14 Feb 2022 16:57:50 GMT
content-type: text/html
last-modified: Sun, 13 Feb 2022 22:37:30 GMT
[...skip...]
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Wordle - The New York Times</title>
    <meta http-equiv="cache-control" content="no-cache" />
    <meta name="description" content="Guess the hidden word in 6 tries. A new puzzle is available each day" />
  </head>
  <body>
    <script type="text/javascript">
      const ls = window.localStorage;
      // Ensure we're always sending something
      let stringifiedData = '{}';
      // Default to production
      let url = 'https://www.nytimes.com/games/wordle'

      // Don't attempt unless our user has local storage enabled
      if (ls) {
        const errors = [];
        const localData = {
          time: new Date().getTime(),
          statistics: null,
          darkTheme: null,
          colorBlindTheme: null
        };

        // Attempt to pull and parse the stats and themes
        try {
          localData.statistics = JSON.parse(ls.getItem('statistics'));
          localData.darkTheme = JSON.parse(ls.getItem('darkTheme'));
          localData.colorBlindTheme = JSON.parse(ls.getItem('colorBlindTheme'));
        } catch (e) {
          // Anything that's not valid JSON will not be sent
          errors.push(e);
        }

        try {
          stringifiedData = JSON.stringify(localData);
        } catch (e) {
          errors.push(e);
        }
      }

      // Everyone will redirect, regardless of local storage
      window.location.assign(`${url}?data=${stringifiedData}`)
    </script>
  </body>
</html>
```

It serves a simple HTML document with an inline script that grabs all the statistics of the original game from `localStorage`, put all the important data in the URL, and forwards the browser to the nytimes.com URL.

Once that page loads (there's a `301` permanent redirect to `index.html` but that doesn't do much), that data is parsed and saved locally again with this code:
```js
function os() {
    if (localStorage) {
        try {
            var e = new Proxy(new URLSearchParams(window.location.search),{
                get: function(e, a) {
                    return e.get(a)
                }
            });
            if (e.data)
                !function(e) {
                    if (!e.statistics)
                        throw new Error("User local data does not contain statistics. Aborting transfer.");
                    if (ns(e.statistics, e.force)) {
                        localStorage.setItem('nyt-wordle-statistics', JSON.stringify(e.statistics));
                        var a = e.darkTheme;
                        window.themeManager.setDarkTheme(a);
                        var s = !!e.colorBlindTheme;
                        window.themeManager.setColorBlindTheme(s)
                    }
                }(JSON.parse(e.data))
        } catch (e) {}
        window.history.replaceState({}, document.title, new URL(location.pathname,location.href).href)
    }
}
```

You can see in the call to the `ns` function that you can force an override of the data. By default, `ns` short-circuits the data transfer when it finds local data.

And if you do force it by passing `"force": true`, `ns` looks at the given statistics, and makes sure you played at least one game but also more games on the original game than the new site.

Finally, note the call to `window.history.replaceState`. This strips all the data passed in the URL. Using `replaceState` makes sure that clicking "Back" in your browser won't bring back all the transfer data in the URL. That makes it less likely that someone will share that URL with somebody and erase their statistics by accident.

And that's it!

---

There IS one part that I haven't quite figured out while looking at this.

I play Wordle on my phone and the old URL was saved in my history so I would just type `po` and the rest would show up. After the transfer, it stopped populating completely as if I had never been to `https://www.powerlanguage.co.uk/wordle/`.

But looking at the steps above I don't know why it would just disappear from my local history. There's a `301` in the process, but it's from `https://www.nytimes.com/games/wordle?data=...` to `/games/wordle/index.html?data=...`.

I'd love to hear if you have ideas about that.

---

P.S.: I'm aware that the transfer had some bugs for some people, but I believe it was all fixed up.