# Service Worker FFI

**URL:** <https://discourse.elm-lang.org/t/service-worker-ffi/6408>\
**Category:** Show and Tell\
**Created:** [October 18, 2020, 2:52pm UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408 "2020-10-18T14:52:31Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![ronanyeah](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/ronanyeah/32/5497_2.png) [@ronanyeah](https://discourse.elm-lang.org/u/ronanyeah)\
**Post date:** [October 18, 2020, 2:52pm UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/1 "2020-10-18T14:52:31Z")

</div>

For [a project](https://bolster.pro/) using [WebCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) where all text data has to be encrypted on the client side before being sent to the server, and decrypted on the way back, using ports would have been too much overhead.

Since tasks can be used to chain asynchronous work in Elm, I needed to find a way to access the WebCrypto API using a task.

Inspired by [this post](https://www.gizra.com/content/elm-port-alternatives/#with-service-workers), I started looking into the feasibility of using service workers as an interface to JavaScript. Since the app already had a service worker due to being a [PWA](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps), it seemed like a good match.

* * *

On the Elm side I am sending a `Http.task` request with a particular method:

```elm
serviceWorkerRequest : String -> Json.Decode.Value -> Task Http.Error a
serviceWorkerRequest key body =
    Http.task
        { method = "CRYPTO"
        , headers = []
        , url = "https://" ++ key
        , body = Http.jsonBody body
        , resolver = resolver
        , timeout = Nothing
        }

```

If the service worker identifies this method it intercepts the request:

```javascript
self.addEventListener("fetch", (e) =>
  e.request.method === "CRYPTO" ? e.respondWith(handlers(e.request)) : e
);

```

I’m using the url to differentiate between ‘actions’, then parsing the body and passing it to the relevant function:

```javascript
const handlers = async (request) => {
  try {
    const action = new URL(request.url).host;

    switch (action) {
      case "decrypt": {
        const body = await request.json();

        return jsonResponse(decrypt(body));
      }
      case "encrypt": {
        const body = await request.json();

        return jsonResponse(encrypt(body));
      }
      default: {
        return errorResponse("unmatched-url");
      }
    }
  } catch (_) {
    return errorResponse("execution-failure");
  }
};

```

The [Response object](https://developer.mozilla.org/en-US/docs/Web/API/Response) can be used to return a result:

```javascript
const jsonResponse = (data) => new Response(JSON.stringify(data));

```

Or send back an error if something goes wrong:

```javascript
const errorResponse = (code) =>
  new Response(JSON.stringify({ error: code }), { status: 400 });

```

Since the service worker is essential for certain features in the app, Elm needs to know if the service worker has been registered successfully:

```javascript
const { Elm } = require("./Main.elm");

const startApp = (swActive) =>
  Elm.Main.init({
    node: document.getElementById("app"),
    flags: { swActive },
  });

const swEnabled = Boolean(
  window.navigator.serviceWorker &&
    typeof window.navigator.serviceWorker.register === "function"
);

if (swEnabled) {
  window.navigator.serviceWorker.register("/sw.js").then(
    () => startApp(true),
    () => /* registration failure */ startApp(false)
  );
} else {
  startApp(false);
}

```

I’m also using [clients.claim](https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim) in the service worker activation handler to ensure all requests start passing through the “fetch” handler immediately:

```javascript
self.addEventListener("activate", (_event) =>
  self.clients.claim()
);

```

Overall I am happy with the results and would consider using this approach in future to interact with other JS APIs such as localStorage or IndexedDB.

* * *

**Code:**

- [Project repo](https://github.com/tarbh-engineering/journal)
- [Service worker](https://github.com/tarbh-engineering/journal/blob/master/src/sw.js)
- [Elm requests](https://github.com/tarbh-engineering/journal/blob/master/src/Crypto.elm)

* * *

**Caveat:**  
Service workers are **not** available in [Firefox private browsing](https://support.mozilla.org/en-US/kb/private-browsing-use-firefox-without-history) or in [non-Safari iOS browsers](https://webkit.org/blog/8090/workers-at-your-service/).

---

<div class="post-metadata">

**Author:** ![francescortiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/francescortiz/32/1721_2.png) [@francescortiz](https://discourse.elm-lang.org/u/francescortiz)\
**Post date:** [October 19, 2020, 12:28pm UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/2 "2020-10-19T12:28:12Z")

</div>

Hi! Looks really good. I have made something similar by monkey patching the XMLHttpRequest object. I also implemented handling of generators by using Partial content responses: [https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206) think that allows to have websockets or similar stuff.

Do you know if it is possible to achieve the same with the technique you implemented?

---

<div class="post-metadata">

**Author:** ![ronanyeah](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/ronanyeah/32/5497_2.png) [@ronanyeah](https://discourse.elm-lang.org/u/ronanyeah)\
**Post date:** [October 19, 2020, 2:06pm UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/3 "2020-10-19T14:06:29Z")

</div>

I’m averse to monkey patching anything, but I’d like to see what you mean by generator usage. Do you have any code you could post?

---

<div class="post-metadata">

**Author:** ![francescortiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/francescortiz/32/1721_2.png) [@francescortiz](https://discourse.elm-lang.org/u/francescortiz)\
**Post date:** [October 21, 2020, 8:24am UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/4 "2020-10-21T08:24:26Z")

</div>

I will check if monkeypatching ellie I can have a working example.

---

<div class="post-metadata">

**Author:** ![francescortiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/francescortiz/32/1721_2.png) [@francescortiz](https://discourse.elm-lang.org/u/francescortiz)\
**Post date:** [November 1, 2020, 10:10am UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/6 "2020-11-01T10:10:32Z")

</div>

I write to prevent the post from expiring.

---

<div class="post-metadata">

**Author:** ![hans-helmut](https://avatars.discourse-cdn.com/v4/letter/h/d9b06d/32.png) [@hans-helmut](https://discourse.elm-lang.org/u/hans-helmut)\
**Post date:** [November 1, 2020, 6:35pm UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/7 "2020-11-01T18:35:30Z")

</div>

> [@ronanyeah](#):
>
> Overall I am happy with the results and would consider using this approach in future to interact with other JS APIs such as localStorage or IndexedDB.

I just want to point to this link: [Rethinking Offline First sync for Service Workers | by Nolan Lawson | Offline Camp | Medium](https://medium.com/offline-camp/rethinking-offline-first-sync-for-service-workers-da4727b6dee)

JS-APIs using states may have problems in Service Workers.

---

<div class="post-metadata">

**Author:** ![elmdev](https://avatars.discourse-cdn.com/v4/letter/e/919ad9/32.png) [@elmdev](https://discourse.elm-lang.org/u/elmdev)\
**Post date:** [November 6, 2020, 8:00am UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/8 "2020-11-06T08:00:52Z")

</div>

This is soooooo clever!!

Elm really needs to embrace the notion of synchronous port Tasks that are - as in this example - pure computations. Others have tried to use web components to the same end

---

<div class="post-metadata">

**Author:** ![francescortiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/francescortiz/32/1721_2.png) [@francescortiz](https://discourse.elm-lang.org/u/francescortiz)\
**Post date:** [November 12, 2020, 9:27am UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/9 "2020-11-12T09:27:45Z")

</div>

I think that what I did will be doable with service workers. I didn’t publish my package to the public repository because of its dependency in monkey patching. The trick for handling websockets is to return a status code 206 instead of 200. This keeps the client waiting for more chunks from the server.

I show you the places that take care of this functionality:

1. If the function to call is a generator, we are gonna handle it as a chunked http response until the generator is consumed.  
_NOTE: I decided to use status 206 instead of 200 in order to treat the callback in some kind of way that tells elm that more things are going to come just in case some update would kill this functionality, but I right now it don’t think it is needed._  
[https://github.com/francescortiz/elm-effects-proxy/blob/de2d19fc887f2d91faed8604e5e20ddb0fe2c080/src/websocket-effects-patch.ts#L95,L110](https://github.com/francescortiz/elm-effects-proxy/blob/de2d19fc887f2d91faed8604e5e20ddb0fe2c080/src/websocket-effects-patch.ts#L95,L110)
2. In elm response handling, we don’t need to do anything, because the elm/http package already considers status 206 to be a successful response.  
[https://github.com/elm/http/blob/34a9a27411c2492d3e247ac75cd48e22b473bef5/src/Elm/Kernel/Http.js#L65](https://github.com/elm/http/blob/34a9a27411c2492d3e247ac75cd48e22b473bef5/src/Elm/Kernel/Http.js#L65)
3. We create some handler for asynchronous code, like a websockets wrapper.  
[https://github.com/francescortiz/elm-effects-proxy/blob/de2d19fc887f2d91faed8604e5e20ddb0fe2c080/src/elm-effects-proxy-demo.html#L15-L23](https://github.com/francescortiz/elm-effects-proxy/blob/de2d19fc887f2d91faed8604e5e20ddb0fe2c080/src/elm-effects-proxy-demo.html#L15-L23)
4. We trigger the asynchronous call from elm, either as command:  
[https://github.com/francescortiz/elm-effects-proxy/blob/de2d19fc887f2d91faed8604e5e20ddb0fe2c080/src/EffectsProxyDemo.elm#L56-L59](https://github.com/francescortiz/elm-effects-proxy/blob/de2d19fc887f2d91faed8604e5e20ddb0fe2c080/src/EffectsProxyDemo.elm#L56-L59)  
or as a chainable task:  
[https://github.com/francescortiz/elm-effects-proxy/blob/de2d19fc887f2d91faed8604e5e20ddb0fe2c080/src/EffectsProxyDemo.elm#L64-L72](https://github.com/francescortiz/elm-effects-proxy/blob/de2d19fc887f2d91faed8604e5e20ddb0fe2c080/src/EffectsProxyDemo.elm#L64-L72)

This is all. I think the same should be achievable with service workers. Do you know if this is the case?

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [November 22, 2020, 9:27am UTC](https://discourse.elm-lang.org/t/service-worker-ffi/6408/10 "2020-11-22T09:27:49Z")

</div>

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
