Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 08557e8f authored by Simon Chan's avatar Simon Chan
Browse files

feat(demo): make it offline capable

fixes #482
parent 91827752
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -32,3 +32,10 @@ yarn-error.log*

# vercel
.vercel

public/manifest.json
public/fallback-*.js
public/sw.js
public/sw.js.map
public/workbox-*.js
public/workbox-*.js.map
+61 −53
Original line number Diff line number Diff line
@@ -7,9 +7,16 @@ const withMDX = require("@next/mdx")({
    },
});

const basePath = process.env.BASE_PATH ?? "";

const withPwa = require("@yume-chan/next-pwa")({
    dest: "public",
});

/** @type {import('next').NextConfig} */
module.exports = withMDX({
    basePath: process.env.BASE_PATH || "",
module.exports = withPwa(
    withMDX({
        basePath,
        pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
        reactStrictMode: false,
        productionBrowserSourceMaps: true,
@@ -18,7 +25,7 @@ module.exports = withMDX({
            esmExternals: "loose",
        },
        publicRuntimeConfig: {
        basePath: process.env.BASE_PATH || "",
            basePath,
        },
        webpack(config, options) {
            config.module.rules.push({
@@ -63,4 +70,5 @@ module.exports = withMDX({
                },
            ];
        },
});
    })
);
+2 −1
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
    "version": "0.1.0",
    "private": true,
    "scripts": {
        "postinstall": "fetch-scrcpy-server 1.25",
        "postinstall": "fetch-scrcpy-server 1.25 && node scripts/manifest.mjs",
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
@@ -49,6 +49,7 @@
        "@next/mdx": "^13.2.4",
        "@types/node": "^18.15.0",
        "@types/react": "18.0.28",
        "@yume-chan/next-pwa": "^5.6.0",
        "eslint": "^8.36.0",
        "eslint-config-next": "13.2.4",
        "prettier": "^2.8.4",
+152 −145
Original line number Diff line number Diff line
@@ -38,7 +38,12 @@ let scope = ''

    function registerWorker() {
        return navigator.serviceWorker.getRegistration('./').then(swReg => {
    return swReg || navigator.serviceWorker.register('sw.js', { scope: './' })
            let baseUrl = location.protocol + "//" + location.host + location.pathname;
            baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/"));
            if (swReg.scope === baseUrl) {
                return swReg;
            }
            return navigator.serviceWorker.register('sw.js', { scope: './' })
        }).then(swReg => {
            const swRegTmp = swReg.installing || swReg.waiting

@@ -63,11 +68,13 @@ function onMessage (event) {
        // It's important to have a messageChannel, don't want to interfere
        // with other simultaneous downloads
        if (!ports || !ports.length) {
    throw new TypeError("[StreamSaver] You didn't send a messageChannel")
            console.error("[StreamSaver] You didn't send a messageChannel")
            return;
        }

        if (typeof data !== 'object') {
    throw new TypeError("[StreamSaver] You didn't send a object")
            console.error("[StreamSaver] You didn't send a object")
            return;
        }

        // the default public service worker for StreamSaver is shared among others.
+148 −120
Original line number Diff line number Diff line
/* global self ReadableStream Response */

self.addEventListener('install', () => {
  self.skipWaiting()
})

self.addEventListener('activate', event => {
  event.waitUntil(self.clients.claim())
})

const map = new Map()
self.addEventListener("install", () => {
    self.skipWaiting();
});

console.log("updated");

self.addEventListener("activate", (event) => {
    const url = serviceWorker.scriptURL;
    const baseUrl = url.substring(0, url.lastIndexOf("/"));
    event.waitUntil(
        (async () => {
            const cache = await caches.open("StreamSaver");
            await cache.add(baseUrl + "/mitm.html");
            await clients.claim();
        })()
    );
});

const map = new Map();

// This should be called once per download
// Each event has a dataChannel that the data will be piped through
self.onmessage = event => {
self.onmessage = (event) => {
    // We send a heartbeat every x second to keep the
    // service worker alive if a transferable stream is not sent
  if (event.data === 'ping') {
    return
    if (event.data === "ping") {
        return;
    }

  const data = event.data
  const downloadUrl = data.url || Math.random() + '/' + (typeof data === 'string' ? data : data.filename)
  const port = event.ports[0]
  const metadata = new Array(3) // [stream, data, port]
    const data = event.data;
    const downloadUrl =
        data.url ||
        Math.random() + "/" + (typeof data === "string" ? data : data.filename);
    const port = event.ports[0];
    const metadata = new Array(3); // [stream, data, port]

  metadata[1] = data
  metadata[2] = port
    metadata[1] = data;
    metadata[2] = port;

    // Note to self:
    // old streamsaver v1.2.0 might still use `readableStream`...
    // but v2.0.0 will always transfer the stream through MessageChannel #94
    if (event.data.readableStream) {
    metadata[0] = event.data.readableStream
        metadata[0] = event.data.readableStream;
    } else if (event.data.transferringReadable) {
    port.onmessage = evt => {
      port.onmessage = null
      metadata[0] = evt.data.readableStream
    }
        port.onmessage = (evt) => {
            port.onmessage = null;
            metadata[0] = evt.data.readableStream;
        };
    } else {
    metadata[0] = createStream(port)
        metadata[0] = createStream(port);
    }

  map.set(downloadUrl, metadata)
  port.postMessage({ download: downloadUrl })
}
    map.set(downloadUrl, metadata);
    port.postMessage({ download: downloadUrl });
};

function createStream(port) {
    // ReadableStream is only supported by chrome 52
@@ -51,80 +63,96 @@ function createStream (port) {
        start(controller) {
            // When we receive data on the messageChannel, we write
            port.onmessage = ({ data }) => {
        if (data === 'end') {
          return controller.close()
                if (data === "end") {
                    return controller.close();
                }

        if (data === 'abort') {
          controller.error('Aborted the download')
          return
                if (data === "abort") {
                    controller.error("Aborted the download");
                    return;
                }

        controller.enqueue(data)
      }
                controller.enqueue(data);
            };
        },
        cancel(reason) {
      console.log('user aborted', reason)
      port.postMessage({ abort: true })
    }
  })
            console.log("user aborted", reason);
            port.postMessage({ abort: true });
        },
    });
}

self.onfetch = event => {
  const url = event.request.url
self.onfetch = async (event) => {
    event.respondWith(
        (async () => {
            const url = event.request.url;

  // this only works for Firefox
  if (url.endsWith('/ping')) {
    return event.respondWith(new Response('pong'))
            const cache = await caches.open("StreamSaver");
            const response = await cache.match(event.request);
            if (response) {
                return response;
            }

  const hijacke = map.get(url)

  if (!hijacke) return null
            // this only works for Firefox
            if (url.endsWith("/ping")) {
                return new Response("pong");
            }

  const [ stream, data, port ] = hijacke
            const hijacked = map.get(url);
            if (!hijacked) return null;
            map.delete(url);

  map.delete(url)
            const [stream, data, port] = hijacked;

            // Not comfortable letting any user control all headers
            // so we only copy over the length & disposition
            const responseHeaders = new Headers({
    'Content-Type': 'application/octet-stream; charset=utf-8',
                "Content-Type": "application/octet-stream; charset=utf-8",

                // To be on the safe side, The link can be opened in a iframe.
                // but octet-stream should stop it.
    'Content-Security-Policy': "default-src 'none'",
    'X-Content-Security-Policy': "default-src 'none'",
    'Cross-Origin-Embedder-Policy': 'require-corp',
    'X-WebKit-CSP': "default-src 'none'",
    'X-XSS-Protection': '1; mode=block'
  })

  let headers = new Headers(data.headers || {})

  if (headers.has('Content-Length')) {
    responseHeaders.set('Content-Length', headers.get('Content-Length'))
                "Content-Security-Policy": "default-src 'none'",
                "X-Content-Security-Policy": "default-src 'none'",
                "Cross-Origin-Embedder-Policy": "require-corp",
                "X-WebKit-CSP": "default-src 'none'",
                "X-XSS-Protection": "1; mode=block",
            });

            const headers = new Headers(data.headers || {});
            if (headers.has("Content-Length")) {
                responseHeaders.set(
                    "Content-Length",
                    headers.get("Content-Length")
                );
            }

  if (headers.has('Content-Disposition')) {
    responseHeaders.set('Content-Disposition', headers.get('Content-Disposition'))
            if (headers.has("Content-Disposition")) {
                responseHeaders.set(
                    "Content-Disposition",
                    headers.get("Content-Disposition")
                );
            }

            // data, data.filename and size should not be used anymore
            if (data.size) {
    console.warn('Depricated')
    responseHeaders.set('Content-Length', data.size)
                console.warn("Deprecated");
                responseHeaders.set("Content-Length", data.size);
            }

  let fileName = typeof data === 'string' ? data : data.filename
            const fileName = typeof data === "string" ? data : data.filename;
            if (fileName) {
    console.warn('Depricated')
                console.warn("Deprecated");
                // Make filename RFC5987 compatible
    fileName = encodeURIComponent(fileName).replace(/['()]/g, escape).replace(/\*/g, '%2A')
    responseHeaders.set('Content-Disposition', "attachment; filename*=UTF-8''" + fileName)
                fileName = encodeURIComponent(fileName)
                    .replace(/['()]/g, escape)
                    .replace(/\*/g, "%2A");
                responseHeaders.set(
                    "Content-Disposition",
                    "attachment; filename*=UTF-8''" + fileName
                );
            }

  event.respondWith(new Response(stream, { headers: responseHeaders }))

  port.postMessage({ debug: 'Download started' })
}
            port.postMessage({ debug: "Download started" });
            return new Response(stream, { headers: responseHeaders });
        })()
    );
};
Loading