npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

elm-rings

v0.3.1

Published

Capture and record the state of your Elm application in Elm 0.19 and 0.18.

Downloads

5

Readme

elm-rings Build Status

Programmatically record the state of your Elm application as your users use it in Elm 0.19 and 0.18

If you’ve ever received a support ticket, you’ve seen something like this: “Carmen M. can’t complete her quiz, it’s not affecting any other students”. Great. Was she unable to select answers to the questions? Able to answer but the submit button stays disabled? Able to tap submit but to no avail?

Users don’t submit detailed or complete error reports because they don’t experience or describe an app the same way its developers do. Nor should they! They have better things to do than learn our lingo.

But what if we could see our apps through our users’ eyes?

With the Elm language, we can. Because all the possible states of an Elm program are clearly defined and state only changes in response to messages, Elm can track everything that happens -- and make it available to you to debug. You can even export that history and even import it another session to see exactly what your users saw:

Unfortunately, Elm doesn't natively provide an interface to extract this data programmatically. Fortunately, we can work around that.

Enter ElmRings, a lightweight, dependency-free library to record the command history of your Elm app behind the scenes.

With ElmRings, you can snapshot the history periodically during a user's session, anytime you experience an error, however you want to do it. This data becomes an invaluable tool for debugging and supporting user issues -- when you get a confusing support ticket, you can download the history and see your app through your users' eyes.

😎

Installation

Installing elm-rings is pretty straightforward:

yarn add elm-rings
# or
npm install elm-rings

Usage

Capturing History

And add code like this to your application wherever you initialize the Elm app you want to listen to.

import ElmRings from "javascript/ElmRings.js";

// IMPORTANT: the Elm app must be compiled with debug mode enabled for this to work
// That said, ElmRings won't crash if debug mode is off, so you could run it for every users
// and have a server-side flag for who gets debug mode and who gets normal Elm (as we do)
const app = Elm.MyApp.fullscreen();

const elmHistoryRecorder = new ElmRings({
  // whether users shoud be allowed to download/export the history on their own
  // default: true
  allowDownload: true,

  // if you'd like to capture history on an interval, you can set this value in milliseconds and ElmRings will automatically report appropriately
  // if set to a falsy value (false, null, 0, etc.) history will only be exported when you explicitly call `exportHistory`
  // default: 60000 (e.g. 60 seconds)
  trackingFrequency: 30000,

  // a function that determines whether to record the history at any given point. (You may choose to only record it under certain circumstances, such as a user being logged in.)
  // default: true (always record)
  shouldSendHistory: isUserLoggedIn,

  // a function that's called each time ElmRings grabs the Elm history
  // send it to a server, process it locally, whatever you'd like
  storeHistory: historyDataAsJsonString => {
    sendToServer(historyDataAsJsonString);
  },

  // Any Elm object constructor names or record keys that contain sensitive data.
  // These will be passed to a sanitizing function you provide.
  // See the next section for more details.
  watchWords: ["password", "token"],
  historySanitizer: mySanitizingFunction
});

// start tracking history
recorder.startTracking();

// manually trigger a history export, which will be passed to the function you specify in storeHistory
recorder.exportHistory();

Note: it's not a good idea to pass the Elm history object back into Elm in your storeHistory method. Things get real big real fast.

Sanitizing History

Of course, once you have that history data in hand, you'll also have a big challenge: security.

Every keystroke of a user’s password and all the sensitive personal information they enter in your app go into Elm’s history. It’s no good hashing passwords on the user model if you're sending them unencrypted over the network (even via HTTPS) or storing them in plain text in a logging table. You need to sanitize this data carefully before you cit.

Fortunately, ElmRings is comes with a built-in mechanism to sanitize this data before it leaves the browser. Because every Elm program is different, there's no one-size-fits-all way to clean up the data -- you'll need to think through your data structures and you'll want to write some tests.

Let's say you have an Elm message like

MySecretData "access_token" {password = "myP@ssw0rd", expiration = "tomorrow"}

As an Elm history entry, this will look like

// 0.19
{"$": "MySecretData", "a": "access_token", "b": {"password": "myP@ssw0rd", "expiration": "tomorrow"}}
// 0.18
{"ctor": "MySecretData", "_0": "access_token", "_1": {"password": "myP@ssw0rd", "expiration": "tomorrow"}}

For more information on the format Elm uses to export debugging history data, see an upcoming blog post.

ElmRing's sanitization is done by the sanitizeElmHistory function in source/HistorySanitizer.js. If we were to run

sanitizeElmHistory(historyData, ["MySecretData", "password"], function(
  elmObjectOrRecord
) {
  // for 0.18, replace $ with ctor and a with _1
  if (elmObjectOrRecord.$ == "MySecretData") {
    // replace the access token
    // make sure to return the updated object!
    return { ...elmObjectOrRecord, a: "[FILTERED]" };
  } else {
    // we have a record, replace the password field
    return { ...elmObjectOrRecord, password: "[FILTERED]" };
  }
});

We'll receive back a sanitized entry:

{"$": "MySecretData", "a": "[FILTERED]", "b": {"password": "[FILTERED]", "expiration": "tomorrow"}}

If you specify the watchWords and historySanitizer function, ElmRings will automatically process the entry before it's passed to your storeHistory function.

Sanitization is highly encouraged; ElmRings will log a warning to your console if it's not set up (though you can silence this if you really want to playDangerousWithData).

Example

Want to see ElmRings in action?

0.18

If you're using 0.18, you can check out the example app! It's all in the browser, but you can imagine how you could send the data to your server and display the results on a support page if you'd like.

To run the example locally:

  1. Check out the repo
  2. Run yarn install && elm-package install
  3. Run yarn example18
  4. Visit http://localhost:8080/example18/

The example in action:

0.19

If you're on 0.19, I've adapted a copy of @rtfeldman's Elm SPA example to work with ElmRings. (The ElmRings example app is blocked by an apparent bug in Elm 0.19's debug mode.) It's less flashy, but it's all there.

To run the example locally:

  1. Check out the repo
  2. Run yarn install && elm-package install
  3. Run yarn example
  4. Visit http://localhost:8080/example19/
  5. Open the browser console to see the history output
  6. Click on a tag and verify that the next history export sanitized which tag you chose

elm-rings-0 19

Caveats

Of course, there are caveats, even after you clean the history of sensitive data.

  • Performance: an app that generates a lot of entries may well run into performance problems eventually., especially on lower-powered hardware (such as the Chromebooks or iPads schools use). I haven’t measured when those would occur, but if you’re storing a lot of data or generating a flood of events, keep that in mind. (I’d be grateful for any data!)
  • Exposing your internals: with debug mode enabled your users (and, in theory, any Javascript on your page to see exactly what data your app stores and how it’s structured. All front end applications have to assume any data is open to the world, but this makes it unusually accessible.

Given those caveats, you may decide (like us) to only capture state for particular users for whom a flag is enabled. The big drawback of that approach is that you can’t proactively know who will encounter an error — it’ll be difficult to capture hard-to-reproduce problems.

There’s one additional limitation that’s both obvious and worth stating explicitly: the Elm history only captures what happens in Elm. If your app is all Elm, you’re golden. If you’re integrating with Javascript libraries to do fun things like in-browser video recording with Javascript and WebRTC (more on that soon), you’ll have blind spots in your log. For this and many many other reasons, the more you can put in Elm the better.

In your own logs

You'll want to make sure that the history data doesn't end up in your own logs, too. Even though it will be already sanitized, it's quite verbose and annoying.

Different backend applications will handle this differently. In Rails, for instance, you can to filter out the history you send up and also hide the database statements like so:

# in application.rb, add the appropriate parameter to Rails' built-in log filtering to ensure all the data doesn't get written to the log
# you could, alternatively, add all the individual fields you want to sanitize (password, token, US social security number, etc.) if you do want the history in the log
# but that does create a lot of data/noise over time
config.filter_parameters += [:elm_history]

# in your controller
def store_history
  # Silencing Active::Record logging is important if you record SQL queries in the environment you're running in
  # both for security and (often more pressingly in development) to keep your logs manageable
  old_logger = ActiveRecord::Base.logger
  begin
    old_logger.info "ActiveRecord logging disabled for Elm history logging"
    # disable database logging
    ActiveRecord::Base.logger = nil

    history_data = params.require(:elm_history)

    sanitized_history = sanitize_history(history_data)
    save_history!(sanitized_history)

    render json: {result: "stored"}
  ensure
    ActiveRecord::Base.logger = old_logger
  end
end

Contributing

See a bug? Want to add a feature? Awesome!

Building the library locally is simple: just run yarn build. You can then test your changes using the example app (yarn example, see above) or in your own app.

When filing an issue, please include a good description of what's happening and screenshots (if possible). Code to reproduce the issue would be much appreciated.

When submitting a pull request:

  • make sure that all tests pass when running yarn test
  • write a good description of the problem your code solves

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. See code-of-conduct.md for more information.