mnfst 0.5.135 → 0.5.137
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 +537 -47
- package/lib/manifest.chart.css +49 -0
- package/lib/manifest.charts.js +155 -3
- package/lib/manifest.css +272 -1
- package/lib/manifest.data.js +23 -3
- package/lib/manifest.dropdown.css +1 -1
- package/lib/manifest.integrity.json +8 -8
- package/lib/manifest.js +1 -1
- package/lib/manifest.localization.js +17 -0
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.payments.js +11 -1
- package/lib/manifest.schema.json +11 -2
- package/lib/manifest.tailwind.js +87 -110
- package/lib/manifest.utilities.js +36 -0
- package/package.json +9 -9
|
@@ -69,12 +69,14 @@ async function getAppwriteConfig() {
|
|
|
69
69
|
// Get auth methods from config (defaults to ["magic", "oauth"] if not specified)
|
|
70
70
|
const authMethods = appwriteConfig.auth?.methods || ["magic", "oauth"];
|
|
71
71
|
|
|
72
|
-
// Guest session support: "guest-auto" = automatic, "guest-manual" = manual only
|
|
73
|
-
|
|
72
|
+
// Guest session support: "guest"/"guest-auto" = automatic, "guest-manual" = manual only.
|
|
73
|
+
// ("guest" is the schema's documented spelling; "guest-auto" is accepted as a synonym.)
|
|
74
|
+
const guestAuto = authMethods.includes("guest") || authMethods.includes("guest-auto");
|
|
74
75
|
const guestManual = authMethods.includes("guest-manual");
|
|
75
76
|
const hasGuest = guestAuto || guestManual;
|
|
76
77
|
|
|
77
78
|
const magicEnabled = authMethods.includes("magic");
|
|
79
|
+
const otpEnabled = authMethods.includes("otp");
|
|
78
80
|
const oauthEnabled = authMethods.includes("oauth");
|
|
79
81
|
|
|
80
82
|
// Teams support: presence of teams object enables it
|
|
@@ -82,6 +84,15 @@ async function getAppwriteConfig() {
|
|
|
82
84
|
const permanentTeams = appwriteConfig.auth?.teams?.permanent || null; // Array of team names (immutable)
|
|
83
85
|
const templateTeams = appwriteConfig.auth?.teams?.template || null; // Array of team names (can be deleted and reapplied)
|
|
84
86
|
const teamsPollInterval = appwriteConfig.auth?.teams?.pollInterval || null; // Polling interval in milliseconds (null = disabled)
|
|
87
|
+
const guestTeams = !!appwriteConfig.auth?.teams?.guests; // Seed default teams for guest (anonymous) sessions
|
|
88
|
+
|
|
89
|
+
// Guest upgrade: preserve the anonymous account (and its teams) when a guest signs in,
|
|
90
|
+
// rather than discarding it and minting a fresh user. Supported by magic links and OAuth;
|
|
91
|
+
// email OTP cannot convert anonymous accounts (Appwrite limitation). Defaults to guestTeams,
|
|
92
|
+
// since orphaning guest-created teams on signup is the failure mode guest teams introduce.
|
|
93
|
+
const guestUpgrade = appwriteConfig.auth?.guestUpgrade !== undefined
|
|
94
|
+
? !!appwriteConfig.auth.guestUpgrade
|
|
95
|
+
: guestTeams;
|
|
85
96
|
|
|
86
97
|
// Default roles: permanent (cannot be deleted) and template (can be deleted)
|
|
87
98
|
// These are objects mapping role names to permissions: { "Admin": ["inviteMembers", ...] }
|
|
@@ -97,6 +108,11 @@ async function getAppwriteConfig() {
|
|
|
97
108
|
// Creator role: string reference to a role in memberRoles (role creator gets by default)
|
|
98
109
|
const creatorRole = appwriteConfig.auth?.creatorRole || null;
|
|
99
110
|
|
|
111
|
+
// Guest migration: id of the deployed guest-migration Appwrite Function. When set,
|
|
112
|
+
// a guest's teams are carried over to the account they sign into via OTP (which
|
|
113
|
+
// Appwrite can't convert in place). See templates/guest-migration-function.
|
|
114
|
+
const guestMigrationFunctionId = appwriteConfig.auth?.guestMigration?.functionId || null;
|
|
115
|
+
|
|
100
116
|
return {
|
|
101
117
|
endpoint,
|
|
102
118
|
projectId,
|
|
@@ -107,11 +123,15 @@ async function getAppwriteConfig() {
|
|
|
107
123
|
guestManual: guestManual,
|
|
108
124
|
anonymous: guestAuto, // For backwards compatibility with existing code
|
|
109
125
|
magic: magicEnabled,
|
|
126
|
+
otp: otpEnabled, // Email one-time-passcode authentication
|
|
110
127
|
oauth: oauthEnabled,
|
|
111
128
|
teams: teamsEnabled,
|
|
112
129
|
permanentTeams: permanentTeams, // Array of team names (cannot be deleted)
|
|
113
130
|
templateTeams: templateTeams, // Array of team names (can be deleted and reapplied)
|
|
114
131
|
teamsPollInterval: teamsPollInterval, // Polling interval in milliseconds (null = disabled)
|
|
132
|
+
guestTeams: guestTeams, // Seed default teams for guest (anonymous) sessions
|
|
133
|
+
guestUpgrade: guestUpgrade, // Preserve guest account + teams on sign-in (magic/oauth)
|
|
134
|
+
guestMigrationFunctionId: guestMigrationFunctionId, // Carry guest teams to the OTP account via this function
|
|
115
135
|
memberRoles: memberRoles, // Role definitions: { "RoleName": ["permission1", "permission2"] }
|
|
116
136
|
permanentRoles: permanentRoles, // Object: { "RoleName": ["permission1", ...] } (cannot be deleted)
|
|
117
137
|
templateRoles: templateRoles, // Object: { "RoleName": ["permission1", ...] } (can be deleted)
|
|
@@ -161,6 +181,7 @@ async function getAppwriteClient() {
|
|
|
161
181
|
account: appwriteAccount,
|
|
162
182
|
teams: appwriteTeams,
|
|
163
183
|
users: appwriteUsers, // Add users service for fetching user details
|
|
184
|
+
functions: window.Appwrite?.Functions ? new window.Appwrite.Functions(appwriteClient) : null, // For guest-migration function calls
|
|
164
185
|
realtime: window.Appwrite?.Realtime ? new window.Appwrite.Realtime(appwriteClient) : null // Realtime service for subscriptions
|
|
165
186
|
};
|
|
166
187
|
}
|
|
@@ -223,6 +244,8 @@ function initializeAuthStore() {
|
|
|
223
244
|
store.session = state.session;
|
|
224
245
|
store.magicLinkSent = state.magicLinkSent || false;
|
|
225
246
|
store.magicLinkExpired = state.magicLinkExpired || false;
|
|
247
|
+
store.otpSent = state.otpSent || false;
|
|
248
|
+
store.otpExpired = state.otpExpired || false;
|
|
226
249
|
store.error = state.error;
|
|
227
250
|
}
|
|
228
251
|
} catch (error) {
|
|
@@ -241,6 +264,8 @@ function initializeAuthStore() {
|
|
|
241
264
|
session: sanitizeSessionForStorage(store.session),
|
|
242
265
|
magicLinkSent: store.magicLinkSent,
|
|
243
266
|
magicLinkExpired: store.magicLinkExpired,
|
|
267
|
+
otpSent: store.otpSent,
|
|
268
|
+
otpExpired: store.otpExpired,
|
|
244
269
|
error: store.error
|
|
245
270
|
};
|
|
246
271
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
@@ -258,6 +283,10 @@ function initializeAuthStore() {
|
|
|
258
283
|
error: null,
|
|
259
284
|
magicLinkSent: false,
|
|
260
285
|
magicLinkExpired: false,
|
|
286
|
+
otpSent: false, // Email OTP: a code has been emailed and is awaiting entry
|
|
287
|
+
otpExpired: false, // Email OTP: the entered code was wrong/expired
|
|
288
|
+
otpPhrase: null, // Email OTP: security phrase to display (when enabled)
|
|
289
|
+
_otpUserId: null, // Email OTP: userId returned by createEmailToken, used by verifyOTP
|
|
261
290
|
teams: [], // List of user's teams
|
|
262
291
|
currentTeam: null, // Currently selected/active team
|
|
263
292
|
_teamsPollInterval: null, // Interval ID for teams polling (deprecated, use realtime instead)
|
|
@@ -390,15 +419,20 @@ function initializeAuthStore() {
|
|
|
390
419
|
return null;
|
|
391
420
|
},
|
|
392
421
|
|
|
393
|
-
// Get authentication method (
|
|
422
|
+
// Get authentication method (anonymous, magic, otp, phone, oauth)
|
|
394
423
|
getMethod() {
|
|
395
424
|
if (!this.session) return null;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
425
|
+
// Appwrite session.provider: anonymous | magic-url | email (OTP) |
|
|
426
|
+
// token (OTP, some versions) | phone | oauth2 (or a specific provider).
|
|
427
|
+
switch (this.session.provider) {
|
|
428
|
+
case 'anonymous': return 'anonymous';
|
|
429
|
+
case 'magic-url': return 'magic';
|
|
430
|
+
case 'email': return 'otp'; // this plugin uses email tokens for OTP
|
|
431
|
+
case 'token': return 'otp';
|
|
432
|
+
case 'phone': return 'phone';
|
|
433
|
+
case 'oauth2': return 'oauth';
|
|
434
|
+
default: return this.session.provider ? 'oauth' : null;
|
|
435
|
+
}
|
|
402
436
|
},
|
|
403
437
|
|
|
404
438
|
// Get OAuth provider name (google, github, etc.) or null for non-OAuth methods
|
|
@@ -411,8 +445,10 @@ function initializeAuthStore() {
|
|
|
411
445
|
const sessionProvider = this.session.provider;
|
|
412
446
|
|
|
413
447
|
// For OAuth, return the stored provider name (google, github, etc.)
|
|
414
|
-
// session.provider returns "oauth2" generically, so we use _oauthProvider
|
|
415
|
-
|
|
448
|
+
// session.provider returns "oauth2" generically, so we use _oauthProvider.
|
|
449
|
+
// Only OAuth sessions have a provider name — magic/otp/phone/anonymous don't,
|
|
450
|
+
// so gate on getMethod() to avoid a pointless identities lookup for those.
|
|
451
|
+
if (this.getMethod() === 'oauth') {
|
|
416
452
|
// Try to get from store first, then localStorage, then sessionStorage
|
|
417
453
|
let provider = this._oauthProvider;
|
|
418
454
|
if (!provider) {
|
|
@@ -548,14 +584,12 @@ function initializeAuthStore() {
|
|
|
548
584
|
this.isAnonymous = false;
|
|
549
585
|
}
|
|
550
586
|
|
|
551
|
-
// Load teams if enabled and user is authenticated
|
|
552
|
-
|
|
587
|
+
// Load teams if enabled and user is authenticated. Guests (anonymous)
|
|
588
|
+
// only seed default teams when auth.teams.guests is enabled.
|
|
589
|
+
if (this.isAuthenticated && appwriteConfig?.teams && this.listTeams
|
|
590
|
+
&& (!this.isAnonymous || appwriteConfig.guestTeams)) {
|
|
553
591
|
try {
|
|
554
|
-
await this.
|
|
555
|
-
// Auto-create default teams if enabled
|
|
556
|
-
if ((appwriteConfig.permanentTeams || appwriteConfig.templateTeams) && window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams) {
|
|
557
|
-
await window.ManifestAppwriteAuthTeamsDefaults.ensureDefaultTeams(this);
|
|
558
|
-
}
|
|
592
|
+
await this._loadTeamsAndSeed(appwriteConfig);
|
|
559
593
|
} catch (teamsError) {
|
|
560
594
|
// Don't fail initialization if teams fail to load
|
|
561
595
|
}
|
|
@@ -589,6 +623,58 @@ function initializeAuthStore() {
|
|
|
589
623
|
}
|
|
590
624
|
},
|
|
591
625
|
|
|
626
|
+
// Clear all team-related state. Used when the active identity changes to a
|
|
627
|
+
// DIFFERENT user — e.g. a guest replaced by a fresh account on OTP sign-in
|
|
628
|
+
// (Appwrite can't convert anonymous → OTP). Without this, the previous user's
|
|
629
|
+
// currentTeam/teams leak into the new session and listTeams queries teams the
|
|
630
|
+
// new user can't access, producing 404s on prefs/memberships.
|
|
631
|
+
_resetTeamsState() {
|
|
632
|
+
this.teams = [];
|
|
633
|
+
this.currentTeam = null;
|
|
634
|
+
this.currentTeamMemberships = [];
|
|
635
|
+
this.deletedTemplateTeams = [];
|
|
636
|
+
this.deletedTemplateRoles = [];
|
|
637
|
+
this._teamImmutableCache = {};
|
|
638
|
+
if (this.stopTeamsRealtime) {
|
|
639
|
+
try { this.stopTeamsRealtime(); } catch (e) { /* ignore */ }
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
// Call the deployed guest-migration function (templates/guest-migration-function).
|
|
644
|
+
// The current Appwrite session authenticates the call — Appwrite forwards the
|
|
645
|
+
// user id to the function. Returns the parsed JSON response, or null on any
|
|
646
|
+
// failure (migration is best-effort: a failure must never block sign-in).
|
|
647
|
+
async _callGuestMigration(path, body) {
|
|
648
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
649
|
+
const fnId = appwriteConfig?.guestMigrationFunctionId;
|
|
650
|
+
if (!fnId || !this._appwrite?.functions) {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
const exec = await this._appwrite.functions.createExecution(
|
|
655
|
+
fnId, JSON.stringify(body || {}), false, path, 'POST'
|
|
656
|
+
);
|
|
657
|
+
const raw = exec?.responseBody ?? exec?.response ?? '';
|
|
658
|
+
try { return JSON.parse(raw); } catch (e) { return null; }
|
|
659
|
+
} catch (e) {
|
|
660
|
+
console.warn(`[Manifest Appwrite Auth] Guest migration ${path} failed:`, e.message);
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
|
|
665
|
+
// Load the user's teams and seed any configured default (permanent/template)
|
|
666
|
+
// teams. Shared by the guest, magic-link, OAuth, and init/restore paths.
|
|
667
|
+
async _loadTeamsAndSeed(appwriteConfig) {
|
|
668
|
+
const cfg = appwriteConfig || await config.getAppwriteConfig();
|
|
669
|
+
if (!cfg?.teams || !this.listTeams) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
await this.listTeams();
|
|
673
|
+
if ((cfg.permanentTeams || cfg.templateTeams) && window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams) {
|
|
674
|
+
await window.ManifestAppwriteAuthTeamsDefaults.ensureDefaultTeams(this);
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
|
|
592
678
|
// Manually create guest session (only works if guest-manual is enabled)
|
|
593
679
|
async createGuest() {
|
|
594
680
|
if (!this._guestManual) {
|
|
@@ -632,9 +718,16 @@ function initializeAuthStore() {
|
|
|
632
718
|
// Ignore
|
|
633
719
|
}
|
|
634
720
|
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
|
|
721
|
+
// Guests are full Appwrite sessions, so they can own teams. When
|
|
722
|
+
// auth.teams.guests is enabled, seed their default teams just like a
|
|
723
|
+
// signed-in user; otherwise keep the historical behavior (no teams).
|
|
724
|
+
const cfg = await config.getAppwriteConfig();
|
|
725
|
+
if (cfg?.guestTeams) {
|
|
726
|
+
await this._loadTeamsAndSeed(cfg);
|
|
727
|
+
} else {
|
|
728
|
+
this.teams = [];
|
|
729
|
+
this.currentTeam = null;
|
|
730
|
+
}
|
|
638
731
|
|
|
639
732
|
syncStateToStorage(this);
|
|
640
733
|
window.dispatchEvent(new CustomEvent('manifest:auth:anonymous', {
|
|
@@ -698,6 +791,12 @@ function initializeAuthStore() {
|
|
|
698
791
|
this.magicLinkSent = false;
|
|
699
792
|
this.magicLinkExpired = false;
|
|
700
793
|
|
|
794
|
+
// Clear email OTP flags
|
|
795
|
+
this.otpSent = false;
|
|
796
|
+
this.otpExpired = false;
|
|
797
|
+
this.otpPhrase = null;
|
|
798
|
+
this._otpUserId = null;
|
|
799
|
+
|
|
701
800
|
// Stop teams realtime subscription if active
|
|
702
801
|
if (this.stopTeamsRealtime) {
|
|
703
802
|
this.stopTeamsRealtime();
|
|
@@ -4031,6 +4130,11 @@ window.ManifestAppwriteAuthTeamsUserRoles = {
|
|
|
4031
4130
|
|
|
4032
4131
|
/* Auth teams - Membership operations */
|
|
4033
4132
|
|
|
4133
|
+
// The Users service is server-only (node-appwrite); it is never present on the
|
|
4134
|
+
// browser SDK, so member-email enrichment degrades gracefully. Warn once rather
|
|
4135
|
+
// than on every membership lookup to avoid flooding the console.
|
|
4136
|
+
let _usersServiceWarned = false;
|
|
4137
|
+
|
|
4034
4138
|
// Add membership methods to auth store
|
|
4035
4139
|
function initializeTeamsMembers() {
|
|
4036
4140
|
if (typeof Alpine === 'undefined') {
|
|
@@ -4176,7 +4280,10 @@ function initializeTeamsMembers() {
|
|
|
4176
4280
|
try {
|
|
4177
4281
|
// Check if users service is available
|
|
4178
4282
|
if (!this._appwrite || !this._appwrite.users || typeof this._appwrite.users.get !== 'function') {
|
|
4179
|
-
|
|
4283
|
+
if (!_usersServiceWarned) {
|
|
4284
|
+
_usersServiceWarned = true;
|
|
4285
|
+
console.warn('[Manifest Appwrite Auth] Users service unavailable on the browser SDK — member emails will not be enriched. (This is expected; logged once.)');
|
|
4286
|
+
}
|
|
4180
4287
|
} else {
|
|
4181
4288
|
const user = await this._appwrite.users.get({ userId: membership.userId });
|
|
4182
4289
|
if (user && user.email) {
|
|
@@ -4211,7 +4318,10 @@ function initializeTeamsMembers() {
|
|
|
4211
4318
|
try {
|
|
4212
4319
|
// Check if users service is available
|
|
4213
4320
|
if (!this._appwrite || !this._appwrite.users || typeof this._appwrite.users.get !== 'function') {
|
|
4214
|
-
|
|
4321
|
+
if (!_usersServiceWarned) {
|
|
4322
|
+
_usersServiceWarned = true;
|
|
4323
|
+
console.warn('[Manifest Appwrite Auth] Users service unavailable on the browser SDK — member emails will not be enriched. (This is expected; logged once.)');
|
|
4324
|
+
}
|
|
4215
4325
|
} else {
|
|
4216
4326
|
const user = await this._appwrite.users.get({ userId: membership.userId });
|
|
4217
4327
|
if (user && user.email) {
|
|
@@ -5503,6 +5613,16 @@ function initializeAnonymous() {
|
|
|
5503
5613
|
this.isAuthenticated = true;
|
|
5504
5614
|
this.isAnonymous = true;
|
|
5505
5615
|
|
|
5616
|
+
// Seed default teams for the guest when auth.teams.guests is enabled
|
|
5617
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
5618
|
+
if (appwriteConfig?.guestTeams && this._loadTeamsAndSeed) {
|
|
5619
|
+
try {
|
|
5620
|
+
await this._loadTeamsAndSeed(appwriteConfig);
|
|
5621
|
+
} catch (teamsError) {
|
|
5622
|
+
// Don't fail guest creation if teams fail to load
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5625
|
+
|
|
5506
5626
|
// Sync state to localStorage for cross-tab synchronization
|
|
5507
5627
|
if (this._syncStateToStorage) {
|
|
5508
5628
|
this._syncStateToStorage(this);
|
|
@@ -5601,9 +5721,17 @@ function initializeMagicLinks() {
|
|
|
5601
5721
|
|
|
5602
5722
|
const account = this._appwrite.account;
|
|
5603
5723
|
|
|
5724
|
+
// Guest upgrade: when enabled and we're currently a guest, pass the
|
|
5725
|
+
// anonymous user's own id so Appwrite links the email to that same
|
|
5726
|
+
// account (preserving its teams) rather than minting a fresh user.
|
|
5727
|
+
// Otherwise generate a unique id for a brand-new account.
|
|
5728
|
+
const magicUserId = (appwriteConfig?.guestUpgrade && this.isAnonymous && this.user?.$id)
|
|
5729
|
+
? this.user.$id
|
|
5730
|
+
: ((window.Appwrite?.ID?.unique) ? window.Appwrite.ID.unique() : 'unique()');
|
|
5731
|
+
|
|
5604
5732
|
// Try createMagicURLSession first (standard method)
|
|
5605
5733
|
if (typeof account.createMagicURLSession === 'function') {
|
|
5606
|
-
const token = await account.createMagicURLSession(
|
|
5734
|
+
const token = await account.createMagicURLSession(magicUserId, email, cleanRedirectUrl);
|
|
5607
5735
|
this.magicLinkSent = true;
|
|
5608
5736
|
this.magicLinkExpired = false;
|
|
5609
5737
|
this.error = null;
|
|
@@ -5615,7 +5743,7 @@ function initializeMagicLinks() {
|
|
|
5615
5743
|
|
|
5616
5744
|
// Fallback: try createMagicURLToken (alternative method name)
|
|
5617
5745
|
if (typeof account.createMagicURLToken === 'function') {
|
|
5618
|
-
const token = await account.createMagicURLToken(
|
|
5746
|
+
const token = await account.createMagicURLToken(magicUserId, email, redirectUrl);
|
|
5619
5747
|
this.magicLinkSent = true;
|
|
5620
5748
|
this.magicLinkExpired = false;
|
|
5621
5749
|
this.error = null;
|
|
@@ -5758,8 +5886,18 @@ function initializeMagicLinks() {
|
|
|
5758
5886
|
this.magicLinkSent = false;
|
|
5759
5887
|
|
|
5760
5888
|
try {
|
|
5761
|
-
|
|
5762
|
-
|
|
5889
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
5890
|
+
const upgradingGuest = !!(appwriteConfig?.guestUpgrade && this.isAnonymous);
|
|
5891
|
+
// A guest being replaced (not upgraded) by a different account — its
|
|
5892
|
+
// team state must be cleared before loading the new user's teams.
|
|
5893
|
+
const replacingGuest = this.isAnonymous && !upgradingGuest;
|
|
5894
|
+
|
|
5895
|
+
// Delete the existing anonymous session first — UNLESS we're upgrading
|
|
5896
|
+
// the guest in place. For an upgrade the magic token was created against
|
|
5897
|
+
// the anonymous user's own id, so createSession converts that same
|
|
5898
|
+
// account (keeping its teams); deleting it first would orphan the teams
|
|
5899
|
+
// and force a brand-new user.
|
|
5900
|
+
if (this.session && this.isAnonymous && !upgradingGuest) {
|
|
5763
5901
|
try {
|
|
5764
5902
|
await this._appwrite.account.deleteSession(this.session.$id);
|
|
5765
5903
|
} catch (deleteError) {
|
|
@@ -5767,8 +5905,20 @@ function initializeMagicLinks() {
|
|
|
5767
5905
|
}
|
|
5768
5906
|
}
|
|
5769
5907
|
|
|
5770
|
-
// Create session from magic link credentials
|
|
5771
|
-
|
|
5908
|
+
// Create session from magic link credentials. When upgrading a guest the
|
|
5909
|
+
// anonymous session may still be active; Appwrite can reject the duplicate
|
|
5910
|
+
// with a "prohibited" error, in which case the account is already upgraded
|
|
5911
|
+
// and we just reuse the current session.
|
|
5912
|
+
let session;
|
|
5913
|
+
try {
|
|
5914
|
+
session = await this._appwrite.account.createSession(userId, secret);
|
|
5915
|
+
} catch (createError) {
|
|
5916
|
+
if (upgradingGuest && createError.message?.includes('prohibited')) {
|
|
5917
|
+
session = await this._appwrite.account.getSession('current');
|
|
5918
|
+
} else {
|
|
5919
|
+
throw createError;
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5772
5922
|
this.session = session;
|
|
5773
5923
|
this.user = await this._appwrite.account.get();
|
|
5774
5924
|
this.isAuthenticated = true;
|
|
@@ -5784,20 +5934,21 @@ function initializeMagicLinks() {
|
|
|
5784
5934
|
// Ignore
|
|
5785
5935
|
}
|
|
5786
5936
|
|
|
5937
|
+
// Replacing a guest with a different account: drop the guest's stale
|
|
5938
|
+
// team state so listTeams doesn't query teams the new user can't access.
|
|
5939
|
+
if (replacingGuest && this._resetTeamsState) {
|
|
5940
|
+
this._resetTeamsState();
|
|
5941
|
+
}
|
|
5942
|
+
|
|
5787
5943
|
// Sync state
|
|
5788
5944
|
if (this._syncStateToStorage) {
|
|
5789
5945
|
this._syncStateToStorage(this);
|
|
5790
5946
|
}
|
|
5791
5947
|
|
|
5792
|
-
// Load teams if enabled
|
|
5793
|
-
const appwriteConfig = await config.getAppwriteConfig();
|
|
5948
|
+
// Load teams if enabled (and seed any configured default teams)
|
|
5794
5949
|
if (appwriteConfig?.teams && this.listTeams) {
|
|
5795
5950
|
try {
|
|
5796
|
-
await this.
|
|
5797
|
-
// Auto-create default teams if enabled
|
|
5798
|
-
if ((appwriteConfig.permanentTeams || appwriteConfig.templateTeams) && window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams) {
|
|
5799
|
-
await window.ManifestAppwriteAuthTeamsDefaults.ensureDefaultTeams(this);
|
|
5800
|
-
}
|
|
5951
|
+
await this._loadTeamsAndSeed(appwriteConfig);
|
|
5801
5952
|
} catch (teamsError) {
|
|
5802
5953
|
console.warn('[Manifest Appwrite Auth] Failed to load teams after magic link login:', teamsError);
|
|
5803
5954
|
// Don't fail login if teams fail to load
|
|
@@ -5981,6 +6132,334 @@ window.ManifestAppwriteAuthMagicLinks = {
|
|
|
5981
6132
|
handleCallbacks: handleMagicLinkCallbacks
|
|
5982
6133
|
};
|
|
5983
6134
|
|
|
6135
|
+
/* Auth email OTP (one-time passcode) */
|
|
6136
|
+
|
|
6137
|
+
// Email OTP is a two-step, in-page flow (no redirect, unlike magic links):
|
|
6138
|
+
// 1. createEmailOTP(email) -> Appwrite emails a 6-digit code, returns a userId
|
|
6139
|
+
// 2. verifyOTP(code) -> createSession(userId, code) completes login
|
|
6140
|
+
// Because there is no URL round-trip, this module never touches users.callbacks.js.
|
|
6141
|
+
//
|
|
6142
|
+
// NOTE: Appwrite does NOT support converting an anonymous (guest) session via email
|
|
6143
|
+
// OTP — only magic links, phone, email/password, and OAuth can do that. So when a
|
|
6144
|
+
// guest verifies an OTP we mint a fresh account (the anonymous session is deleted),
|
|
6145
|
+
// which discards any guest-created teams. Use magic links if you need guest upgrade.
|
|
6146
|
+
|
|
6147
|
+
function initializeEmailOTP() {
|
|
6148
|
+
if (typeof Alpine === 'undefined') {
|
|
6149
|
+
return;
|
|
6150
|
+
}
|
|
6151
|
+
|
|
6152
|
+
const config = window.ManifestAppwriteAuthConfig;
|
|
6153
|
+
if (!config) {
|
|
6154
|
+
return;
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
// Resolve an email string from the same range of inputs sendMagicLink accepts:
|
|
6158
|
+
// an input element, a selector, an Alpine { email } object, a bare string, or
|
|
6159
|
+
// nothing (auto-find the nearest email input). Returns { email, inputEl, dataObj }.
|
|
6160
|
+
function resolveEmailInput(emailInputOrRef) {
|
|
6161
|
+
let email = null;
|
|
6162
|
+
let inputEl = null;
|
|
6163
|
+
let dataObj = null;
|
|
6164
|
+
|
|
6165
|
+
if (emailInputOrRef === undefined || emailInputOrRef === null) {
|
|
6166
|
+
let eventTarget = (typeof window !== 'undefined' && window.event) ? window.event.target : null;
|
|
6167
|
+
if (eventTarget) {
|
|
6168
|
+
const form = eventTarget.closest('form');
|
|
6169
|
+
const scope = form || eventTarget.parentElement;
|
|
6170
|
+
if (scope) {
|
|
6171
|
+
inputEl = scope.querySelector('input[type="email"]');
|
|
6172
|
+
if (inputEl) email = inputEl.value;
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
if (!inputEl) {
|
|
6176
|
+
inputEl = document.querySelector('input[type="email"]');
|
|
6177
|
+
if (inputEl) email = inputEl.value;
|
|
6178
|
+
}
|
|
6179
|
+
} else if (typeof emailInputOrRef === 'string') {
|
|
6180
|
+
try {
|
|
6181
|
+
const element = document.querySelector(emailInputOrRef);
|
|
6182
|
+
if (element && element.tagName === 'INPUT' && element.type === 'email') {
|
|
6183
|
+
inputEl = element;
|
|
6184
|
+
email = element.value;
|
|
6185
|
+
} else {
|
|
6186
|
+
email = emailInputOrRef; // Treat as a direct email string
|
|
6187
|
+
}
|
|
6188
|
+
} catch (e) {
|
|
6189
|
+
email = emailInputOrRef; // Invalid selector -> treat as email string
|
|
6190
|
+
}
|
|
6191
|
+
} else if (emailInputOrRef && typeof emailInputOrRef === 'object') {
|
|
6192
|
+
if (emailInputOrRef.tagName === 'INPUT' || emailInputOrRef.matches?.('input[type="email"]')) {
|
|
6193
|
+
inputEl = emailInputOrRef;
|
|
6194
|
+
email = inputEl.value;
|
|
6195
|
+
} else if ('email' in emailInputOrRef) {
|
|
6196
|
+
email = emailInputOrRef.email;
|
|
6197
|
+
dataObj = emailInputOrRef;
|
|
6198
|
+
}
|
|
6199
|
+
}
|
|
6200
|
+
|
|
6201
|
+
return { email, inputEl, dataObj };
|
|
6202
|
+
}
|
|
6203
|
+
|
|
6204
|
+
const waitForStore = () => {
|
|
6205
|
+
const store = Alpine.store('auth');
|
|
6206
|
+
if (store && !store.createEmailOTP) {
|
|
6207
|
+
// Step 1: request a one-time passcode by email.
|
|
6208
|
+
// Pass { phrase: true } to enable Appwrite's security phrase (anti-phishing);
|
|
6209
|
+
// when enabled the phrase is stored on the store as `otpPhrase` for display.
|
|
6210
|
+
store.createEmailOTP = async function (email, options = {}) {
|
|
6211
|
+
if (!this._appwrite) {
|
|
6212
|
+
this._appwrite = await config.getAppwriteClient();
|
|
6213
|
+
}
|
|
6214
|
+
if (!this._appwrite) {
|
|
6215
|
+
return { success: false, error: 'Appwrite not configured' };
|
|
6216
|
+
}
|
|
6217
|
+
|
|
6218
|
+
// Don't allow OTP request if already signed in (non-anonymous)
|
|
6219
|
+
if (this.isAuthenticated && !this.isAnonymous) {
|
|
6220
|
+
return { success: false, error: 'Already signed in. Please logout first.' };
|
|
6221
|
+
}
|
|
6222
|
+
|
|
6223
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
6224
|
+
if (appwriteConfig && !appwriteConfig.otp) {
|
|
6225
|
+
return { success: false, error: 'Email OTP authentication is not enabled' };
|
|
6226
|
+
}
|
|
6227
|
+
|
|
6228
|
+
// Appwrite can't convert an anonymous account via OTP. Warn (once) so guest
|
|
6229
|
+
// teams aren't silently lost; the login still proceeds as a fresh account.
|
|
6230
|
+
if (this.isAnonymous && appwriteConfig?.guestUpgrade) {
|
|
6231
|
+
console.warn('[Manifest Appwrite Auth] Email OTP cannot upgrade a guest account (Appwrite limitation); the guest session and any guest-created teams will be replaced. Use magic links for guest upgrade.');
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
const account = this._appwrite.account;
|
|
6235
|
+
if (typeof account.createEmailToken !== 'function') {
|
|
6236
|
+
return {
|
|
6237
|
+
success: false,
|
|
6238
|
+
error: 'Email OTP method not available. Please ensure you are using a recent Appwrite SDK.'
|
|
6239
|
+
};
|
|
6240
|
+
}
|
|
6241
|
+
|
|
6242
|
+
this.inProgress = true;
|
|
6243
|
+
this.error = null;
|
|
6244
|
+
this.otpExpired = false;
|
|
6245
|
+
|
|
6246
|
+
try {
|
|
6247
|
+
const uniqueId = (window.Appwrite?.ID?.unique) ? window.Appwrite.ID.unique() : 'unique()';
|
|
6248
|
+
// Third arg toggles Appwrite's security phrase feature.
|
|
6249
|
+
const token = await account.createEmailToken(uniqueId, email, options.phrase === true);
|
|
6250
|
+
|
|
6251
|
+
// Stash the userId Appwrite assigned; verifyOTP needs it to complete login.
|
|
6252
|
+
this._otpUserId = token.userId;
|
|
6253
|
+
this.otpPhrase = token.phrase || null;
|
|
6254
|
+
this.otpSent = true;
|
|
6255
|
+
this.otpExpired = false;
|
|
6256
|
+
this.error = null;
|
|
6257
|
+
|
|
6258
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:otp-sent', {
|
|
6259
|
+
detail: { email, phrase: this.otpPhrase }
|
|
6260
|
+
}));
|
|
6261
|
+
|
|
6262
|
+
return { success: true, message: 'OTP sent to email', phrase: this.otpPhrase };
|
|
6263
|
+
} catch (error) {
|
|
6264
|
+
// Appwrite returns 501 Not Implemented when Email OTP isn't enabled
|
|
6265
|
+
// for the project. Surface an actionable message instead of the raw error.
|
|
6266
|
+
const code = error.code || error.statusCode;
|
|
6267
|
+
const notEnabled = code === 501 || /not implemented/i.test(error.message || '');
|
|
6268
|
+
this.error = notEnabled
|
|
6269
|
+
? 'Email OTP is not enabled for this Appwrite project. Enable it under Auth → Settings.'
|
|
6270
|
+
: error.message;
|
|
6271
|
+
this.otpSent = false;
|
|
6272
|
+
this.otpExpired = false;
|
|
6273
|
+
return { success: false, error: this.error };
|
|
6274
|
+
} finally {
|
|
6275
|
+
this.inProgress = false;
|
|
6276
|
+
}
|
|
6277
|
+
};
|
|
6278
|
+
|
|
6279
|
+
// Convenience: resolve the email from an input/selector/object/string and send.
|
|
6280
|
+
// Clears the email input on success (mirrors sendMagicLink).
|
|
6281
|
+
store.sendEmailOTP = async function (emailInputOrRef, options = {}) {
|
|
6282
|
+
const { email, inputEl, dataObj } = resolveEmailInput(emailInputOrRef);
|
|
6283
|
+
|
|
6284
|
+
if (!email || !email.trim()) {
|
|
6285
|
+
return { success: false, error: 'Email is required' };
|
|
6286
|
+
}
|
|
6287
|
+
|
|
6288
|
+
const result = await this.createEmailOTP(email.trim(), options);
|
|
6289
|
+
|
|
6290
|
+
if (result.success) {
|
|
6291
|
+
Promise.resolve().then(() => {
|
|
6292
|
+
if (inputEl) {
|
|
6293
|
+
inputEl.value = '';
|
|
6294
|
+
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
6295
|
+
} else if (dataObj) {
|
|
6296
|
+
dataObj.email = '';
|
|
6297
|
+
}
|
|
6298
|
+
});
|
|
6299
|
+
}
|
|
6300
|
+
|
|
6301
|
+
return result;
|
|
6302
|
+
};
|
|
6303
|
+
|
|
6304
|
+
// Step 2: verify the code and create the session.
|
|
6305
|
+
store.verifyOTP = async function (code) {
|
|
6306
|
+
if (!this._appwrite) {
|
|
6307
|
+
this._appwrite = await config.getAppwriteClient();
|
|
6308
|
+
}
|
|
6309
|
+
if (!this._appwrite) {
|
|
6310
|
+
return { success: false, error: 'Appwrite not configured' };
|
|
6311
|
+
}
|
|
6312
|
+
if (!this._otpUserId) {
|
|
6313
|
+
return { success: false, error: 'Request a code first' };
|
|
6314
|
+
}
|
|
6315
|
+
if (!code || !String(code).trim()) {
|
|
6316
|
+
return { success: false, error: 'Code is required' };
|
|
6317
|
+
}
|
|
6318
|
+
|
|
6319
|
+
this.inProgress = true;
|
|
6320
|
+
this.error = null;
|
|
6321
|
+
|
|
6322
|
+
try {
|
|
6323
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
6324
|
+
const wasGuest = this.isAnonymous;
|
|
6325
|
+
|
|
6326
|
+
// Guest team carryover: OTP can't convert the anonymous account, so we
|
|
6327
|
+
// migrate its teams to the new account instead. Issue the migration
|
|
6328
|
+
// ticket NOW, while the guest session is still authenticated — it's
|
|
6329
|
+
// redeemed after the new session exists. Best-effort; never blocks login.
|
|
6330
|
+
let migrationTicket = null;
|
|
6331
|
+
if (wasGuest && appwriteConfig?.guestMigrationFunctionId && this._callGuestMigration) {
|
|
6332
|
+
const prep = await this._callGuestMigration('/prepare', {});
|
|
6333
|
+
if (prep?.ok && prep.ticket) migrationTicket = prep.ticket;
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
// Appwrite can't convert anonymous accounts via OTP, so delete the
|
|
6337
|
+
// guest session first to avoid a "session prohibited" conflict.
|
|
6338
|
+
if (this.session && this.isAnonymous) {
|
|
6339
|
+
try {
|
|
6340
|
+
await this._appwrite.account.deleteSession(this.session.$id);
|
|
6341
|
+
} catch (deleteError) {
|
|
6342
|
+
// Could not delete anonymous session
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
|
|
6346
|
+
const session = await this._appwrite.account.createSession(this._otpUserId, String(code).trim());
|
|
6347
|
+
this.session = session;
|
|
6348
|
+
this.user = await this._appwrite.account.get();
|
|
6349
|
+
this.isAuthenticated = true;
|
|
6350
|
+
this.isAnonymous = false;
|
|
6351
|
+
this.otpSent = false;
|
|
6352
|
+
this.otpExpired = false;
|
|
6353
|
+
this.otpPhrase = null;
|
|
6354
|
+
this._otpUserId = null;
|
|
6355
|
+
this.error = null;
|
|
6356
|
+
|
|
6357
|
+
// OTP replaces any prior guest with a different account (no conversion),
|
|
6358
|
+
// so clear the guest's team state before loading the new user's teams —
|
|
6359
|
+
// otherwise the stale currentTeam triggers 404s in listTeams.
|
|
6360
|
+
if (this._resetTeamsState) {
|
|
6361
|
+
this._resetTeamsState();
|
|
6362
|
+
}
|
|
6363
|
+
|
|
6364
|
+
// Redeem the migration ticket as the new account: carries the guest's
|
|
6365
|
+
// teams over. Best-effort — a failure leaves the guest's teams for GC
|
|
6366
|
+
// but never blocks the (already successful) sign-in.
|
|
6367
|
+
if (migrationTicket && this._callGuestMigration) {
|
|
6368
|
+
await this._callGuestMigration('/commit', { ticket: migrationTicket });
|
|
6369
|
+
}
|
|
6370
|
+
|
|
6371
|
+
if (this._syncStateToStorage) {
|
|
6372
|
+
this._syncStateToStorage(this);
|
|
6373
|
+
}
|
|
6374
|
+
|
|
6375
|
+
// Load teams + seed any configured default teams for the new account
|
|
6376
|
+
if (appwriteConfig?.teams && this.listTeams) {
|
|
6377
|
+
try {
|
|
6378
|
+
await this._loadTeamsAndSeed(appwriteConfig);
|
|
6379
|
+
} catch (teamsError) {
|
|
6380
|
+
console.warn('[Manifest Appwrite Auth] Failed to load teams after OTP login:', teamsError);
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
|
|
6384
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:login', {
|
|
6385
|
+
detail: { user: this.user }
|
|
6386
|
+
}));
|
|
6387
|
+
|
|
6388
|
+
return { success: true, user: this.user };
|
|
6389
|
+
} catch (error) {
|
|
6390
|
+
const errorMessage = error.message || '';
|
|
6391
|
+
const errorCode = error.code || error.statusCode || '';
|
|
6392
|
+
const isExpiredOrInvalid = errorMessage && (
|
|
6393
|
+
errorMessage.includes('expired') ||
|
|
6394
|
+
errorMessage.includes('Invalid token') ||
|
|
6395
|
+
errorMessage.includes('invalid') ||
|
|
6396
|
+
errorMessage.includes('not found') ||
|
|
6397
|
+
errorCode === 401 || errorCode === 404
|
|
6398
|
+
);
|
|
6399
|
+
|
|
6400
|
+
this.otpExpired = !!isExpiredOrInvalid;
|
|
6401
|
+
this.error = isExpiredOrInvalid ? null : error.message;
|
|
6402
|
+
this.isAuthenticated = false;
|
|
6403
|
+
this.isAnonymous = false;
|
|
6404
|
+
|
|
6405
|
+
if (this._syncStateToStorage) {
|
|
6406
|
+
this._syncStateToStorage(this);
|
|
6407
|
+
}
|
|
6408
|
+
|
|
6409
|
+
return { success: false, error: error.message };
|
|
6410
|
+
} finally {
|
|
6411
|
+
this.inProgress = false;
|
|
6412
|
+
}
|
|
6413
|
+
};
|
|
6414
|
+
|
|
6415
|
+
// Convenience: resolve the code from an input/selector/object/string and verify.
|
|
6416
|
+
store.submitOTP = async function (codeInputOrRef) {
|
|
6417
|
+
let code = null;
|
|
6418
|
+
if (codeInputOrRef === undefined || codeInputOrRef === null) {
|
|
6419
|
+
const el = document.querySelector('input[name="otp"], input[autocomplete="one-time-code"], input[inputmode="numeric"]');
|
|
6420
|
+
if (el) code = el.value;
|
|
6421
|
+
} else if (typeof codeInputOrRef === 'string') {
|
|
6422
|
+
try {
|
|
6423
|
+
const el = document.querySelector(codeInputOrRef);
|
|
6424
|
+
code = (el && el.tagName === 'INPUT') ? el.value : codeInputOrRef;
|
|
6425
|
+
} catch (e) {
|
|
6426
|
+
code = codeInputOrRef;
|
|
6427
|
+
}
|
|
6428
|
+
} else if (codeInputOrRef && typeof codeInputOrRef === 'object') {
|
|
6429
|
+
if (codeInputOrRef.tagName === 'INPUT') {
|
|
6430
|
+
code = codeInputOrRef.value;
|
|
6431
|
+
} else if ('code' in codeInputOrRef) {
|
|
6432
|
+
code = codeInputOrRef.code;
|
|
6433
|
+
} else if ('otp' in codeInputOrRef) {
|
|
6434
|
+
code = codeInputOrRef.otp;
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
|
|
6438
|
+
return await this.verifyOTP(code);
|
|
6439
|
+
};
|
|
6440
|
+
} else if (!store) {
|
|
6441
|
+
setTimeout(waitForStore, 50);
|
|
6442
|
+
}
|
|
6443
|
+
};
|
|
6444
|
+
|
|
6445
|
+
setTimeout(waitForStore, 100);
|
|
6446
|
+
}
|
|
6447
|
+
|
|
6448
|
+
// Initialize when Alpine is ready
|
|
6449
|
+
document.addEventListener('alpine:init', () => {
|
|
6450
|
+
try {
|
|
6451
|
+
initializeEmailOTP();
|
|
6452
|
+
} catch (error) {
|
|
6453
|
+
// Failed to initialize email OTP
|
|
6454
|
+
}
|
|
6455
|
+
});
|
|
6456
|
+
|
|
6457
|
+
// Export email OTP interface
|
|
6458
|
+
window.ManifestAppwriteAuthEmailOTP = {
|
|
6459
|
+
initialize: initializeEmailOTP
|
|
6460
|
+
};
|
|
6461
|
+
|
|
6462
|
+
|
|
5984
6463
|
/* Auth OAuth */
|
|
5985
6464
|
|
|
5986
6465
|
// Add OAuth methods to auth store
|
|
@@ -6022,8 +6501,10 @@ function initializeOAuth() {
|
|
|
6022
6501
|
|
|
6023
6502
|
// Delete any existing anonymous sessions before OAuth
|
|
6024
6503
|
// This prevents conflicts where anonymous sessions might interfere with OAuth
|
|
6025
|
-
// Appwrite will create a new account for OAuth if needed
|
|
6026
|
-
|
|
6504
|
+
// Appwrite will create a new account for OAuth if needed.
|
|
6505
|
+
// EXCEPTION: when guest upgrade is enabled we keep the anonymous session
|
|
6506
|
+
// active so Appwrite can link the OAuth identity to it (preserving teams).
|
|
6507
|
+
if (this.isAnonymous && this.session && !appwriteConfig?.guestUpgrade) {
|
|
6027
6508
|
try {
|
|
6028
6509
|
await this._appwrite.account.deleteSession(this.session.$id);
|
|
6029
6510
|
this.session = null;
|
|
@@ -6175,8 +6656,16 @@ function handleOAuthCallbacks() {
|
|
|
6175
6656
|
store.magicLinkSent = false;
|
|
6176
6657
|
|
|
6177
6658
|
try {
|
|
6178
|
-
|
|
6179
|
-
|
|
6659
|
+
const appwriteConfig = await window.ManifestAppwriteAuthConfig.getAppwriteConfig();
|
|
6660
|
+
const upgradingGuest = !!(appwriteConfig?.guestUpgrade && store.isAnonymous);
|
|
6661
|
+
// A guest being replaced (not upgraded) by a different account — its team
|
|
6662
|
+
// state must be cleared before loading the new user's teams.
|
|
6663
|
+
const replacingGuest = store.isAnonymous && !upgradingGuest;
|
|
6664
|
+
|
|
6665
|
+
// Delete the existing anonymous session first — UNLESS we're upgrading the
|
|
6666
|
+
// guest in place, in which case Appwrite linked the OAuth identity to that
|
|
6667
|
+
// account and the "prohibited" branch below reuses the upgraded session.
|
|
6668
|
+
if (store.session && store.isAnonymous && !upgradingGuest) {
|
|
6180
6669
|
try {
|
|
6181
6670
|
await store._appwrite.account.deleteSession(store.session.$id);
|
|
6182
6671
|
} catch (deleteError) {
|
|
@@ -6223,20 +6712,21 @@ function handleOAuthCallbacks() {
|
|
|
6223
6712
|
}
|
|
6224
6713
|
}
|
|
6225
6714
|
|
|
6715
|
+
// Replacing a guest with a different account: drop the guest's stale team
|
|
6716
|
+
// state so listTeams doesn't query teams the new user can't access.
|
|
6717
|
+
if (replacingGuest && store._resetTeamsState) {
|
|
6718
|
+
store._resetTeamsState();
|
|
6719
|
+
}
|
|
6720
|
+
|
|
6226
6721
|
// Sync state
|
|
6227
6722
|
if (store._syncStateToStorage) {
|
|
6228
6723
|
store._syncStateToStorage(store);
|
|
6229
6724
|
}
|
|
6230
6725
|
|
|
6231
|
-
// Load teams if enabled
|
|
6232
|
-
const appwriteConfig = await window.ManifestAppwriteAuthConfig.getAppwriteConfig();
|
|
6726
|
+
// Load teams if enabled (and seed any configured default teams)
|
|
6233
6727
|
if (appwriteConfig?.teams && store.listTeams) {
|
|
6234
6728
|
try {
|
|
6235
|
-
await store.
|
|
6236
|
-
// Auto-create default teams if enabled
|
|
6237
|
-
if ((appwriteConfig.permanentTeams || appwriteConfig.templateTeams) && window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams) {
|
|
6238
|
-
await window.ManifestAppwriteAuthTeamsDefaults.ensureDefaultTeams(store);
|
|
6239
|
-
}
|
|
6729
|
+
await store._loadTeamsAndSeed(appwriteConfig);
|
|
6240
6730
|
} catch (teamsError) {
|
|
6241
6731
|
console.warn('[Manifest Appwrite Auth] Failed to load teams after OAuth login:', teamsError);
|
|
6242
6732
|
// Don't fail login if teams fail to load
|