mnfst 0.5.199-next.1 → 0.5.199-next.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.
- package/lib/manifest.appwrite.auth.js +33 -6
- package/lib/manifest.appwrite.data.js +1 -1
- package/lib/manifest.chat.js +48 -19
- package/lib/manifest.components.js +2 -0
- package/lib/manifest.data.js +64 -15
- package/lib/manifest.integrity.json +9 -9
- package/lib/manifest.js +17 -5
- package/lib/manifest.payments.js +3 -2
- package/lib/manifest.status.js +5 -4
- package/lib/manifest.sw.js +1 -1
- package/package.json +1 -1
|
@@ -26,13 +26,14 @@ async function ensureManifest() {
|
|
|
26
26
|
return window.__manifestLoaded;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
if (window.__manifestPromise) {
|
|
30
|
+
const shared = await window.__manifestPromise.catch(() => null);
|
|
31
|
+
if (shared) return shared;
|
|
32
|
+
}
|
|
29
33
|
try {
|
|
30
34
|
const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// No-loader path: resolve ${VAR} placeholders the dynamic loader would have.
|
|
34
|
-
window.ManifestDataConfig?.interpolateManifest?.(manifest);
|
|
35
|
-
return manifest;
|
|
35
|
+
window.__manifestPromise = fetch(manifestUrl).then(r => r.json()).then(m => { window.ManifestDataConfig?.interpolateManifest?.(m); return m; });
|
|
36
|
+
return await window.__manifestPromise;
|
|
36
37
|
} catch (error) {
|
|
37
38
|
return null;
|
|
38
39
|
}
|
|
@@ -2239,6 +2240,7 @@ function initializeTeamsCore() {
|
|
|
2239
2240
|
this.error = null;
|
|
2240
2241
|
|
|
2241
2242
|
try {
|
|
2243
|
+
invalidateRolesCache(teamId);
|
|
2242
2244
|
const result = await this._appwrite.teams.updatePrefs({
|
|
2243
2245
|
teamId: teamId,
|
|
2244
2246
|
prefs: prefs
|
|
@@ -2950,6 +2952,7 @@ function initializeTeamsRolesDefaults() {
|
|
|
2950
2952
|
roles: updatedRoles,
|
|
2951
2953
|
deletedTemplateRoles: deletedRoles // Update deleted list in team preferences
|
|
2952
2954
|
};
|
|
2955
|
+
invalidateRolesCache(teamId);
|
|
2953
2956
|
await this._appwrite.teams.updatePrefs({
|
|
2954
2957
|
teamId: teamId,
|
|
2955
2958
|
prefs: updatedPrefs
|
|
@@ -3053,6 +3056,7 @@ function initializeTeamsRolesDefaults() {
|
|
|
3053
3056
|
...currentPrefs,
|
|
3054
3057
|
roles: updatedRoles
|
|
3055
3058
|
};
|
|
3059
|
+
invalidateRolesCache(teamId);
|
|
3056
3060
|
await this._appwrite.teams.updatePrefs({
|
|
3057
3061
|
teamId: teamId,
|
|
3058
3062
|
prefs: updatedPrefs
|
|
@@ -3204,12 +3208,29 @@ function roleHasAllOwnerPermissions(roleName, memberRoles) {
|
|
|
3204
3208
|
return OWNER_PERMISSIONS.every(perm => permissions.includes(perm));
|
|
3205
3209
|
}
|
|
3206
3210
|
|
|
3207
|
-
//
|
|
3211
|
+
// User-generated roles live in team prefs; one read per team is shared by every permission check
|
|
3212
|
+
// for a short window and dropped on any auth change or prefs write
|
|
3213
|
+
const ROLES_CACHE_TTL_MS = 15000;
|
|
3214
|
+
const rolesCache = new Map(); // teamId -> { at, promise }
|
|
3215
|
+
function invalidateRolesCache(teamId) { if (teamId === undefined) rolesCache.clear(); else rolesCache.delete(teamId); }
|
|
3216
|
+
if (typeof window !== 'undefined') {
|
|
3217
|
+
['manifest:auth:login', 'manifest:auth:logout', 'manifest:auth:anonymous', 'manifest:auth:session-cleared', 'manifest:auth:initialized', 'manifest:auth:teams-loaded']
|
|
3218
|
+
.forEach(type => window.addEventListener(type, () => invalidateRolesCache()));
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3208
3221
|
async function getUserGeneratedRoles(teamId, appwrite) {
|
|
3209
3222
|
if (!appwrite || !appwrite.teams) {
|
|
3210
3223
|
return null;
|
|
3211
3224
|
}
|
|
3225
|
+
const hit = rolesCache.get(teamId);
|
|
3226
|
+
if (hit && Date.now() - hit.at < ROLES_CACHE_TTL_MS) return hit.promise;
|
|
3227
|
+
const promise = readUserGeneratedRoles(teamId, appwrite);
|
|
3228
|
+
rolesCache.set(teamId, { at: Date.now(), promise });
|
|
3229
|
+
promise.catch(() => rolesCache.delete(teamId)); // a null result (no custom roles) is a valid, cached answer
|
|
3230
|
+
return promise;
|
|
3231
|
+
}
|
|
3212
3232
|
|
|
3233
|
+
async function readUserGeneratedRoles(teamId, appwrite) {
|
|
3213
3234
|
try {
|
|
3214
3235
|
const prefs = await appwrite.teams.getPrefs({ teamId });
|
|
3215
3236
|
return prefs?.roles || null;
|
|
@@ -3661,6 +3682,7 @@ function initializeTeamsUserRoles() {
|
|
|
3661
3682
|
...currentPrefs,
|
|
3662
3683
|
roles: updatedRoles
|
|
3663
3684
|
};
|
|
3685
|
+
invalidateRolesCache(teamId);
|
|
3664
3686
|
await this._appwrite.teams.updatePrefs({
|
|
3665
3687
|
teamId: teamId,
|
|
3666
3688
|
prefs: updatedPrefs
|
|
@@ -3779,6 +3801,7 @@ function initializeTeamsUserRoles() {
|
|
|
3779
3801
|
...currentPrefs,
|
|
3780
3802
|
roles: updatedRoles
|
|
3781
3803
|
};
|
|
3804
|
+
invalidateRolesCache(teamId);
|
|
3782
3805
|
await this._appwrite.teams.updatePrefs({
|
|
3783
3806
|
teamId: teamId,
|
|
3784
3807
|
prefs: updatedPrefs
|
|
@@ -3880,6 +3903,7 @@ function initializeTeamsUserRoles() {
|
|
|
3880
3903
|
...currentPrefs,
|
|
3881
3904
|
roles: updatedRoles
|
|
3882
3905
|
};
|
|
3906
|
+
invalidateRolesCache(teamId);
|
|
3883
3907
|
await this._appwrite.teams.updatePrefs({
|
|
3884
3908
|
teamId: teamId,
|
|
3885
3909
|
prefs: updatedPrefs
|
|
@@ -4045,6 +4069,8 @@ function initializeTeamsUserRoles() {
|
|
|
4045
4069
|
deletedTemplateRoles: deletedRoles
|
|
4046
4070
|
};
|
|
4047
4071
|
|
|
4072
|
+
invalidateRolesCache(teamId);
|
|
4073
|
+
|
|
4048
4074
|
await this._appwrite.teams.updatePrefs({
|
|
4049
4075
|
teamId: teamId,
|
|
4050
4076
|
prefs: updatedPrefs
|
|
@@ -4080,6 +4106,7 @@ function initializeTeamsUserRoles() {
|
|
|
4080
4106
|
...currentPrefs,
|
|
4081
4107
|
roles: updatedRoles
|
|
4082
4108
|
};
|
|
4109
|
+
invalidateRolesCache(teamId);
|
|
4083
4110
|
await this._appwrite.teams.updatePrefs({
|
|
4084
4111
|
teamId: teamId,
|
|
4085
4112
|
prefs: updatedPrefs
|
package/lib/manifest.chat.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
pending: new Map(), // conversationId | INDEX -> due time
|
|
36
36
|
timer: null,
|
|
37
37
|
saved: new Map(), // conversationId -> { messages, savedAt }
|
|
38
|
+
windows: new Map(), // conversationId -> { messages, savedAt } last known window (disk read or written snapshot) for synchronous re-opens
|
|
38
39
|
generation: 0,
|
|
39
40
|
watching: false,
|
|
40
41
|
resetQueued: false
|
|
@@ -132,6 +133,7 @@
|
|
|
132
133
|
state.indexPromise = null;
|
|
133
134
|
state.indexDirty = false;
|
|
134
135
|
state.saved.clear();
|
|
136
|
+
state.windows.clear();
|
|
135
137
|
for (const h of state.handles.values()) { try { h.reset(); } catch (_) { } }
|
|
136
138
|
cancel();
|
|
137
139
|
}
|
|
@@ -158,25 +160,36 @@
|
|
|
158
160
|
return state.indexPromise;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
//
|
|
163
|
+
// Move to the front of the index; beyond the cap the oldest conversations' records are deleted
|
|
164
|
+
function promote(recent, conversationId) {
|
|
165
|
+
const i = recent.indexOf(conversationId);
|
|
166
|
+
if (i === 0) return;
|
|
167
|
+
if (i > -1) recent.splice(i, 1);
|
|
168
|
+
recent.unshift(conversationId);
|
|
169
|
+
const evicted = recent.splice(state.config.conversations);
|
|
170
|
+
const r = records();
|
|
171
|
+
for (const id of evicted) { state.pending.delete(id); state.saved.delete(id); state.windows.delete(id); }
|
|
172
|
+
if (evicted.length) r.delete(evicted.map(id => r.key('chat', id))).catch(noop);
|
|
173
|
+
state.indexDirty = true;
|
|
174
|
+
state.pending.set(INDEX, Date.now() + WRITE_DEBOUNCE_MS);
|
|
175
|
+
schedule();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Recency bump for a conversation already in the index; a new one earns its slot on its first non-empty write
|
|
162
179
|
function opened(conversationId) {
|
|
163
180
|
if (conversationId == null) return;
|
|
164
181
|
const gen = state.generation;
|
|
165
182
|
ensureIndex().then(recent => {
|
|
166
183
|
if (gen !== state.generation || !enabled()) return;
|
|
167
|
-
|
|
168
|
-
if (i > -1) recent.splice(i, 1);
|
|
169
|
-
recent.unshift(conversationId);
|
|
170
|
-
const evicted = recent.splice(state.config.conversations);
|
|
171
|
-
const r = records();
|
|
172
|
-
for (const id of evicted) { state.pending.delete(id); state.saved.delete(id); }
|
|
173
|
-
if (evicted.length) r.delete(evicted.map(id => r.key('chat', id))).catch(noop);
|
|
174
|
-
state.indexDirty = true;
|
|
175
|
-
state.pending.set(INDEX, Date.now() + WRITE_DEBOUNCE_MS);
|
|
176
|
-
schedule();
|
|
184
|
+
if (recent.includes(conversationId)) promote(recent, conversationId);
|
|
177
185
|
}).catch(noop);
|
|
178
186
|
}
|
|
179
187
|
|
|
188
|
+
// Last known window for a conversation this generation (synchronous; re-opens and handle swaps)
|
|
189
|
+
function peek(conversationId) {
|
|
190
|
+
return (enabled() && conversationId != null && state.windows.get(conversationId)) || null;
|
|
191
|
+
}
|
|
192
|
+
|
|
180
193
|
// ---- hydrate (read path) ----------------------------------------------------
|
|
181
194
|
// Record + index in one transaction on the first open; later opens read the record only
|
|
182
195
|
function hydrate(conversationId) {
|
|
@@ -208,7 +221,9 @@
|
|
|
208
221
|
return null;
|
|
209
222
|
}
|
|
210
223
|
state.saved.set(conversationId, { messages: rec.rows.length, savedAt: rec.savedAt });
|
|
211
|
-
|
|
224
|
+
const window_ = { messages: rec.rows, savedAt: rec.savedAt };
|
|
225
|
+
if (!state.windows.has(conversationId)) state.windows.set(conversationId, window_);
|
|
226
|
+
return window_;
|
|
212
227
|
}
|
|
213
228
|
|
|
214
229
|
// ---- snapshot (write path) --------------------------------------------------
|
|
@@ -267,14 +282,21 @@
|
|
|
267
282
|
const gen = state.generation;
|
|
268
283
|
const r = records();
|
|
269
284
|
const savedAt = Date.now();
|
|
270
|
-
const
|
|
285
|
+
const candidates = new Map();
|
|
271
286
|
for (const id of due) {
|
|
272
287
|
if (id === INDEX) continue;
|
|
273
288
|
const h = state.handles.get(id);
|
|
274
|
-
if (!h
|
|
289
|
+
if (!h) continue;
|
|
275
290
|
let rows = null;
|
|
276
291
|
try { const msgs = h.snapshot(); rows = msgs && msgs.length ? snapshotOf(msgs) : null; } catch (_) { rows = null; }
|
|
277
292
|
if (!rows) continue;
|
|
293
|
+
if (!recent.includes(id)) promote(recent, id); // first non-empty write earns the slot
|
|
294
|
+
candidates.set(id, rows);
|
|
295
|
+
}
|
|
296
|
+
const list = [];
|
|
297
|
+
for (const [id, rows] of candidates) {
|
|
298
|
+
if (!recent.includes(id)) continue; // evicted by a later candidate in this same flush
|
|
299
|
+
state.windows.set(id, { messages: rows, savedAt });
|
|
278
300
|
list.push({ key: r.key('chat', id), kind: 'chat', conversation: id, rows, savedAt });
|
|
279
301
|
}
|
|
280
302
|
if (state.indexDirty) { list.push({ key: r.key('chat'), kind: 'chat-index', recent: recent.slice(), savedAt }); state.indexDirty = false; }
|
|
@@ -317,7 +339,7 @@
|
|
|
317
339
|
|
|
318
340
|
window.ManifestChatPersist = {
|
|
319
341
|
WRITE_DEBOUNCE_MS,
|
|
320
|
-
configure, bootstrap, enabled, hydrate, opened, touch, attach, anyStale, persistence, flushPending, reset,
|
|
342
|
+
configure, bootstrap, enabled, hydrate, opened, peek, touch, attach, anyStale, persistence, flushPending, reset,
|
|
321
343
|
config: () => state.config,
|
|
322
344
|
state
|
|
323
345
|
};
|
|
@@ -429,10 +451,15 @@
|
|
|
429
451
|
function commitParticipants() { state.participants = [..._participants.values()]; rev().n++; }
|
|
430
452
|
function commitTyping() { state.typing = [..._typing.values()]; rev().n++; }
|
|
431
453
|
|
|
432
|
-
// server echo reconciles body/media/status onto the local copy
|
|
454
|
+
// server echo reconciles body/media/status onto the local copy; meta merges so a
|
|
455
|
+
// re-delivery from another source (no translation, no channel) never drops fields
|
|
433
456
|
function merge(target, incoming) {
|
|
434
457
|
Object.assign(target.body, incoming.body);
|
|
435
|
-
for (const k of Object.keys(incoming))
|
|
458
|
+
for (const k of Object.keys(incoming)) {
|
|
459
|
+
if (k === 'body' || k === '_seq') continue;
|
|
460
|
+
if (k === 'meta' && target.meta && incoming.meta && typeof incoming.meta === 'object') { Object.assign(target.meta, incoming.meta); continue; }
|
|
461
|
+
target[k] = incoming[k];
|
|
462
|
+
}
|
|
436
463
|
}
|
|
437
464
|
|
|
438
465
|
// own echo beating the ack: claim the pending row by meta.clientId
|
|
@@ -562,12 +589,14 @@
|
|
|
562
589
|
commit(); commitParticipants(); commitTyping();
|
|
563
590
|
}
|
|
564
591
|
|
|
565
|
-
if (P() && !isAggregate) detach = P().attach(conversationId, { snapshot, reset, stale: () => state.stale, count: () => _msgs.length });
|
|
592
|
+
if (P() && !isAggregate && opts.persistWindow !== false) detach = P().attach(conversationId, { snapshot, reset, stale: () => state.stale, count: () => _msgs.length });
|
|
566
593
|
|
|
567
594
|
async function open() {
|
|
568
595
|
state.status = 'loading';
|
|
569
596
|
if (detach) {
|
|
570
|
-
P().
|
|
597
|
+
const known = P().peek(conversationId); // re-open or handle swap: window already in memory, no read to lose the race
|
|
598
|
+
if (known) hydrateWindow(known.messages);
|
|
599
|
+
else P().hydrate(conversationId).then(h => { if (h) hydrateWindow(h.messages); }).catch(() => { });
|
|
571
600
|
P().opened(conversationId);
|
|
572
601
|
}
|
|
573
602
|
try {
|
|
@@ -61,6 +61,7 @@ window.ManifestComponentsRegistry = {
|
|
|
61
61
|
// manifest on window yet). This must be async — a synchronous XHR on
|
|
62
62
|
// the main thread is deprecated and was flagged by PageSpeed.
|
|
63
63
|
let manifest = window.__manifestLoaded || this.manifest;
|
|
64
|
+
if (!manifest && window.__manifestPromise) manifest = await window.__manifestPromise.catch(() => null);
|
|
64
65
|
if (!manifest) {
|
|
65
66
|
try {
|
|
66
67
|
const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
|
|
@@ -72,6 +73,7 @@ window.ManifestComponentsRegistry = {
|
|
|
72
73
|
manifest = await res.json();
|
|
73
74
|
// No-loader path: resolve ${VAR} placeholders the dynamic loader would have.
|
|
74
75
|
window.ManifestDataConfig?.interpolateManifest?.(manifest);
|
|
76
|
+
window.__manifestPromise = Promise.resolve(manifest); // share with plugins that init after us
|
|
75
77
|
} else {
|
|
76
78
|
console.warn('[Manifest] Failed to load manifest.json (HTTP', res.status + ')');
|
|
77
79
|
}
|
package/lib/manifest.data.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
|
|
5
|
-
const MANIFEST_BUILD_VERSION = '0.5.199-next.
|
|
5
|
+
const MANIFEST_BUILD_VERSION = '0.5.199-next.3';
|
|
6
6
|
|
|
7
7
|
/* Manifest Data Sources - Configuration */
|
|
8
8
|
|
|
@@ -21,12 +21,15 @@ async function ensureManifest() {
|
|
|
21
21
|
return window.__manifestLoaded;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// One request per boot, shared with the loader and the other plugins (window.__manifestPromise)
|
|
25
|
+
if (window.__manifestPromise) {
|
|
26
|
+
const shared = await window.__manifestPromise.catch(() => null);
|
|
27
|
+
if (looksComplete(shared)) return shared;
|
|
28
|
+
}
|
|
24
29
|
try {
|
|
25
30
|
const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
interpolateManifest(manifest);
|
|
29
|
-
return manifest;
|
|
31
|
+
window.__manifestPromise = fetch(manifestUrl).then(r => r.json()).then(m => { interpolateManifest(m); return m; });
|
|
32
|
+
return await window.__manifestPromise;
|
|
30
33
|
} catch (error) {
|
|
31
34
|
console.error('[Manifest Data] Failed to load manifest:', error);
|
|
32
35
|
return null;
|
|
@@ -1536,6 +1539,11 @@ window.ManifestDataStore = {
|
|
|
1536
1539
|
sources: new Map(), // source -> config (+ rows/savedAt bookkeeping)
|
|
1537
1540
|
filters: new Map(), // source -> row filter
|
|
1538
1541
|
scopeExpr: null,
|
|
1542
|
+
scopePending: false, // a scope expression that has not resolved yet (auth still booting): no keys, no wipes, no resets
|
|
1543
|
+
authExpected: false, // manifest.appwrite.auth present: the scope may still resolve once identity/teams load
|
|
1544
|
+
teamsExpected: false,
|
|
1545
|
+
authSettled: false,
|
|
1546
|
+
settleTimer: null,
|
|
1539
1547
|
scopeFn: null,
|
|
1540
1548
|
scope: '',
|
|
1541
1549
|
generation: 0,
|
|
@@ -1634,7 +1642,9 @@ window.ManifestDataStore = {
|
|
|
1634
1642
|
state.dbName = `manifest:${origin}${projectId ? `:${projectId}` : ''}`;
|
|
1635
1643
|
state.enabled = (state.sources.size > 0 || state.external) && typeof indexedDB !== 'undefined' && !!indexedDB;
|
|
1636
1644
|
if (!state.enabled) return false;
|
|
1645
|
+
watchAuthSettle(manifest);
|
|
1637
1646
|
state.scope = evaluateScope();
|
|
1647
|
+
state.scopePending = pendingScope(state.scope);
|
|
1638
1648
|
watchScope();
|
|
1639
1649
|
return state.sources.size > 0;
|
|
1640
1650
|
}
|
|
@@ -1648,6 +1658,7 @@ window.ManifestDataStore = {
|
|
|
1648
1658
|
if (!state.dbName) state.dbName = `manifest:${(typeof location !== 'undefined' && location.origin) || 'null'}`;
|
|
1649
1659
|
if (!state.frameworkVersion) state.frameworkVersion = buildVersion;
|
|
1650
1660
|
state.scope = evaluateScope();
|
|
1661
|
+
state.scopePending = pendingScope(state.scope);
|
|
1651
1662
|
watchScope();
|
|
1652
1663
|
return true;
|
|
1653
1664
|
}
|
|
@@ -1692,6 +1703,36 @@ window.ManifestDataStore = {
|
|
|
1692
1703
|
});
|
|
1693
1704
|
}
|
|
1694
1705
|
|
|
1706
|
+
// With an expression configured and auth still booting, an empty value means "not known yet"
|
|
1707
|
+
function pendingScope(value) { return !!state.scopeExpr && value === '' && state.authExpected && !state.authSettled; }
|
|
1708
|
+
|
|
1709
|
+
// Identity and teams are known (or will never load): an empty scope is now a real single-tenant scope
|
|
1710
|
+
function settleAuth() {
|
|
1711
|
+
if (state.authSettled) return;
|
|
1712
|
+
state.authSettled = true;
|
|
1713
|
+
if (state.settleTimer) { clearTimeout(state.settleTimer); state.settleTimer = null; }
|
|
1714
|
+
if (state.enabled && state.scopePending) changeScope(evaluateScope());
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
function watchAuthSettle(manifest) {
|
|
1718
|
+
state.authExpected = !!(manifest && manifest.appwrite && manifest.appwrite.auth);
|
|
1719
|
+
state.teamsExpected = !!(state.authExpected && manifest.appwrite.auth.teams);
|
|
1720
|
+
state.authSettled = false;
|
|
1721
|
+
if (!state.authExpected || typeof window === 'undefined') { state.authSettled = true; return; }
|
|
1722
|
+
const auth = (typeof Alpine !== 'undefined' && Alpine.store) ? Alpine.store('auth') : null;
|
|
1723
|
+
if (auth && auth._initialized && (!state.teamsExpected || !auth.isAuthenticated || (auth.teams && auth.teams.length))) { state.authSettled = true; return; }
|
|
1724
|
+
if (!state.settleWatching) {
|
|
1725
|
+
state.settleWatching = true;
|
|
1726
|
+
window.addEventListener('manifest:auth:teams-loaded', settleAuth);
|
|
1727
|
+
window.addEventListener('manifest:auth:initialized', (e) => { if (!state.teamsExpected || !(e.detail && e.detail.isAuthenticated)) settleAuth(); });
|
|
1728
|
+
window.addEventListener('manifest:auth:logout', settleAuth);
|
|
1729
|
+
window.addEventListener('manifest:auth:session-cleared', settleAuth);
|
|
1730
|
+
window.addEventListener('manifest:auth:anonymous', () => { if (!state.teamsExpected) settleAuth(); });
|
|
1731
|
+
}
|
|
1732
|
+
if (state.settleTimer) clearTimeout(state.settleTimer);
|
|
1733
|
+
state.settleTimer = setTimeout(settleAuth, 10000); // never pending forever
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1695
1736
|
function evaluateScope() {
|
|
1696
1737
|
if (!state.scopeExpr) return '';
|
|
1697
1738
|
try {
|
|
@@ -1734,19 +1775,27 @@ window.ManifestDataStore = {
|
|
|
1734
1775
|
// cleared (older loads discarded), new scope hydrates
|
|
1735
1776
|
function changeScope(next) {
|
|
1736
1777
|
const prev = state.scope;
|
|
1778
|
+
const wasPending = state.scopePending;
|
|
1737
1779
|
state.scope = next;
|
|
1780
|
+
state.scopePending = pendingScope(next);
|
|
1738
1781
|
state.generation++;
|
|
1739
1782
|
cancelWrites();
|
|
1740
1783
|
state.hydrated.clear();
|
|
1741
1784
|
state.fetchKicked.clear();
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
cfg.
|
|
1746
|
-
|
|
1785
|
+
// pending → resolved is boot, not a switch: rows loaded meanwhile stay (hydrate skips fresh sources), nothing is wiped
|
|
1786
|
+
if (!wasPending) {
|
|
1787
|
+
const ds = dataStore();
|
|
1788
|
+
for (const [source, cfg] of state.sources) {
|
|
1789
|
+
cfg.rows = null;
|
|
1790
|
+
cfg.savedAt = null;
|
|
1791
|
+
ds?.resetSource?.(source);
|
|
1792
|
+
}
|
|
1793
|
+
if (prev !== next) deleteScope(prev);
|
|
1794
|
+
}
|
|
1795
|
+
if (!state.scopePending) {
|
|
1796
|
+
hydrateBoot();
|
|
1797
|
+
if (wasPending) onLanded([...state.sources.keys()]); // rows that landed while pending get written under the real scope
|
|
1747
1798
|
}
|
|
1748
|
-
if (prev !== next) deleteScope(prev);
|
|
1749
|
-
hydrateBoot();
|
|
1750
1799
|
if (typeof window !== 'undefined') {
|
|
1751
1800
|
try { window.dispatchEvent(new CustomEvent('manifest:persist:scope', { detail: { scope: next, previous: prev } })); } catch { /* no-op */ }
|
|
1752
1801
|
}
|
|
@@ -1948,7 +1997,7 @@ window.ManifestDataStore = {
|
|
|
1948
1997
|
|
|
1949
1998
|
// Landing hook (store flush): each landing restarts that source's 500ms debounce
|
|
1950
1999
|
function onLanded(sources) {
|
|
1951
|
-
if (!state.enabled || state.disabled) return;
|
|
2000
|
+
if (!state.enabled || state.disabled || state.scopePending) return; // nothing is keyed under an unresolved scope
|
|
1952
2001
|
const at = Date.now() + WRITE_DEBOUNCE_MS;
|
|
1953
2002
|
for (const source of sources) if (state.sources.has(source)) state.pending.set(source, at);
|
|
1954
2003
|
if (state.writeTimer !== null) { clearTimeout(state.writeTimer); state.writeTimer = null; }
|
|
@@ -1971,7 +2020,7 @@ window.ManifestDataStore = {
|
|
|
1971
2020
|
async function hydrate(sources) {
|
|
1972
2021
|
const ds = dataStore();
|
|
1973
2022
|
const pending = sources.filter(source => state.sources.has(source) && !state.hydrated.has(source));
|
|
1974
|
-
if (!ds || !pending.length || !state.enabled || state.disabled) return;
|
|
2023
|
+
if (!ds || !pending.length || !state.enabled || state.disabled || state.scopePending) return;
|
|
1975
2024
|
for (const source of pending) state.hydrated.add(source);
|
|
1976
2025
|
const generation = state.generation;
|
|
1977
2026
|
const scope = state.scope;
|
|
@@ -2002,7 +2051,7 @@ window.ManifestDataStore = {
|
|
|
2002
2051
|
|
|
2003
2052
|
// Boot tier: all keys in one read; the caller caps the wait (cold boot never blocks)
|
|
2004
2053
|
function hydrateBoot(options = {}) {
|
|
2005
|
-
if (!state.enabled || state.disabled) return Promise.resolve();
|
|
2054
|
+
if (!state.enabled || state.disabled || state.scopePending) return Promise.resolve();
|
|
2006
2055
|
const run = hydrate(bootSources()).catch(() => { /* disabled */ });
|
|
2007
2056
|
const maxWaitMs = options.maxWaitMs;
|
|
2008
2057
|
if (!Number.isFinite(maxWaitMs)) return run;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"manifest.appwrite.auth.js": "sha384-
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
2
|
+
"manifest.appwrite.auth.js": "sha384-HMJmRXnvdDBWyuteXL+DTxWM2llSCdjl4EDamEI7iHNAVlqeOCE4GDTm8ZHipPRj",
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-dLQJibkJHbQh7N2j73pCWHSUl2j/O3M0W/Zuge0d7VYjUBXv0PlW55Wylhk6YEsp",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
6
|
"manifest.charts.js": "sha384-qQrjhWkIDi4oU5uQrpy8DdlMjY+gfiivBuX6mssNiFdkawgkVBvKneGgp+nd3PYJ",
|
|
7
|
-
"manifest.chat.js": "sha384-
|
|
7
|
+
"manifest.chat.js": "sha384-mD8exNWzRhkGW07xBxLyDagykP5knEx/i0/trG47rM2n1XJJ0t3SlJSskwcFmbm6",
|
|
8
8
|
"manifest.code.js": "sha384-aoQ4E26vrhNe/+tL7K0h90c8/0ies7e8oIR6Ay92he2X3dnWZ+OPcn2Dna8fmc+c",
|
|
9
9
|
"manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
|
|
10
10
|
"manifest.colorpicker.js": "sha384-1+ZV5Hhjf7WnGP7b71Cqa5h3BKW8UgGT5V14i8Q+aF5zH8Xxwowt/ibSUVsiIHSm",
|
|
11
11
|
"manifest.combobox.js": "sha384-SN3XHumoebxcduFQqx5zt65/Cd10y8nuf9Gkco6UnsZ42sadRd+gvVLfWPdkkuPH",
|
|
12
|
-
"manifest.components.js": "sha384-
|
|
12
|
+
"manifest.components.js": "sha384-DLGSqCbnGkS87AWJ8GpKoAFP4nCosgC5SFSg6C10hH/nWu/MxI+0ntHlnFEfs++u",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384-
|
|
14
|
+
"manifest.data.js": "sha384-KV4OtKbidiW4Am0EZBdMApC4JPetFLnscK23r4L+qUoCzCL7CLjmzRPP1+vRXcCI",
|
|
15
15
|
"manifest.datepicker.js": "sha384-8vTJYkLqqvgXgm1w1becuaRR/jUEpiLBjBJ6RsRbUr+ZkypvspD6EsssuDrX4SQq",
|
|
16
16
|
"manifest.defer.js": "sha384-t/g++osVS3qDXeY8+gfxUtLF9o0SCQQJKNxHGpb8NkIciDvxOAvA6sFIVcM0AKeA",
|
|
17
17
|
"manifest.dropdowns.js": "sha384-4HuHkBt4i8sTPUDLB9rNLli2/lWLY1tjtCLzey37gRwueG+FRLCnVH1q7ljiVvJR",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"manifest.localization.js": "sha384-p8MRlfpjQuMar7rerGRjfdRKqIPoU/QOwVmnwN+PjhrlEZNpetYLZhqO2i5BZn9s",
|
|
22
22
|
"manifest.markdown.js": "sha384-HFE2VwBRX5vhDGQ3c8XgZ10Y5ENkXxHzgX9DjmIEffqLoUDbnJYNk3TFWT9k3e+f",
|
|
23
23
|
"manifest.native.js": "sha384-nIngj4CM5hV4bvQvgs8C7JmSy1UUzobsbhvDE2NH5dXqQVNW/Hiat34wHUxMqSaO",
|
|
24
|
-
"manifest.payments.js": "sha384-
|
|
24
|
+
"manifest.payments.js": "sha384-8pofk2zhxtapPMpwnFyTelPMrYhShNhDCE722uQh/EYFvZFuPLmSdgunqsarVQjv",
|
|
25
25
|
"manifest.resize.js": "sha384-+0IsyrGP/4gZmNbD6oU8dCgEyhpBsXMm74NIenYeUGuGRWyJDurcG27xE/CA0/aA",
|
|
26
26
|
"manifest.router.js": "sha384-4qmGEkRpgAo9xoQkW6+hHIhowvcyAggKB2J3Wf8tJtXp+L0hufoVAqzfhnqw4cNR",
|
|
27
27
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
28
|
-
"manifest.status.js": "sha384-
|
|
28
|
+
"manifest.status.js": "sha384-nwvUSbT+gpRMqhYx6MeBYswUMH72rY92mjgxv0vrSG7Ht7l83XYpEB6fCTbXjjfK",
|
|
29
29
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
30
|
-
"manifest.sw.js": "sha384-
|
|
30
|
+
"manifest.sw.js": "sha384-s7ZLcM3CZ8Yyt+KsC9Mc1bSgvL7JJsNfWRHBEldBOEWvrRDohTRPDWSVqXprHf3k",
|
|
31
31
|
"manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
|
|
32
32
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
33
33
|
"manifest.text.edit.js": "sha384-1LsuOJyMCgab7W9w6DJEu5URzbadYxtq/cHtB8sN3hBFa/BHPnjdnTRbiKyM0FcC",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
|
|
37
37
|
"manifest.utilities.js": "sha384-RvUXxxMLv5q6Psng6jjfK5O1xw05mV6BdLjV92zQW7Zuy3XWctl3QnF5XYNuPyYq",
|
|
38
38
|
"manifest.virtual.js": "sha384-YwwmAsZNddajnAkLhBWtrK8edHMJOhD7cJkQi3qmm5o1Thg0wbtDU0dsfrCHKTIQ",
|
|
39
|
-
"manifest.js": "sha384-
|
|
39
|
+
"manifest.js": "sha384-uZNE076xOWVEeO51qX8XpEMh+OQZEkGd3AI0yuy4skn320IkuX1j9F1THVuOgzgf"
|
|
40
40
|
}
|
package/lib/manifest.js
CHANGED
|
@@ -710,6 +710,7 @@
|
|
|
710
710
|
const devServer = !!window.__mnfstRun;
|
|
711
711
|
const devOrigin = isDevHost(loc.hostname);
|
|
712
712
|
let manifest = window.__manifestLoaded || null;
|
|
713
|
+
if (!manifest && window.__manifestPromise) manifest = await window.__manifestPromise.catch(() => null);
|
|
713
714
|
if (!manifest) {
|
|
714
715
|
const url = document.querySelector('link[rel="manifest"]')?.getAttribute('href') || '/manifest.json';
|
|
715
716
|
manifest = await fetch(url).then(r => r.ok ? r.json() : null).catch(() => null);
|
|
@@ -829,13 +830,26 @@
|
|
|
829
830
|
walk(obj);
|
|
830
831
|
};
|
|
831
832
|
|
|
833
|
+
// One manifest.json request per boot: published on window the moment it starts so
|
|
834
|
+
// plugins that init before __manifestLoaded is set (data, auth, components) await it
|
|
835
|
+
// instead of fetching their own. Resolves interpolated, or null.
|
|
836
|
+
const shareManifestFetch = () => {
|
|
837
|
+
if (window.__manifestLoaded) return Promise.resolve(window.__manifestLoaded);
|
|
838
|
+
if (!window.__manifestPromise) {
|
|
839
|
+
window.__manifestPromise = fetch(manifestUrl).then(r => r.ok ? r.json() : null)
|
|
840
|
+
.then(m => { if (m && !m.__interpolated) { interpolateManifestEnv(m); Object.defineProperty(m, '__interpolated', { value: true, enumerable: false }); } return m; })
|
|
841
|
+
.catch(() => null);
|
|
842
|
+
}
|
|
843
|
+
return window.__manifestPromise;
|
|
844
|
+
};
|
|
845
|
+
|
|
832
846
|
const loadPlugins = async () => {
|
|
833
847
|
let manifest = null;
|
|
834
848
|
let pluginsToLoad = config.plugins;
|
|
835
849
|
let manifestPromise = null;
|
|
836
850
|
|
|
837
851
|
if (config.deriveFromManifest) {
|
|
838
|
-
manifest = await
|
|
852
|
+
manifest = await shareManifestFetch();
|
|
839
853
|
const corePlugins = getDefaultPluginsFromManifest(manifest);
|
|
840
854
|
const appwritePlugins = detectAppwritePlugins(manifest);
|
|
841
855
|
const paymentsPlugins = detectPaymentsPlugins(manifest);
|
|
@@ -846,7 +860,7 @@
|
|
|
846
860
|
pluginsToLoad = resolveDependencies([...ALWAYS_ON, ...pluginsToLoad.filter(p => !ALWAYS_ON.includes(p))]);
|
|
847
861
|
const needsManifest = config.plugins.some(p => MANIFEST_DEPENDENT_PLUGINS.includes(p));
|
|
848
862
|
if (needsManifest) {
|
|
849
|
-
manifestPromise =
|
|
863
|
+
manifestPromise = shareManifestFetch();
|
|
850
864
|
}
|
|
851
865
|
// Inside a Capacitor container, ensure the native umbrella loads even on
|
|
852
866
|
// the explicit data-plugins path (matches the derive-path auto-inject).
|
|
@@ -868,9 +882,7 @@
|
|
|
868
882
|
manifest = await manifestPromise;
|
|
869
883
|
}
|
|
870
884
|
if (manifest && typeof window !== 'undefined') {
|
|
871
|
-
//
|
|
872
|
-
// appwrite-auth etc. read window.__manifestLoaded directly.
|
|
873
|
-
interpolateManifestEnv(manifest);
|
|
885
|
+
// Already interpolated by shareManifestFetch; appwrite-auth etc. read window.__manifestLoaded directly.
|
|
874
886
|
window.__manifestLoaded = manifest;
|
|
875
887
|
if (window.ManifestComponentsRegistry) {
|
|
876
888
|
window.ManifestComponentsRegistry.manifest = manifest;
|
package/lib/manifest.payments.js
CHANGED
|
@@ -21,10 +21,11 @@ function resolvedOrNull(value, fieldName) {
|
|
|
21
21
|
async function ensureManifest() {
|
|
22
22
|
if (window.ManifestComponentsRegistry?.manifest) return window.ManifestComponentsRegistry.manifest;
|
|
23
23
|
if (window.__manifestLoaded) return window.__manifestLoaded;
|
|
24
|
+
if (window.__manifestPromise) { const shared = await window.__manifestPromise.catch(() => null); if (shared) return shared; }
|
|
24
25
|
try {
|
|
25
26
|
const url = document.querySelector('link[rel="manifest"]')?.getAttribute('href') || '/manifest.json';
|
|
26
|
-
|
|
27
|
-
return await
|
|
27
|
+
window.__manifestPromise = fetch(url).then(r => r.json());
|
|
28
|
+
return await window.__manifestPromise;
|
|
28
29
|
} catch (_) {
|
|
29
30
|
return null;
|
|
30
31
|
}
|
package/lib/manifest.status.js
CHANGED
|
@@ -64,13 +64,14 @@
|
|
|
64
64
|
// cache a normalized copy that omits keys they don't consume.
|
|
65
65
|
async function ensureStatusManifest() {
|
|
66
66
|
const cached = window.ManifestComponentsRegistry?.manifest || window.__manifestLoaded;
|
|
67
|
-
if (cached
|
|
67
|
+
if (cached) return cached; // a loaded manifest without `status` means no status config, not a stale copy
|
|
68
|
+
if (window.__manifestPromise) { const shared = await window.__manifestPromise.catch(() => null); if (shared) return shared; }
|
|
68
69
|
try {
|
|
69
70
|
const url = document.querySelector('link[rel="manifest"]')?.getAttribute('href') || '/manifest.json';
|
|
70
|
-
|
|
71
|
-
return await
|
|
71
|
+
window.__manifestPromise = fetch(url).then(r => r.json());
|
|
72
|
+
return await window.__manifestPromise;
|
|
72
73
|
} catch (_) {
|
|
73
|
-
return
|
|
74
|
+
return null;
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
package/lib/manifest.sw.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
// Build stamps the published version here; unstamped (dev) falls back to the
|
|
18
18
|
// registration URL's `v`, then 'dev'.
|
|
19
|
-
const BUILD_VERSION = '0.5.199-next.
|
|
19
|
+
const BUILD_VERSION = '0.5.199-next.3';
|
|
20
20
|
const CACHE_PREFIX = 'mnfst-sw:';
|
|
21
21
|
const META_CACHE = CACHE_PREFIX + 'meta';
|
|
22
22
|
const SWR_CAP = 200;
|