mnfst 0.5.162 → 0.5.164
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 +107 -199
- package/lib/manifest.appwrite.data.js +1 -3
- package/lib/manifest.charts.js +88 -97
- package/lib/manifest.chat.js +515 -0
- package/lib/manifest.code.js +110 -340
- package/lib/manifest.colorpicker.js +252 -551
- package/lib/manifest.combobox.js +73 -91
- package/lib/manifest.components.js +34 -95
- package/lib/manifest.css +18 -144
- package/lib/manifest.data.js +189 -562
- package/lib/manifest.datepicker.js +43 -128
- package/lib/manifest.dropdowns.js +18 -39
- package/lib/manifest.export.js +29 -147
- package/lib/manifest.form.css +18 -12
- package/lib/manifest.icons.js +9 -24
- package/lib/manifest.integrity.json +23 -23
- package/lib/manifest.js +62 -126
- package/lib/manifest.localization.js +67 -200
- package/lib/manifest.markdown.js +65 -174
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.payments.js +40 -139
- package/lib/manifest.router.js +24 -73
- package/lib/manifest.status.js +20 -56
- package/lib/manifest.svg.js +12 -30
- package/lib/manifest.tooltips.js +28 -65
- package/lib/manifest.utilities.js +40 -107
- package/lib/manifest.virtual.js +15 -53
- package/package.json +1 -1
- package/lib/manifest.appwrite.presence.js +0 -1650
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
/* Manifest Appwrite Auth
|
|
1
|
+
/* Manifest Appwrite Auth — config resolution
|
|
2
2
|
/* By Andrew Matlock under MIT license
|
|
3
|
-
/* https://github.com/andrewmatlock/Manifest
|
|
4
|
-
/*
|
|
5
|
-
/* Supports authentication with an Appwrite project
|
|
6
|
-
/* Requires Alpine JS (alpinejs.dev) to operate
|
|
7
3
|
*/
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Refuse strings that still contain an unresolved ${VAR} reference. The loader
|
|
12
|
-
// runs window.ManifestDataConfig.interpolateManifest at manifest-load time, so
|
|
13
|
-
// by the time we read these fields the env-var substitution has already been
|
|
14
|
-
// applied. Anything still matching ${VAR} is an undefined env var — passing it
|
|
15
|
-
// to Appwrite would either silently fail or, worse, be sent verbatim as an
|
|
16
|
-
// HTTP header value, leaking the env var name. Loud-fail instead.
|
|
5
|
+
// Reject strings still holding an unresolved ${VAR} — an undefined env var that
|
|
6
|
+
// would leak verbatim into an Appwrite HTTP header. Loud-fail instead.
|
|
17
7
|
function resolvedOrNull(value, fieldName) {
|
|
18
8
|
if (typeof value !== 'string') return value;
|
|
19
9
|
if (/\$\{[^}]+\}/.test(value)) {
|
|
@@ -23,7 +13,7 @@ function resolvedOrNull(value, fieldName) {
|
|
|
23
13
|
return value;
|
|
24
14
|
}
|
|
25
15
|
|
|
26
|
-
// Load manifest if not already loaded
|
|
16
|
+
// Load manifest if not already loaded
|
|
27
17
|
async function ensureManifest() {
|
|
28
18
|
if (window.ManifestComponentsRegistry?.manifest) {
|
|
29
19
|
return window.ManifestComponentsRegistry.manifest;
|
|
@@ -35,7 +25,10 @@ async function ensureManifest() {
|
|
|
35
25
|
try {
|
|
36
26
|
const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
|
|
37
27
|
const response = await fetch(manifestUrl);
|
|
38
|
-
|
|
28
|
+
const manifest = await response.json();
|
|
29
|
+
// No-loader path: resolve ${VAR} placeholders the dynamic loader would have.
|
|
30
|
+
window.ManifestDataConfig?.interpolateManifest?.(manifest);
|
|
31
|
+
return manifest;
|
|
39
32
|
} catch (error) {
|
|
40
33
|
return null;
|
|
41
34
|
}
|
|
@@ -51,26 +44,21 @@ async function getAppwriteConfig() {
|
|
|
51
44
|
const appwriteConfig = manifest.appwrite;
|
|
52
45
|
const endpoint = resolvedOrNull(appwriteConfig.endpoint, 'endpoint');
|
|
53
46
|
const projectId = resolvedOrNull(appwriteConfig.projectId, 'projectId');
|
|
54
|
-
// Optional dev key to bypass rate limits in development.
|
|
55
|
-
// documents `${VAR_NAME}` interpolation for this field specifically —
|
|
56
|
-
// refuse to forward a literal placeholder as an HTTP header.
|
|
47
|
+
// Optional dev key to bypass rate limits in development.
|
|
57
48
|
const devKey = appwriteConfig.devKey ? resolvedOrNull(appwriteConfig.devKey, 'devKey') : undefined;
|
|
58
49
|
|
|
59
50
|
if (!endpoint || !projectId) {
|
|
60
51
|
return null;
|
|
61
52
|
}
|
|
62
|
-
// devKey
|
|
63
|
-
// resolvedOrNull returned null (and logged) — drop the field rather than
|
|
64
|
-
// initialize Appwrite with a literal `${VAR}` header.
|
|
53
|
+
// Supplied-but-unresolved devKey: drop the config rather than send a literal ${VAR} header.
|
|
65
54
|
if (appwriteConfig.devKey && devKey === null) {
|
|
66
55
|
return null;
|
|
67
56
|
}
|
|
68
57
|
|
|
69
|
-
//
|
|
58
|
+
// Auth methods (defaults to magic + oauth)
|
|
70
59
|
const authMethods = appwriteConfig.auth?.methods || ["magic", "oauth"];
|
|
71
60
|
|
|
72
|
-
// Guest
|
|
73
|
-
// ("guest" is the schema's documented spelling; "guest-auto" is accepted as a synonym.)
|
|
61
|
+
// Guest sessions: "guest"/"guest-auto" = automatic, "guest-manual" = manual only.
|
|
74
62
|
const guestAuto = authMethods.includes("guest") || authMethods.includes("guest-auto");
|
|
75
63
|
const guestManual = authMethods.includes("guest-manual");
|
|
76
64
|
const hasGuest = guestAuto || guestManual;
|
|
@@ -79,41 +67,34 @@ async function getAppwriteConfig() {
|
|
|
79
67
|
const otpEnabled = authMethods.includes("otp");
|
|
80
68
|
const oauthEnabled = authMethods.includes("oauth");
|
|
81
69
|
|
|
82
|
-
// Teams
|
|
70
|
+
// Teams (presence of teams object enables it)
|
|
83
71
|
const teamsEnabled = !!appwriteConfig.auth?.teams;
|
|
84
|
-
const permanentTeams = appwriteConfig.auth?.teams?.permanent || null; //
|
|
85
|
-
const templateTeams = appwriteConfig.auth?.teams?.template || null; //
|
|
86
|
-
const teamsPollInterval = appwriteConfig.auth?.teams?.pollInterval || null; //
|
|
87
|
-
const guestTeams = !!appwriteConfig.auth?.teams?.guests; //
|
|
88
|
-
|
|
89
|
-
// Guest upgrade: preserve the anonymous account
|
|
90
|
-
//
|
|
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.
|
|
72
|
+
const permanentTeams = appwriteConfig.auth?.teams?.permanent || null; // immutable
|
|
73
|
+
const templateTeams = appwriteConfig.auth?.teams?.template || null; // deletable + reappliable
|
|
74
|
+
const teamsPollInterval = appwriteConfig.auth?.teams?.pollInterval || null; // ms, null = disabled
|
|
75
|
+
const guestTeams = !!appwriteConfig.auth?.teams?.guests; // seed default teams for guests
|
|
76
|
+
|
|
77
|
+
// Guest upgrade: preserve the anonymous account + teams on sign-in (magic/oauth;
|
|
78
|
+
// OTP can't convert anonymous accounts). Defaults to guestTeams.
|
|
93
79
|
const guestUpgrade = appwriteConfig.auth?.guestUpgrade !== undefined
|
|
94
80
|
? !!appwriteConfig.auth.guestUpgrade
|
|
95
81
|
: guestTeams;
|
|
96
82
|
|
|
97
|
-
// Default roles:
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const templateRoles = appwriteConfig.auth?.roles?.template || null; // Object: { "RoleName": ["permission1", ...] }
|
|
83
|
+
// Default roles: { "RoleName": ["permission", ...] }
|
|
84
|
+
const permanentRoles = appwriteConfig.auth?.roles?.permanent || null; // not deletable
|
|
85
|
+
const templateRoles = appwriteConfig.auth?.roles?.template || null; // deletable
|
|
101
86
|
|
|
102
|
-
// Member roles:
|
|
103
|
-
// This is used for role normalization, permission checking, and creatorRole logic
|
|
87
|
+
// Member roles: permanent + template merged (fallback to legacy memberRoles)
|
|
104
88
|
const memberRoles = permanentRoles || templateRoles
|
|
105
89
|
? { ...(permanentRoles || {}), ...(templateRoles || {}) }
|
|
106
|
-
: (appwriteConfig.auth?.memberRoles || null);
|
|
90
|
+
: (appwriteConfig.auth?.memberRoles || null);
|
|
107
91
|
|
|
108
|
-
// Creator role:
|
|
92
|
+
// Creator role: memberRoles key the creator gets by default (legacy singular)
|
|
109
93
|
const creatorRole = appwriteConfig.auth?.creatorRole || null;
|
|
110
94
|
|
|
111
|
-
// Creator roles (plural):
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
// over the legacy singular `creatorRole`. When NEITHER is set, createTeam keeps its
|
|
115
|
-
// historical default (first defined role). Resolved to: array (configured, [] = owner-only)
|
|
116
|
-
// or null (unconfigured → use the historical default).
|
|
95
|
+
// Creator roles (plural): role(s) assigned to the team creator atomically at creation.
|
|
96
|
+
// string | string[] | null; explicit null/[] = owner-only. Wins over creatorRole.
|
|
97
|
+
// Resolves to array (configured) or null (use historical default of first role).
|
|
117
98
|
let creatorRoles = null;
|
|
118
99
|
if (appwriteConfig.auth && Object.prototype.hasOwnProperty.call(appwriteConfig.auth, 'creatorRoles')) {
|
|
119
100
|
const raw = appwriteConfig.auth.creatorRoles;
|
|
@@ -122,35 +103,34 @@ async function getAppwriteConfig() {
|
|
|
122
103
|
creatorRoles = [creatorRole];
|
|
123
104
|
}
|
|
124
105
|
|
|
125
|
-
// Guest migration:
|
|
126
|
-
//
|
|
127
|
-
// Appwrite can't convert in place). See templates/guest-migration-function.
|
|
106
|
+
// Guest migration: deployed Appwrite Function id that carries guest teams to
|
|
107
|
+
// the OTP account (which Appwrite can't convert in place).
|
|
128
108
|
const guestMigrationFunctionId = appwriteConfig.auth?.guestMigration?.functionId || null;
|
|
129
109
|
|
|
130
110
|
return {
|
|
131
111
|
endpoint,
|
|
132
112
|
projectId,
|
|
133
|
-
devKey,
|
|
113
|
+
devKey,
|
|
134
114
|
authMethods,
|
|
135
115
|
guest: hasGuest,
|
|
136
116
|
guestAuto: guestAuto,
|
|
137
117
|
guestManual: guestManual,
|
|
138
|
-
anonymous: guestAuto, //
|
|
118
|
+
anonymous: guestAuto, // back-compat alias
|
|
139
119
|
magic: magicEnabled,
|
|
140
|
-
otp: otpEnabled,
|
|
120
|
+
otp: otpEnabled,
|
|
141
121
|
oauth: oauthEnabled,
|
|
142
122
|
teams: teamsEnabled,
|
|
143
|
-
permanentTeams: permanentTeams,
|
|
144
|
-
templateTeams: templateTeams,
|
|
145
|
-
teamsPollInterval: teamsPollInterval,
|
|
146
|
-
guestTeams: guestTeams,
|
|
147
|
-
guestUpgrade: guestUpgrade,
|
|
148
|
-
guestMigrationFunctionId: guestMigrationFunctionId,
|
|
149
|
-
memberRoles: memberRoles,
|
|
150
|
-
permanentRoles: permanentRoles,
|
|
151
|
-
templateRoles: templateRoles,
|
|
152
|
-
creatorRole: creatorRole,
|
|
153
|
-
creatorRoles: creatorRoles
|
|
123
|
+
permanentTeams: permanentTeams,
|
|
124
|
+
templateTeams: templateTeams,
|
|
125
|
+
teamsPollInterval: teamsPollInterval,
|
|
126
|
+
guestTeams: guestTeams,
|
|
127
|
+
guestUpgrade: guestUpgrade,
|
|
128
|
+
guestMigrationFunctionId: guestMigrationFunctionId,
|
|
129
|
+
memberRoles: memberRoles,
|
|
130
|
+
permanentRoles: permanentRoles,
|
|
131
|
+
templateRoles: templateRoles,
|
|
132
|
+
creatorRole: creatorRole,
|
|
133
|
+
creatorRoles: creatorRoles
|
|
154
134
|
};
|
|
155
135
|
}
|
|
156
136
|
|
|
@@ -161,7 +141,6 @@ let appwriteTeams = null;
|
|
|
161
141
|
let appwriteUsers = null;
|
|
162
142
|
|
|
163
143
|
async function getAppwriteClient() {
|
|
164
|
-
// Check if Appwrite SDK is loaded
|
|
165
144
|
if (!window.Appwrite || !window.Appwrite.Client || !window.Appwrite.Account) {
|
|
166
145
|
return null;
|
|
167
146
|
}
|
|
@@ -176,8 +155,7 @@ async function getAppwriteClient() {
|
|
|
176
155
|
.setEndpoint(config.endpoint)
|
|
177
156
|
.setProject(config.projectId);
|
|
178
157
|
|
|
179
|
-
//
|
|
180
|
-
// See: https://appwrite.io/docs/advanced/platform/rate-limits#dev-keys
|
|
158
|
+
// Dev key header bypasses rate limits in development.
|
|
181
159
|
if (config.devKey) {
|
|
182
160
|
appwriteClient.headers['X-Appwrite-Dev-Key'] = config.devKey;
|
|
183
161
|
}
|
|
@@ -185,7 +163,6 @@ async function getAppwriteClient() {
|
|
|
185
163
|
appwriteAccount = new window.Appwrite.Account(appwriteClient);
|
|
186
164
|
appwriteTeams = new window.Appwrite.Teams(appwriteClient);
|
|
187
165
|
|
|
188
|
-
// Initialize Users service if available (for fetching user details)
|
|
189
166
|
if (window.Appwrite.Users) {
|
|
190
167
|
appwriteUsers = new window.Appwrite.Users(appwriteClient);
|
|
191
168
|
}
|
|
@@ -195,9 +172,9 @@ async function getAppwriteClient() {
|
|
|
195
172
|
client: appwriteClient,
|
|
196
173
|
account: appwriteAccount,
|
|
197
174
|
teams: appwriteTeams,
|
|
198
|
-
users: appwriteUsers,
|
|
199
|
-
functions: window.Appwrite?.Functions ? new window.Appwrite.Functions(appwriteClient) : null,
|
|
200
|
-
realtime: window.Appwrite?.Realtime ? new window.Appwrite.Realtime(appwriteClient) : null
|
|
175
|
+
users: appwriteUsers,
|
|
176
|
+
functions: window.Appwrite?.Functions ? new window.Appwrite.Functions(appwriteClient) : null,
|
|
177
|
+
realtime: window.Appwrite?.Realtime ? new window.Appwrite.Realtime(appwriteClient) : null
|
|
201
178
|
};
|
|
202
179
|
}
|
|
203
180
|
|
|
@@ -224,12 +201,8 @@ function initializeAuthStore() {
|
|
|
224
201
|
// Cross-tab synchronization using localStorage events
|
|
225
202
|
const STORAGE_KEY = 'manifest:auth:state';
|
|
226
203
|
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
// `providerRefreshToken`, and `providerAccessTokenExpiry`. The cookie set
|
|
230
|
-
// on the Appwrite domain is the actual auth of record; this localStorage
|
|
231
|
-
// copy only supports UI cross-tab sync ("someone just logged in here").
|
|
232
|
-
// An XSS on this origin must not be able to lift session secrets out.
|
|
204
|
+
// Session fields safe to mirror across tabs. Excludes `secret` and provider
|
|
205
|
+
// tokens — this copy is only for UI cross-tab sync, not the auth of record.
|
|
233
206
|
const SAFE_SESSION_FIELDS = [
|
|
234
207
|
'$id', 'userId', 'provider', 'expire', 'current',
|
|
235
208
|
'clientName', 'osName', 'osCode', 'deviceName',
|
|
@@ -429,8 +402,7 @@ function initializeAuthStore() {
|
|
|
429
402
|
|
|
430
403
|
// Get personal team (convenience getter - returns first default team)
|
|
431
404
|
get personalTeam() {
|
|
432
|
-
//
|
|
433
|
-
// Return null and let users call getPersonalTeam() or getDefaultTeams() directly
|
|
405
|
+
// Lookup is async; use getPersonalTeam()/getDefaultTeams() instead
|
|
434
406
|
return null;
|
|
435
407
|
},
|
|
436
408
|
|
|
@@ -450,19 +422,16 @@ function initializeAuthStore() {
|
|
|
450
422
|
}
|
|
451
423
|
},
|
|
452
424
|
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
// For existing sessions without stored provider, triggers async fetch from Appwrite identities
|
|
425
|
+
// OAuth provider name (google, github, …), or null for non-OAuth methods.
|
|
426
|
+
// Reads stored provider, else falls back to session.provider / identities fetch.
|
|
456
427
|
getProvider() {
|
|
457
428
|
if (!this.session) {
|
|
458
429
|
return null;
|
|
459
430
|
}
|
|
460
431
|
const sessionProvider = this.session.provider;
|
|
461
432
|
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
// Only OAuth sessions have a provider name — magic/otp/phone/anonymous don't,
|
|
465
|
-
// so gate on getMethod() to avoid a pointless identities lookup for those.
|
|
433
|
+
// session.provider is generically "oauth2", so use _oauthProvider. Gate on
|
|
434
|
+
// getMethod() so non-OAuth sessions skip the pointless identities lookup.
|
|
466
435
|
if (this.getMethod() === 'oauth') {
|
|
467
436
|
// Try to get from store first, then localStorage, then sessionStorage
|
|
468
437
|
let provider = this._oauthProvider;
|
|
@@ -568,8 +537,7 @@ function initializeAuthStore() {
|
|
|
568
537
|
this.isAuthenticated = true;
|
|
569
538
|
this.isAnonymous = currentSession.provider === 'anonymous';
|
|
570
539
|
|
|
571
|
-
// Restore OAuth provider from
|
|
572
|
-
// This ensures provider name persists across page refreshes
|
|
540
|
+
// Restore OAuth provider from storage (persists across redirects/refresh)
|
|
573
541
|
if (!this.isAnonymous && currentSession.provider !== 'magic-url') {
|
|
574
542
|
try {
|
|
575
543
|
// Try localStorage first (persists across redirects), fallback to sessionStorage
|
|
@@ -602,10 +570,8 @@ function initializeAuthStore() {
|
|
|
602
570
|
this.isAnonymous = false;
|
|
603
571
|
}
|
|
604
572
|
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
// so a session gate (app splash) isn't held up by team listing +
|
|
608
|
-
// member hydration.
|
|
573
|
+
// Team loading is deferred (not awaited) — runs in the background after
|
|
574
|
+
// manifest:auth:initialized so a session gate isn't held up by it.
|
|
609
575
|
} catch (error) {
|
|
610
576
|
// No existing session - this is expected
|
|
611
577
|
this.isAuthenticated = false;
|
|
@@ -617,11 +583,8 @@ function initializeAuthStore() {
|
|
|
617
583
|
// Sync state to localStorage
|
|
618
584
|
syncStateToStorage(this);
|
|
619
585
|
} catch (error) {
|
|
620
|
-
//
|
|
621
|
-
//
|
|
622
|
-
// the raw message in $auth.error, which is reserved for sign-in/action
|
|
623
|
-
// failures the user can act on (a sign-in card binding $auth.error must
|
|
624
|
-
// never show benign "no session" / "blocked account" scope errors).
|
|
586
|
+
// Passive lifecycle step: resolve to signed-out and log. Don't set
|
|
587
|
+
// $auth.error — that's reserved for user-actionable sign-in failures.
|
|
625
588
|
console.warn('[Manifest Appwrite Auth] Session restore failed (treating as signed out):', error?.message || error);
|
|
626
589
|
this.isAuthenticated = false;
|
|
627
590
|
this.isAnonymous = false;
|
|
@@ -630,9 +593,8 @@ function initializeAuthStore() {
|
|
|
630
593
|
this._initialized = true;
|
|
631
594
|
this._initializing = false;
|
|
632
595
|
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
// load — so a session gate / splash clears in a few hundred ms.
|
|
596
|
+
// Fire as soon as identity is known, before teams load, so a session
|
|
597
|
+
// gate / splash clears in a few hundred ms.
|
|
636
598
|
window.dispatchEvent(new CustomEvent('manifest:auth:initialized', {
|
|
637
599
|
detail: {
|
|
638
600
|
isAuthenticated: this.isAuthenticated,
|
|
@@ -641,9 +603,8 @@ function initializeAuthStore() {
|
|
|
641
603
|
}));
|
|
642
604
|
}
|
|
643
605
|
|
|
644
|
-
// Background
|
|
645
|
-
//
|
|
646
|
-
// manifest:auth:teams-loaded event fires for anything that needs the full set.
|
|
606
|
+
// Background load + seed teams after init. Populates reactively; fires
|
|
607
|
+
// manifest:auth:teams-loaded for anything needing the full set.
|
|
647
608
|
if (appwriteConfig && this.isAuthenticated && appwriteConfig.teams
|
|
648
609
|
&& (!this.isAnonymous || appwriteConfig.guestTeams)) {
|
|
649
610
|
this._loadTeamsAndSeed(appwriteConfig)
|
|
@@ -654,11 +615,8 @@ function initializeAuthStore() {
|
|
|
654
615
|
}
|
|
655
616
|
},
|
|
656
617
|
|
|
657
|
-
// Clear
|
|
658
|
-
//
|
|
659
|
-
// (Appwrite can't convert anonymous → OTP). Without this, the previous user's
|
|
660
|
-
// currentTeam/teams leak into the new session and listTeams queries teams the
|
|
661
|
-
// new user can't access, producing 404s on prefs/memberships.
|
|
618
|
+
// Clear team state when the identity changes to a different user (e.g. guest
|
|
619
|
+
// replaced on OTP sign-in). Otherwise stale currentTeam/teams cause 404s.
|
|
662
620
|
_resetTeamsState() {
|
|
663
621
|
this.teams = [];
|
|
664
622
|
this.currentTeam = null;
|
|
@@ -671,10 +629,8 @@ function initializeAuthStore() {
|
|
|
671
629
|
}
|
|
672
630
|
},
|
|
673
631
|
|
|
674
|
-
// Call the deployed guest-migration function
|
|
675
|
-
//
|
|
676
|
-
// user id to the function. Returns the parsed JSON response, or null on any
|
|
677
|
-
// failure (migration is best-effort: a failure must never block sign-in).
|
|
632
|
+
// Call the deployed guest-migration function; the current session authenticates it.
|
|
633
|
+
// Returns parsed JSON, or null on failure (best-effort — never blocks sign-in).
|
|
678
634
|
async _callGuestMigration(path, body) {
|
|
679
635
|
const appwriteConfig = await config.getAppwriteConfig();
|
|
680
636
|
const fnId = appwriteConfig?.guestMigrationFunctionId;
|
|
@@ -693,17 +649,15 @@ function initializeAuthStore() {
|
|
|
693
649
|
}
|
|
694
650
|
},
|
|
695
651
|
|
|
696
|
-
// Load the user's teams and seed
|
|
697
|
-
//
|
|
652
|
+
// Load the user's teams and seed configured defaults. Shared by the guest,
|
|
653
|
+
// magic-link, OAuth, and init/restore paths.
|
|
698
654
|
async _loadTeamsAndSeed(appwriteConfig) {
|
|
699
655
|
const cfg = appwriteConfig || await config.getAppwriteConfig();
|
|
700
656
|
if (!cfg?.teams) {
|
|
701
657
|
return;
|
|
702
658
|
}
|
|
703
|
-
// Startup race:
|
|
704
|
-
//
|
|
705
|
-
// ensureDefaultTeams onto the store. Wait briefly for them rather than
|
|
706
|
-
// silently skipping (which left guests with no teams on reload).
|
|
659
|
+
// Startup race: we can arrive before teams.core/defaults wire listTeams +
|
|
660
|
+
// ensureDefaultTeams onto the store. Wait briefly rather than skip.
|
|
707
661
|
const needsSeed = !!(cfg.permanentTeams || cfg.templateTeams);
|
|
708
662
|
const ready = () => typeof this.listTeams === 'function'
|
|
709
663
|
&& (!needsSeed || typeof window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams === 'function');
|
|
@@ -763,9 +717,8 @@ function initializeAuthStore() {
|
|
|
763
717
|
// Ignore
|
|
764
718
|
}
|
|
765
719
|
|
|
766
|
-
// Guests are full
|
|
767
|
-
//
|
|
768
|
-
// signed-in user; otherwise keep the historical behavior (no teams).
|
|
720
|
+
// Guests are full sessions and can own teams: seed defaults when
|
|
721
|
+
// guestTeams is enabled, otherwise none.
|
|
769
722
|
const cfg = await config.getAppwriteConfig();
|
|
770
723
|
if (cfg?.guestTeams) {
|
|
771
724
|
await this._loadTeamsAndSeed(cfg);
|
|
@@ -856,18 +809,8 @@ function initializeAuthStore() {
|
|
|
856
809
|
this.teams = [];
|
|
857
810
|
this.currentTeam = null;
|
|
858
811
|
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
// const userId = this.user?.$id;
|
|
862
|
-
// if (userId) {
|
|
863
|
-
// localStorage.removeItem(`manifest:deleted-teams:${userId}`);
|
|
864
|
-
// }
|
|
865
|
-
// } catch (e) {
|
|
866
|
-
// // Ignore
|
|
867
|
-
// }
|
|
868
|
-
|
|
869
|
-
// Restore to guest state after logout (if guest-auto is enabled)
|
|
870
|
-
// This only applies to non-guest sessions - if logging out from guest, don't create a new guest
|
|
812
|
+
// Restore to guest state after logout (guest-auto only, and not when
|
|
813
|
+
// already a guest — don't mint a new guest on top).
|
|
871
814
|
if (!this.isAnonymous && this._guestAuto && this._createAnonymousSession) {
|
|
872
815
|
await this._createAnonymousSession();
|
|
873
816
|
} else {
|
|
@@ -4773,19 +4716,8 @@ function initializeTeamsConvenience() {
|
|
|
4773
4716
|
const waitForStore = () => {
|
|
4774
4717
|
const store = Alpine.store('auth');
|
|
4775
4718
|
if (store) {
|
|
4776
|
-
//
|
|
4777
|
-
//
|
|
4778
|
-
// sentinel (`isCreatingTeam`) was unreliable: the store itself
|
|
4779
|
-
// defines an `isCreatingTeam()` stub at init (see manifest.appwrite
|
|
4780
|
-
// .auth.store.js — the "Stub team convenience methods" block), so
|
|
4781
|
-
// `typeof store.isCreatingTeam === 'function'` is true BEFORE this
|
|
4782
|
-
// module runs. The check then fired false-positive and skipped the
|
|
4783
|
-
// whole `if (needsReinitialization)` block below, leaving the real
|
|
4784
|
-
// convenience methods (startEditingMember, createTeamFromName,
|
|
4785
|
-
// cancelEditingMember, saveEditingMember, deleteMember, leaveTeam,
|
|
4786
|
-
// toggleInviteRole, etc.) unattached — surfacing as
|
|
4787
|
-
// "$auth.startEditingMember is not a function" the moment a user
|
|
4788
|
-
// clicked an edit-member button.
|
|
4719
|
+
// Sentinel must be a method ONLY this module defines. isCreatingTeam
|
|
4720
|
+
// won't do — the store stubs it at init, so it reads present too early.
|
|
4789
4721
|
const needsReinitialization = typeof store.createTeamFromName !== 'function';
|
|
4790
4722
|
|
|
4791
4723
|
// Ensure cache properties are initialized (methods are already in store)
|
|
@@ -4867,8 +4799,7 @@ function initializeTeamsConvenience() {
|
|
|
4867
4799
|
};
|
|
4868
4800
|
}
|
|
4869
4801
|
|
|
4870
|
-
//
|
|
4871
|
-
// This ensures methods are re-added if the store was replaced or methods were lost after idle
|
|
4802
|
+
// Re-attach methods if the store was replaced or methods were lost after idle
|
|
4872
4803
|
if (needsReinitialization) {
|
|
4873
4804
|
// Convenience method: create team using newTeamName property
|
|
4874
4805
|
store.createTeamFromName = async function () {
|
|
@@ -5398,9 +5329,8 @@ function initializeTeamsConvenience() {
|
|
|
5398
5329
|
const allRoles = this.allTeamRoles({ $id: teamId });
|
|
5399
5330
|
const permissions = allRoles && allRoles[roleName] ? [...allRoles[roleName]] : [];
|
|
5400
5331
|
|
|
5401
|
-
// Set
|
|
5402
|
-
//
|
|
5403
|
-
// after this isn't racing (and getting overwritten by) that fetch.
|
|
5332
|
+
// Set state synchronously, before the async fetch below, so a caller
|
|
5333
|
+
// mutating editingRole.permissions right after isn't overwritten.
|
|
5404
5334
|
this.editingRole = {
|
|
5405
5335
|
teamId: teamId,
|
|
5406
5336
|
oldRoleName: roleName,
|
|
@@ -5419,12 +5349,8 @@ function initializeTeamsConvenience() {
|
|
|
5419
5349
|
// The UI will use editingRole.permissions or pendingPermissions for existing roles
|
|
5420
5350
|
};
|
|
5421
5351
|
|
|
5422
|
-
// Reactive-safe
|
|
5423
|
-
//
|
|
5424
|
-
// reactive editingRole object. Use this for programmatic edits; mutating
|
|
5425
|
-
// $auth.editingRole.permissions directly is fragile under Alpine (the
|
|
5426
|
-
// $auth proxy wraps the already-reactive editingRole and can recurse on
|
|
5427
|
-
// get). updateUserRole refreshes the role cache, so allTeamRoles() re-reads.
|
|
5352
|
+
// Reactive-safe write: persist via updateUserRole, bypassing the reactive
|
|
5353
|
+
// editingRole (mutating $auth.editingRole.permissions directly recurses).
|
|
5428
5354
|
store.updateRolePermissions = async function (teamId, roleName, permissions) {
|
|
5429
5355
|
if (!this.updateUserRole) {
|
|
5430
5356
|
return { success: false, error: 'Roles module not ready' };
|
|
@@ -5435,9 +5361,8 @@ function initializeTeamsConvenience() {
|
|
|
5435
5361
|
return await this.updateUserRole(teamId, roleName, plain);
|
|
5436
5362
|
};
|
|
5437
5363
|
|
|
5438
|
-
//
|
|
5439
|
-
//
|
|
5440
|
-
// reactive nested array). Pair with saveEditingRole().
|
|
5364
|
+
// Set permissions on the in-progress edit by replacing editingRole with a
|
|
5365
|
+
// fresh object (not mutating the reactive nested array). Pair with saveEditingRole().
|
|
5441
5366
|
store.setEditingPermissions = function (permissions) {
|
|
5442
5367
|
if (!this.editingRole) {
|
|
5443
5368
|
return;
|
|
@@ -5541,10 +5466,8 @@ function initializeTeamsConvenience() {
|
|
|
5541
5466
|
return userRoles.includes('owner');
|
|
5542
5467
|
};
|
|
5543
5468
|
|
|
5544
|
-
//
|
|
5545
|
-
//
|
|
5546
|
-
// the async hasTeamPermission() — including custom permission keys, not
|
|
5547
|
-
// just the six built-ins the permission cache pre-computes.
|
|
5469
|
+
// Sync version for Alpine bindings. Resolves from cached allTeamRoles so it
|
|
5470
|
+
// matches async hasTeamPermission, including custom (non-built-in) keys.
|
|
5548
5471
|
store.hasTeamPermissionSync = function (permission) {
|
|
5549
5472
|
if (!this.currentTeam || !this.currentTeamMemberships || !this.user) {
|
|
5550
5473
|
return false;
|
|
@@ -5616,12 +5539,9 @@ function initializeTeamsConvenience() {
|
|
|
5616
5539
|
return userRoles;
|
|
5617
5540
|
};
|
|
5618
5541
|
|
|
5619
|
-
} // End
|
|
5620
|
-
|
|
5621
|
-
// Note: hasPermission from roles module takes (userRoles, permission, teamId)
|
|
5622
|
-
// hasTeamPermission is the convenience wrapper for current team
|
|
5542
|
+
} // End needsReinitialization
|
|
5623
5543
|
|
|
5624
|
-
//
|
|
5544
|
+
// Wrap viewTeam to refresh permission cache after a team is viewed
|
|
5625
5545
|
if (store.viewTeam && !store._viewTeamWrapped) {
|
|
5626
5546
|
const originalViewTeam = store.viewTeam;
|
|
5627
5547
|
store.viewTeam = async function (team) {
|
|
@@ -6232,15 +6152,10 @@ window.ManifestAppwriteAuthMagicLinks = {
|
|
|
6232
6152
|
|
|
6233
6153
|
/* Auth email OTP (one-time passcode) */
|
|
6234
6154
|
|
|
6235
|
-
//
|
|
6236
|
-
//
|
|
6237
|
-
//
|
|
6238
|
-
//
|
|
6239
|
-
//
|
|
6240
|
-
// NOTE: Appwrite does NOT support converting an anonymous (guest) session via email
|
|
6241
|
-
// OTP — only magic links, phone, email/password, and OAuth can do that. So when a
|
|
6242
|
-
// guest verifies an OTP we mint a fresh account (the anonymous session is deleted),
|
|
6243
|
-
// which discards any guest-created teams. Use magic links if you need guest upgrade.
|
|
6155
|
+
// Two-step in-page flow (no redirect): createEmailOTP(email) emails a code + returns
|
|
6156
|
+
// a userId, verifyOTP(code) creates the session.
|
|
6157
|
+
// Gotcha: Appwrite can't convert an anonymous guest via OTP — a guest verifying an OTP
|
|
6158
|
+
// gets a fresh account (guest teams lost). Use magic links for guest upgrade.
|
|
6244
6159
|
|
|
6245
6160
|
function initializeEmailOTP() {
|
|
6246
6161
|
if (typeof Alpine === 'undefined') {
|
|
@@ -6252,9 +6167,8 @@ function initializeEmailOTP() {
|
|
|
6252
6167
|
return;
|
|
6253
6168
|
}
|
|
6254
6169
|
|
|
6255
|
-
// Resolve an email
|
|
6256
|
-
//
|
|
6257
|
-
// nothing (auto-find the nearest email input). Returns { email, inputEl, dataObj }.
|
|
6170
|
+
// Resolve an email from an input/selector/{ email } object/string, or auto-find
|
|
6171
|
+
// the nearest email input. Returns { email, inputEl, dataObj }.
|
|
6258
6172
|
function resolveEmailInput(emailInputOrRef) {
|
|
6259
6173
|
let email = null;
|
|
6260
6174
|
let inputEl = null;
|
|
@@ -6302,9 +6216,8 @@ function initializeEmailOTP() {
|
|
|
6302
6216
|
const waitForStore = () => {
|
|
6303
6217
|
const store = Alpine.store('auth');
|
|
6304
6218
|
if (store && !store.createEmailOTP) {
|
|
6305
|
-
// Step 1:
|
|
6306
|
-
//
|
|
6307
|
-
// when enabled the phrase is stored on the store as `otpPhrase` for display.
|
|
6219
|
+
// Step 1: email a passcode. { phrase: true } enables Appwrite's anti-phishing
|
|
6220
|
+
// security phrase, surfaced on the store as `otpPhrase`.
|
|
6308
6221
|
store.createEmailOTP = async function (email, options = {}) {
|
|
6309
6222
|
if (!this._appwrite) {
|
|
6310
6223
|
this._appwrite = await config.getAppwriteClient();
|
|
@@ -6323,8 +6236,7 @@ function initializeEmailOTP() {
|
|
|
6323
6236
|
return { success: false, error: 'Email OTP authentication is not enabled' };
|
|
6324
6237
|
}
|
|
6325
6238
|
|
|
6326
|
-
//
|
|
6327
|
-
// teams aren't silently lost; the login still proceeds as a fresh account.
|
|
6239
|
+
// OTP can't convert a guest — warn so lost guest teams aren't a surprise.
|
|
6328
6240
|
if (this.isAnonymous && appwriteConfig?.guestUpgrade) {
|
|
6329
6241
|
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.');
|
|
6330
6242
|
}
|
|
@@ -6359,8 +6271,8 @@ function initializeEmailOTP() {
|
|
|
6359
6271
|
|
|
6360
6272
|
return { success: true, message: 'OTP sent to email', phrase: this.otpPhrase };
|
|
6361
6273
|
} catch (error) {
|
|
6362
|
-
// Appwrite returns 501
|
|
6363
|
-
//
|
|
6274
|
+
// Appwrite returns 501 when Email OTP isn't enabled — surface an
|
|
6275
|
+
// actionable message rather than the raw error.
|
|
6364
6276
|
const code = error.code || error.statusCode;
|
|
6365
6277
|
const notEnabled = code === 501 || /not implemented/i.test(error.message || '');
|
|
6366
6278
|
this.error = notEnabled
|
|
@@ -6421,10 +6333,8 @@ function initializeEmailOTP() {
|
|
|
6421
6333
|
const appwriteConfig = await config.getAppwriteConfig();
|
|
6422
6334
|
const wasGuest = this.isAnonymous;
|
|
6423
6335
|
|
|
6424
|
-
// Guest team carryover:
|
|
6425
|
-
//
|
|
6426
|
-
// ticket NOW, while the guest session is still authenticated — it's
|
|
6427
|
-
// redeemed after the new session exists. Best-effort; never blocks login.
|
|
6336
|
+
// Guest team carryover: issue the migration ticket now, while the guest
|
|
6337
|
+
// session is still authenticated; redeemed after the new session exists.
|
|
6428
6338
|
let migrationTicket = null;
|
|
6429
6339
|
if (wasGuest && appwriteConfig?.guestMigrationFunctionId && this._callGuestMigration) {
|
|
6430
6340
|
const prep = await this._callGuestMigration('/prepare', {});
|
|
@@ -6452,16 +6362,14 @@ function initializeEmailOTP() {
|
|
|
6452
6362
|
this._otpUserId = null;
|
|
6453
6363
|
this.error = null;
|
|
6454
6364
|
|
|
6455
|
-
//
|
|
6456
|
-
//
|
|
6457
|
-
// otherwise the stale currentTeam triggers 404s in listTeams.
|
|
6365
|
+
// Clear the guest's team state before loading the new user's teams,
|
|
6366
|
+
// else the stale currentTeam triggers 404s in listTeams.
|
|
6458
6367
|
if (this._resetTeamsState) {
|
|
6459
6368
|
this._resetTeamsState();
|
|
6460
6369
|
}
|
|
6461
6370
|
|
|
6462
|
-
// Redeem the migration ticket as the new account
|
|
6463
|
-
//
|
|
6464
|
-
// but never blocks the (already successful) sign-in.
|
|
6371
|
+
// Redeem the migration ticket as the new account to carry teams over.
|
|
6372
|
+
// Best-effort; failure never blocks the already-successful sign-in.
|
|
6465
6373
|
if (migrationTicket && this._callGuestMigration) {
|
|
6466
6374
|
await this._callGuestMigration('/commit', { ticket: migrationTicket });
|
|
6467
6375
|
}
|
|
@@ -1214,9 +1214,7 @@ window.ManifestDataQueries = {
|
|
|
1214
1214
|
};
|
|
1215
1215
|
|
|
1216
1216
|
|
|
1217
|
-
/* Manifest Data Sources - Pagination */
|
|
1218
|
-
|
|
1219
|
-
// Pagination helper functions for Appwrite data sources
|
|
1217
|
+
/* Manifest Data Sources - Pagination (Appwrite) */
|
|
1220
1218
|
|
|
1221
1219
|
/**
|
|
1222
1220
|
* Get first page of results (cursor-based)
|