qurantathbit 1.0.2 → 1.0.3

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.
Binary file
Binary file
Binary file
package/index.html CHANGED
@@ -92,7 +92,8 @@ body {
92
92
  </style>
93
93
  <link id="-gd-engine-icon" rel="icon" type="image/png" href="index.icon.png" />
94
94
  <link rel="apple-touch-icon" href="index.apple-touch-icon.png"/>
95
-
95
+ <link rel="manifest" href="index.manifest.json">
96
+ <style>body{background-color:#faf5ed}#status::before{content:'QuranTathbit';display:block;font:700 30px/1.25 system-ui,sans-serif;color:#14513d;margin-bottom:14px}#status::after{content:'the first visit downloads the app, then it is cached';display:block;font:400 13px/1.5 system-ui,sans-serif;color:#5c6b62;margin-top:18px}#status-progress{-webkit-appearance:none;appearance:none;border:0;height:10px;width:min(60vw,420px);border-radius:99px;overflow:hidden;background:#e6dfce}#status-progress::-webkit-progress-bar{background:#e6dfce;border-radius:99px}#status-progress::-webkit-progress-value{background:#1e6b52;border-radius:99px;transition:width .25s ease-out}#status-progress::-moz-progress-bar{background:#1e6b52;border-radius:99px}</style><script>(function(){if(typeof window.fetch!=='function'||typeof DecompressionStream==='undefined'){return}var nativeFetch=window.fetch.bind(window);function wasmUrl(u){var i=u.indexOf('index.wasm');if(i===-1){return false}var before=(i===0)?'':u.charAt(i-1);var after=u.charAt(i+10);return(before===''||before==='/')&&(after===''||after==='?'||after==='#')}window.fetch=function(input,init){var url=(typeof input==='string')?input:((input&&typeof input.url==='string')?input.url:'');if(!wasmUrl(url)){return nativeFetch(input,init)}return nativeFetch(url.replace('index.wasm','index.wasm.gz'),{credentials:'same-origin'}).then(function(resp){if(!resp.ok){throw new Error('gz missing')}var enc=(resp.headers.get('content-encoding')||'').toLowerCase();if(enc.indexOf('gzip')!==-1){return resp}return new Response(resp.body.pipeThrough(new DecompressionStream('gzip')),{status:200,headers:{'content-type':'application/wasm'}})}).catch(function(){return nativeFetch(input,init)})}})();</script>
96
97
  </head>
97
98
  <body>
98
99
  <canvas id="canvas">
@@ -111,7 +112,7 @@ body {
111
112
 
112
113
  <script src="index.js"></script>
113
114
  <script>
114
- const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"emscriptenPoolSize":8,"ensureCrossOriginIsolationHeaders":true,"executable":"index","experimentalVK":false,"fileSizes":{"index.pck":979112,"index.wasm":39514754},"focusCanvas":true,"gdextensionLibs":[],"godotPoolSize":4};
115
+ const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"emscriptenPoolSize":8,"ensureCrossOriginIsolationHeaders":true,"executable":"index","experimentalVK":false,"fileSizes":{"index.pck":983384,"index.wasm":39514754},"focusCanvas":true,"gdextensionLibs":[],"godotPoolSize":4,"serviceWorker":"index.service.worker.js"};
115
116
  const GODOT_THREADS_ENABLED = false;
116
117
  const engine = new Engine(GODOT_CONFIG);
117
118
 
@@ -0,0 +1 @@
1
+ {"background_color":"#faf5ed","theme_color":"#14513d","display":"standalone","icons":[{"sizes":"144x144","src":"index.144x144.png","type":"image/png"},{"sizes":"180x180","src":"index.180x180.png","type":"image/png"},{"sizes":"512x512","src":"index.512x512.png","type":"image/png"}],"name":"QuranTathbit","orientation":"any","start_url":"./index.html"}
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>You are offline</title>
8
+ <style>
9
+ html {
10
+ background-color: #000000;
11
+ color: #ffffff;
12
+ }
13
+
14
+ body {
15
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
16
+ margin: 2rem;
17
+ }
18
+
19
+ p {
20
+ margin-block: 1rem;
21
+ }
22
+
23
+ button {
24
+ display: block;
25
+ padding: 1rem 2rem;
26
+ margin: 3rem auto 0;
27
+ }
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <h1>You are offline</h1>
32
+ <p>This application requires an Internet connection to run for the first time.</p>
33
+ <p>Press the button below to try reloading:</p>
34
+ <button type="button">Reload</button>
35
+ <script>
36
+ document.querySelector('button').addEventListener('click', () => {
37
+ window.location.reload();
38
+ });
39
+ </script>
40
+ </body>
41
+ </html>
package/index.pck CHANGED
Binary file
@@ -0,0 +1,166 @@
1
+ // This service worker is required to expose an exported Godot project as a
2
+ // Progressive Web App. It provides an offline fallback page telling the user
3
+ // that they need an Internet connection to run the project if desired.
4
+ // Incrementing CACHE_VERSION will kick off the install event and force
5
+ // previously cached resources to be updated from the network.
6
+ /** @type {string} */
7
+ const CACHE_VERSION = '1792101335|2440927';
8
+ /** @type {string} */
9
+ const CACHE_PREFIX = 'QuranTathbit-sw-cache-';
10
+ const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;
11
+ /** @type {string} */
12
+ const OFFLINE_URL = 'index.offline.html';
13
+ /** @type {boolean} */
14
+ const ENSURE_CROSSORIGIN_ISOLATION_HEADERS = true;
15
+ // Files that will be cached on load.
16
+ /** @type {string[]} */
17
+ const CACHED_FILES = ["index.html","index.js","index.offline.html","index.icon.png","index.apple-touch-icon.png","index.audio.worklet.js","index.audio.position.worklet.js"];
18
+ // Files that we might not want the user to preload, and will only be cached on first load.
19
+ /** @type {string[]} */
20
+ const CACHEABLE_FILES = ["index.wasm","index.pck"];
21
+ const FULL_CACHE = CACHED_FILES.concat(CACHEABLE_FILES);
22
+
23
+ self.addEventListener('install', (event) => {
24
+ event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(CACHED_FILES)));
25
+ });
26
+
27
+ self.addEventListener('activate', (event) => {
28
+ event.waitUntil(caches.keys().then(
29
+ function (keys) {
30
+ // Remove old caches.
31
+ return Promise.all(keys.filter((key) => key.startsWith(CACHE_PREFIX) && key !== CACHE_NAME).map((key) => caches.delete(key)));
32
+ }
33
+ ).then(function () {
34
+ // Enable navigation preload if available.
35
+ return ('navigationPreload' in self.registration) ? self.registration.navigationPreload.enable() : Promise.resolve();
36
+ }));
37
+ });
38
+
39
+ /**
40
+ * Ensures that the response has the correct COEP/COOP headers
41
+ * @param {Response} response
42
+ * @returns {Response}
43
+ */
44
+ function ensureCrossOriginIsolationHeaders(response) {
45
+ if (response.headers.get('Cross-Origin-Embedder-Policy') === 'require-corp'
46
+ && response.headers.get('Cross-Origin-Opener-Policy') === 'same-origin') {
47
+ return response;
48
+ }
49
+
50
+ const crossOriginIsolatedHeaders = new Headers(response.headers);
51
+ crossOriginIsolatedHeaders.set('Cross-Origin-Embedder-Policy', 'require-corp');
52
+ crossOriginIsolatedHeaders.set('Cross-Origin-Opener-Policy', 'same-origin');
53
+ const newResponse = new Response(response.body, {
54
+ status: response.status,
55
+ statusText: response.statusText,
56
+ headers: crossOriginIsolatedHeaders,
57
+ });
58
+
59
+ return newResponse;
60
+ }
61
+
62
+ /**
63
+ * Calls fetch and cache the result if it is cacheable
64
+ * @param {FetchEvent} event
65
+ * @param {Cache} cache
66
+ * @param {boolean} isCacheable
67
+ * @returns {Response}
68
+ */
69
+ async function fetchAndCache(event, cache, isCacheable) {
70
+ // Use the preloaded response, if it's there
71
+ /** @type { Response } */
72
+ let response = await event.preloadResponse;
73
+ if (response == null) {
74
+ // Or, go over network.
75
+ response = await self.fetch(event.request);
76
+ }
77
+
78
+ if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) {
79
+ response = ensureCrossOriginIsolationHeaders(response);
80
+ }
81
+
82
+ if (isCacheable) {
83
+ // And update the cache
84
+ cache.put(event.request, response.clone());
85
+ }
86
+
87
+ return response;
88
+ }
89
+
90
+ self.addEventListener(
91
+ 'fetch',
92
+ /**
93
+ * Triggered on fetch
94
+ * @param {FetchEvent} event
95
+ */
96
+ (event) => {
97
+ const isNavigate = event.request.mode === 'navigate';
98
+ const url = event.request.url || '';
99
+ const referrer = event.request.referrer || '';
100
+ const base = referrer.slice(0, referrer.lastIndexOf('/') + 1);
101
+ const local = url.startsWith(base) ? url.replace(base, '') : '';
102
+ const isCacheable = FULL_CACHE.some((v) => v === local) || (base === referrer && base.endsWith(CACHED_FILES[0]));
103
+ if (isNavigate || isCacheable) {
104
+ event.respondWith((async () => {
105
+ // Try to use cache first
106
+ const cache = await caches.open(CACHE_NAME);
107
+ if (isNavigate) {
108
+ // Check if we have full cache during HTML page request.
109
+ /** @type {Response[]} */
110
+ const fullCache = await Promise.all(FULL_CACHE.map((name) => cache.match(name)));
111
+ const missing = fullCache.some((v) => v === undefined);
112
+ if (missing) {
113
+ try {
114
+ // Try network if some cached file is missing (so we can display offline page in case).
115
+ const response = await fetchAndCache(event, cache, isCacheable);
116
+ return response;
117
+ } catch (e) {
118
+ // And return the hopefully always cached offline page in case of network failure.
119
+ console.error('Network error: ', e); // eslint-disable-line no-console
120
+ return caches.match(OFFLINE_URL);
121
+ }
122
+ }
123
+ }
124
+ let cached = await cache.match(event.request);
125
+ if (cached != null) {
126
+ if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) {
127
+ cached = ensureCrossOriginIsolationHeaders(cached);
128
+ }
129
+ return cached;
130
+ }
131
+ // Try network if don't have it in cache.
132
+ const response = await fetchAndCache(event, cache, isCacheable);
133
+ return response;
134
+ })());
135
+ } else if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) {
136
+ event.respondWith((async () => {
137
+ let response = await fetch(event.request);
138
+ response = ensureCrossOriginIsolationHeaders(response);
139
+ return response;
140
+ })());
141
+ }
142
+ }
143
+ );
144
+
145
+ self.addEventListener('message', (event) => {
146
+ // No cross origin
147
+ if (event.origin !== self.origin) {
148
+ return;
149
+ }
150
+ const id = event.source.id || '';
151
+ const msg = event.data || '';
152
+ // Ensure it's one of our clients.
153
+ self.clients.get(id).then(function (client) {
154
+ if (!client) {
155
+ return; // Not a valid client.
156
+ }
157
+ if (msg === 'claim') {
158
+ self.skipWaiting().then(() => self.clients.claim());
159
+ } else if (msg === 'clear') {
160
+ caches.delete(CACHE_NAME);
161
+ } else if (msg === 'update') {
162
+ self.skipWaiting().then(() => self.clients.claim()).then(() => self.clients.matchAll()).then((all) => all.forEach((c) => c.navigate(c.url)));
163
+ }
164
+ });
165
+ });
166
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qurantathbit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A quiet companion for keeping the Qur'an close — one deck, one card at a time, with your own GitHub as the cloud. Web build served from any static host or `npx qurantathbit`.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,6 +33,12 @@
33
33
  "index.png",
34
34
  "index.audio.worklet.js",
35
35
  "index.audio.position.worklet.js",
36
+ "index.manifest.json",
37
+ "index.service.worker.js",
38
+ "index.offline.html",
39
+ "index.144x144.png",
40
+ "index.180x180.png",
41
+ "index.512x512.png",
36
42
  "decks/",
37
43
  "README.npm.md",
38
44
  "LICENSE"