@banou/ripple 0.0.4 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +141 -3
  2. package/_headers +14 -0
  3. package/api-Jq4OSTp1-B4uxQLLk.js +338 -0
  4. package/app.webmanifest +50 -0
  5. package/assets/empty-BvRk9kiK.js +0 -0
  6. package/assets/libtorrent-DS_9Yo8M.js +6 -0
  7. package/assets/sintel.torrent +0 -0
  8. package/assets/worker-SJZDGuG-.js +23 -0
  9. package/build-DEOZ3A7n.js +1846 -0
  10. package/index.js +44658 -0
  11. package/jassub/default.woff2 +0 -0
  12. package/jassub/fonts.js +1 -0
  13. package/jassub/jassub-assets.js +0 -0
  14. package/jassub/jassub-worker-modern.wasm +0 -0
  15. package/jassub/jassub-worker.js +1 -0
  16. package/jassub/jassub-worker.wasm +0 -0
  17. package/jassub/worker.js +148 -0
  18. package/libav-jspi.wasm +0 -0
  19. package/libav-worker.js +4824 -0
  20. package/libav.wasm +0 -0
  21. package/package.json +14 -81
  22. package/remote-BLaEXLm6.js +209 -0
  23. package/sw.js +192 -0
  24. package/build/app.webmanifest +0 -65
  25. package/build/assets/index-be95001c.js +0 -211
  26. package/build/assets/index-d5b720ec.js +0 -109
  27. package/build/assets/io-worker-bde41815.js +0 -17681
  28. package/build/index-3576f032.js +0 -14881
  29. package/build/index-65b2197a.js +0 -116
  30. package/build/index-9a320571.js +0 -107
  31. package/build/index.js +0 -33826
  32. package/build/io-worker-190cc8d7.js +0 -2862
  33. package/build/jassub-worker-modern.wasm +0 -0
  34. package/build/jassub-worker.js +0 -15
  35. package/build/libav-worker.js +0 -1565
  36. package/build/libav.wasm +0 -0
  37. package/build/shared-worker.js +0 -13
  38. package/build/worker.js +0 -38
  39. /package/{build/assets → assets}/logo.png +0 -0
  40. /package/{build/assets → assets}/logo.svg +0 -0
  41. /package/{build/index.html → index.html} +0 -0
package/libav.wasm ADDED
Binary file
package/package.json CHANGED
@@ -1,81 +1,14 @@
1
- {
2
- "name": "@banou/ripple",
3
- "version": "0.0.4",
4
- "type": "module",
5
- "main": "build/index.js",
6
- "keywords": [
7
- "fkn",
8
- "fkn-type:app"
9
- ],
10
- "files": [
11
- "build"
12
- ],
13
- "scripts": {
14
- "___dev": "vite --port 5678",
15
- "dev": "vite --port 4560",
16
- "__dev": "shx rm -rf ./build && shx mkdir ./build && npm run copy-libav && npm run copy-libass && concurrently \"vite --port 4560\" \"vite build --mode development --watch\"",
17
- "_dev": "shx rm -rf ./build && shx mkdir ./build && npm run copy-libav && npm run copy-html && npm run copy-libass && concurrently \"serve -s -C -p 4560 build\" \"vite build --mode development --watch\"",
18
- "serve": "serve -s -C -p 4560 build",
19
- "build": "shx rm -rf build/ && vite build && npm run copy-libav && npm run copy-libass && npm run copy-html",
20
- "copy-libav": "shx cp node_modules/libav-wasm/build/worker.js build/libav-worker.js && shx cp node_modules/libav-wasm/build/libav.wasm build/libav.wasm",
21
- "copy-libass": "shx cp node_modules/jassub/dist/jassub-worker.js build/jassub-worker.js && shx cp node_modules/jassub/dist/jassub-worker-modern.wasm build/jassub-worker-modern.wasm",
22
- "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
23
- "preview": "vite preview",
24
- "copy-html": "shx cp src/index.html build/index.html && shx cp src/app.webmanifest build/app.webmanifest && copyfiles -u 2 ./src/assets/* build/assets",
25
- "copy-media-player": "copyfiles -u 4 ./node_modules/@banou/media-player/build/* build/tmp && shx rm build/tmp/index.js && shx mv build/tmp/* build && shx rm -rf build/tmp",
26
- "build-deps": "npm run build-deps-media-player && npm run build-deps-fkn-lib",
27
- "build-deps-local": "npm run build-deps-media-player && npm run build-deps-fkn-lib-local",
28
- "build-deps-media-player": "cd node_modules/@banou/media-player && npm i && npm run build && cd ../../..",
29
- "build-deps-fkn-lib": "cd node_modules/@fkn/lib && npm i && npm run build && cd ../../..",
30
- "build-deps-fkn-lib-local": "cd node_modules/@fkn/lib && npm i && npm run build-dev && cd ../../..",
31
- "remove-build-deps": "shx rm -rf node_modules/@banou/media-player"
32
- },
33
- "dependencies": {
34
- "@banou/media-player": "^0.0.1",
35
- "@emotion/react": "^11.11.1",
36
- "@fkn/lib": "^0.0.2",
37
- "@hookform/resolvers": "^3.3.2",
38
- "@radix-ui/react-hover-card": "^1.0.7",
39
- "@vitejs/plugin-react": "^4.0.4",
40
- "chart.js": "^4.4.0",
41
- "dexie": "^4.0.0-alpha.4",
42
- "idb-keyval": "^6.2.1",
43
- "osra": "^0.0.12",
44
- "parse-torrent": "^11.0.14",
45
- "queue-utils": "^0.0.0",
46
- "react": "^18.2.0",
47
- "react-chartjs-2": "^5.2.0",
48
- "react-dom": "^18.2.0",
49
- "react-dropzone": "^14.2.3",
50
- "react-feather": "^2.0.10",
51
- "react-hook-form": "^7.48.2",
52
- "react-router": "^6.16.0",
53
- "react-router-dom": "^6.16.0",
54
- "rxdb": "^14.17.1",
55
- "rxjs": "^7.8.1",
56
- "webtorrent": "^2.1.25",
57
- "xstate": "^5.0.0-beta.47",
58
- "zod": "^3.22.4"
59
- },
60
- "devDependencies": {
61
- "@rollup/plugin-inject": "^5.0.3",
62
- "@types/react": "^18.2.15",
63
- "@types/react-dom": "^18.2.7",
64
- "@types/webtorrent": "^0.109.3",
65
- "@types/wicg-file-system-access": "^2023.10.1",
66
- "@typescript-eslint/eslint-plugin": "^6.0.0",
67
- "@typescript-eslint/parser": "^6.0.0",
68
- "concurrently": "^8.2.2",
69
- "copyfiles": "^2.4.1",
70
- "eslint": "^8.45.0",
71
- "eslint-plugin-react-hooks": "^4.6.0",
72
- "eslint-plugin-react-refresh": "^0.4.3",
73
- "node-stdlib-browser": "^1.2.0",
74
- "rollup-plugin-polyfill-node": "^0.13.0",
75
- "serve": "^14.2.1",
76
- "shx": "^0.3.4",
77
- "typescript": "^5.2.2",
78
- "vite": "^4.4.5",
79
- "vite-plugin-top-level-await": "^1.3.0"
80
- }
81
- }
1
+ {
2
+ "name": "@banou/ripple",
3
+ "version": "0.0.8",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/banou26/ripple.git"
8
+ },
9
+ "keywords": [
10
+ "fkn",
11
+ "fkn-type:app"
12
+ ],
13
+ "main": "index.js"
14
+ }
@@ -0,0 +1,209 @@
1
+ import { a as e } from "./build-DEOZ3A7n.js";
2
+ //#region node_modules/@banou/media-player/dist/remote/index.js
3
+ var t = [
4
+ "loadstart",
5
+ "loadedmetadata",
6
+ "loadeddata",
7
+ "durationchange",
8
+ "timeupdate",
9
+ "play",
10
+ "playing",
11
+ "pause",
12
+ "seeking",
13
+ "seeked",
14
+ "progress",
15
+ "ratechange",
16
+ "volumechange",
17
+ "ended",
18
+ "waiting",
19
+ "stalled",
20
+ "suspend",
21
+ "canplay",
22
+ "canplaythrough",
23
+ "emptied",
24
+ "resize",
25
+ "error"
26
+ ], n = [
27
+ "currentTime",
28
+ "volume",
29
+ "muted",
30
+ "playbackRate"
31
+ ], r = [
32
+ "play",
33
+ "pause",
34
+ "load"
35
+ ], i = (e) => [
36
+ "loadstart",
37
+ ...e.readyState >= 1 ? ["loadedmetadata", "durationchange"] : [],
38
+ ...e.readyState >= 2 ? ["loadeddata"] : [],
39
+ ...e.readyState >= 3 ? ["canplay"] : [],
40
+ "volumechange",
41
+ "ratechange",
42
+ "progress",
43
+ "resize",
44
+ e.paused ? "pause" : "playing",
45
+ ...e.ended ? ["ended"] : [],
46
+ ...e.error ? ["error"] : []
47
+ ], a = (e) => {
48
+ if (!e) return [];
49
+ let t = [];
50
+ for (let n = 0; n < e.length; n++) t.push([e.start(n), e.end(n)]);
51
+ return t;
52
+ }, o = (e) => ({
53
+ currentTime: s(e.currentTime),
54
+ duration: s(e.duration),
55
+ paused: e.paused,
56
+ ended: e.ended ?? !1,
57
+ seeking: e.seeking,
58
+ readyState: e.readyState,
59
+ volume: e.volume ?? 1,
60
+ muted: e.muted ?? !1,
61
+ playbackRate: e.playbackRate ?? 1,
62
+ src: e.src ?? "",
63
+ currentSrc: e.currentSrc ?? "",
64
+ buffered: a(e.buffered),
65
+ seekable: a(e.seekable),
66
+ error: e.error ? {
67
+ code: e.error.code,
68
+ message: e.error.message
69
+ } : null,
70
+ videoWidth: e.videoWidth ?? 0,
71
+ videoHeight: e.videoHeight ?? 0
72
+ }), s = (e) => Number.isNaN(e) ? 0 : e, c = {
73
+ currentTime: 0,
74
+ duration: 0,
75
+ paused: !0,
76
+ ended: !1,
77
+ seeking: !1,
78
+ readyState: 0,
79
+ volume: 1,
80
+ muted: !1,
81
+ playbackRate: 1,
82
+ src: "",
83
+ currentSrc: "",
84
+ buffered: [],
85
+ seekable: [],
86
+ error: null,
87
+ videoWidth: 0,
88
+ videoHeight: 0
89
+ }, l = 8, u = 32, d = (e, t = {}) => {
90
+ h(t.origin);
91
+ let n = typeof window < "u" && window.parent !== window;
92
+ return !t.transport && !n ? () => {} : m(t).serve(e, t);
93
+ }, f = /* @__PURE__ */ new WeakMap(), p = /* @__PURE__ */ new Map(), m = (a) => {
94
+ let s = a.channel ?? "banou-media-player", d = a.transport, m = `${s}|${a.origin ?? "*"}`, h = d ? f.get(d) ?? (f.set(d, /* @__PURE__ */ new Map()), f.get(d)) : p, g = h.get(m);
95
+ if (g) return g;
96
+ let v = /* @__PURE__ */ new Map(), y = /* @__PURE__ */ new Map(), b = (e) => y.get(e) ?? y.set(e, /* @__PURE__ */ new Set()).get(e), x = (e, t) => {
97
+ y.get(e)?.delete(t);
98
+ try {
99
+ t.onmessage = null, t.close();
100
+ } catch {}
101
+ }, S = (e, t, n) => {
102
+ for (let r of n ? [n] : [...b(e)]) try {
103
+ r.postMessage(t);
104
+ } catch {
105
+ x(e, r);
106
+ }
107
+ }, C = (e) => {
108
+ let t = v.get(e);
109
+ return t ? o(t) : void 0;
110
+ }, w = (e, t) => {
111
+ if (!y.get(e)?.size) return;
112
+ let n = C(e);
113
+ n && S(e, {
114
+ event: t,
115
+ snapshot: n
116
+ });
117
+ }, T = {
118
+ subscribe: (e, t) => {
119
+ if (typeof e != "string") throw TypeError("a player id must be a string");
120
+ if (typeof MessagePort < "u" && !(t instanceof MessagePort)) throw TypeError("subscribe takes a MessagePort");
121
+ if (!y.has(e) && y.size >= u) throw RangeError("too many players on this channel");
122
+ let n = b(e);
123
+ if (n.size >= l) throw RangeError("too many embedders for this player");
124
+ n.add(t), t.onmessage = () => x(e, t), t.start?.();
125
+ let r = C(e);
126
+ r && S(e, {
127
+ event: "loadstart",
128
+ snapshot: r
129
+ }, t);
130
+ },
131
+ set: (e, t, r) => {
132
+ let i = v.get(e);
133
+ if (!(!i || !n.includes(t))) try {
134
+ i[t] = r;
135
+ } catch {}
136
+ },
137
+ call: async (e, t) => {
138
+ let n = v.get(e);
139
+ if (!(!n || !r.includes(t))) try {
140
+ t === "play" ? await n.play() : t === "pause" ? n.pause() : n.load?.();
141
+ } finally {
142
+ w(e, "timeupdate");
143
+ }
144
+ }
145
+ }, E = new AbortController();
146
+ E.signal.addEventListener("abort", () => {
147
+ h.delete(m);
148
+ for (let [e, t] of y) for (let n of [...t]) x(e, n);
149
+ y.clear(), v.clear();
150
+ }, { once: !0 }), e(T, {
151
+ transport: a.transport ?? {
152
+ emit: window.parent,
153
+ receive: _(() => window.parent, a.origin)
154
+ },
155
+ origin: a.origin,
156
+ key: s,
157
+ unregisterSignal: E.signal
158
+ });
159
+ let D = { serve: (e, n) => {
160
+ let r = n.id ?? "default", a = t.map((t) => [t, () => {
161
+ v.get(r) === e && w(r, t);
162
+ }]);
163
+ for (let [t, n] of a) e.addEventListener(t, n);
164
+ let s = v.has(r);
165
+ v.set(r, e), s && S(r, {
166
+ event: "emptied",
167
+ snapshot: { ...c }
168
+ });
169
+ for (let t of i(o(e))) w(r, t);
170
+ let l = () => {
171
+ for (let [t, n] of a) e.removeEventListener(t, n);
172
+ v.get(r) === e && (v.delete(r), S(r, {
173
+ event: "emptied",
174
+ snapshot: { ...c }
175
+ }));
176
+ }, u = () => {
177
+ l(), E.abort();
178
+ };
179
+ return n.signal && (n.signal.aborted ? u() : n.signal.addEventListener("abort", u, { once: !0 })), l;
180
+ } };
181
+ return h.set(m, D), D;
182
+ }, h = (e) => {
183
+ if (e === void 0 || e === "*") return;
184
+ if (e === "null") throw TypeError("an opaque origin (\"null\") cannot be spoken to; leave `origin` unset and the sender check stands alone");
185
+ let t;
186
+ try {
187
+ t = new URL(e);
188
+ } catch {
189
+ throw TypeError(`\`origin\` must be an origin like https://example.com, not ${e}`);
190
+ }
191
+ if (t.origin !== e) throw TypeError(`\`origin\` must be an origin like ${t.origin}, not ${e}`);
192
+ }, g = (e) => {
193
+ let t = e && e !== "*" ? e : void 0;
194
+ return {
195
+ target: () => t && t !== "null" ? t : "*",
196
+ admits: (e) => t ? e === t : (e && (t = e), !0)
197
+ };
198
+ }, _ = (e, t, n = g(t)) => (t) => {
199
+ let r = (r) => {
200
+ r.source === e() && n.admits(r.origin) && t(r.data, {
201
+ source: r.source,
202
+ origin: r.origin,
203
+ receiveTransport: window
204
+ });
205
+ };
206
+ return window.addEventListener("message", r), () => window.removeEventListener("message", r);
207
+ };
208
+ //#endregion
209
+ export { d as exposePlayer };
package/sw.js ADDED
@@ -0,0 +1,192 @@
1
+ // Copied by the build rather than bundled, so it cannot import. `scripts/build-sw.mjs` replaces the
2
+ // three @stamp lines below and refuses to ship a worker where any of them is still a placeholder.
3
+
4
+ const PREFIX = new URL('/__ripple-stream/', self.location.href).href
5
+
6
+ const live = new Map()
7
+
8
+ /* Stamped at build time. These are the dev defaults and must never reach production: a worker whose
9
+ BUILD never changes is never reinstalled, so the update this feature announces would never fire. */
10
+ const MODE = "cache" // @stamp:mode
11
+ const BUILD = "bb553bfd251c2ac2" // @stamp:build
12
+ const MANIFEST = [["/api-Jq4OSTp1-B4uxQLLk.js","c10b405839d5d665"],["/assets/empty-BvRk9kiK.js","e3b0c44298fc1c14"],["/assets/libtorrent-DS_9Yo8M.js","9cdbeb18165a6fa6"],["/assets/worker-SJZDGuG-.js","5d60edb0c4b932c0"],["/build-DEOZ3A7n.js","ed3b894978c81b60"],["/remote-BLaEXLm6.js","23f487ac31e13c9f"]] // @stamp:manifest
13
+
14
+ const CACHE = 'ripple-assets-' + BUILD
15
+
16
+ /**
17
+ * Which requests this worker may answer from its cache, and the whole of it.
18
+ *
19
+ * ONLY hash-named JavaScript chunks. Never the document, never /index.js, never /sw.js. That
20
+ * narrowness is the design, not a shortcut:
21
+ *
22
+ * - A hashed path names its BYTES, so a hit from any build is correct by construction. There is no
23
+ * stale-content failure available here, only a hit or a miss.
24
+ * - Everything else stays on the network, which means a bad build is always replaceable by the next
25
+ * deploy. A worker that served the document from cache could pin somebody to a broken build with
26
+ * no page JavaScript left to run the update check, and nothing short of clearing site data would
27
+ * get them out.
28
+ *
29
+ * It exists because a deploy rotates these filenames and Cloudflare answers the old path with the
30
+ * SPA fallback: 200, text/html, which the browser then runs as a module worker. Measured against
31
+ * production, an earlier build's chunk is byte-identical to one that never existed.
32
+ */
33
+ const HASHED = /^\/(?:assets\/)?[A-Za-z0-9_.$-]+-[A-Za-z0-9_-]{8,}\.js$/
34
+
35
+ self.addEventListener('install', (event) => {
36
+ // PURGE is the remote kill switch: ship src/sw.mode as `purge` and the next deploy's worker takes
37
+ // over at once, deletes every cache, and answers nothing. It needs no cooperation from the page.
38
+ if (MODE === 'purge') { self.skipWaiting(); return }
39
+ /*
40
+ * No skipWaiting. A new worker installs and WAITS, so a page keeps the build it loaded with until
41
+ * somebody presses Update. That is the opposite of what this worker used to do, and it is the
42
+ * point: taking over immediately is what would swap the chunk set under a running engine.
43
+ */
44
+ event.waitUntil(
45
+ caches.open(CACHE)
46
+ .then((cache) => cache.addAll(MANIFEST.map(([path]) => path)))
47
+ // A failed precache must not fail the install. The worker still routes and still fills the
48
+ // cache on demand; a rejected install would leave the origin with no worker at all, which is
49
+ // strictly worse than one with a cold cache.
50
+ .catch(() => undefined),
51
+ )
52
+ })
53
+
54
+ self.addEventListener('activate', (event) => {
55
+ event.waitUntil((async () => {
56
+ if (MODE === 'purge') {
57
+ for (const key of await caches.keys()) await caches.delete(key)
58
+ await self.clients.claim()
59
+ return
60
+ }
61
+ // every generation but this one: a hashed chunk is immutable, so old generations are only ever
62
+ // wasted room, never wrong answers
63
+ for (const key of await caches.keys()) {
64
+ if (key.startsWith('ripple-assets-') && key !== CACHE) await caches.delete(key)
65
+ }
66
+ await self.clients.claim()
67
+ })())
68
+ })
69
+
70
+ self.addEventListener('message', (event) => {
71
+ const data = event.data
72
+ if (!data) return
73
+ if (data.type === 'stream-open') {
74
+ const port = event.ports[0]
75
+ if (!port) return
76
+ live.set(data.id, { name: data.name, size: data.size, port, resolvePull: null })
77
+ port.postMessage({ type: 'stream-ready' })
78
+ return
79
+ }
80
+ // Answered only while the download is still live: a worker killed mid-download comes back with an empty map.
81
+ if (data.type === 'ping' && live.has(data.id) && event.source) {
82
+ event.source.postMessage({ type: 'pong', id: data.id })
83
+ return
84
+ }
85
+ // Pressed Update. Only a WAITING worker ever receives this, and taking over fires controllerchange
86
+ // in every open page, which is what reloads them all together.
87
+ if (data.type === 'take-over') self.skipWaiting()
88
+ if (data.type === 'which-build' && event.source) {
89
+ event.source.postMessage({ type: 'build', build: BUILD, mode: MODE })
90
+ }
91
+ })
92
+
93
+ const disposition = (name) => {
94
+ const safe = String(name || 'download').replace(/[\r\n"\\]/g, '_')
95
+ return `attachment; filename="${safe}"; filename*=UTF-8''${encodeURIComponent(name || 'download')}`
96
+ }
97
+
98
+ self.addEventListener('fetch', (event) => {
99
+ const url = event.request.url
100
+
101
+ /*
102
+ * The cache route. Checked BEFORE the stream prefix bails out, and deliberately after nothing:
103
+ * the two never overlap, since a stream URL is under /__ripple-stream/ and can never match a
104
+ * hashed .js path.
105
+ *
106
+ * Cache first, network second, and a miss simply proxies, which is byte-identical to not
107
+ * intercepting at all. Only same-origin GETs, so a request to any other host is untouched.
108
+ */
109
+ if (MODE !== 'purge' && event.request.method === 'GET' && !url.startsWith(PREFIX)) {
110
+ const path = new URL(url).pathname
111
+ if (new URL(url).origin === self.location.origin && HASHED.test(path)) {
112
+ event.respondWith((async () => {
113
+ const cache = await caches.open(CACHE)
114
+ const hit = await cache.match(path)
115
+ if (hit) return hit
116
+ const fresh = await fetch(event.request)
117
+ // An HTML answer here IS the bug this worker exists for: the chunk is gone and Cloudflare
118
+ // served the SPA fallback with a 200. Never store that, or the cache would preserve the
119
+ // failure across the very reload meant to cure it.
120
+ const type = fresh.headers.get('content-type') || ''
121
+ if (fresh.ok && type.includes('javascript')) await cache.put(path, fresh.clone())
122
+ return fresh
123
+ })())
124
+ return
125
+ }
126
+ }
127
+
128
+ if (!url.startsWith(PREFIX)) return
129
+
130
+ const id = url.slice(PREFIX.length).split('/')[0]
131
+ const entry = live.get(id)
132
+ // Answered rather than left to fall through: an unclaimed synthetic URL would be served the app's own HTML.
133
+ if (!entry) {
134
+ event.respondWith(new Response('Unknown download', { status: 404 }))
135
+ return
136
+ }
137
+
138
+ const finish = () => {
139
+ if (entry.done) return
140
+ entry.done = true
141
+ live.delete(id)
142
+ const resolve = entry.resolvePull
143
+ entry.resolvePull = null
144
+ if (resolve) resolve()
145
+ }
146
+
147
+ const body = new ReadableStream({
148
+ start(controller) {
149
+ entry.port.onmessage = (e) => {
150
+ const msg = e.data
151
+ if (!msg || entry.done) return
152
+ if (msg.type === 'chunk') {
153
+ controller.enqueue(msg.data)
154
+ const resolve = entry.resolvePull
155
+ entry.resolvePull = null
156
+ if (resolve) resolve()
157
+ return
158
+ }
159
+ if (msg.type === 'end') {
160
+ controller.close()
161
+ finish()
162
+ return
163
+ }
164
+ if (msg.type === 'abort') {
165
+ controller.error(new Error(msg.reason || 'aborted'))
166
+ finish()
167
+ }
168
+ }
169
+ },
170
+ // Returning a promise that only settles once the chunk arrives is what creates the backpressure.
171
+ pull() {
172
+ return new Promise((resolve) => {
173
+ entry.resolvePull = resolve
174
+ entry.port.postMessage({ type: 'pull' })
175
+ })
176
+ },
177
+ cancel(reason) {
178
+ entry.port.postMessage({ type: 'cancel', reason: String(reason) })
179
+ finish()
180
+ },
181
+ })
182
+
183
+ const headers = {
184
+ 'Content-Type': 'application/octet-stream',
185
+ 'Content-Disposition': disposition(entry.name),
186
+ 'Cache-Control': 'no-store',
187
+ }
188
+ // Only when it is known exactly: a zip's length is not known until it has been written.
189
+ if (entry.size > 0) headers['Content-Length'] = String(entry.size)
190
+
191
+ event.respondWith(new Response(body, { headers }))
192
+ })
@@ -1,65 +0,0 @@
1
- {
2
- "short_name": "Ripple",
3
- "name": "Ripple - A torrent web app",
4
- "description": "A torrent app on the web",
5
- "icons": [
6
- {
7
- "src": "./assets/logo.png",
8
- "sizes": "512x512",
9
- "type": "image/png"
10
- }
11
- ],
12
- "id": "/?source=pwa",
13
- "start_url": "/?source=pwa",
14
- "background_color": "#3367D6",
15
- "display": "standalone",
16
- "scope": "/",
17
- "theme_color": "#3367D6",
18
- "shortcuts": [
19
- {
20
- "name": "Pause all torrents",
21
- "short_name": "Pause all",
22
- "description": "Pause all currently downloading torrents",
23
- "url": "/?source=pwa&pauseAll=true"
24
- }
25
- ],
26
- "protocol_handlers": [
27
- {
28
- "protocol": "magnet",
29
- "url": "/protocol-handler?magnet=%s"
30
- }
31
- ],
32
- "file_handlers": [
33
- {
34
- "action": "/file-handler",
35
- "accept": {
36
- "application/x-bittorrent": [".torrent"],
37
- "x-scheme-handler/magnet": ["magnet:?xt=urn:btih:"]
38
- },
39
- "icons": [
40
- {
41
- "src": "csv-icon.png",
42
- "sizes": "256x256",
43
- "type": "image/png"
44
- }
45
- ]
46
- }
47
- ],
48
- "share_target": {
49
- "action": "/share-target",
50
- "method": "POST",
51
- "enctype": "multipart/form-data",
52
- "params": {
53
- "files": [
54
- {
55
- "name": "file",
56
- "accept": [
57
- "application/vnd.ripple+json",
58
- "application/json",
59
- ".ripple"
60
- ]
61
- }
62
- ]
63
- }
64
- }
65
- }