mnfst 0.5.223 → 0.5.224
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 +173 -56
- package/lib/manifest.appwrite.data.js +6 -3
- package/lib/manifest.data.js +220 -53
- package/lib/manifest.integrity.json +4 -4
- package/lib/manifest.sw.js +1 -1
- package/package.json +1 -1
|
@@ -218,6 +218,32 @@ function initializeAuthStore() {
|
|
|
218
218
|
|
|
219
219
|
// Cross-tab synchronization using localStorage events
|
|
220
220
|
const STORAGE_KEY = 'manifest:auth:state';
|
|
221
|
+
// Cap on holding scoped data for a guest/callback sign-in that may fail
|
|
222
|
+
const SIGN_IN_PENDING_MS = 5000;
|
|
223
|
+
['manifest:auth:login', 'manifest:auth:anonymous', 'manifest:auth:logout', 'manifest:auth:session-cleared'].forEach(type =>
|
|
224
|
+
window.addEventListener(type, () => { const store = Alpine.store('auth'); if (store) { store._signInPendingUntil = 0; store._authEpoch++; markVerified(store); } }));
|
|
225
|
+
|
|
226
|
+
// Unverified session: recheck on reconnect, on return to the tab, and on a bounded backoff
|
|
227
|
+
const RECHECK_DELAYS_MS = [2000, 5000, 15000, 60000];
|
|
228
|
+
const recheck = () => { Alpine.store('auth')?._recheckSession?.(); };
|
|
229
|
+
window.addEventListener('online', recheck);
|
|
230
|
+
if (typeof document !== 'undefined') document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') recheck(); });
|
|
231
|
+
|
|
232
|
+
function markVerified(store) {
|
|
233
|
+
store._sessionUnverified = false;
|
|
234
|
+
store._recheckAttempt = 0;
|
|
235
|
+
if (store._recheckTimer) { clearTimeout(store._recheckTimer); store._recheckTimer = null; }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function scheduleRecheck(store) {
|
|
239
|
+
if (!store._sessionUnverified || store._recheckTimer) return;
|
|
240
|
+
const delay = RECHECK_DELAYS_MS[Math.min(store._recheckAttempt++, RECHECK_DELAYS_MS.length - 1)];
|
|
241
|
+
store._recheckTimer = setTimeout(() => { store._recheckTimer = null; store._recheckSession(); }, delay);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Appwrite's "no session" answer; anything else (offline, 5xx) leaves the session unknown
|
|
245
|
+
const NO_SESSION_TYPES = ['general_unauthorized_scope', 'user_unauthorized', 'user_session_not_found', 'user_jwt_invalid', 'user_not_found', 'user_blocked'];
|
|
246
|
+
const isNoSession = error => error?.code === 401 || NO_SESSION_TYPES.includes(error?.type);
|
|
221
247
|
|
|
222
248
|
// Session fields safe to mirror across tabs. Excludes `secret` and provider
|
|
223
249
|
// tokens — this copy is only for UI cross-tab sync, not the auth of record.
|
|
@@ -243,7 +269,10 @@ function initializeAuthStore() {
|
|
|
243
269
|
const state = JSON.parse(e.newValue);
|
|
244
270
|
const store = Alpine.store('auth');
|
|
245
271
|
if (store) {
|
|
272
|
+
const prevId = store.user?.$id || null;
|
|
246
273
|
// Update store state from other tab
|
|
274
|
+
store._authEpoch++;
|
|
275
|
+
if (state.isAuthenticated) markVerified(store);
|
|
247
276
|
store.isAuthenticated = state.isAuthenticated;
|
|
248
277
|
store.isAnonymous = state.isAnonymous;
|
|
249
278
|
store.user = state.user;
|
|
@@ -253,6 +282,15 @@ function initializeAuthStore() {
|
|
|
253
282
|
store.otpSent = state.otpSent || false;
|
|
254
283
|
store.otpExpired = state.otpExpired || false;
|
|
255
284
|
store.error = state.error;
|
|
285
|
+
// Identity ended or changed in another tab: scoped data must drop this tab's rows
|
|
286
|
+
const nextId = state.isAuthenticated ? (state.user?.$id || null) : null;
|
|
287
|
+
if (prevId && nextId !== prevId) {
|
|
288
|
+
if (!nextId) {
|
|
289
|
+
store.teams = [];
|
|
290
|
+
store.currentTeam = null;
|
|
291
|
+
}
|
|
292
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
293
|
+
}
|
|
256
294
|
}
|
|
257
295
|
} catch (error) {
|
|
258
296
|
// Failed to sync state from other tab
|
|
@@ -262,6 +300,8 @@ function initializeAuthStore() {
|
|
|
262
300
|
|
|
263
301
|
// Helper to sync state to localStorage (for cross-tab communication)
|
|
264
302
|
function syncStateToStorage(store) {
|
|
303
|
+
// An unverified signed-out state must not sign other tabs out
|
|
304
|
+
if (store._sessionUnverified && !store.isAuthenticated) return;
|
|
265
305
|
try {
|
|
266
306
|
const state = {
|
|
267
307
|
isAuthenticated: store.isAuthenticated,
|
|
@@ -340,6 +380,11 @@ function initializeAuthStore() {
|
|
|
340
380
|
_appwrite: null,
|
|
341
381
|
_guestAuto: false,
|
|
342
382
|
_guestManual: false,
|
|
383
|
+
_signInPendingUntil: 0, // signed-out init with guest-auto or an auth callback still to sign in
|
|
384
|
+
_sessionUnverified: false, // init could not reach Appwrite: signed out locally, never broadcast
|
|
385
|
+
_authEpoch: 0, // bumped by every identity change; an in-flight recheck from an older epoch is dropped
|
|
386
|
+
_recheckTimer: null,
|
|
387
|
+
_recheckAttempt: 0,
|
|
343
388
|
guestManualEnabled: false,
|
|
344
389
|
_oauthProvider: null, // Store OAuth provider name (google, github, etc.) when login is initiated
|
|
345
390
|
_syncStateToStorage: syncStateToStorage,
|
|
@@ -545,57 +590,18 @@ function initializeAuthStore() {
|
|
|
545
590
|
|
|
546
591
|
// Try to restore existing session
|
|
547
592
|
try {
|
|
548
|
-
|
|
549
|
-
const sessionsResponse = await appwrite.account.listSessions();
|
|
550
|
-
const allSessions = sessionsResponse.sessions || [];
|
|
551
|
-
const currentSession = allSessions.find(s => s.current === true) || allSessions[0];
|
|
552
|
-
|
|
553
|
-
if (currentSession) {
|
|
554
|
-
this.session = currentSession;
|
|
555
|
-
this.isAuthenticated = true;
|
|
556
|
-
this.isAnonymous = currentSession.provider === 'anonymous';
|
|
557
|
-
|
|
558
|
-
// Restore OAuth provider from storage (persists across redirects/refresh)
|
|
559
|
-
if (!this.isAnonymous && currentSession.provider !== 'magic-url') {
|
|
560
|
-
try {
|
|
561
|
-
// Try localStorage first (persists across redirects), fallback to sessionStorage
|
|
562
|
-
let storedProvider = localStorage.getItem('manifest:oauth:provider');
|
|
563
|
-
if (!storedProvider) {
|
|
564
|
-
storedProvider = sessionStorage.getItem('manifest:oauth:provider');
|
|
565
|
-
}
|
|
566
|
-
if (storedProvider) {
|
|
567
|
-
this._oauthProvider = storedProvider;
|
|
568
|
-
}
|
|
569
|
-
} catch (e) {
|
|
570
|
-
// Storage error
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// If guest is disabled but we have anonymous session, clear it
|
|
575
|
-
if (this.isAnonymous && !this._guestAuto && !this._guestManual) {
|
|
576
|
-
try {
|
|
577
|
-
await appwrite.account.deleteSession(this.session.$id);
|
|
578
|
-
this.isAuthenticated = false;
|
|
579
|
-
this.isAnonymous = false;
|
|
580
|
-
this.user = null;
|
|
581
|
-
this.session = null;
|
|
582
|
-
} catch (deleteError) {
|
|
583
|
-
// Failed to delete guest session
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
} else {
|
|
587
|
-
this.isAuthenticated = true; // User exists, session might be managed by cookies
|
|
588
|
-
this.isAnonymous = false;
|
|
589
|
-
}
|
|
593
|
+
await this._restoreSession(appwrite);
|
|
590
594
|
|
|
591
595
|
// Team loading is deferred (not awaited) — runs in the background after
|
|
592
596
|
// manifest:auth:initialized so a session gate isn't held up by it.
|
|
593
597
|
} catch (error) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
598
|
+
this._clearIdentity();
|
|
599
|
+
// Signed out for now; other tabs keep their session
|
|
600
|
+
if (!isNoSession(error)) {
|
|
601
|
+
this._sessionUnverified = true;
|
|
602
|
+
scheduleRecheck(this);
|
|
603
|
+
console.warn('[Manifest Appwrite Auth] Session check failed (signed out until reachable):', error?.message || error);
|
|
604
|
+
}
|
|
599
605
|
}
|
|
600
606
|
|
|
601
607
|
// Sync state to localStorage
|
|
@@ -611,6 +617,17 @@ function initializeAuthStore() {
|
|
|
611
617
|
this._initialized = true;
|
|
612
618
|
this._initializing = false;
|
|
613
619
|
|
|
620
|
+
// Guest-auto / callback sign-in follows: scoped data holds instead of flashing signed-out
|
|
621
|
+
if (!this.isAuthenticated) {
|
|
622
|
+
let callback = false;
|
|
623
|
+
try {
|
|
624
|
+
// Team invites need an existing session (acceptInvite refuses signed-out), so they don't sign in
|
|
625
|
+
const info = window.ManifestAppwriteAuthCallbacks?.detect?.();
|
|
626
|
+
callback = !!(info?.hasCallback && !info.isTeamInvite);
|
|
627
|
+
} catch (e) { /* no-op */ }
|
|
628
|
+
if (this._guestAuto || callback) this._signInPendingUntil = Date.now() + SIGN_IN_PENDING_MS;
|
|
629
|
+
}
|
|
630
|
+
|
|
614
631
|
// Fire as soon as identity is known, before teams load, so a session
|
|
615
632
|
// gate / splash clears in a few hundred ms.
|
|
616
633
|
window.dispatchEvent(new CustomEvent('manifest:auth:initialized', {
|
|
@@ -633,6 +650,95 @@ function initializeAuthStore() {
|
|
|
633
650
|
}
|
|
634
651
|
},
|
|
635
652
|
|
|
653
|
+
// Adopt the session Appwrite reports (throws when there is none or it is unreachable)
|
|
654
|
+
async _restoreSession(appwrite, isCurrent = () => true) {
|
|
655
|
+
const user = await appwrite.account.get();
|
|
656
|
+
const sessionsResponse = await appwrite.account.listSessions();
|
|
657
|
+
if (!isCurrent()) return false;
|
|
658
|
+
this.user = user;
|
|
659
|
+
const allSessions = sessionsResponse.sessions || [];
|
|
660
|
+
const currentSession = allSessions.find(s => s.current === true) || allSessions[0];
|
|
661
|
+
|
|
662
|
+
if (currentSession) {
|
|
663
|
+
this.session = currentSession;
|
|
664
|
+
this.isAuthenticated = true;
|
|
665
|
+
this.isAnonymous = currentSession.provider === 'anonymous';
|
|
666
|
+
|
|
667
|
+
// Restore OAuth provider from storage (persists across redirects/refresh)
|
|
668
|
+
if (!this.isAnonymous && currentSession.provider !== 'magic-url') {
|
|
669
|
+
try {
|
|
670
|
+
// Try localStorage first (persists across redirects), fallback to sessionStorage
|
|
671
|
+
let storedProvider = localStorage.getItem('manifest:oauth:provider');
|
|
672
|
+
if (!storedProvider) {
|
|
673
|
+
storedProvider = sessionStorage.getItem('manifest:oauth:provider');
|
|
674
|
+
}
|
|
675
|
+
if (storedProvider) {
|
|
676
|
+
this._oauthProvider = storedProvider;
|
|
677
|
+
}
|
|
678
|
+
} catch (e) {
|
|
679
|
+
// Storage error
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// If guest is disabled but we have anonymous session, clear it
|
|
684
|
+
if (this.isAnonymous && !this._guestAuto && !this._guestManual) {
|
|
685
|
+
try {
|
|
686
|
+
await appwrite.account.deleteSession(this.session.$id);
|
|
687
|
+
this.isAuthenticated = false;
|
|
688
|
+
this.isAnonymous = false;
|
|
689
|
+
this.user = null;
|
|
690
|
+
this.session = null;
|
|
691
|
+
} catch (deleteError) {
|
|
692
|
+
// Failed to delete guest session
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
} else {
|
|
696
|
+
this.isAuthenticated = true; // User exists, session might be managed by cookies
|
|
697
|
+
this.isAnonymous = false;
|
|
698
|
+
}
|
|
699
|
+
return true;
|
|
700
|
+
},
|
|
701
|
+
|
|
702
|
+
// Offline-booted tab: re-run the session check once Appwrite is reachable
|
|
703
|
+
async _recheckSession() {
|
|
704
|
+
if (!this._sessionUnverified || this._rechecking || !this._appwrite) return;
|
|
705
|
+
if (this._recheckTimer) { clearTimeout(this._recheckTimer); this._recheckTimer = null; }
|
|
706
|
+
this._rechecking = true;
|
|
707
|
+
const epoch = this._authEpoch;
|
|
708
|
+
const current = () => this._authEpoch === epoch && this._sessionUnverified && !this.isAuthenticated;
|
|
709
|
+
try {
|
|
710
|
+
if (!await this._restoreSession(this._appwrite, current)) return;
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (!current()) return;
|
|
713
|
+
if (!isNoSession(error)) { scheduleRecheck(this); return; }
|
|
714
|
+
this._clearIdentity();
|
|
715
|
+
markVerified(this);
|
|
716
|
+
syncStateToStorage(this);
|
|
717
|
+
if (this._guestAuto && this._createAnonymousSession) await this._createAnonymousSession();
|
|
718
|
+
return;
|
|
719
|
+
} finally {
|
|
720
|
+
this._rechecking = false;
|
|
721
|
+
}
|
|
722
|
+
markVerified(this);
|
|
723
|
+
const cfg = await config.getAppwriteConfig();
|
|
724
|
+
if (this._authEpoch !== epoch) return;
|
|
725
|
+
if (this.isAuthenticated && cfg?.teams && (!this.isAnonymous || cfg.guestTeams)) {
|
|
726
|
+
try { await this._loadTeamsAndSeed(cfg); } catch (e) { console.warn('[Manifest Appwrite Auth] Team load failed:', e?.message || e); }
|
|
727
|
+
if (this._authEpoch !== epoch) return;
|
|
728
|
+
}
|
|
729
|
+
syncStateToStorage(this);
|
|
730
|
+
if (this.isAuthenticated) {
|
|
731
|
+
window.dispatchEvent(new CustomEvent(this.isAnonymous ? 'manifest:auth:anonymous' : 'manifest:auth:login', { detail: { user: this.user } }));
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
_clearIdentity() {
|
|
736
|
+
this.isAuthenticated = false;
|
|
737
|
+
this.isAnonymous = false;
|
|
738
|
+
this.user = null;
|
|
739
|
+
this.session = null;
|
|
740
|
+
},
|
|
741
|
+
|
|
636
742
|
// Clear team state when the identity changes to a different user (e.g. guest
|
|
637
743
|
// replaced on OTP sign-in). Otherwise stale currentTeam/teams cause 404s.
|
|
638
744
|
_resetTeamsState() {
|
|
@@ -804,6 +910,8 @@ function initializeAuthStore() {
|
|
|
804
910
|
return { success: true };
|
|
805
911
|
}
|
|
806
912
|
|
|
913
|
+
this._authEpoch++;
|
|
914
|
+
markVerified(this);
|
|
807
915
|
this.inProgress = true;
|
|
808
916
|
|
|
809
917
|
try {
|
|
@@ -847,15 +955,12 @@ function initializeAuthStore() {
|
|
|
847
955
|
|
|
848
956
|
// Restore to guest state after logout (guest-auto only, and not when
|
|
849
957
|
// already a guest — don't mint a new guest on top).
|
|
958
|
+
let restored = false;
|
|
850
959
|
if (!this.isAnonymous && this._guestAuto && this._createAnonymousSession) {
|
|
851
960
|
await this._createAnonymousSession();
|
|
852
|
-
|
|
853
|
-
// Clear auth state completely
|
|
854
|
-
this.isAuthenticated = false;
|
|
855
|
-
this.isAnonymous = false;
|
|
856
|
-
this.user = null;
|
|
857
|
-
this.session = null;
|
|
961
|
+
restored = this.isAuthenticated && this.isAnonymous;
|
|
858
962
|
}
|
|
963
|
+
if (!restored) this._clearIdentity();
|
|
859
964
|
|
|
860
965
|
syncStateToStorage(this);
|
|
861
966
|
window.dispatchEvent(new CustomEvent('manifest:auth:logout'));
|
|
@@ -893,6 +998,10 @@ function initializeAuthStore() {
|
|
|
893
998
|
// Clear teams on logout error too
|
|
894
999
|
this.teams = [];
|
|
895
1000
|
this.currentTeam = null;
|
|
1001
|
+
if (!this.isAuthenticated) this._clearIdentity();
|
|
1002
|
+
// Expired session (401) is still a logout: scoped data must drop the old rows
|
|
1003
|
+
syncStateToStorage(this);
|
|
1004
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:logout'));
|
|
896
1005
|
return { success: false, error: error.message };
|
|
897
1006
|
} finally {
|
|
898
1007
|
this.inProgress = false;
|
|
@@ -905,6 +1014,8 @@ function initializeAuthStore() {
|
|
|
905
1014
|
return { success: false, error: 'Appwrite not configured' };
|
|
906
1015
|
}
|
|
907
1016
|
|
|
1017
|
+
this._authEpoch++;
|
|
1018
|
+
markVerified(this);
|
|
908
1019
|
this.inProgress = true;
|
|
909
1020
|
|
|
910
1021
|
try {
|
|
@@ -944,6 +1055,10 @@ function initializeAuthStore() {
|
|
|
944
1055
|
this.session = null;
|
|
945
1056
|
this.magicLinkSent = false;
|
|
946
1057
|
this.magicLinkExpired = false;
|
|
1058
|
+
this.teams = [];
|
|
1059
|
+
this.currentTeam = null;
|
|
1060
|
+
syncStateToStorage(this);
|
|
1061
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
947
1062
|
return { success: false, error: error.message };
|
|
948
1063
|
} finally {
|
|
949
1064
|
this.inProgress = false;
|
|
@@ -958,10 +1073,12 @@ function initializeAuthStore() {
|
|
|
958
1073
|
|
|
959
1074
|
try {
|
|
960
1075
|
this.user = await this._appwrite.account.get();
|
|
1076
|
+
if (this.isAuthenticated) markVerified(this);
|
|
961
1077
|
syncStateToStorage(this);
|
|
962
1078
|
return this.user;
|
|
963
1079
|
} catch (error) {
|
|
964
|
-
|
|
1080
|
+
if (!isNoSession(error)) throw error;
|
|
1081
|
+
markVerified(this);
|
|
965
1082
|
this.isAuthenticated = false;
|
|
966
1083
|
this.isAnonymous = false;
|
|
967
1084
|
this.user = null;
|
|
@@ -1038,8 +1155,8 @@ async function initializeAppwriteAuthPlugin() {
|
|
|
1038
1155
|
}
|
|
1039
1156
|
}
|
|
1040
1157
|
|
|
1041
|
-
// If no session and guest-auto is enabled, create guest session
|
|
1042
|
-
if (!store.isAuthenticated && store._guestAuto && store._createAnonymousSession) {
|
|
1158
|
+
// If no session and guest-auto is enabled, create guest session (not while the session check is unreachable)
|
|
1159
|
+
if (!store.isAuthenticated && !store._sessionUnverified && store._guestAuto && store._createAnonymousSession) {
|
|
1043
1160
|
store._createAnonymousSession();
|
|
1044
1161
|
}
|
|
1045
1162
|
}, { once: true });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
|
|
5
|
-
const MANIFEST_BUILD_VERSION = '0.5.
|
|
5
|
+
const MANIFEST_BUILD_VERSION = '0.5.224';
|
|
6
6
|
|
|
7
7
|
/* Manifest Data Sources - Appwrite Integration */
|
|
8
8
|
|
|
@@ -671,7 +671,8 @@ window.ManifestDataAppwrite = {
|
|
|
671
671
|
const subscriptions = new Map(); // Map<dataSourceName, unsubscribeFunction>
|
|
672
672
|
|
|
673
673
|
// Subscribe to storage bucket file changes
|
|
674
|
-
|
|
674
|
+
// isCurrent: false once the requesting load is superseded (logout) — skip, never touch a newer entry
|
|
675
|
+
async function subscribeToStorageBucket(dataSourceName, bucketId, scope, onEvent, isCurrent) {
|
|
675
676
|
// Unsubscribe from existing subscription if any
|
|
676
677
|
if (subscriptions.has(dataSourceName)) {
|
|
677
678
|
const unsubscribe = subscriptions.get(dataSourceName);
|
|
@@ -682,6 +683,7 @@ async function subscribeToStorageBucket(dataSourceName, bucketId, scope, onEvent
|
|
|
682
683
|
}
|
|
683
684
|
|
|
684
685
|
const services = await window.ManifestDataAppwrite.getAppwriteDataServices();
|
|
686
|
+
if (isCurrent && !isCurrent()) return null;
|
|
685
687
|
if (!services?.realtime) {
|
|
686
688
|
console.warn('[Manifest Data] Realtime service not available for', dataSourceName);
|
|
687
689
|
return null;
|
|
@@ -751,7 +753,7 @@ function unsubscribeAll() {
|
|
|
751
753
|
}
|
|
752
754
|
|
|
753
755
|
// Subscribe to database table row changes
|
|
754
|
-
async function subscribeToTable(dataSourceName, databaseId, tableId, scope, onEvent) {
|
|
756
|
+
async function subscribeToTable(dataSourceName, databaseId, tableId, scope, onEvent, isCurrent) {
|
|
755
757
|
// Unsubscribe from existing subscription if any
|
|
756
758
|
if (subscriptions.has(dataSourceName)) {
|
|
757
759
|
const unsubscribe = subscriptions.get(dataSourceName);
|
|
@@ -762,6 +764,7 @@ async function subscribeToTable(dataSourceName, databaseId, tableId, scope, onEv
|
|
|
762
764
|
}
|
|
763
765
|
|
|
764
766
|
const services = await window.ManifestDataAppwrite.getAppwriteDataServices();
|
|
767
|
+
if (isCurrent && !isCurrent()) return null;
|
|
765
768
|
if (!services?.realtime) {
|
|
766
769
|
console.warn('[Manifest Data] Realtime service not available for', dataSourceName);
|
|
767
770
|
return null;
|
package/lib/manifest.data.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
|
|
5
|
-
const MANIFEST_BUILD_VERSION = '0.5.
|
|
5
|
+
const MANIFEST_BUILD_VERSION = '0.5.224';
|
|
6
6
|
|
|
7
7
|
/* Manifest Data Sources - Configuration */
|
|
8
8
|
|
|
@@ -590,8 +590,14 @@ function writeSource(dataSourceName, data, options = {}) {
|
|
|
590
590
|
}
|
|
591
591
|
|
|
592
592
|
// Synchronous replace: local writes ($register, init preload, state-only updates)
|
|
593
|
+
// A write captured under an older generation (scope reset since, e.g. logout) never lands
|
|
594
|
+
function isStaleWrite(dataSourceName, options) {
|
|
595
|
+
return !!options && options.generation !== undefined && options.generation !== sourceGeneration(dataSourceName);
|
|
596
|
+
}
|
|
597
|
+
|
|
593
598
|
function updateStore(dataSourceName, data, options = {}) {
|
|
594
599
|
if (isInitializing && !options.allowDuringInit) return;
|
|
600
|
+
if (isStaleWrite(dataSourceName, options)) return;
|
|
595
601
|
const state = writeSource(dataSourceName, data, { ...options, mode: 'replace' });
|
|
596
602
|
if (!state) return;
|
|
597
603
|
touchSource(dataSourceName);
|
|
@@ -601,10 +607,12 @@ function updateStore(dataSourceName, data, options = {}) {
|
|
|
601
607
|
// Network landing (page load, paged append, realtime batch): buffered, applied
|
|
602
608
|
// with every other landing of the same frame in ONE flush. Resolves once visible.
|
|
603
609
|
function landRows(dataSourceName, rows, options = {}) {
|
|
610
|
+
if (isStaleWrite(dataSourceName, options)) return Promise.resolve();
|
|
604
611
|
return queueLanding({ source: dataSourceName, rows, options: { mode: 'replace', ...options } });
|
|
605
612
|
}
|
|
606
613
|
|
|
607
614
|
function landRemove(dataSourceName, ids, options = {}) {
|
|
615
|
+
if (isStaleWrite(dataSourceName, options)) return Promise.resolve();
|
|
608
616
|
return queueLanding({ source: dataSourceName, remove: Array.isArray(ids) ? ids : [ids], options });
|
|
609
617
|
}
|
|
610
618
|
|
|
@@ -1132,6 +1140,11 @@ function setupTeamChangeListener() {
|
|
|
1132
1140
|
}
|
|
1133
1141
|
};
|
|
1134
1142
|
|
|
1143
|
+
// Logout already reset scoped sources (data main); don't reload them again as a team change
|
|
1144
|
+
const syncTeam = () => { lastTeamId = Alpine.store('auth')?.currentTeam?.$id || null; };
|
|
1145
|
+
window.addEventListener('manifest:auth:logout', syncTeam);
|
|
1146
|
+
window.addEventListener('manifest:auth:session-cleared', syncTeam);
|
|
1147
|
+
|
|
1135
1148
|
if (document.readyState === 'loading') {
|
|
1136
1149
|
document.addEventListener('DOMContentLoaded', startPolling);
|
|
1137
1150
|
} else {
|
|
@@ -1188,10 +1201,15 @@ function checkAndDispatchRenderReady() {
|
|
|
1188
1201
|
}, RENDER_READY_QUIET_MS);
|
|
1189
1202
|
}
|
|
1190
1203
|
|
|
1204
|
+
// Latest locale change owns _localeChanging; an older one finishing must not clear it
|
|
1205
|
+
let localeChangeSeq = 0;
|
|
1206
|
+
|
|
1191
1207
|
// Listen for locale changes to reload data
|
|
1192
1208
|
function setupLocaleChangeListener() {
|
|
1193
1209
|
window.addEventListener('localechange', async (event) => {
|
|
1194
1210
|
const newLocale = event.detail.locale;
|
|
1211
|
+
const seq = ++localeChangeSeq;
|
|
1212
|
+
const latest = () => seq === localeChangeSeq;
|
|
1195
1213
|
|
|
1196
1214
|
// Set loading state to prevent flicker
|
|
1197
1215
|
const store = Alpine.store('data');
|
|
@@ -1293,7 +1311,7 @@ function setupLocaleChangeListener() {
|
|
|
1293
1311
|
delete store[dataSourceName];
|
|
1294
1312
|
delete store[`_${dataSourceName}_state`];
|
|
1295
1313
|
});
|
|
1296
|
-
store._localeChanging = false;
|
|
1314
|
+
if (latest()) store._localeChanging = false;
|
|
1297
1315
|
touchSources(store, localizedDataSources);
|
|
1298
1316
|
}
|
|
1299
1317
|
|
|
@@ -1308,7 +1326,7 @@ function setupLocaleChangeListener() {
|
|
|
1308
1326
|
|
|
1309
1327
|
// All localized sources have reloaded — check if everything is settled.
|
|
1310
1328
|
// This fires manifest:render-ready after a locale change completes end-to-end.
|
|
1311
|
-
checkAndDispatchRenderReady();
|
|
1329
|
+
if (latest()) checkAndDispatchRenderReady();
|
|
1312
1330
|
|
|
1313
1331
|
} catch (error) {
|
|
1314
1332
|
console.error('[Manifest Data] Error handling locale change:', error);
|
|
@@ -1323,13 +1341,13 @@ function setupLocaleChangeListener() {
|
|
|
1323
1341
|
if (!key.startsWith('_') && typeof raw[key] !== 'function') delete store[key];
|
|
1324
1342
|
}
|
|
1325
1343
|
store._initialized = true;
|
|
1326
|
-
store._localeChanging = false;
|
|
1344
|
+
if (latest()) store._localeChanging = false;
|
|
1327
1345
|
bumpAllVersions();
|
|
1328
1346
|
}
|
|
1329
1347
|
} finally {
|
|
1330
1348
|
// Early exits (no manifest.data) must not strand the render-ready gate
|
|
1331
1349
|
const store = Alpine.store('data');
|
|
1332
|
-
if (store?._localeChanging) {
|
|
1350
|
+
if (latest() && store?._localeChanging) {
|
|
1333
1351
|
store._localeChanging = false;
|
|
1334
1352
|
checkAndDispatchRenderReady();
|
|
1335
1353
|
}
|
|
@@ -1506,6 +1524,7 @@ window.ManifestDataStore = {
|
|
|
1506
1524
|
sourceFreshness,
|
|
1507
1525
|
// Persistence (§12.2)
|
|
1508
1526
|
sourceGeneration,
|
|
1527
|
+
isStaleWrite,
|
|
1509
1528
|
resetSource,
|
|
1510
1529
|
touchSource,
|
|
1511
1530
|
bumpAllVersions,
|
|
@@ -1576,6 +1595,7 @@ window.ManifestDataStore = {
|
|
|
1576
1595
|
pending: new Map(), // source -> time its debounced write is due
|
|
1577
1596
|
writeTimer: null,
|
|
1578
1597
|
hydrated: new Set(), // sources hydrated (or attempted) this generation
|
|
1598
|
+
hydratedIdentity: null, // identity auth-dependent hydration last ran under
|
|
1579
1599
|
fetchKicked: new Set(),
|
|
1580
1600
|
watching: false,
|
|
1581
1601
|
deployment: null,
|
|
@@ -1589,6 +1609,26 @@ window.ManifestDataStore = {
|
|
|
1589
1609
|
const liveLocale = () => (typeof document !== 'undefined' && document.documentElement?.lang)
|
|
1590
1610
|
|| (typeof Alpine !== 'undefined' && Alpine.store?.('locale')?.current) || 'en';
|
|
1591
1611
|
|
|
1612
|
+
// Auth-dependent sources (scope / $auth. queries) carry the identity they were read under
|
|
1613
|
+
function isAuthDependent(source) {
|
|
1614
|
+
if (!source || typeof source !== 'object') return false;
|
|
1615
|
+
if (source.scope) return true;
|
|
1616
|
+
try { return JSON.stringify(source.queries || '').includes('$auth.'); } catch { return false; }
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Current identity: '' signed out (or no auth plugin), null while auth is still booting
|
|
1620
|
+
function authIdentity() {
|
|
1621
|
+
const auth = (typeof Alpine !== 'undefined' && Alpine.store) ? Alpine.store('auth') : null;
|
|
1622
|
+
if (!auth) return '';
|
|
1623
|
+
if (auth._initialized !== true) return null;
|
|
1624
|
+
return auth.isAuthenticated === true ? String(auth.user?.$id || auth.user?.id || '') : '';
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
function authUnverified() {
|
|
1628
|
+
const auth = (typeof Alpine !== 'undefined' && Alpine.store) ? Alpine.store('auth') : null;
|
|
1629
|
+
return auth?._sessionUnverified === true;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1592
1632
|
function warnOnce(key, message, error) {
|
|
1593
1633
|
if (state.warned.has(key)) return;
|
|
1594
1634
|
state.warned.add(key);
|
|
@@ -1657,7 +1697,7 @@ window.ManifestDataStore = {
|
|
|
1657
1697
|
for (const [name, source] of Object.entries(manifest?.data || {})) {
|
|
1658
1698
|
if (!source || typeof source !== 'object') continue;
|
|
1659
1699
|
const cfg = normalizeConfig(source.persist);
|
|
1660
|
-
if (cfg) state.sources.set(name, cfg);
|
|
1700
|
+
if (cfg) { cfg.authDependent = isAuthDependent(source); state.sources.set(name, cfg); }
|
|
1661
1701
|
}
|
|
1662
1702
|
state.scopeExpr = typeof manifest?.persistence?.scope === 'string' && manifest.persistence.scope.trim()
|
|
1663
1703
|
? manifest.persistence.scope.trim() : null;
|
|
@@ -1787,6 +1827,7 @@ window.ManifestDataStore = {
|
|
|
1787
1827
|
if (!state.enabled) return;
|
|
1788
1828
|
if (WIPE_EVENTS.has(type)) deleteScope(state.scope);
|
|
1789
1829
|
refreshScope();
|
|
1830
|
+
if (!state.scopePending) hydrate(bootSources()).catch(() => { /* disabled */ });
|
|
1790
1831
|
});
|
|
1791
1832
|
}
|
|
1792
1833
|
// Reactive re-evaluation (team switches that fire no event); the switch itself runs outside the effect
|
|
@@ -1963,6 +2004,8 @@ window.ManifestDataStore = {
|
|
|
1963
2004
|
if (!cfg || !ds) return null;
|
|
1964
2005
|
const raw = ds.getRawData(source);
|
|
1965
2006
|
if (raw === null || raw === undefined) return null;
|
|
2007
|
+
const identity = cfg.authDependent ? authIdentity() : undefined;
|
|
2008
|
+
if (identity === null) return null;
|
|
1966
2009
|
try {
|
|
1967
2010
|
const snapshot = snapshotOf(source, raw, cfg);
|
|
1968
2011
|
if (snapshot === null) return null;
|
|
@@ -1974,7 +2017,8 @@ window.ManifestDataStore = {
|
|
|
1974
2017
|
savedAt: Date.now(),
|
|
1975
2018
|
frameworkVersion: state.frameworkVersion,
|
|
1976
2019
|
deployment: state.deployment,
|
|
1977
|
-
locale: liveLocale()
|
|
2020
|
+
locale: liveLocale(),
|
|
2021
|
+
...(cfg.authDependent ? { identity } : {})
|
|
1978
2022
|
};
|
|
1979
2023
|
} catch (error) {
|
|
1980
2024
|
warnOnce(`snapshot:${source}`, `persistence skipped "${source}" (rows are not serialisable)`, error);
|
|
@@ -2040,6 +2084,9 @@ window.ManifestDataStore = {
|
|
|
2040
2084
|
if (typeof record.savedAt !== 'number' || Date.now() - record.savedAt > cfg.ttl) return { ok: false, drop: true };
|
|
2041
2085
|
if (majorMinor(record.frameworkVersion) !== majorMinor(state.frameworkVersion)) return { ok: false, drop: true };
|
|
2042
2086
|
if (record.locale && record.locale !== liveLocale()) return { ok: false };
|
|
2087
|
+
// Another identity's (or an unstamped) snapshot never hydrates: the scope alone may be '' for everyone
|
|
2088
|
+
// An unverified (offline) signed-out boot keeps it for the session check to come back
|
|
2089
|
+
if (cfg.authDependent && record.identity !== authIdentity()) return authUnverified() ? { ok: false } : { ok: false, drop: true };
|
|
2043
2090
|
return { ok: true };
|
|
2044
2091
|
}
|
|
2045
2092
|
|
|
@@ -2047,7 +2094,15 @@ window.ManifestDataStore = {
|
|
|
2047
2094
|
// fresh landing (or after a scope change) is discarded
|
|
2048
2095
|
async function hydrate(sources) {
|
|
2049
2096
|
const ds = dataStore();
|
|
2050
|
-
|
|
2097
|
+
// Auth-dependent sources wait for a known identity (re-run on auth events), and retry when it changes
|
|
2098
|
+
const identity = authIdentity();
|
|
2099
|
+
const known = identity !== null;
|
|
2100
|
+
if (known && identity !== state.hydratedIdentity) {
|
|
2101
|
+
for (const [source, cfg] of state.sources) if (cfg.authDependent) state.hydrated.delete(source);
|
|
2102
|
+
state.hydratedIdentity = identity;
|
|
2103
|
+
}
|
|
2104
|
+
const pending = sources.filter(source => state.sources.has(source) && !state.hydrated.has(source)
|
|
2105
|
+
&& (known || !state.sources.get(source).authDependent));
|
|
2051
2106
|
if (!ds || !pending.length || !state.enabled || state.disabled || state.scopePending) return;
|
|
2052
2107
|
for (const source of pending) state.hydrated.add(source);
|
|
2053
2108
|
const generation = state.generation;
|
|
@@ -2853,6 +2908,7 @@ function updateEntryInStore(dataSourceName, entryId, updates, options = {}) {
|
|
|
2853
2908
|
if (typeof Alpine === 'undefined' || !Alpine.store) {
|
|
2854
2909
|
return false;
|
|
2855
2910
|
}
|
|
2911
|
+
if (window.ManifestDataStore?.isStaleWrite?.(dataSourceName, options)) return false;
|
|
2856
2912
|
|
|
2857
2913
|
const { store, arr, index } = findRawIndex(dataSourceName, entryId);
|
|
2858
2914
|
if (!store || !arr || index === -1) {
|
|
@@ -2884,6 +2940,7 @@ function addEntryToStore(dataSourceName, entry, options = {}) {
|
|
|
2884
2940
|
if (typeof Alpine === 'undefined' || !Alpine.store) {
|
|
2885
2941
|
return false;
|
|
2886
2942
|
}
|
|
2943
|
+
if (window.ManifestDataStore?.isStaleWrite?.(dataSourceName, options)) return false;
|
|
2887
2944
|
|
|
2888
2945
|
const ds = window.ManifestDataStore;
|
|
2889
2946
|
const target = ds?.ensureSourceArray?.(dataSourceName);
|
|
@@ -2915,6 +2972,7 @@ function removeEntryFromStore(dataSourceName, entryId, options = {}) {
|
|
|
2915
2972
|
if (typeof Alpine === 'undefined' || !Alpine.store) {
|
|
2916
2973
|
return false;
|
|
2917
2974
|
}
|
|
2975
|
+
if (window.ManifestDataStore?.isStaleWrite?.(dataSourceName, options)) return false;
|
|
2918
2976
|
|
|
2919
2977
|
const { store, arr, index } = findRawIndex(dataSourceName, entryId);
|
|
2920
2978
|
if (!store || !arr || index === -1) {
|
|
@@ -2991,6 +3049,8 @@ async function executeMutation(mutationConfig) {
|
|
|
2991
3049
|
} = mutationConfig;
|
|
2992
3050
|
|
|
2993
3051
|
const mutationId = generateMutationId();
|
|
3052
|
+
// Writes after the API call carry this; a logout/scope reset meanwhile drops them
|
|
3053
|
+
const writeOptions = { ...options, generation: window.ManifestDataStore?.sourceGeneration?.(dataSourceName) };
|
|
2994
3054
|
let originalData = null;
|
|
2995
3055
|
let optimisticData = null;
|
|
2996
3056
|
let resolvedEntryId = entryId;
|
|
@@ -3083,12 +3143,12 @@ async function executeMutation(mutationConfig) {
|
|
|
3083
3143
|
clearSourceCaches(dataSourceName);
|
|
3084
3144
|
} else {
|
|
3085
3145
|
// Temporary entry not found, just add the real one
|
|
3086
|
-
addEntryToStore(dataSourceName, result,
|
|
3146
|
+
addEntryToStore(dataSourceName, result, writeOptions);
|
|
3087
3147
|
}
|
|
3088
3148
|
}
|
|
3089
3149
|
} else if (type === 'update') {
|
|
3090
3150
|
// Update with server response (may have additional fields)
|
|
3091
|
-
updateEntryInStore(dataSourceName, entryId, result,
|
|
3151
|
+
updateEntryInStore(dataSourceName, entryId, result, writeOptions);
|
|
3092
3152
|
}
|
|
3093
3153
|
// For delete, entry is already removed optimistically, nothing to sync
|
|
3094
3154
|
}
|
|
@@ -3123,11 +3183,11 @@ async function executeMutation(mutationConfig) {
|
|
|
3123
3183
|
|
|
3124
3184
|
const rollbackFn = () => {
|
|
3125
3185
|
if (type === 'create' && optimisticData) {
|
|
3126
|
-
removeEntryFromStore(dataSourceName, optimisticData.$id);
|
|
3186
|
+
removeEntryFromStore(dataSourceName, optimisticData.$id, writeOptions);
|
|
3127
3187
|
} else if (type === 'update' && originalData) {
|
|
3128
|
-
updateEntryInStore(dataSourceName, entryId, originalData);
|
|
3188
|
+
updateEntryInStore(dataSourceName, entryId, originalData, writeOptions);
|
|
3129
3189
|
} else if (type === 'delete' && originalData) {
|
|
3130
|
-
addEntryToStore(dataSourceName, originalData);
|
|
3190
|
+
addEntryToStore(dataSourceName, originalData, writeOptions);
|
|
3131
3191
|
}
|
|
3132
3192
|
};
|
|
3133
3193
|
|
|
@@ -3151,10 +3211,11 @@ async function executeMutation(mutationConfig) {
|
|
|
3151
3211
|
|
|
3152
3212
|
// Sync entry from server (background reconciliation)
|
|
3153
3213
|
async function syncEntryFromServer(dataSourceName, entryId, syncFunction) {
|
|
3214
|
+
const generation = window.ManifestDataStore?.sourceGeneration?.(dataSourceName);
|
|
3154
3215
|
try {
|
|
3155
3216
|
const serverData = await syncFunction();
|
|
3156
3217
|
if (serverData && serverData.$id) {
|
|
3157
|
-
updateEntryInStore(dataSourceName, entryId, serverData);
|
|
3218
|
+
updateEntryInStore(dataSourceName, entryId, serverData, { generation });
|
|
3158
3219
|
return serverData;
|
|
3159
3220
|
}
|
|
3160
3221
|
} catch (error) {
|
|
@@ -7112,6 +7173,7 @@ async function linkFileToEntry(tableName, entryId, fileId, fileIdsColumn = 'file
|
|
|
7112
7173
|
|
|
7113
7174
|
if (executeMutation && updateEntryInStore) {
|
|
7114
7175
|
// Optimistic update: immediately update store
|
|
7176
|
+
const generation = window.ManifestDataStore?.sourceGeneration?.(tableName);
|
|
7115
7177
|
updateEntryInStore(tableName, entryId, { [fileIdsColumn]: fileIds });
|
|
7116
7178
|
|
|
7117
7179
|
// Background sync: update server
|
|
@@ -7135,7 +7197,7 @@ async function linkFileToEntry(tableName, entryId, fileId, fileIdsColumn = 'file
|
|
|
7135
7197
|
if (originalEntry) {
|
|
7136
7198
|
const originalFileIds = originalEntry[fileIdsColumn] || [];
|
|
7137
7199
|
const rolledBackFileIds = originalFileIds.filter(id => id !== fileId);
|
|
7138
|
-
updateEntryInStore(tableName, entryId, { [fileIdsColumn]: rolledBackFileIds });
|
|
7200
|
+
updateEntryInStore(tableName, entryId, { [fileIdsColumn]: rolledBackFileIds }, { generation });
|
|
7139
7201
|
}
|
|
7140
7202
|
}
|
|
7141
7203
|
throw updateError;
|
|
@@ -7301,6 +7363,7 @@ async function unlinkFileFromEntry(tableName, entryId, fileId, fileIdsColumn = '
|
|
|
7301
7363
|
|
|
7302
7364
|
if (updateEntryInStore) {
|
|
7303
7365
|
// Optimistic update: immediately update store
|
|
7366
|
+
const generation = window.ManifestDataStore?.sourceGeneration?.(tableName);
|
|
7304
7367
|
updateEntryInStore(tableName, entryId, { [fileIdsColumn]: fileIds });
|
|
7305
7368
|
|
|
7306
7369
|
// Background sync: update server
|
|
@@ -7322,7 +7385,7 @@ async function unlinkFileFromEntry(tableName, entryId, fileId, fileIdsColumn = '
|
|
|
7322
7385
|
if (!originalFileIds.includes(fileId)) {
|
|
7323
7386
|
originalFileIds.push(fileId);
|
|
7324
7387
|
}
|
|
7325
|
-
updateEntryInStore(tableName, entryId, { [fileIdsColumn]: originalFileIds });
|
|
7388
|
+
updateEntryInStore(tableName, entryId, { [fileIdsColumn]: originalFileIds }, { generation });
|
|
7326
7389
|
}
|
|
7327
7390
|
}
|
|
7328
7391
|
throw updateError;
|
|
@@ -7922,7 +7985,8 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
7922
7985
|
}
|
|
7923
7986
|
// Dedupe key: source + serialized queries (never across different queries)
|
|
7924
7987
|
const key = `${dataSourceName}:$query:${JSON.stringify(appwriteQueries.map(q => String(q)))}`;
|
|
7925
|
-
const { runDeduped, landRows } = window.ManifestDataStore || {};
|
|
7988
|
+
const { runDeduped, landRows, sourceGeneration } = window.ManifestDataStore || {};
|
|
7989
|
+
const generation = sourceGeneration?.(dataSourceName);
|
|
7926
7990
|
const run = async () => {
|
|
7927
7991
|
const result = await window.ManifestDataAppwrite.loadTableRows(
|
|
7928
7992
|
appwriteConfig.databaseId,
|
|
@@ -7931,7 +7995,7 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
7931
7995
|
);
|
|
7932
7996
|
// Network landing: query result replaces the source (coalesced, identity-preserving)
|
|
7933
7997
|
if (result !== undefined && landRows) {
|
|
7934
|
-
await landRows(dataSourceName, result, { mode: 'replace', fresh: true });
|
|
7998
|
+
await landRows(dataSourceName, result, { mode: 'replace', fresh: true, generation });
|
|
7935
7999
|
}
|
|
7936
8000
|
return result;
|
|
7937
8001
|
};
|
|
@@ -8100,6 +8164,7 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
8100
8164
|
// Use optimistic update via mutation system
|
|
8101
8165
|
const addEntryToStore = window.ManifestDataMutations?.addEntryToStore;
|
|
8102
8166
|
const removeEntryFromStore = window.ManifestDataMutations?.removeEntryFromStore;
|
|
8167
|
+
const writeOptions = { generation: window.ManifestDataStore?.sourceGeneration?.(dataSourceName) };
|
|
8103
8168
|
const manifest = await window.ManifestDataConfig.ensureManifest();
|
|
8104
8169
|
const dataSource = manifest?.data?.[dataSourceName];
|
|
8105
8170
|
const scope = window.ManifestDataConfig.getScope(dataSource);
|
|
@@ -8147,7 +8212,7 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
8147
8212
|
_optimistic: true // Mark as optimistic
|
|
8148
8213
|
};
|
|
8149
8214
|
// Optimistic update: immediately add file to store
|
|
8150
|
-
addEntryToStore(dataSourceName, optimisticFile);
|
|
8215
|
+
addEntryToStore(dataSourceName, optimisticFile, writeOptions);
|
|
8151
8216
|
}
|
|
8152
8217
|
|
|
8153
8218
|
let result;
|
|
@@ -8171,7 +8236,7 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
8171
8236
|
}
|
|
8172
8237
|
// Rollback optimistic update on error
|
|
8173
8238
|
if (optimisticFile && removeEntryFromStore) {
|
|
8174
|
-
removeEntryFromStore(dataSourceName, optimisticFile.$id);
|
|
8239
|
+
removeEntryFromStore(dataSourceName, optimisticFile.$id, writeOptions);
|
|
8175
8240
|
}
|
|
8176
8241
|
throw error;
|
|
8177
8242
|
}
|
|
@@ -8179,12 +8244,12 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
8179
8244
|
// Replace optimistic file with real one from server
|
|
8180
8245
|
if (optimisticFile && addEntryToStore && removeEntryFromStore) {
|
|
8181
8246
|
// Remove optimistic entry
|
|
8182
|
-
removeEntryFromStore(dataSourceName, optimisticFile.$id);
|
|
8247
|
+
removeEntryFromStore(dataSourceName, optimisticFile.$id, writeOptions);
|
|
8183
8248
|
// Add real entry
|
|
8184
|
-
addEntryToStore(dataSourceName, result);
|
|
8249
|
+
addEntryToStore(dataSourceName, result, writeOptions);
|
|
8185
8250
|
} else if (addEntryToStore) {
|
|
8186
8251
|
// If no optimistic update was done, add now
|
|
8187
|
-
addEntryToStore(dataSourceName, result);
|
|
8252
|
+
addEntryToStore(dataSourceName, result, writeOptions);
|
|
8188
8253
|
}
|
|
8189
8254
|
|
|
8190
8255
|
// Link the uploaded file to a table entry, resolved from the
|
|
@@ -8627,6 +8692,7 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
8627
8692
|
? Promise.all(fileId.map(id => window.ManifestDataAppwrite.deleteFile(bucketId, id.$id || id)))
|
|
8628
8693
|
: window.ManifestDataAppwrite.deleteFile(bucketId, actualFileId);
|
|
8629
8694
|
|
|
8695
|
+
const generation = window.ManifestDataStore?.sourceGeneration?.(dataSourceName);
|
|
8630
8696
|
const reloadPromise = window.ManifestDataAppwrite.listBucketFiles(bucketId, []);
|
|
8631
8697
|
|
|
8632
8698
|
// Do delete and reload in parallel
|
|
@@ -8634,7 +8700,7 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
8634
8700
|
|
|
8635
8701
|
// Network landing: reloaded bucket listing replaces the source (coalesced)
|
|
8636
8702
|
if (reloadedData && window.ManifestDataStore?.landRows) {
|
|
8637
|
-
await window.ManifestDataStore.landRows(dataSourceName, reloadedData, { mode: 'replace' });
|
|
8703
|
+
await window.ManifestDataStore.landRows(dataSourceName, reloadedData, { mode: 'replace', generation });
|
|
8638
8704
|
}
|
|
8639
8705
|
|
|
8640
8706
|
// Reload affected table data sources so fileIds arrays and counters
|
|
@@ -11361,11 +11427,93 @@ function scheduleAuthRetry(dataSourceName, locale) {
|
|
|
11361
11427
|
AUTH_RETRY_EVENTS.forEach(type => window.addEventListener(type, retry));
|
|
11362
11428
|
}
|
|
11363
11429
|
|
|
11430
|
+
// Teams known for this identity: login/anonymous dispatch after teams load, teams-loaded after the background load
|
|
11431
|
+
let teamsSettledFor = null;
|
|
11432
|
+
// A sign-in deadline already answered by an auth event no longer holds sources
|
|
11433
|
+
let answeredDeadline = 0;
|
|
11434
|
+
if (typeof window !== 'undefined') {
|
|
11435
|
+
['manifest:auth:teams-loaded', 'manifest:auth:login', 'manifest:auth:anonymous'].forEach(type =>
|
|
11436
|
+
window.addEventListener(type, () => { teamsSettledFor = authUserId(currentAuth()); }));
|
|
11437
|
+
// Kept only when logout restored the same (guest) identity whose teams already loaded
|
|
11438
|
+
['manifest:auth:logout', 'manifest:auth:session-cleared', 'manifest:auth:initialized'].forEach(type =>
|
|
11439
|
+
window.addEventListener(type, () => { if (type === 'manifest:auth:initialized' || teamsSettledFor !== authUserId(currentAuth())) teamsSettledFor = null; }));
|
|
11440
|
+
['manifest:auth:login', 'manifest:auth:anonymous', 'manifest:auth:logout', 'manifest:auth:session-cleared'].forEach(type =>
|
|
11441
|
+
window.addEventListener(type, () => { answeredDeadline = currentAuth()?._signInPendingUntil || 0; }));
|
|
11442
|
+
}
|
|
11443
|
+
|
|
11444
|
+
function currentAuth() {
|
|
11445
|
+
return typeof Alpine !== 'undefined' ? Alpine.store('auth') : null;
|
|
11446
|
+
}
|
|
11447
|
+
|
|
11448
|
+
function authUserId(auth) {
|
|
11449
|
+
return auth?.user?.$id || auth?.user?.id || null;
|
|
11450
|
+
}
|
|
11451
|
+
|
|
11452
|
+
// Guest-auto or an auth callback is signing in after a signed-out init (bounded by the auth store)
|
|
11453
|
+
// Prerender never waits on (or bakes) a guest's rows
|
|
11454
|
+
function signInWaitMs(auth) {
|
|
11455
|
+
if (!auth || auth.isAuthenticated === true || window.__manifestRender) return 0;
|
|
11456
|
+
const until = auth._signInPendingUntil || 0;
|
|
11457
|
+
if (until === answeredDeadline) return 0;
|
|
11458
|
+
return Math.max(0, until - Date.now());
|
|
11459
|
+
}
|
|
11460
|
+
|
|
11461
|
+
// Drop only this load's subscription; a newer one under the same name stays
|
|
11462
|
+
function dropSubscription(dataSourceName, unsubscribe) {
|
|
11463
|
+
if (typeof unsubscribe !== 'function') return;
|
|
11464
|
+
unsubscribe();
|
|
11465
|
+
const subs = window.ManifestDataRealtime?.subscriptions;
|
|
11466
|
+
if (subs?.get?.(dataSourceName) === unsubscribe) subs.delete(dataSourceName);
|
|
11467
|
+
}
|
|
11468
|
+
|
|
11364
11469
|
// Auth settled with no identity (or no auth plugin): a scoped read can't resolve until sign-in
|
|
11365
11470
|
function authSettledSignedOut() {
|
|
11366
|
-
const auth =
|
|
11471
|
+
const auth = currentAuth();
|
|
11367
11472
|
if (!auth) return true;
|
|
11368
|
-
return auth._initialized === true && auth.isAuthenticated !== true;
|
|
11473
|
+
return auth._initialized === true && auth.isAuthenticated !== true && signInWaitMs(auth) === 0;
|
|
11474
|
+
}
|
|
11475
|
+
|
|
11476
|
+
function teamsSettled(auth, manifest) {
|
|
11477
|
+
const id = authUserId(auth);
|
|
11478
|
+
if (id && teamsSettledFor === id) return true;
|
|
11479
|
+
const teams = manifest?.appwrite?.auth?.teams;
|
|
11480
|
+
if (!teams) return true;
|
|
11481
|
+
const guests = typeof teams.guests === 'string' ? /^(true|1|yes|on)$/i.test(teams.guests.trim()) : !!teams.guests;
|
|
11482
|
+
return auth.isAnonymous === true && !guests;
|
|
11483
|
+
}
|
|
11484
|
+
|
|
11485
|
+
// Auth fully settled (identity + teams) yet the scoped read still can't resolve: it never will
|
|
11486
|
+
function authSettledUnresolvable(manifest) {
|
|
11487
|
+
if (authSettledSignedOut()) return true;
|
|
11488
|
+
const auth = currentAuth();
|
|
11489
|
+
return auth._initialized === true && auth.isAuthenticated === true && teamsSettled(auth, manifest);
|
|
11490
|
+
}
|
|
11491
|
+
|
|
11492
|
+
function isAuthDependent(config) {
|
|
11493
|
+
if (!config || typeof config !== 'object') return false;
|
|
11494
|
+
if (window.ManifestDataConfig?.getScope?.(config)) return true;
|
|
11495
|
+
try { return JSON.stringify(config.queries || '').includes('$auth.'); } catch { return false; }
|
|
11496
|
+
}
|
|
11497
|
+
|
|
11498
|
+
// Logout / session cleared: drop the previous identity's rows now, then settle for the new one
|
|
11499
|
+
function setupAuthResetListener() {
|
|
11500
|
+
const reset = async () => {
|
|
11501
|
+
const manifest = await window.ManifestDataConfig?.ensureManifest?.();
|
|
11502
|
+
const ds = window.ManifestDataStore;
|
|
11503
|
+
const store = typeof Alpine !== 'undefined' ? Alpine.store('data') : null;
|
|
11504
|
+
if (!manifest || !ds?.resetSource || !store) return;
|
|
11505
|
+
const raw = ds.rawOf ? ds.rawOf(store) : store;
|
|
11506
|
+
const configs = { ...(manifest.appwrite || {}), ...(manifest.data || {}) };
|
|
11507
|
+
const locale = liveLocale();
|
|
11508
|
+
for (const [name, config] of Object.entries(configs)) {
|
|
11509
|
+
if (!isAuthDependent(config) || raw[`_${name}_state`] === undefined) continue;
|
|
11510
|
+
window.ManifestDataRealtime?.unsubscribeFromDataSource?.(name);
|
|
11511
|
+
ds.resetSource(name);
|
|
11512
|
+
loadDataSource(name, locale, { reload: true });
|
|
11513
|
+
}
|
|
11514
|
+
};
|
|
11515
|
+
window.addEventListener('manifest:auth:logout', reset);
|
|
11516
|
+
window.addEventListener('manifest:auth:session-cleared', reset);
|
|
11369
11517
|
}
|
|
11370
11518
|
|
|
11371
11519
|
// Client-side scope filter: Appwrite returns all accessible files, so narrow to
|
|
@@ -11468,7 +11616,7 @@ async function filterFilesByScope(files, scope) {
|
|
|
11468
11616
|
}
|
|
11469
11617
|
|
|
11470
11618
|
// Handle real-time storage events
|
|
11471
|
-
async function handleStorageRealtimeEvent(dataSourceName, bucketId, scope, eventType, payload) {
|
|
11619
|
+
async function handleStorageRealtimeEvent(dataSourceName, bucketId, scope, eventType, payload, generation) {
|
|
11472
11620
|
|
|
11473
11621
|
// Deduplicate events
|
|
11474
11622
|
const eventKey = getEventKey(eventType, payload);
|
|
@@ -11495,7 +11643,7 @@ async function handleStorageRealtimeEvent(dataSourceName, bucketId, scope, event
|
|
|
11495
11643
|
if (file && file.$id) {
|
|
11496
11644
|
const fileMatchesScope = await checkFileMatchesScope(file, scope);
|
|
11497
11645
|
if (fileMatchesScope) {
|
|
11498
|
-
landRows(dataSourceName, [file], { mode: 'append' });
|
|
11646
|
+
landRows(dataSourceName, [file], { mode: 'append', generation });
|
|
11499
11647
|
|
|
11500
11648
|
// Emit custom event for new file creation so UI can refresh project files
|
|
11501
11649
|
window.dispatchEvent(new CustomEvent('manifest:file-created', {
|
|
@@ -11510,16 +11658,16 @@ async function handleStorageRealtimeEvent(dataSourceName, bucketId, scope, event
|
|
|
11510
11658
|
if (file && file.$id) {
|
|
11511
11659
|
const fileMatchesScope = await checkFileMatchesScope(file, scope);
|
|
11512
11660
|
if (fileMatchesScope) {
|
|
11513
|
-
landRows(dataSourceName, [file], { mode: 'append' });
|
|
11661
|
+
landRows(dataSourceName, [file], { mode: 'append', generation });
|
|
11514
11662
|
} else {
|
|
11515
11663
|
// File no longer matches scope, remove it
|
|
11516
|
-
landRemove(dataSourceName, [file.$id]);
|
|
11664
|
+
landRemove(dataSourceName, [file.$id], { generation });
|
|
11517
11665
|
}
|
|
11518
11666
|
}
|
|
11519
11667
|
} else if (eventType === 'delete') {
|
|
11520
11668
|
const fileId = payload?.$id || payload?.file?.$id || payload?.fileId || payload;
|
|
11521
11669
|
if (fileId) {
|
|
11522
|
-
landRemove(dataSourceName, [fileId.$id || fileId]);
|
|
11670
|
+
landRemove(dataSourceName, [fileId.$id || fileId], { generation });
|
|
11523
11671
|
}
|
|
11524
11672
|
}
|
|
11525
11673
|
}
|
|
@@ -11570,7 +11718,7 @@ function markEventProcessed(eventKey) {
|
|
|
11570
11718
|
}
|
|
11571
11719
|
|
|
11572
11720
|
// Handle real-time events for database tables
|
|
11573
|
-
async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, scope, scopeColumns, eventType, payload) {
|
|
11721
|
+
async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, scope, scopeColumns, eventType, payload, generation) {
|
|
11574
11722
|
|
|
11575
11723
|
// Deduplicate events
|
|
11576
11724
|
const eventKey = getEventKey(eventType, payload);
|
|
@@ -11599,7 +11747,7 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
|
|
|
11599
11747
|
if (row && row.$id) {
|
|
11600
11748
|
const rowMatchesScope = await checkRowMatchesScope(row, scope, scopeColumns);
|
|
11601
11749
|
if (rowMatchesScope) {
|
|
11602
|
-
landRows(dataSourceName, [row], { mode: 'append' });
|
|
11750
|
+
landRows(dataSourceName, [row], { mode: 'append', generation });
|
|
11603
11751
|
}
|
|
11604
11752
|
} else {
|
|
11605
11753
|
console.warn('[Manifest Data] Invalid row payload in create event:', payload);
|
|
@@ -11611,12 +11759,12 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
|
|
|
11611
11759
|
const rowMatchesScope = await checkRowMatchesScope(row, scope, scopeColumns);
|
|
11612
11760
|
if (!rowMatchesScope) {
|
|
11613
11761
|
// Row no longer matches scope, remove it
|
|
11614
|
-
if (existingRow) landRemove(dataSourceName, [row.$id]);
|
|
11762
|
+
if (existingRow) landRemove(dataSourceName, [row.$id], { generation });
|
|
11615
11763
|
return;
|
|
11616
11764
|
}
|
|
11617
11765
|
if (!existingRow) {
|
|
11618
11766
|
// Row not in list, but matches scope now - add it
|
|
11619
|
-
landRows(dataSourceName, [row], { mode: 'append' });
|
|
11767
|
+
landRows(dataSourceName, [row], { mode: 'append', generation });
|
|
11620
11768
|
return;
|
|
11621
11769
|
}
|
|
11622
11770
|
|
|
@@ -11676,7 +11824,7 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
|
|
|
11676
11824
|
const fileIdsChanged = isProjectWithFiles &&
|
|
11677
11825
|
JSON.stringify(existingFileIds) !== JSON.stringify(incomingFileIds);
|
|
11678
11826
|
|
|
11679
|
-
landRows(dataSourceName, [row], { mode: 'append' });
|
|
11827
|
+
landRows(dataSourceName, [row], { mode: 'append', generation });
|
|
11680
11828
|
|
|
11681
11829
|
// Emit custom event for project file updates so UI can refresh
|
|
11682
11830
|
if (fileIdsChanged && dataSourceName === 'projects') {
|
|
@@ -11689,7 +11837,7 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
|
|
|
11689
11837
|
} else if (eventType === 'delete') {
|
|
11690
11838
|
const rowId = payload?.$id || payload?.row?.$id || payload?.rowId || payload;
|
|
11691
11839
|
if (rowId) {
|
|
11692
|
-
landRemove(dataSourceName, [rowId.$id || rowId]);
|
|
11840
|
+
landRemove(dataSourceName, [rowId.$id || rowId], { generation });
|
|
11693
11841
|
}
|
|
11694
11842
|
}
|
|
11695
11843
|
|
|
@@ -11871,9 +12019,20 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
11871
12019
|
const generation = sourceGeneration ? sourceGeneration(dataSourceName) : 0;
|
|
11872
12020
|
const superseded = () => sourceGeneration && sourceGeneration(dataSourceName) !== generation;
|
|
11873
12021
|
|
|
11874
|
-
//
|
|
11875
|
-
|
|
11876
|
-
|
|
12022
|
+
// Unresolvable scope (signed out, or teamless): settle empty, not $loading forever; auth events reload.
|
|
12023
|
+
// A pending sign-in holds the source unsettled (one render-ready), bounded by its deadline.
|
|
12024
|
+
const settleUnresolved = (manifest) => {
|
|
12025
|
+
if (isInitializing || superseded()) return false;
|
|
12026
|
+
const wait = signInWaitMs(currentAuth());
|
|
12027
|
+
if (wait > 0) {
|
|
12028
|
+
setTimeout(() => {
|
|
12029
|
+
const state = Alpine.store('data')?.[`_${dataSourceName}_state`];
|
|
12030
|
+
if (state?.ready || superseded() || !authSettledSignedOut()) return;
|
|
12031
|
+
updateStore(dataSourceName, [], { loading: false, error: null, ready: true });
|
|
12032
|
+
}, wait + 10);
|
|
12033
|
+
return false;
|
|
12034
|
+
}
|
|
12035
|
+
if (!authSettledUnresolvable(manifest)) return false;
|
|
11877
12036
|
updateStore(dataSourceName, [], { loading: false, error: null, ready: true });
|
|
11878
12037
|
return true;
|
|
11879
12038
|
};
|
|
@@ -11957,7 +12116,7 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
11957
12116
|
// Not ready (auth/scope unresolved): skip this read, retry on an auth event
|
|
11958
12117
|
if (queries === null) {
|
|
11959
12118
|
scheduleAuthRetry(dataSourceName, locale);
|
|
11960
|
-
if (
|
|
12119
|
+
if (settleUnresolved(manifest)) landed = true;
|
|
11961
12120
|
return null;
|
|
11962
12121
|
}
|
|
11963
12122
|
|
|
@@ -11970,19 +12129,21 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
11970
12129
|
tableId,
|
|
11971
12130
|
queries
|
|
11972
12131
|
);
|
|
12132
|
+
if (superseded()) { landed = true; return null; }
|
|
11973
12133
|
|
|
11974
|
-
// Subscribe to real-time updates for this table
|
|
12134
|
+
// Subscribe to real-time updates for this table (events from a superseded identity drop)
|
|
11975
12135
|
if (window.ManifestDataRealtime && window.ManifestDataRealtime.subscribeToTable) {
|
|
11976
|
-
await window.ManifestDataRealtime.subscribeToTable(
|
|
12136
|
+
const unsubscribe = await window.ManifestDataRealtime.subscribeToTable(
|
|
11977
12137
|
dataSourceName,
|
|
11978
12138
|
appwriteConfig.databaseId,
|
|
11979
12139
|
tableId,
|
|
11980
12140
|
scope,
|
|
11981
12141
|
async (eventType, payload) => {
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
12142
|
+
await handleTableRealtimeEvent(dataSourceName, appwriteConfig.databaseId, tableId, scope, scopeColumns, eventType, payload, generation);
|
|
12143
|
+
},
|
|
12144
|
+
() => !superseded()
|
|
11985
12145
|
);
|
|
12146
|
+
if (superseded()) { dropSubscription(dataSourceName, unsubscribe); landed = true; return null; }
|
|
11986
12147
|
}
|
|
11987
12148
|
} else if (bucketId) {
|
|
11988
12149
|
// Load from Appwrite storage bucket
|
|
@@ -11996,7 +12157,7 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
11996
12157
|
// Not ready (a $auth. arg in queriesConfig unresolved): skip, retry on an auth event
|
|
11997
12158
|
if (queries === null) {
|
|
11998
12159
|
scheduleAuthRetry(dataSourceName, locale);
|
|
11999
|
-
if (
|
|
12160
|
+
if (settleUnresolved(manifest)) landed = true;
|
|
12000
12161
|
return null;
|
|
12001
12162
|
}
|
|
12002
12163
|
|
|
@@ -12017,17 +12178,21 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
12017
12178
|
|
|
12018
12179
|
data = files;
|
|
12019
12180
|
|
|
12020
|
-
|
|
12181
|
+
if (superseded()) { landed = true; return null; }
|
|
12182
|
+
|
|
12183
|
+
// Subscribe to real-time updates for this bucket (events from a superseded identity drop)
|
|
12021
12184
|
if (window.ManifestDataRealtime && window.ManifestDataRealtime.subscribeToStorageBucket) {
|
|
12022
|
-
await window.ManifestDataRealtime.subscribeToStorageBucket(
|
|
12185
|
+
const unsubscribe = await window.ManifestDataRealtime.subscribeToStorageBucket(
|
|
12023
12186
|
dataSourceName,
|
|
12024
12187
|
bucketId,
|
|
12025
12188
|
scope,
|
|
12026
12189
|
async (eventType, payload) => {
|
|
12027
|
-
|
|
12028
|
-
await handleStorageRealtimeEvent(dataSourceName, bucketId, scope, eventType, payload);
|
|
12029
|
-
}
|
|
12190
|
+
if (superseded()) return;
|
|
12191
|
+
await handleStorageRealtimeEvent(dataSourceName, bucketId, scope, eventType, payload, generation);
|
|
12192
|
+
},
|
|
12193
|
+
() => !superseded()
|
|
12030
12194
|
);
|
|
12195
|
+
if (superseded()) { dropSubscription(dataSourceName, unsubscribe); landed = true; return null; }
|
|
12031
12196
|
}
|
|
12032
12197
|
} else {
|
|
12033
12198
|
console.warn(`[Manifest Data] Appwrite data source "${dataSourceName}" missing tableId or bucketId`);
|
|
@@ -12161,7 +12326,7 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
|
|
|
12161
12326
|
// Network landing (coalesced per frame, merges by $id); resolves once visible
|
|
12162
12327
|
if (!isInitializing && !staleLocale) {
|
|
12163
12328
|
landed = true;
|
|
12164
|
-
await landRows(dataSourceName, enhancedData, { mode: 'replace', loading: false, error: null, ready: true, fresh: true });
|
|
12329
|
+
await landRows(dataSourceName, enhancedData, { mode: 'replace', loading: false, error: null, ready: true, fresh: true, generation });
|
|
12165
12330
|
}
|
|
12166
12331
|
|
|
12167
12332
|
// Return unsealed version for our proxy system
|
|
@@ -12307,6 +12472,8 @@ async function initializeDataSourcesPlugin() {
|
|
|
12307
12472
|
// Setup URL change listeners
|
|
12308
12473
|
setupUrlChangeListeners();
|
|
12309
12474
|
|
|
12475
|
+
setupAuthResetListener();
|
|
12476
|
+
|
|
12310
12477
|
// Register $x magic method (only if Alpine is available)
|
|
12311
12478
|
|
|
12312
12479
|
if (typeof Alpine !== 'undefined' && window.ManifestDataProxies) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"manifest.appwrite.auth.js": "sha384-
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
2
|
+
"manifest.appwrite.auth.js": "sha384-tcpyR3WJvAL3TenZRsevGCGCHUrGgbU5wHuOYG5Emvny/wukRiaPEagrXiH5v7R6",
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-gnniX4ojmePGmJsC3KRZqP+jdIGe852Sr/XSkO+HJWljiNMeZVph5LYgYdcwoRNf",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
6
|
"manifest.charts.js": "sha384-gEAv/98+29RbhvNkiNWTNyBPbLTFr2Ld15wLu29FLCL57j1cwGChRrL9Pfk7dBtT",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
|
|
12
12
|
"manifest.components.js": "sha384-4bc3HgPvF9DHMiOG08w+nWXI7Hho01RuAPAUL9xyLxOiS2igeZpulCVcvr5aT9v6",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384-
|
|
14
|
+
"manifest.data.js": "sha384-5kg35XDQb/nTD4oED1HxAkyAhCpfieoUhaY1oCO/vLTc6oPXiPATzyW6/95KSPLw",
|
|
15
15
|
"manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
|
|
16
16
|
"manifest.defer.js": "sha384-WcCf8YQ1bKIXCLSZ24a0bJwepbYntYc8TzQM50hklo5VMIH8AtzDnwl6WU4prRv1",
|
|
17
17
|
"manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
29
29
|
"manifest.status.js": "sha384-MBO345ab7H6Uq1X2dZLt4Ge1Gcra+Z2qYApvlAeIV8CJhDDJrz2FPXpp+Kcp8zcM",
|
|
30
30
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
31
|
-
"manifest.sw.js": "sha384-
|
|
31
|
+
"manifest.sw.js": "sha384-AQB+zglu9/quyKkdAo2oIiWzfgCH7UqiOmFwrS2hqfzYsaSpYeDpmjb5d2tl/RTB",
|
|
32
32
|
"manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
|
|
33
33
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
34
34
|
"manifest.text.edit.js": "sha384-RzOvLQE8GbmI6gntAwiotDewYT/08OHcd7sJ5hLon8e8u1Xbq1D7VfEGuaRVd2CE",
|
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.
|
|
19
|
+
const BUILD_VERSION = '0.5.224';
|
|
20
20
|
const CACHE_PREFIX = 'mnfst-sw:';
|
|
21
21
|
const META_CACHE = CACHE_PREFIX + 'meta';
|
|
22
22
|
// Sized to hold a full precache.json warm-up (PRECACHE_MAX_ENTRIES server-side)
|