mnfst 0.5.225 → 0.5.227
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 +140 -55
- package/lib/manifest.appwrite.data.js +1 -1
- package/lib/manifest.d.ts +1 -1
- package/lib/manifest.data.js +116 -40
- package/lib/manifest.integrity.json +4 -5
- package/lib/manifest.sw.js +1 -1
- package/package.json +3 -2
- package/lib/manifest.resize.css +0 -194
- package/lib/manifest.resize.js +0 -488
|
@@ -255,6 +255,19 @@ function initializeAuthStore() {
|
|
|
255
255
|
'deviceBrand', 'deviceModel', 'countryCode', 'countryName'
|
|
256
256
|
];
|
|
257
257
|
|
|
258
|
+
// User id in a mirrored state ('' / null when signed out)
|
|
259
|
+
function mirroredUserId(raw) {
|
|
260
|
+
try { const saved = JSON.parse(raw || 'null'); return saved?.isAuthenticated ? (saved.user?.$id || null) : null; } catch { return null; }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Identity this tab is showing data for: while unverified, the last one mirrored
|
|
264
|
+
function shownUserId(store, mirrored) {
|
|
265
|
+
if (store.user?.$id) return store.user.$id;
|
|
266
|
+
if (!store._sessionUnverified) return null;
|
|
267
|
+
if (mirrored === undefined) { try { mirrored = localStorage.getItem(STORAGE_KEY); } catch { mirrored = null; } }
|
|
268
|
+
return mirroredUserId(mirrored);
|
|
269
|
+
}
|
|
270
|
+
|
|
258
271
|
function sanitizeSessionForStorage(session) {
|
|
259
272
|
if (!session || typeof session !== 'object') return session;
|
|
260
273
|
const safe = {};
|
|
@@ -271,7 +284,8 @@ function initializeAuthStore() {
|
|
|
271
284
|
const state = JSON.parse(e.newValue);
|
|
272
285
|
const store = Alpine.store('auth');
|
|
273
286
|
if (store) {
|
|
274
|
-
const prevId = store.
|
|
287
|
+
const prevId = shownUserId(store, e.oldValue);
|
|
288
|
+
const wasAuthenticated = store.isAuthenticated === true;
|
|
275
289
|
// Update store state from other tab
|
|
276
290
|
store._authEpoch++;
|
|
277
291
|
if (state.isAuthenticated) markVerified(store);
|
|
@@ -283,15 +297,19 @@ function initializeAuthStore() {
|
|
|
283
297
|
store.magicLinkExpired = state.magicLinkExpired || false;
|
|
284
298
|
store.otpSent = state.otpSent || false;
|
|
285
299
|
store.otpExpired = state.otpExpired || false;
|
|
300
|
+
// A code row shown here must be verifiable here
|
|
301
|
+
if (state.otpSent && state.otpUserId) store._otpUserId = state.otpUserId;
|
|
286
302
|
store.error = state.error;
|
|
287
303
|
// Identity ended or changed in another tab: scoped data must drop this tab's rows
|
|
288
304
|
const nextId = state.isAuthenticated ? (state.user?.$id || null) : null;
|
|
289
305
|
if (prevId && nextId !== prevId) {
|
|
290
|
-
|
|
291
|
-
store.teams = [];
|
|
292
|
-
store.currentTeam = null;
|
|
293
|
-
}
|
|
306
|
+
store._resetTeamsState();
|
|
294
307
|
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
308
|
+
if (nextId) store._adoptCrossTabTeams(nextId);
|
|
309
|
+
} else if (nextId && !wasAuthenticated) {
|
|
310
|
+
// Signed in from another tab: this tab's identity-bound data reloads
|
|
311
|
+
window.dispatchEvent(new CustomEvent(state.isAnonymous ? 'manifest:auth:anonymous' : 'manifest:auth:login', { detail: { user: state.user, crossTab: true } }));
|
|
312
|
+
store._adoptCrossTabTeams(nextId);
|
|
295
313
|
}
|
|
296
314
|
}
|
|
297
315
|
} catch (error) {
|
|
@@ -314,6 +332,7 @@ function initializeAuthStore() {
|
|
|
314
332
|
magicLinkExpired: store.magicLinkExpired,
|
|
315
333
|
otpSent: store.otpSent,
|
|
316
334
|
otpExpired: store.otpExpired,
|
|
335
|
+
otpUserId: store.otpSent ? store._otpUserId : null,
|
|
317
336
|
error: store.error
|
|
318
337
|
};
|
|
319
338
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
@@ -335,6 +354,7 @@ function initializeAuthStore() {
|
|
|
335
354
|
otpExpired: false, // OTP (email/phone): the entered code was wrong/expired
|
|
336
355
|
otpPhrase: null, // Email OTP: security phrase to display (when enabled; phone has none)
|
|
337
356
|
_otpUserId: null, // OTP: userId returned by createEmailToken/createPhoneToken, used by verifyOTP
|
|
357
|
+
_otpMigrationTicket: null, // OTP: { ticket, userId } guest-migration ticket, kept across failed verify attempts
|
|
338
358
|
teams: [], // List of user's teams
|
|
339
359
|
currentTeam: null, // Currently selected/active team
|
|
340
360
|
_teamsPollInterval: null, // Interval ID for teams polling (deprecated, use realtime instead)
|
|
@@ -384,6 +404,8 @@ function initializeAuthStore() {
|
|
|
384
404
|
_guestManual: false,
|
|
385
405
|
_signInPendingUntil: 0, // signed-out init with guest-auto or an auth callback still to sign in
|
|
386
406
|
_sessionUnverified: false, // init could not reach Appwrite: signed out locally, never broadcast
|
|
407
|
+
_crossTabTeams: null, // latest cross-tab team load; older ones skip their writes
|
|
408
|
+
_teamsEpoch: 0, // bumped by every team reset; an in-flight listTeams from an older one is discarded
|
|
387
409
|
_authEpoch: 0, // bumped by every identity change; an in-flight recheck from an older epoch is dropped
|
|
388
410
|
_recheckTimer: null,
|
|
389
411
|
_recheckAttempt: 0,
|
|
@@ -619,8 +641,9 @@ function initializeAuthStore() {
|
|
|
619
641
|
this._initialized = true;
|
|
620
642
|
this._initializing = false;
|
|
621
643
|
|
|
622
|
-
// Guest-auto / callback sign-in follows: scoped data holds instead of flashing signed-out
|
|
623
|
-
|
|
644
|
+
// Guest-auto / callback sign-in follows: scoped data holds instead of flashing signed-out.
|
|
645
|
+
// Not while unverified: nothing signs in until Appwrite is reachable (the recheck handles it)
|
|
646
|
+
if (!this.isAuthenticated && !this._sessionUnverified) {
|
|
624
647
|
let callback = false;
|
|
625
648
|
try {
|
|
626
649
|
// Team invites need an existing session (acceptInvite refuses signed-out), so they don't sign in
|
|
@@ -644,10 +667,14 @@ function initializeAuthStore() {
|
|
|
644
667
|
// manifest:auth:teams-loaded for anything needing the full set.
|
|
645
668
|
if (appwriteConfig && this.isAuthenticated && appwriteConfig.teams
|
|
646
669
|
&& (!this.isAnonymous || appwriteConfig.guestTeams)) {
|
|
670
|
+
const owner = this.user?.$id;
|
|
647
671
|
this._loadTeamsAndSeed(appwriteConfig)
|
|
648
|
-
.then(() =>
|
|
649
|
-
|
|
650
|
-
|
|
672
|
+
.then(() => {
|
|
673
|
+
if (!this.isAuthenticated || this.user?.$id !== owner) return;
|
|
674
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:teams-loaded', {
|
|
675
|
+
detail: { teams: this.teams, currentTeam: this.currentTeam }
|
|
676
|
+
}));
|
|
677
|
+
})
|
|
651
678
|
.catch(e => console.warn('[Manifest Appwrite Auth] Background team load failed:', e?.message || e));
|
|
652
679
|
}
|
|
653
680
|
},
|
|
@@ -713,9 +740,12 @@ function initializeAuthStore() {
|
|
|
713
740
|
} catch (error) {
|
|
714
741
|
if (!current()) return;
|
|
715
742
|
if (!isNoSession(error)) { scheduleRecheck(this); return; }
|
|
743
|
+
const prevId = shownUserId(this);
|
|
716
744
|
this._clearIdentity();
|
|
717
745
|
markVerified(this);
|
|
718
746
|
syncStateToStorage(this);
|
|
747
|
+
// Rows shown for the last-known user drop
|
|
748
|
+
if (prevId) window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
719
749
|
if (this._guestAuto && this._createAnonymousSession) await this._createAnonymousSession();
|
|
720
750
|
return;
|
|
721
751
|
} finally {
|
|
@@ -750,9 +780,34 @@ function initializeAuthStore() {
|
|
|
750
780
|
this.deletedTemplateTeams = [];
|
|
751
781
|
this.deletedTemplateRoles = [];
|
|
752
782
|
this._teamImmutableCache = {};
|
|
783
|
+
this._teamsEpoch++;
|
|
753
784
|
if (this.stopTeamsRealtime) {
|
|
754
785
|
try { this.stopTeamsRealtime(); } catch (e) { /* ignore */ }
|
|
755
786
|
}
|
|
787
|
+
if (this.stopTeamsPolling) {
|
|
788
|
+
try { this.stopTeamsPolling(); } catch (e) { /* ignore */ }
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
|
|
792
|
+
// Teams for an identity adopted from another tab. That tab seeds; this one
|
|
793
|
+
// only lists. A newer adoption or identity change supersedes this load.
|
|
794
|
+
_adoptCrossTabTeams(userId) {
|
|
795
|
+
const token = {};
|
|
796
|
+
this._crossTabTeams = token;
|
|
797
|
+
const current = () => this._crossTabTeams === token && this.isAuthenticated === true && this.user?.$id === userId;
|
|
798
|
+
(async () => {
|
|
799
|
+
try {
|
|
800
|
+
const cfg = await config.getAppwriteConfig();
|
|
801
|
+
if (!cfg?.teams || (this.isAnonymous && !cfg.guestTeams) || !current()) return;
|
|
802
|
+
await this._loadTeamsAndSeed(cfg, { seed: false });
|
|
803
|
+
} catch (e) {
|
|
804
|
+
console.warn('[Manifest Appwrite Auth] Cross-tab team load failed:', e?.message || e);
|
|
805
|
+
}
|
|
806
|
+
if (!current()) return;
|
|
807
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:teams-loaded', {
|
|
808
|
+
detail: { teams: this.teams, currentTeam: this.currentTeam }
|
|
809
|
+
}));
|
|
810
|
+
})();
|
|
756
811
|
},
|
|
757
812
|
|
|
758
813
|
// Call the deployed guest-migration function; the current session authenticates it.
|
|
@@ -777,14 +832,14 @@ function initializeAuthStore() {
|
|
|
777
832
|
|
|
778
833
|
// Load the user's teams and seed configured defaults. Shared by the guest,
|
|
779
834
|
// magic-link, OAuth, and init/restore paths.
|
|
780
|
-
async _loadTeamsAndSeed(appwriteConfig) {
|
|
835
|
+
async _loadTeamsAndSeed(appwriteConfig, { seed = true } = {}) {
|
|
781
836
|
const cfg = appwriteConfig || await config.getAppwriteConfig();
|
|
782
837
|
if (!cfg?.teams) {
|
|
783
838
|
return;
|
|
784
839
|
}
|
|
785
840
|
// Startup race: we can arrive before teams.core/defaults wire listTeams +
|
|
786
841
|
// ensureDefaultTeams onto the store. Wait briefly rather than skip.
|
|
787
|
-
const needsSeed = !!(cfg.permanentTeams || cfg.templateTeams);
|
|
842
|
+
const needsSeed = seed && !!(cfg.permanentTeams || cfg.templateTeams);
|
|
788
843
|
const ready = () => typeof this.listTeams === 'function'
|
|
789
844
|
&& (!needsSeed || typeof window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams === 'function');
|
|
790
845
|
for (let i = 0; i < 40 && !ready(); i++) {
|
|
@@ -907,6 +962,9 @@ function initializeAuthStore() {
|
|
|
907
962
|
return { success: false, error: 'Appwrite not configured' };
|
|
908
963
|
}
|
|
909
964
|
|
|
965
|
+
// A ticket kept from a failed guest OTP must not outlive an explicit logout
|
|
966
|
+
this._otpMigrationTicket = null;
|
|
967
|
+
|
|
910
968
|
// If not authenticated, nothing to logout from
|
|
911
969
|
if (!this.isAuthenticated) {
|
|
912
970
|
return { success: true };
|
|
@@ -941,19 +999,8 @@ function initializeAuthStore() {
|
|
|
941
999
|
this.otpPhrase = null;
|
|
942
1000
|
this._otpUserId = null;
|
|
943
1001
|
|
|
944
|
-
//
|
|
945
|
-
|
|
946
|
-
this.stopTeamsRealtime();
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
// Stop teams polling if active (fallback)
|
|
950
|
-
if (this.stopTeamsPolling) {
|
|
951
|
-
this.stopTeamsPolling();
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// Clear teams on logout
|
|
955
|
-
this.teams = [];
|
|
956
|
-
this.currentTeam = null;
|
|
1002
|
+
// Clear teams (stops realtime/polling, discards in-flight lists)
|
|
1003
|
+
this._resetTeamsState();
|
|
957
1004
|
|
|
958
1005
|
// Restore to guest state after logout (guest-auto only, and not when
|
|
959
1006
|
// already a guest — don't mint a new guest on top).
|
|
@@ -987,19 +1034,8 @@ function initializeAuthStore() {
|
|
|
987
1034
|
this.user = null;
|
|
988
1035
|
this.session = null;
|
|
989
1036
|
}
|
|
990
|
-
// Stop teams realtime subscription if active
|
|
991
|
-
if (this.stopTeamsRealtime) {
|
|
992
|
-
this.stopTeamsRealtime();
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// Stop teams polling if active (fallback)
|
|
996
|
-
if (this.stopTeamsPolling) {
|
|
997
|
-
this.stopTeamsPolling();
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
1037
|
// Clear teams on logout error too
|
|
1001
|
-
this.
|
|
1002
|
-
this.currentTeam = null;
|
|
1038
|
+
this._resetTeamsState();
|
|
1003
1039
|
if (!this.isAuthenticated) this._clearIdentity();
|
|
1004
1040
|
// Expired session (401) is still a logout: scoped data must drop the old rows
|
|
1005
1041
|
syncStateToStorage(this);
|
|
@@ -1035,8 +1071,7 @@ function initializeAuthStore() {
|
|
|
1035
1071
|
this._oauthProvider = null;
|
|
1036
1072
|
|
|
1037
1073
|
// Clear teams
|
|
1038
|
-
this.
|
|
1039
|
-
this.currentTeam = null;
|
|
1074
|
+
this._resetTeamsState();
|
|
1040
1075
|
|
|
1041
1076
|
// Clear OAuth provider from storage
|
|
1042
1077
|
try {
|
|
@@ -1057,8 +1092,7 @@ function initializeAuthStore() {
|
|
|
1057
1092
|
this.session = null;
|
|
1058
1093
|
this.magicLinkSent = false;
|
|
1059
1094
|
this.magicLinkExpired = false;
|
|
1060
|
-
this.
|
|
1061
|
-
this.currentTeam = null;
|
|
1095
|
+
this._resetTeamsState();
|
|
1062
1096
|
syncStateToStorage(this);
|
|
1063
1097
|
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
1064
1098
|
return { success: false, error: error.message };
|
|
@@ -1080,12 +1114,18 @@ function initializeAuthStore() {
|
|
|
1080
1114
|
return this.user;
|
|
1081
1115
|
} catch (error) {
|
|
1082
1116
|
if (!isNoSession(error)) throw error;
|
|
1117
|
+
const prevId = shownUserId(this);
|
|
1083
1118
|
markVerified(this);
|
|
1084
1119
|
this.isAuthenticated = false;
|
|
1085
1120
|
this.isAnonymous = false;
|
|
1086
1121
|
this.user = null;
|
|
1087
1122
|
this.session = null;
|
|
1088
1123
|
syncStateToStorage(this);
|
|
1124
|
+
if (prevId) {
|
|
1125
|
+
this.teams = [];
|
|
1126
|
+
this.currentTeam = null;
|
|
1127
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
1128
|
+
}
|
|
1089
1129
|
throw error;
|
|
1090
1130
|
}
|
|
1091
1131
|
}
|
|
@@ -1487,6 +1527,12 @@ function initializeTeamsCore() {
|
|
|
1487
1527
|
return { success: false, error: 'You must be signed in to list teams' };
|
|
1488
1528
|
}
|
|
1489
1529
|
|
|
1530
|
+
// Answer for this identity only: a reset or identity change discards it
|
|
1531
|
+
const owner = this.user?.$id || null;
|
|
1532
|
+
const generation = this._teamsEpoch;
|
|
1533
|
+
const stale = () => !this.isAuthenticated || (this.user?.$id || null) !== owner || this._teamsEpoch !== generation;
|
|
1534
|
+
const discarded = { success: false, error: 'Identity changed during team load', stale: true };
|
|
1535
|
+
|
|
1490
1536
|
try {
|
|
1491
1537
|
const params = {
|
|
1492
1538
|
queries: queries
|
|
@@ -1495,6 +1541,7 @@ function initializeTeamsCore() {
|
|
|
1495
1541
|
params.search = search;
|
|
1496
1542
|
}
|
|
1497
1543
|
const result = await this._appwrite.teams.list(params);
|
|
1544
|
+
if (stale()) return discarded;
|
|
1498
1545
|
|
|
1499
1546
|
// Update store with teams
|
|
1500
1547
|
this.teams = result.teams || [];
|
|
@@ -1514,19 +1561,24 @@ function initializeTeamsCore() {
|
|
|
1514
1561
|
if (window.ManifestAppwriteAuthTeamsDefaults && this.isTeamImmutable) {
|
|
1515
1562
|
for (const team of this.teams) {
|
|
1516
1563
|
if (!this._teamImmutableCache[team.$id]) {
|
|
1517
|
-
|
|
1564
|
+
const immutable = await this.isTeamImmutable(team.$id);
|
|
1565
|
+
if (stale()) return discarded;
|
|
1566
|
+
this._teamImmutableCache[team.$id] = immutable;
|
|
1518
1567
|
}
|
|
1519
1568
|
}
|
|
1520
1569
|
}
|
|
1521
1570
|
|
|
1522
1571
|
// Load deleted template teams (if defaults module is loaded)
|
|
1523
1572
|
if (window.ManifestAppwriteAuthTeamsDefaults && this.getDeletedTemplateTeams) {
|
|
1524
|
-
|
|
1573
|
+
const deletedTemplateTeams = await this.getDeletedTemplateTeams();
|
|
1574
|
+
if (stale()) return discarded;
|
|
1575
|
+
this.deletedTemplateTeams = deletedTemplateTeams;
|
|
1525
1576
|
}
|
|
1526
1577
|
|
|
1527
1578
|
// Clean up duplicate default teams (permanent/template) if any exist
|
|
1528
1579
|
if (window.ManifestAppwriteAuthTeamsDefaults && this.cleanupDuplicateDefaultTeams) {
|
|
1529
1580
|
const cleanupResult = await this.cleanupDuplicateDefaultTeams();
|
|
1581
|
+
if (stale()) return discarded;
|
|
1530
1582
|
if (cleanupResult.cleaned > 0) {
|
|
1531
1583
|
if (cleanupResult.errors && cleanupResult.errors.length > 0) {
|
|
1532
1584
|
console.warn('[Manifest Appwrite Auth] Some duplicate teams could not be deleted:', cleanupResult.errors);
|
|
@@ -1535,12 +1587,15 @@ function initializeTeamsCore() {
|
|
|
1535
1587
|
if (this.listTeams) {
|
|
1536
1588
|
await this.listTeams();
|
|
1537
1589
|
}
|
|
1590
|
+
if (stale()) return discarded;
|
|
1538
1591
|
}
|
|
1539
1592
|
}
|
|
1540
1593
|
|
|
1541
1594
|
// Load deleted template roles for current team (if roles defaults module is loaded)
|
|
1542
1595
|
if (this.currentTeam && this.currentTeam.$id && window.ManifestAppwriteAuthTeamsRolesDefaults && this.getDeletedTemplateRoles) {
|
|
1543
|
-
|
|
1596
|
+
const deletedTemplateRoles = await this.getDeletedTemplateRoles(this.currentTeam.$id);
|
|
1597
|
+
if (stale()) return discarded;
|
|
1598
|
+
this.deletedTemplateRoles = deletedTemplateRoles;
|
|
1544
1599
|
}
|
|
1545
1600
|
|
|
1546
1601
|
// Set currentTeam to first default team if available and not already set
|
|
@@ -1549,8 +1604,10 @@ function initializeTeamsCore() {
|
|
|
1549
1604
|
const hasDefaultTeams = (appwriteConfig?.permanentTeams && Array.isArray(appwriteConfig.permanentTeams) && appwriteConfig.permanentTeams.length > 0) ||
|
|
1550
1605
|
(appwriteConfig?.templateTeams && Array.isArray(appwriteConfig.templateTeams) && appwriteConfig.templateTeams.length > 0);
|
|
1551
1606
|
|
|
1607
|
+
if (stale()) return discarded;
|
|
1552
1608
|
if (hasDefaultTeams && window.ManifestAppwriteAuthTeamsDefaults && this.getDefaultTeams) {
|
|
1553
1609
|
const defaultTeams = await this.getDefaultTeams();
|
|
1610
|
+
if (stale()) return discarded;
|
|
1554
1611
|
if (defaultTeams.length > 0) {
|
|
1555
1612
|
this.currentTeam = defaultTeams[0];
|
|
1556
1613
|
} else {
|
|
@@ -1565,12 +1622,14 @@ function initializeTeamsCore() {
|
|
|
1565
1622
|
if (this.currentTeam && this.currentTeam.$id && window.ManifestAppwriteAuthTeamsMembers && this.listMemberships) {
|
|
1566
1623
|
try {
|
|
1567
1624
|
const membershipsResult = await this.listMemberships(this.currentTeam.$id);
|
|
1625
|
+
if (stale()) return discarded;
|
|
1568
1626
|
if (membershipsResult.success) {
|
|
1569
1627
|
this.currentTeamMemberships = membershipsResult.memberships || [];
|
|
1570
1628
|
}
|
|
1571
1629
|
} catch (error) {
|
|
1572
1630
|
// Silently handle errors (e.g., team was deleted)
|
|
1573
1631
|
// listMemberships already handles "team not found" gracefully
|
|
1632
|
+
if (stale()) return discarded;
|
|
1574
1633
|
this.currentTeamMemberships = [];
|
|
1575
1634
|
}
|
|
1576
1635
|
}
|
|
@@ -1580,6 +1639,7 @@ function initializeTeamsCore() {
|
|
|
1580
1639
|
// Check for any truthy value (function, object, or true flag)
|
|
1581
1640
|
if (!this._teamsRealtimeUnsubscribe && !this._teamsRealtimeSubscribing) {
|
|
1582
1641
|
const appwriteConfig = await config.getAppwriteConfig();
|
|
1642
|
+
if (stale()) return discarded;
|
|
1583
1643
|
if (this._appwrite?.realtime && this.startTeamsRealtime) {
|
|
1584
1644
|
this.startTeamsRealtime();
|
|
1585
1645
|
} else if (appwriteConfig?.teamsPollInterval && typeof appwriteConfig.teamsPollInterval === 'number' && appwriteConfig.teamsPollInterval > 0) {
|
|
@@ -1593,6 +1653,7 @@ function initializeTeamsCore() {
|
|
|
1593
1653
|
|
|
1594
1654
|
return { success: true, teams: result.teams || [], total: result.total || 0 };
|
|
1595
1655
|
} catch (error) {
|
|
1656
|
+
if (stale()) return discarded;
|
|
1596
1657
|
this.error = error.message;
|
|
1597
1658
|
return { success: false, error: error.message };
|
|
1598
1659
|
}
|
|
@@ -6474,26 +6535,33 @@ function initializeEmailOTP() {
|
|
|
6474
6535
|
|
|
6475
6536
|
this.inProgress = true;
|
|
6476
6537
|
this.error = null;
|
|
6538
|
+
let guestEnded = false;
|
|
6477
6539
|
|
|
6478
6540
|
try {
|
|
6479
6541
|
const appwriteConfig = await config.getAppwriteConfig();
|
|
6480
6542
|
const wasGuest = this.isAnonymous;
|
|
6481
6543
|
|
|
6482
|
-
// Guest team carryover: issue the migration ticket
|
|
6483
|
-
//
|
|
6484
|
-
|
|
6544
|
+
// Guest team carryover: issue the migration ticket while the guest session
|
|
6545
|
+
// still authenticates. Kept on the store, bound to this code's account, so a
|
|
6546
|
+
// retry after a wrong code redeems it and no other sign-in can.
|
|
6485
6547
|
if (wasGuest && appwriteConfig?.guestMigrationFunctionId && this._callGuestMigration) {
|
|
6486
6548
|
const prep = await this._callGuestMigration('/prepare', {});
|
|
6487
|
-
if (prep?.ok && prep.ticket)
|
|
6549
|
+
if (prep?.ok && prep.ticket) {
|
|
6550
|
+
this._otpMigrationTicket = { ticket: prep.ticket, userId: this._otpUserId };
|
|
6551
|
+
}
|
|
6488
6552
|
}
|
|
6553
|
+
const kept = this._otpMigrationTicket;
|
|
6554
|
+
const migrationTicket = kept?.userId === this._otpUserId ? kept.ticket : null;
|
|
6489
6555
|
|
|
6490
|
-
// Appwrite
|
|
6491
|
-
//
|
|
6556
|
+
// Appwrite refuses createSession while any session is active, before it
|
|
6557
|
+
// checks the code, so the guest session must go first.
|
|
6492
6558
|
if (this.session && this.isAnonymous) {
|
|
6493
6559
|
try {
|
|
6494
6560
|
await this._appwrite.account.deleteSession(this.session.$id);
|
|
6561
|
+
guestEnded = true;
|
|
6495
6562
|
} catch (deleteError) {
|
|
6496
|
-
|
|
6563
|
+
const status = deleteError?.code || deleteError?.statusCode;
|
|
6564
|
+
guestEnded = status === 401 || status === 404;
|
|
6497
6565
|
}
|
|
6498
6566
|
}
|
|
6499
6567
|
|
|
@@ -6516,6 +6584,7 @@ function initializeEmailOTP() {
|
|
|
6516
6584
|
|
|
6517
6585
|
// Redeem the migration ticket as the new account to carry teams over.
|
|
6518
6586
|
// Best-effort; failure never blocks the already-successful sign-in.
|
|
6587
|
+
this._otpMigrationTicket = null;
|
|
6519
6588
|
if (migrationTicket && this._callGuestMigration) {
|
|
6520
6589
|
await this._callGuestMigration('/commit', { ticket: migrationTicket });
|
|
6521
6590
|
}
|
|
@@ -6541,7 +6610,7 @@ function initializeEmailOTP() {
|
|
|
6541
6610
|
} catch (error) {
|
|
6542
6611
|
const errorMessage = error.message || '';
|
|
6543
6612
|
const errorCode = error.code || error.statusCode || '';
|
|
6544
|
-
const isExpiredOrInvalid = errorMessage && (
|
|
6613
|
+
const isExpiredOrInvalid = error.type !== 'user_session_already_exists' && errorMessage && (
|
|
6545
6614
|
errorMessage.includes('expired') ||
|
|
6546
6615
|
errorMessage.includes('Invalid token') ||
|
|
6547
6616
|
errorMessage.includes('invalid') ||
|
|
@@ -6551,14 +6620,30 @@ function initializeEmailOTP() {
|
|
|
6551
6620
|
|
|
6552
6621
|
this.otpExpired = !!isExpiredOrInvalid;
|
|
6553
6622
|
this.error = isExpiredOrInvalid ? null : error.message;
|
|
6554
|
-
|
|
6555
|
-
|
|
6623
|
+
|
|
6624
|
+
// Guest session is gone though sign-in failed: drop its identity and teams.
|
|
6625
|
+
// otpSent, the code's userId and the migration ticket stay for a retry.
|
|
6626
|
+
if (guestEnded) {
|
|
6627
|
+
this.isAuthenticated = false;
|
|
6628
|
+
this.isAnonymous = false;
|
|
6629
|
+
this.user = null;
|
|
6630
|
+
this.session = null;
|
|
6631
|
+
if (this._resetTeamsState) {
|
|
6632
|
+
this._resetTeamsState();
|
|
6633
|
+
}
|
|
6634
|
+
}
|
|
6556
6635
|
|
|
6557
6636
|
if (this._syncStateToStorage) {
|
|
6558
6637
|
this._syncStateToStorage(this);
|
|
6559
6638
|
}
|
|
6560
6639
|
|
|
6561
|
-
|
|
6640
|
+
if (guestEnded) {
|
|
6641
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared', {
|
|
6642
|
+
detail: { reason: 'otp-failed' }
|
|
6643
|
+
}));
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
return { success: false, error: error.message, guestEnded };
|
|
6562
6647
|
} finally {
|
|
6563
6648
|
this.inProgress = false;
|
|
6564
6649
|
}
|
package/lib/manifest.d.ts
CHANGED
|
@@ -434,7 +434,7 @@ export interface ManifestTextEdit {
|
|
|
434
434
|
export type ManifestDirective =
|
|
435
435
|
| 'x-route' | 'x-anchors'
|
|
436
436
|
| 'x-icon' | 'x-svg' | 'x-markdown' | 'x-code' | 'x-code-group'
|
|
437
|
-
| 'x-toast' | 'x-tooltip' | 'x-carousel' | 'x-virtual' | 'x-
|
|
437
|
+
| 'x-toast' | 'x-tooltip' | 'x-carousel' | 'x-virtual' | 'x-computed' | 'x-defer'
|
|
438
438
|
| 'x-chart' | 'x-date' | 'x-color' | 'x-colorpicker' | 'x-combobox'
|
|
439
439
|
| 'x-export' | 'x-import' | 'x-pay' | 'x-files' | 'x-data-files'
|
|
440
440
|
| 'x-edit' | 'x-text-edit';
|