edmaxlabs-core 2.7.3 → 2.9.4
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/dist/index.cjs +440 -370
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +96 -19
- package/dist/index.d.ts +96 -19
- package/dist/index.mjs +440 -370
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,40 +32,90 @@ module.exports = __toCommonJS(src_exports);
|
|
|
32
32
|
|
|
33
33
|
// src/authentication/Credentials.ts
|
|
34
34
|
var Credentials = class _Credentials {
|
|
35
|
-
constructor(uid, displayInfo, data, verified, createdAt, updatedAt) {
|
|
35
|
+
constructor(uid, displayInfo, data, verified, disabled, provider, createdAt, updatedAt, lastLogin) {
|
|
36
36
|
this.uid = uid;
|
|
37
37
|
this.displayInfo = displayInfo;
|
|
38
38
|
this.data = data;
|
|
39
39
|
this.verified = verified;
|
|
40
|
+
this.disabled = disabled;
|
|
41
|
+
this.provider = provider;
|
|
40
42
|
this.createdAt = createdAt;
|
|
41
43
|
this.updatedAt = updatedAt;
|
|
44
|
+
this.lastLogin = lastLogin;
|
|
42
45
|
}
|
|
43
46
|
static fromMap(map) {
|
|
44
|
-
const uid = map.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const uid = map.uid ?? map.id ?? map._id ?? null;
|
|
48
|
+
let displayInfo;
|
|
49
|
+
let data;
|
|
50
|
+
let createdAt;
|
|
51
|
+
let updatedAt;
|
|
52
|
+
let lastLogin;
|
|
53
|
+
let verified;
|
|
54
|
+
let disabled;
|
|
55
|
+
let provider;
|
|
56
|
+
const inner = map;
|
|
57
|
+
displayInfo = inner.displayInfo ?? {
|
|
58
|
+
firstname: "",
|
|
59
|
+
lastname: "",
|
|
60
|
+
surname: "",
|
|
61
|
+
profile: "",
|
|
62
|
+
email: "",
|
|
63
|
+
phone: ""
|
|
64
|
+
};
|
|
65
|
+
data = inner.data ?? {};
|
|
66
|
+
createdAt = inner.createdAt ?? map.createdAt ?? null;
|
|
67
|
+
updatedAt = inner.updatedAt ?? map.updatedAt ?? null;
|
|
68
|
+
lastLogin = inner.lastLogin ?? map.lastLogin ?? null;
|
|
69
|
+
verified = inner.verified ?? map.verified ?? false;
|
|
70
|
+
disabled = inner.disabled ?? map.disabled ?? false;
|
|
71
|
+
provider = inner.provider ?? map.provider ?? "EAuth";
|
|
72
|
+
const output = new _Credentials(
|
|
50
73
|
uid,
|
|
51
74
|
displayInfo,
|
|
52
|
-
data
|
|
53
|
-
|
|
75
|
+
data,
|
|
76
|
+
verified,
|
|
77
|
+
disabled,
|
|
78
|
+
provider,
|
|
54
79
|
createdAt,
|
|
55
|
-
updatedAt
|
|
80
|
+
updatedAt,
|
|
81
|
+
lastLogin
|
|
56
82
|
);
|
|
57
|
-
return
|
|
83
|
+
return output;
|
|
58
84
|
}
|
|
85
|
+
// toMap() produces the nested shape — used by:
|
|
86
|
+
// - localStorage.setItem("eauth", JSON.stringify(creds.toMap()))
|
|
87
|
+
// - authState snapshot listener: Credentials.fromMap(snapshot.toMap())
|
|
88
|
+
// Keep this stable so existing persisted sessions restore correctly.
|
|
59
89
|
toMap() {
|
|
60
90
|
return {
|
|
61
91
|
uid: this.uid,
|
|
62
92
|
displayInfo: this.displayInfo,
|
|
63
93
|
data: this.data,
|
|
64
94
|
verified: this.verified,
|
|
95
|
+
disabled: this.disabled,
|
|
96
|
+
provider: this.provider,
|
|
65
97
|
createdAt: this.createdAt,
|
|
66
|
-
updatedAt: this.updatedAt
|
|
98
|
+
updatedAt: this.updatedAt,
|
|
99
|
+
lastLogin: this.lastLogin
|
|
67
100
|
};
|
|
68
101
|
}
|
|
102
|
+
// ── Convenience getters ───────────────────────────────────────────────────
|
|
103
|
+
// So callers don't have to drill into displayInfo everywhere.
|
|
104
|
+
get email() {
|
|
105
|
+
return this.displayInfo.email ?? "";
|
|
106
|
+
}
|
|
107
|
+
get firstname() {
|
|
108
|
+
return this.displayInfo.firstname ?? "";
|
|
109
|
+
}
|
|
110
|
+
get lastname() {
|
|
111
|
+
return this.displayInfo.lastname ?? "";
|
|
112
|
+
}
|
|
113
|
+
get displayName() {
|
|
114
|
+
return `${this.firstname} ${this.lastname}`.trim() || this.email;
|
|
115
|
+
}
|
|
116
|
+
get photoUrl() {
|
|
117
|
+
return this.displayInfo.profile ?? "";
|
|
118
|
+
}
|
|
69
119
|
};
|
|
70
120
|
|
|
71
121
|
// src/utils/HttpsRequest.ts
|
|
@@ -98,11 +148,7 @@ var HttpsRequest = class {
|
|
|
98
148
|
headers: this.headers,
|
|
99
149
|
body: formData
|
|
100
150
|
});
|
|
101
|
-
|
|
102
|
-
const errorText = await response2.text().catch(() => "Network error");
|
|
103
|
-
throw new Error(`HTTP ${response2.status}: ${errorText}`);
|
|
104
|
-
}
|
|
105
|
-
return await response2.json().catch(() => ({}));
|
|
151
|
+
return response2;
|
|
106
152
|
}
|
|
107
153
|
const response = await fetch(this.endpoint, {
|
|
108
154
|
method: this.method,
|
|
@@ -112,11 +158,7 @@ var HttpsRequest = class {
|
|
|
112
158
|
},
|
|
113
159
|
body: this.method !== "GET" /* GET */ ? JSON.stringify(this.body) : void 0
|
|
114
160
|
});
|
|
115
|
-
|
|
116
|
-
const errorText = await response.text().catch(() => "Network error");
|
|
117
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
118
|
-
}
|
|
119
|
-
return await response.json().catch(() => ({}));
|
|
161
|
+
return response;
|
|
120
162
|
} catch (error) {
|
|
121
163
|
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
|
122
164
|
throw new Error("Network connection failed. Please check your internet connection.");
|
|
@@ -132,187 +174,280 @@ var HttpsRequest = class {
|
|
|
132
174
|
// src/authentication/Authentication.ts
|
|
133
175
|
var _Authentication = class _Authentication {
|
|
134
176
|
constructor() {
|
|
177
|
+
// accessToken lives in MEMORY ONLY — never written to localStorage or logged.
|
|
178
|
+
// Reason: localStorage is readable by any JS on the page (XSS).
|
|
179
|
+
// On page reload it starts null; the first authenticated request that gets
|
|
180
|
+
// 401 "Access token expired" triggers _refreshAccessToken() automatically.
|
|
181
|
+
this.accessToken = null;
|
|
182
|
+
// The user SHAPE (no tokens, no passwordHash) is safe to persist.
|
|
183
|
+
// Used to restore who is signed in across page reloads without a network call.
|
|
184
|
+
this.eUser = null;
|
|
185
|
+
this.isRefreshing = false;
|
|
186
|
+
this.refreshQueue = [];
|
|
135
187
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
136
|
-
|
|
137
|
-
this.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return true;
|
|
141
|
-
if (!a || !b)
|
|
142
|
-
return false;
|
|
143
|
-
return JSON.stringify(a.toMap()) === JSON.stringify(b.toMap());
|
|
144
|
-
};
|
|
145
|
-
this.createUserWithEmailAndPassword = async ({
|
|
146
|
-
email,
|
|
147
|
-
password
|
|
148
|
-
}) => {
|
|
149
|
-
this.eUser = void 0;
|
|
150
|
-
this.saveCredentials(null);
|
|
151
|
-
const res = await new HttpsRequest({
|
|
188
|
+
// ─── Sign up ──────────────────────────────────────────────────────────────
|
|
189
|
+
this.createUserWithEmailAndPassword = async (data) => {
|
|
190
|
+
this._saveSession(null);
|
|
191
|
+
const r = await new HttpsRequest({
|
|
152
192
|
method: "POST" /* POST */,
|
|
153
|
-
endpoint: `${this.app?.getBaseUrl()}/auth/
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
password,
|
|
158
|
-
project: this.client?.getConfig().project
|
|
159
|
-
}
|
|
193
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/signup`,
|
|
194
|
+
// ← /auth/signup
|
|
195
|
+
headers: this.buildProjectHeaders(),
|
|
196
|
+
body: data
|
|
160
197
|
}).sendRequest();
|
|
161
|
-
|
|
162
|
-
|
|
198
|
+
const res = await r.json().catch(() => ({}));
|
|
199
|
+
const error = res?.error ?? null;
|
|
200
|
+
if (error) {
|
|
201
|
+
console.error(`[Authentication] Registration failed :`, error);
|
|
202
|
+
throw new Error(error);
|
|
163
203
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const userRef = res.result;
|
|
168
|
-
this.eUser = Credentials.fromMap(userRef);
|
|
169
|
-
this.saveCredentials(this.eUser);
|
|
170
|
-
return Credentials.fromMap(userRef.toMap());
|
|
204
|
+
const creds = Credentials.fromMap(res.result);
|
|
205
|
+
this._saveSession(creds, res.accessToken ?? null);
|
|
206
|
+
return creds;
|
|
171
207
|
};
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}) => {
|
|
176
|
-
const res = await new HttpsRequest({
|
|
208
|
+
// ─── Sign in ──────────────────────────────────────────────────────────────
|
|
209
|
+
this.signInWithEmailAndPassword = async ({ email, password }) => {
|
|
210
|
+
const r = await new HttpsRequest({
|
|
177
211
|
method: "POST" /* POST */,
|
|
178
212
|
endpoint: `${this.app?.getBaseUrl()}/auth/login`,
|
|
179
|
-
headers:
|
|
180
|
-
body: {
|
|
181
|
-
email,
|
|
182
|
-
password,
|
|
183
|
-
project: this.client?.getConfig().project
|
|
184
|
-
}
|
|
213
|
+
headers: this.buildProjectHeaders(),
|
|
214
|
+
body: { email, password }
|
|
185
215
|
}).sendRequest();
|
|
186
|
-
|
|
187
|
-
|
|
216
|
+
const res = await r.json().catch(() => ({}));
|
|
217
|
+
const error = res?.error ?? null;
|
|
218
|
+
if (error) {
|
|
219
|
+
console.error(`[Authentication] Login failed :`, error);
|
|
220
|
+
throw new Error(error);
|
|
188
221
|
}
|
|
189
|
-
|
|
190
|
-
|
|
222
|
+
const creds = Credentials.fromMap(res.result);
|
|
223
|
+
this._saveSession(creds, res.accessToken ?? null);
|
|
224
|
+
return creds;
|
|
225
|
+
};
|
|
226
|
+
// ─── Sign out ─────────────────────────────────────────────────────────────
|
|
227
|
+
//
|
|
228
|
+
// Server call bumps tokenVersion — instantly invalidates all existing
|
|
229
|
+
// access tokens for this user without waiting for the 1h expiry.
|
|
230
|
+
// Local state is always cleared even if the server call fails.
|
|
231
|
+
this.signOut = async () => {
|
|
232
|
+
try {
|
|
233
|
+
await new HttpsRequest({
|
|
234
|
+
method: "POST" /* POST */,
|
|
235
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/signout`,
|
|
236
|
+
headers: this.buildUserHeaders(),
|
|
237
|
+
body: {}
|
|
238
|
+
}).sendRequest();
|
|
239
|
+
} catch {
|
|
240
|
+
} finally {
|
|
241
|
+
this._saveSession(null);
|
|
191
242
|
}
|
|
192
|
-
const _data = res.result;
|
|
193
|
-
this.eUser = Credentials.fromMap(_data);
|
|
194
|
-
this.saveCredentials(this.eUser);
|
|
195
|
-
return Credentials.fromMap(_data);
|
|
196
243
|
};
|
|
244
|
+
// ─── Delete user ──────────────────────────────────────────────────────────
|
|
197
245
|
this.deleteUser = async () => {
|
|
198
|
-
if (!this.eUser)
|
|
199
|
-
throw new Error("No
|
|
200
|
-
|
|
201
|
-
const res = await new HttpsRequest({
|
|
246
|
+
if (!this.eUser)
|
|
247
|
+
throw new Error("No user signed in");
|
|
248
|
+
const res = await this.makeRequest({
|
|
202
249
|
method: "POST" /* POST */,
|
|
203
|
-
endpoint: `${this.app?.getBaseUrl()}/auth/delete`,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}).sendRequest();
|
|
210
|
-
if (!res.status) {
|
|
211
|
-
throw new Error("Something went wrong. Try Again.");
|
|
212
|
-
}
|
|
213
|
-
if (res.status === false) {
|
|
214
|
-
throw new Error(res.error ?? "Authentication Failed");
|
|
215
|
-
}
|
|
216
|
-
this.eUser = void 0;
|
|
217
|
-
this.saveCredentials(null);
|
|
250
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/users/delete`,
|
|
251
|
+
body: { userId: this.eUser.uid }
|
|
252
|
+
});
|
|
253
|
+
if (!res.success)
|
|
254
|
+
throw new Error(res.error ?? "Delete failed");
|
|
255
|
+
this._saveSession(null);
|
|
218
256
|
};
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
257
|
+
// ─── Password reset ───────────────────────────────────────────────────────
|
|
258
|
+
//
|
|
259
|
+
// Takes email — NOT uid. Never expose uid in client-controlled reset flows.
|
|
260
|
+
// Server always responds with success regardless of whether the email exists
|
|
261
|
+
// (prevents email enumeration attacks).
|
|
262
|
+
this.resetPassword = async ({ email }) => {
|
|
263
|
+
await new HttpsRequest({
|
|
223
264
|
method: "POST" /* POST */,
|
|
224
|
-
endpoint: `${this.app?.getBaseUrl()}/auth/reset`,
|
|
225
|
-
headers:
|
|
226
|
-
body: {
|
|
227
|
-
uid: luid,
|
|
228
|
-
project: this.client?.getConfig().project
|
|
229
|
-
}
|
|
265
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/password/reset-request`,
|
|
266
|
+
headers: this.buildProjectHeaders(),
|
|
267
|
+
body: { email }
|
|
230
268
|
}).sendRequest();
|
|
231
|
-
if (!res.status) {
|
|
232
|
-
throw new Error("Something went wrong. Try Again.");
|
|
233
|
-
}
|
|
234
|
-
if (res.status === false) {
|
|
235
|
-
throw new Error(res.error ?? "Authentication Failed");
|
|
236
|
-
}
|
|
237
|
-
const url = res.url;
|
|
238
|
-
return url;
|
|
239
|
-
};
|
|
240
|
-
this.signOut = async () => {
|
|
241
|
-
this.eUser = void 0;
|
|
242
|
-
this.saveCredentials(null);
|
|
243
|
-
return;
|
|
244
269
|
};
|
|
245
270
|
if (_Authentication.instance)
|
|
246
271
|
return _Authentication.instance;
|
|
247
|
-
this.client = EdmaxLabs.instance;
|
|
248
272
|
this.app = EdmaxLabs.instance;
|
|
249
273
|
_Authentication.instance = this;
|
|
250
|
-
this.
|
|
251
|
-
}
|
|
252
|
-
restoreSession() {
|
|
253
|
-
const saved = this.currentUser();
|
|
254
|
-
if (saved) {
|
|
255
|
-
this.eUser = saved;
|
|
256
|
-
this.emitValue("creds", saved);
|
|
257
|
-
}
|
|
274
|
+
this._restoreSession();
|
|
258
275
|
}
|
|
259
|
-
// Better singleton
|
|
260
276
|
static getInstance() {
|
|
261
|
-
if (!_Authentication.instance)
|
|
262
|
-
|
|
263
|
-
}
|
|
277
|
+
if (!_Authentication.instance)
|
|
278
|
+
new _Authentication();
|
|
264
279
|
return _Authentication.instance;
|
|
265
280
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
onValue(key, callback) {
|
|
274
|
-
const listeners = this.eventListeners.get(key) || [];
|
|
275
|
-
listeners.push(callback);
|
|
276
|
-
this.eventListeners.set(key, listeners);
|
|
277
|
-
return () => {
|
|
278
|
-
const filtered = listeners.filter((cb) => cb !== callback);
|
|
279
|
-
this.eventListeners.set(key, filtered);
|
|
281
|
+
// ─── Header builders ──────────────────────────────────────────────────────
|
|
282
|
+
buildProjectHeaders() {
|
|
283
|
+
return {
|
|
284
|
+
"Authorization": `${this.app?.getConfig().token ?? ""}`,
|
|
285
|
+
"X-Project": `${this.app?.getConfig().project ?? ""}`,
|
|
286
|
+
"Content-Type": "application/json"
|
|
280
287
|
};
|
|
281
288
|
}
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
289
|
+
buildUserHeaders() {
|
|
290
|
+
const headers = this.buildProjectHeaders();
|
|
291
|
+
const userToken = localStorage.getItem("user_token");
|
|
292
|
+
if (userToken) {
|
|
293
|
+
headers["X-User"] = `${userToken}`;
|
|
294
|
+
}
|
|
295
|
+
return headers;
|
|
296
|
+
}
|
|
297
|
+
/** Exposed so database/RequestHeaders.ts can read current headers */
|
|
298
|
+
getHeaders() {
|
|
299
|
+
return this.buildUserHeaders();
|
|
300
|
+
}
|
|
301
|
+
getUserToken() {
|
|
302
|
+
const userToken = localStorage.getItem("user_token");
|
|
303
|
+
if (userToken) {
|
|
304
|
+
return userToken;
|
|
305
|
+
}
|
|
306
|
+
return "";
|
|
307
|
+
}
|
|
308
|
+
// ─── Session persistence ──────────────────────────────────────────────────
|
|
309
|
+
//
|
|
310
|
+
// WHAT IS STORED:
|
|
311
|
+
// localStorage["eauth"] — Credentials shape (uid, email, displayName …)
|
|
312
|
+
// Safe to persist. No tokens. No secrets.
|
|
313
|
+
//
|
|
314
|
+
// WHAT IS NOT STORED:
|
|
315
|
+
// accessToken — memory only. Lost on page reload.
|
|
316
|
+
// Restored transparently via auto-refresh on first
|
|
317
|
+
// authenticated request.
|
|
318
|
+
// refreshToken — never touches JS at all. Lives in an httpOnly
|
|
319
|
+
// cookie the server sets. The browser sends it
|
|
320
|
+
// automatically on POST /auth/refresh.
|
|
321
|
+
/** Synchronous. Reads only from localStorage — no network call. */
|
|
322
|
+
_restoreSession() {
|
|
323
|
+
const raw = localStorage.getItem("eauth");
|
|
324
|
+
const userToken = localStorage.getItem("user_token");
|
|
325
|
+
if (userToken) {
|
|
326
|
+
this.accessToken = userToken;
|
|
327
|
+
}
|
|
328
|
+
if (!raw)
|
|
329
|
+
return;
|
|
286
330
|
try {
|
|
287
|
-
|
|
331
|
+
const creds = Credentials.fromMap(JSON.parse(raw));
|
|
332
|
+
this.eUser = creds;
|
|
333
|
+
this._emit("creds", creds);
|
|
288
334
|
} catch {
|
|
289
335
|
localStorage.removeItem("eauth");
|
|
290
|
-
return null;
|
|
291
336
|
}
|
|
292
337
|
}
|
|
293
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Persist the user shape and update the in-memory access token.
|
|
340
|
+
* Pass credentials=null to fully clear the session (sign out).
|
|
341
|
+
*/
|
|
342
|
+
_saveSession(credentials, accessToken) {
|
|
294
343
|
if (!credentials) {
|
|
295
344
|
localStorage.removeItem("eauth");
|
|
345
|
+
localStorage.removeItem("user_token");
|
|
296
346
|
this.eUser = null;
|
|
297
|
-
this.
|
|
347
|
+
this.accessToken = null;
|
|
348
|
+
this._emit("creds", null);
|
|
298
349
|
return;
|
|
299
350
|
}
|
|
300
351
|
localStorage.setItem("eauth", JSON.stringify(credentials.toMap()));
|
|
301
352
|
this.eUser = credentials;
|
|
302
|
-
|
|
353
|
+
if (accessToken !== void 0) {
|
|
354
|
+
localStorage.setItem("user_token", accessToken ?? "");
|
|
355
|
+
this.accessToken = accessToken ?? null;
|
|
356
|
+
}
|
|
357
|
+
this._emit("creds", credentials);
|
|
303
358
|
}
|
|
304
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Returns the locally cached user — no network call.
|
|
361
|
+
* Returns null if nobody is signed in.
|
|
362
|
+
*/
|
|
363
|
+
currentUser() {
|
|
364
|
+
return this.eUser;
|
|
365
|
+
}
|
|
366
|
+
// ─── Auto-refresh ─────────────────────────────────────────────────────────
|
|
367
|
+
//
|
|
368
|
+
// When the access token expires (1h), the next request gets 401.
|
|
369
|
+
// makeRequest() catches this, calls _refreshAccessToken() once, then retries.
|
|
370
|
+
//
|
|
371
|
+
// The queue pattern prevents N parallel requests from triggering N refreshes.
|
|
372
|
+
// Only the first caller refreshes; the rest wait and get the same new token.
|
|
373
|
+
async _refreshAccessToken() {
|
|
374
|
+
if (this.isRefreshing) {
|
|
375
|
+
return new Promise((resolve) => this.refreshQueue.push(resolve));
|
|
376
|
+
}
|
|
377
|
+
this.isRefreshing = true;
|
|
378
|
+
try {
|
|
379
|
+
const r = await new HttpsRequest({
|
|
380
|
+
method: "POST" /* POST */,
|
|
381
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/refresh`,
|
|
382
|
+
headers: this.buildProjectHeaders(),
|
|
383
|
+
body: {}
|
|
384
|
+
}).sendRequest();
|
|
385
|
+
const res = await r.json().catch(() => ({}));
|
|
386
|
+
if (!res.success || !res.accessToken) {
|
|
387
|
+
this._saveSession(null);
|
|
388
|
+
this._drainQueue(null);
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
this.accessToken = res.accessToken;
|
|
392
|
+
this._drainQueue(res.accessToken);
|
|
393
|
+
return res.accessToken;
|
|
394
|
+
} catch {
|
|
395
|
+
this._saveSession(null);
|
|
396
|
+
this._drainQueue(null);
|
|
397
|
+
return null;
|
|
398
|
+
} finally {
|
|
399
|
+
this.isRefreshing = false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
_drainQueue(token) {
|
|
403
|
+
this.refreshQueue.forEach((resolve) => resolve(token));
|
|
404
|
+
this.refreshQueue = [];
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Central request method — all auth HTTP calls go through here.
|
|
408
|
+
* Handles the access-token-expired → refresh → retry cycle automatically.
|
|
409
|
+
*/
|
|
410
|
+
async makeRequest(options) {
|
|
411
|
+
const res = await new HttpsRequest({
|
|
412
|
+
method: options.method,
|
|
413
|
+
endpoint: options.endpoint,
|
|
414
|
+
headers: {
|
|
415
|
+
...options.headers,
|
|
416
|
+
...this.getHeaders()
|
|
417
|
+
},
|
|
418
|
+
body: options.body ?? {},
|
|
419
|
+
file: options.file,
|
|
420
|
+
isMultipart: options.isMultipart ?? !!options.file
|
|
421
|
+
}).sendRequest();
|
|
422
|
+
const body = await res.json().catch(() => ({}));
|
|
423
|
+
const error = body.error ?? "";
|
|
424
|
+
if (!options.isRetry && res.status === 401 && typeof error === "string" && (error.toLowerCase() === "access token expired" || error.toLowerCase() === "invalid access token")) {
|
|
425
|
+
const newToken = await this._refreshAccessToken();
|
|
426
|
+
if (!newToken)
|
|
427
|
+
throw new Error("Session expired. Please sign in again.");
|
|
428
|
+
console.log("[Auth] Retrying request with new access token");
|
|
429
|
+
return this.makeRequest({ ...options, headers: this.buildUserHeaders(), isRetry: true });
|
|
430
|
+
}
|
|
431
|
+
return body;
|
|
432
|
+
}
|
|
433
|
+
// ─── Auth state listener ──────────────────────────────────────────────────
|
|
434
|
+
//
|
|
435
|
+
// Synchronous setup — fires onChange immediately with the current user
|
|
436
|
+
// (from localStorage restore), then streams changes as they happen.
|
|
437
|
+
//
|
|
438
|
+
// Also subscribes to __users via onSnapshot — if the user is disabled
|
|
439
|
+
// server-side, the client signs out in real time without a page reload.
|
|
440
|
+
//
|
|
441
|
+
// Returns an unsubscribe function (no Promise — matches Firebase's API shape).
|
|
305
442
|
authState({
|
|
306
443
|
onChange,
|
|
307
444
|
onSignOut,
|
|
308
445
|
onDeleted
|
|
309
446
|
}) {
|
|
310
|
-
let
|
|
447
|
+
let userDocUnsub;
|
|
311
448
|
const handleCredsChange = (creds) => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
userDocUnsubscribe = void 0;
|
|
315
|
-
}
|
|
449
|
+
userDocUnsub?.();
|
|
450
|
+
userDocUnsub = void 0;
|
|
316
451
|
if (!creds) {
|
|
317
452
|
this.eUser = null;
|
|
318
453
|
onSignOut?.();
|
|
@@ -321,10 +456,10 @@ var _Authentication = class _Authentication {
|
|
|
321
456
|
const userRef = this.app?.getDatabase.collection("__users").doc(creds.uid);
|
|
322
457
|
if (!userRef)
|
|
323
458
|
return;
|
|
324
|
-
|
|
459
|
+
userDocUnsub = userRef.onSnapshot(
|
|
325
460
|
(snapshot, change) => {
|
|
326
461
|
if (change === "delete") {
|
|
327
|
-
this.
|
|
462
|
+
this._saveSession(null);
|
|
328
463
|
onSignOut?.();
|
|
329
464
|
onDeleted?.();
|
|
330
465
|
return;
|
|
@@ -332,33 +467,49 @@ var _Authentication = class _Authentication {
|
|
|
332
467
|
if (change === "insert" || change === "update") {
|
|
333
468
|
if (!snapshot)
|
|
334
469
|
return;
|
|
335
|
-
if (snapshot.data.
|
|
336
|
-
this.
|
|
470
|
+
if (snapshot.data.disabled === true) {
|
|
471
|
+
this._saveSession(null);
|
|
337
472
|
onSignOut?.();
|
|
338
473
|
return;
|
|
339
474
|
}
|
|
340
475
|
const newCreds = Credentials.fromMap(snapshot.toMap());
|
|
341
|
-
if (
|
|
476
|
+
if (JSON.stringify(newCreds.toMap()) !== JSON.stringify(this.eUser?.toMap())) {
|
|
342
477
|
this.eUser = newCreds;
|
|
343
|
-
this.
|
|
478
|
+
this._saveSession(newCreds);
|
|
344
479
|
onChange(newCreds);
|
|
345
480
|
}
|
|
346
481
|
}
|
|
347
482
|
}
|
|
348
483
|
);
|
|
349
484
|
};
|
|
350
|
-
const
|
|
351
|
-
if (
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
handleCredsChange(initialUser);
|
|
485
|
+
const initial = this.currentUser();
|
|
486
|
+
if (initial)
|
|
487
|
+
onChange(initial);
|
|
488
|
+
handleCredsChange(initial);
|
|
356
489
|
const credsUnsub = this.onValue("creds", handleCredsChange);
|
|
357
490
|
return () => {
|
|
358
491
|
credsUnsub();
|
|
359
|
-
|
|
492
|
+
userDocUnsub?.();
|
|
360
493
|
};
|
|
361
494
|
}
|
|
495
|
+
// ─── Event system ─────────────────────────────────────────────────────────
|
|
496
|
+
_emit(key, value) {
|
|
497
|
+
(this.eventListeners.get(key) ?? []).forEach((l) => l(value));
|
|
498
|
+
}
|
|
499
|
+
onValue(key, callback) {
|
|
500
|
+
const listeners = this.eventListeners.get(key) ?? [];
|
|
501
|
+
listeners.push(callback);
|
|
502
|
+
this.eventListeners.set(key, listeners);
|
|
503
|
+
return () => {
|
|
504
|
+
this.eventListeners.set(key, listeners.filter((cb) => cb !== callback));
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
// ─── Dispose ──────────────────────────────────────────────────────────────
|
|
508
|
+
dispose() {
|
|
509
|
+
this._saveSession(null);
|
|
510
|
+
this.eventListeners.clear();
|
|
511
|
+
_Authentication.instance = null;
|
|
512
|
+
}
|
|
362
513
|
};
|
|
363
514
|
_Authentication.instance = null;
|
|
364
515
|
var Authentication = _Authentication;
|
|
@@ -370,7 +521,7 @@ var DocumentSnapshot = class _DocumentSnapshot {
|
|
|
370
521
|
this.data = doc;
|
|
371
522
|
}
|
|
372
523
|
static fromMap(map) {
|
|
373
|
-
const id = map.
|
|
524
|
+
const id = map._id ?? map.id ?? null;
|
|
374
525
|
const document2 = map.document ?? map.documents ?? map.data ?? map;
|
|
375
526
|
return new _DocumentSnapshot(id, document2);
|
|
376
527
|
}
|
|
@@ -432,16 +583,12 @@ var DocumentRef = class {
|
|
|
432
583
|
try {
|
|
433
584
|
validateDocumentData(data, "DocumentRef.update");
|
|
434
585
|
if (!this.persistence) {
|
|
435
|
-
const res = await
|
|
586
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
436
587
|
method: "POST" /* POST */,
|
|
437
588
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
438
|
-
headers: {
|
|
439
|
-
authorization: this.app.getConfig().token,
|
|
440
|
-
"x-project": this.app.getConfig().project,
|
|
441
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
442
|
-
},
|
|
589
|
+
headers: {},
|
|
443
590
|
body: { collection: this.collection, id: this.id, data }
|
|
444
|
-
})
|
|
591
|
+
});
|
|
445
592
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
446
593
|
}
|
|
447
594
|
const old = await this.persistence.getDoc(this.collection, this.id);
|
|
@@ -479,16 +626,12 @@ var DocumentRef = class {
|
|
|
479
626
|
async set(data) {
|
|
480
627
|
validateDocumentData(data, "DocumentRef.set");
|
|
481
628
|
if (!this.persistence) {
|
|
482
|
-
const res = await
|
|
629
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
483
630
|
method: "POST" /* POST */,
|
|
484
631
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
485
|
-
headers: {
|
|
486
|
-
authorization: this.app.getConfig().token,
|
|
487
|
-
"x-project": this.app.getConfig().project,
|
|
488
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
489
|
-
},
|
|
632
|
+
headers: {},
|
|
490
633
|
body: { collection: this.collection, data: { ...data, id: this.id } }
|
|
491
|
-
})
|
|
634
|
+
});
|
|
492
635
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
493
636
|
}
|
|
494
637
|
const existing = await this.persistence.getDoc(this.collection, this.id);
|
|
@@ -536,16 +679,12 @@ var DocumentRef = class {
|
|
|
536
679
|
this.syncEngine?.flush().catch(console.error);
|
|
537
680
|
return true;
|
|
538
681
|
}
|
|
539
|
-
const res = await
|
|
682
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
540
683
|
method: "POST" /* POST */,
|
|
541
684
|
endpoint: `${this.app.getBaseUrl()}/db/delete`,
|
|
542
|
-
headers: {
|
|
543
|
-
authorization: this.app.getConfig().token,
|
|
544
|
-
"x-project": this.app.getConfig().project,
|
|
545
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
546
|
-
},
|
|
685
|
+
headers: {},
|
|
547
686
|
body: { collection: this.collection, id: this.id }
|
|
548
|
-
})
|
|
687
|
+
});
|
|
549
688
|
return !!res?.success;
|
|
550
689
|
}
|
|
551
690
|
onSnapshot(callback) {
|
|
@@ -570,20 +709,17 @@ var DocumentRef = class {
|
|
|
570
709
|
}
|
|
571
710
|
async fetchRemoteSnapshot() {
|
|
572
711
|
try {
|
|
573
|
-
const res = await
|
|
712
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
574
713
|
method: "POST" /* POST */,
|
|
575
714
|
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
576
|
-
headers: {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
single: true
|
|
585
|
-
}
|
|
586
|
-
}).sendRequest();
|
|
715
|
+
headers: {},
|
|
716
|
+
body: { collection: this.collection, id: this.id, single: true }
|
|
717
|
+
});
|
|
718
|
+
const error = res?.error ?? null;
|
|
719
|
+
if (error) {
|
|
720
|
+
console.error(`[DocumentRef]:`, error);
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
587
723
|
if (!res?.success || !res.document)
|
|
588
724
|
return null;
|
|
589
725
|
const snapshot = DocumentSnapshot.fromMap(res.document);
|
|
@@ -592,7 +728,7 @@ var DocumentRef = class {
|
|
|
592
728
|
}
|
|
593
729
|
return snapshot;
|
|
594
730
|
} catch (error) {
|
|
595
|
-
console.error(`[DocumentRef]
|
|
731
|
+
console.error(`[DocumentRef]:`, error);
|
|
596
732
|
return null;
|
|
597
733
|
}
|
|
598
734
|
}
|
|
@@ -694,9 +830,8 @@ var Query = class {
|
|
|
694
830
|
const persistence = this.app.offline().persistence;
|
|
695
831
|
if (this.filter.length > 0 && persistence) {
|
|
696
832
|
const local = await this.getLocalFilteredSnapshots();
|
|
697
|
-
if (typeof navigator === "undefined" || !navigator.onLine)
|
|
833
|
+
if (typeof navigator === "undefined" || !navigator.onLine)
|
|
698
834
|
return local;
|
|
699
|
-
}
|
|
700
835
|
return this.refreshFromRemote();
|
|
701
836
|
}
|
|
702
837
|
if (persistence) {
|
|
@@ -710,17 +845,12 @@ var Query = class {
|
|
|
710
845
|
return this.refreshFromRemote();
|
|
711
846
|
}
|
|
712
847
|
async update(data) {
|
|
713
|
-
|
|
714
|
-
return new HttpsRequest({
|
|
848
|
+
return await this.app.getAuthentication.makeRequest({
|
|
715
849
|
method: "POST" /* POST */,
|
|
716
850
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
717
|
-
headers: {
|
|
718
|
-
body: {
|
|
719
|
-
|
|
720
|
-
filter: genfilter,
|
|
721
|
-
data
|
|
722
|
-
}
|
|
723
|
-
}).sendRequest();
|
|
851
|
+
headers: {},
|
|
852
|
+
body: { collection: this.collection, filter: this.buildFilter(), data }
|
|
853
|
+
});
|
|
724
854
|
}
|
|
725
855
|
onSnapshot(callback) {
|
|
726
856
|
const genfilter = this.buildFilter();
|
|
@@ -850,32 +980,22 @@ var Query = class {
|
|
|
850
980
|
}
|
|
851
981
|
async refreshFromRemote() {
|
|
852
982
|
try {
|
|
853
|
-
const
|
|
854
|
-
const res = await new HttpsRequest({
|
|
983
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
855
984
|
method: "POST" /* POST */,
|
|
856
985
|
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
857
|
-
headers: {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
},
|
|
862
|
-
body: {
|
|
863
|
-
collection: this.collection,
|
|
864
|
-
filter: genfilter
|
|
865
|
-
}
|
|
866
|
-
}).sendRequest();
|
|
867
|
-
if (!res?.success || !Array.isArray(res.documents)) {
|
|
986
|
+
headers: {},
|
|
987
|
+
body: { collection: this.collection, filter: this.buildFilter() }
|
|
988
|
+
});
|
|
989
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
868
990
|
return [];
|
|
869
|
-
}
|
|
870
991
|
const snapshots = res.documents.map((d) => {
|
|
871
992
|
d.id = d.id ?? d._id;
|
|
872
993
|
delete d._id;
|
|
873
994
|
return DocumentSnapshot.fromMap(d);
|
|
874
995
|
});
|
|
875
996
|
const persistence = this.app.offline().persistence;
|
|
876
|
-
if (!persistence)
|
|
997
|
+
if (!persistence)
|
|
877
998
|
return snapshots;
|
|
878
|
-
}
|
|
879
999
|
for (const snapshot of snapshots) {
|
|
880
1000
|
await persistence.applyRemoteDoc(this.collection, snapshot.data);
|
|
881
1001
|
}
|
|
@@ -926,13 +1046,12 @@ var Query = class {
|
|
|
926
1046
|
newIndex: childChange.newIndex,
|
|
927
1047
|
previousDoc: childChange.previousDoc
|
|
928
1048
|
};
|
|
929
|
-
if (childChange.type === "added")
|
|
1049
|
+
if (childChange.type === "added")
|
|
930
1050
|
callbacks.onChildAdded?.(childChange.doc, context);
|
|
931
|
-
|
|
1051
|
+
else if (childChange.type === "modified")
|
|
932
1052
|
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
933
|
-
|
|
1053
|
+
else if (childChange.type === "removed")
|
|
934
1054
|
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
935
|
-
}
|
|
936
1055
|
});
|
|
937
1056
|
},
|
|
938
1057
|
this.buildFilter()
|
|
@@ -1009,19 +1128,12 @@ var CollectionRef = class {
|
|
|
1009
1128
|
this.syncEngine?.flush().catch(console.error);
|
|
1010
1129
|
return snap;
|
|
1011
1130
|
}
|
|
1012
|
-
const res = await
|
|
1131
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1013
1132
|
method: "POST" /* POST */,
|
|
1014
1133
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
1015
|
-
headers: {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
user: JSON.stringify(this.authentication.currentUser()?.toMap())
|
|
1019
|
-
},
|
|
1020
|
-
body: {
|
|
1021
|
-
collection: this.collection,
|
|
1022
|
-
data: { ...data, id: "" }
|
|
1023
|
-
}
|
|
1024
|
-
}).sendRequest();
|
|
1134
|
+
headers: {},
|
|
1135
|
+
body: { collection: this.collection, data: { ...data } }
|
|
1136
|
+
});
|
|
1025
1137
|
if (!res?.success || !res.document)
|
|
1026
1138
|
return null;
|
|
1027
1139
|
return DocumentSnapshot.fromMap(res.document);
|
|
@@ -1070,23 +1182,19 @@ var CollectionRef = class {
|
|
|
1070
1182
|
}
|
|
1071
1183
|
async refreshFromRemote() {
|
|
1072
1184
|
try {
|
|
1073
|
-
const res = await
|
|
1185
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1074
1186
|
method: "POST" /* POST */,
|
|
1075
1187
|
endpoint: `${serverURI}/db/read`,
|
|
1076
|
-
headers: {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
body: {
|
|
1083
|
-
collection: this.collection,
|
|
1084
|
-
filter: {}
|
|
1085
|
-
}
|
|
1086
|
-
}).sendRequest();
|
|
1087
|
-
if (!res?.success || !Array.isArray(res.documents)) {
|
|
1188
|
+
headers: {},
|
|
1189
|
+
body: { collection: this.collection, filter: {} }
|
|
1190
|
+
});
|
|
1191
|
+
const error = res?.error ?? null;
|
|
1192
|
+
if (error) {
|
|
1193
|
+
console.error(`[CollectionRef]:`, error);
|
|
1088
1194
|
return [];
|
|
1089
1195
|
}
|
|
1196
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
1197
|
+
return [];
|
|
1090
1198
|
const normalized = res.documents.map((raw) => {
|
|
1091
1199
|
const doc = { ...raw };
|
|
1092
1200
|
doc.id = doc.id ?? doc._id;
|
|
@@ -1101,36 +1209,32 @@ var CollectionRef = class {
|
|
|
1101
1209
|
}
|
|
1102
1210
|
return normalized.map((doc) => DocumentSnapshot.fromMap(doc));
|
|
1103
1211
|
} catch (error) {
|
|
1104
|
-
console.error(
|
|
1212
|
+
console.error(`[CollectionRef]:`, error);
|
|
1105
1213
|
return [];
|
|
1106
1214
|
}
|
|
1107
1215
|
}
|
|
1108
1216
|
async fetchRemoteSnapshots() {
|
|
1109
1217
|
try {
|
|
1110
|
-
const res = await
|
|
1218
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1111
1219
|
method: "POST" /* POST */,
|
|
1112
1220
|
endpoint: `${serverURI}/db/read`,
|
|
1113
|
-
headers: {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
body: {
|
|
1120
|
-
collection: this.collection,
|
|
1121
|
-
filter: {}
|
|
1122
|
-
}
|
|
1123
|
-
}).sendRequest();
|
|
1124
|
-
if (!res?.success || !Array.isArray(res.documents)) {
|
|
1221
|
+
headers: {},
|
|
1222
|
+
body: { collection: this.collection, filter: {} }
|
|
1223
|
+
});
|
|
1224
|
+
const error = res?.error ?? null;
|
|
1225
|
+
if (error) {
|
|
1226
|
+
console.error(`[CollectionRef]:`, error);
|
|
1125
1227
|
return [];
|
|
1126
1228
|
}
|
|
1229
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
1230
|
+
return [];
|
|
1127
1231
|
return res.documents.map((d) => {
|
|
1128
1232
|
d.id = d.id ?? d._id;
|
|
1129
1233
|
delete d._id;
|
|
1130
1234
|
return DocumentSnapshot.fromMap(d);
|
|
1131
1235
|
});
|
|
1132
1236
|
} catch (error) {
|
|
1133
|
-
console.error(
|
|
1237
|
+
console.error(`[CollectionRef]:`, error);
|
|
1134
1238
|
return [];
|
|
1135
1239
|
}
|
|
1136
1240
|
}
|
|
@@ -1169,20 +1273,18 @@ var CollectionRef = class {
|
|
|
1169
1273
|
newIndex: childChange.newIndex,
|
|
1170
1274
|
previousDoc: childChange.previousDoc
|
|
1171
1275
|
};
|
|
1172
|
-
if (childChange.type === "added")
|
|
1276
|
+
if (childChange.type === "added")
|
|
1173
1277
|
callbacks.onChildAdded?.(childChange.doc, context);
|
|
1174
|
-
|
|
1278
|
+
else if (childChange.type === "modified")
|
|
1175
1279
|
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
1176
|
-
|
|
1280
|
+
else if (childChange.type === "removed")
|
|
1177
1281
|
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
1178
|
-
}
|
|
1179
1282
|
});
|
|
1180
1283
|
});
|
|
1181
1284
|
}
|
|
1182
1285
|
emitInitialChildEvents(snapshots, callbacks) {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const context = {
|
|
1286
|
+
diffSnapshots([], snapshots).forEach((change) => {
|
|
1287
|
+
callbacks.onChildAdded?.(change.doc, {
|
|
1186
1288
|
snapshots,
|
|
1187
1289
|
source: "initial",
|
|
1188
1290
|
changedDocId: change.doc.id,
|
|
@@ -1190,8 +1292,7 @@ var CollectionRef = class {
|
|
|
1190
1292
|
oldIndex: change.oldIndex,
|
|
1191
1293
|
newIndex: change.newIndex,
|
|
1192
1294
|
previousDoc: change.previousDoc
|
|
1193
|
-
};
|
|
1194
|
-
callbacks.onChildAdded?.(change.doc, context);
|
|
1295
|
+
});
|
|
1195
1296
|
});
|
|
1196
1297
|
}
|
|
1197
1298
|
};
|
|
@@ -1203,38 +1304,19 @@ var Batch = class {
|
|
|
1203
1304
|
this.app = app;
|
|
1204
1305
|
}
|
|
1205
1306
|
set(docRef, data) {
|
|
1206
|
-
this.ops.push({
|
|
1207
|
-
op: "set",
|
|
1208
|
-
collection: docRef.collection,
|
|
1209
|
-
id: docRef.id,
|
|
1210
|
-
data
|
|
1211
|
-
});
|
|
1307
|
+
this.ops.push({ op: "set", collection: docRef.collection, id: docRef.id, data });
|
|
1212
1308
|
return this;
|
|
1213
1309
|
}
|
|
1214
1310
|
create(docRef, data) {
|
|
1215
|
-
this.ops.push({
|
|
1216
|
-
op: "create",
|
|
1217
|
-
collection: docRef.collection,
|
|
1218
|
-
id: docRef.id,
|
|
1219
|
-
data
|
|
1220
|
-
});
|
|
1311
|
+
this.ops.push({ op: "create", collection: docRef.collection, id: docRef.id, data });
|
|
1221
1312
|
return this;
|
|
1222
1313
|
}
|
|
1223
1314
|
update(docRef, data) {
|
|
1224
|
-
this.ops.push({
|
|
1225
|
-
op: "update",
|
|
1226
|
-
collection: docRef.collection,
|
|
1227
|
-
id: docRef.id,
|
|
1228
|
-
data
|
|
1229
|
-
});
|
|
1315
|
+
this.ops.push({ op: "update", collection: docRef.collection, id: docRef.id, data });
|
|
1230
1316
|
return this;
|
|
1231
1317
|
}
|
|
1232
1318
|
delete(docRef) {
|
|
1233
|
-
this.ops.push({
|
|
1234
|
-
op: "delete",
|
|
1235
|
-
collection: docRef.collection,
|
|
1236
|
-
id: docRef.id
|
|
1237
|
-
});
|
|
1319
|
+
this.ops.push({ op: "delete", collection: docRef.collection, id: docRef.id });
|
|
1238
1320
|
return this;
|
|
1239
1321
|
}
|
|
1240
1322
|
async commit() {
|
|
@@ -1283,16 +1365,12 @@ var Batch = class {
|
|
|
1283
1365
|
syncEngine?.flush().catch(console.error);
|
|
1284
1366
|
return { success: true, results };
|
|
1285
1367
|
}
|
|
1286
|
-
const res = await
|
|
1368
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1287
1369
|
method: "POST" /* POST */,
|
|
1288
1370
|
endpoint: `${serverURI}/db/batch`,
|
|
1289
|
-
headers: {
|
|
1290
|
-
authorization: this.app.getConfig().token,
|
|
1291
|
-
"x-project": this.app.getConfig().project,
|
|
1292
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
1293
|
-
},
|
|
1371
|
+
headers: {},
|
|
1294
1372
|
body: { ops: this.ops }
|
|
1295
|
-
})
|
|
1373
|
+
});
|
|
1296
1374
|
if (res?.error) {
|
|
1297
1375
|
throw new Error(res.error.message || "Batch commit failed");
|
|
1298
1376
|
}
|
|
@@ -1339,12 +1417,12 @@ var Database = class {
|
|
|
1339
1417
|
}
|
|
1340
1418
|
};
|
|
1341
1419
|
await transactionFn(tx);
|
|
1342
|
-
const res = await
|
|
1420
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1343
1421
|
method: "POST" /* POST */,
|
|
1344
1422
|
endpoint: `${this.app.getBaseUrl()}/db/transaction`,
|
|
1345
|
-
headers: {
|
|
1423
|
+
headers: {},
|
|
1346
1424
|
body: { ops: tx.ops }
|
|
1347
|
-
})
|
|
1425
|
+
});
|
|
1348
1426
|
if (res?.error) {
|
|
1349
1427
|
throw new Error(res.error.message || "Transaction failed");
|
|
1350
1428
|
}
|
|
@@ -1371,6 +1449,14 @@ var Database = class {
|
|
|
1371
1449
|
}
|
|
1372
1450
|
};
|
|
1373
1451
|
|
|
1452
|
+
// src/core/RequestHeaders.ts
|
|
1453
|
+
function socketAuth(app) {
|
|
1454
|
+
const base = {
|
|
1455
|
+
...app.getAuthentication.getHeaders()
|
|
1456
|
+
};
|
|
1457
|
+
return base;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1374
1460
|
// src/database/Realtime.ts
|
|
1375
1461
|
var import_socket = require("socket.io-client");
|
|
1376
1462
|
|
|
@@ -1380,7 +1466,7 @@ function normalizePayload(payload) {
|
|
|
1380
1466
|
if (!raw)
|
|
1381
1467
|
return null;
|
|
1382
1468
|
const doc = { ...raw };
|
|
1383
|
-
doc.id = doc.id ?? raw?.id ??
|
|
1469
|
+
doc.id = raw?._id ?? doc.id ?? raw?.id ?? payload?._id ?? payload?.id;
|
|
1384
1470
|
delete doc._id;
|
|
1385
1471
|
return {
|
|
1386
1472
|
change: payload?.change ?? raw?.change ?? null,
|
|
@@ -1405,11 +1491,7 @@ var Realtime = class {
|
|
|
1405
1491
|
this.socket = (0, import_socket.io)(this.app.getSocketUrl(), {
|
|
1406
1492
|
transports: ["websocket"],
|
|
1407
1493
|
auth: {
|
|
1408
|
-
|
|
1409
|
-
"x-project": this.app.getConfig().project,
|
|
1410
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
1411
|
-
user: this.app.getAuthentication.currentUser()
|
|
1412
|
-
// Pass user info for personalized permissions
|
|
1494
|
+
...socketAuth(this.app)
|
|
1413
1495
|
},
|
|
1414
1496
|
autoConnect: true,
|
|
1415
1497
|
reconnection: true,
|
|
@@ -1453,19 +1535,19 @@ var Realtime = class {
|
|
|
1453
1535
|
/**
|
|
1454
1536
|
* Low-level collection subscription (used by RealtimeBridge)
|
|
1455
1537
|
*/
|
|
1538
|
+
// Realtime.ts - subscribeToCollectionRaw
|
|
1456
1539
|
subscribeToCollectionRaw(collection, callback, filter = {}) {
|
|
1457
1540
|
this.connect();
|
|
1458
|
-
const lid = collection
|
|
1541
|
+
const lid = `${collection}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1459
1542
|
const handlers = [];
|
|
1460
|
-
this.socket.emit("subscribe", { collection, filter });
|
|
1543
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1461
1544
|
this.events.forEach((event) => {
|
|
1462
1545
|
const channel = `${lid}-${event}`;
|
|
1463
1546
|
const fn = (payload) => {
|
|
1464
1547
|
const normalized = normalizePayload(payload);
|
|
1465
1548
|
if (!normalized) {
|
|
1466
|
-
if (event === "delete")
|
|
1549
|
+
if (event === "delete")
|
|
1467
1550
|
callback(payload, "delete");
|
|
1468
|
-
}
|
|
1469
1551
|
return;
|
|
1470
1552
|
}
|
|
1471
1553
|
callback(normalized.data, event);
|
|
@@ -1481,10 +1563,10 @@ var Realtime = class {
|
|
|
1481
1563
|
*/
|
|
1482
1564
|
subscribeToDocumentRaw(collection, id, callback) {
|
|
1483
1565
|
this.connect();
|
|
1484
|
-
const lid = collection
|
|
1566
|
+
const lid = `${collection}_${id}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1485
1567
|
const filter = { _id: id };
|
|
1486
1568
|
const handlers = [];
|
|
1487
|
-
this.socket.emit("subscribe", { collection, filter });
|
|
1569
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1488
1570
|
this.events.forEach((event) => {
|
|
1489
1571
|
const channel = `${lid}-${event}`;
|
|
1490
1572
|
const fn = (payload) => {
|
|
@@ -1544,12 +1626,12 @@ var Functions = class {
|
|
|
1544
1626
|
}
|
|
1545
1627
|
async call(functionName, data) {
|
|
1546
1628
|
try {
|
|
1547
|
-
const res = await
|
|
1629
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1548
1630
|
method: "POST" /* POST */,
|
|
1549
1631
|
endpoint: serverURI + "/functions/call/" + functionName,
|
|
1550
|
-
headers: {
|
|
1632
|
+
headers: {},
|
|
1551
1633
|
body: data
|
|
1552
|
-
})
|
|
1634
|
+
});
|
|
1553
1635
|
return res;
|
|
1554
1636
|
} catch (err) {
|
|
1555
1637
|
throw {
|
|
@@ -1567,15 +1649,15 @@ var Hosting = class {
|
|
|
1567
1649
|
}
|
|
1568
1650
|
async createSubdomain(name) {
|
|
1569
1651
|
try {
|
|
1570
|
-
const res = await
|
|
1652
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1571
1653
|
method: "POST" /* POST */,
|
|
1572
1654
|
endpoint: this.app.getBaseUrl() + "/hosting/register",
|
|
1573
|
-
headers: {
|
|
1655
|
+
headers: {},
|
|
1574
1656
|
body: {
|
|
1575
1657
|
hostname: name,
|
|
1576
1658
|
project: this.app.getConfig().project
|
|
1577
1659
|
}
|
|
1578
|
-
})
|
|
1660
|
+
});
|
|
1579
1661
|
return res;
|
|
1580
1662
|
} catch (err) {
|
|
1581
1663
|
throw {
|
|
@@ -2421,6 +2503,7 @@ var SyncEngine = class {
|
|
|
2421
2503
|
constructor(app, persistence, store, realtimeBridge) {
|
|
2422
2504
|
this.realtimeBridge = null;
|
|
2423
2505
|
this.syncing = false;
|
|
2506
|
+
// Use ReturnType<typeof setTimeout> for cross-env timeout type (works in browser and Node)
|
|
2424
2507
|
this.retryTimeout = null;
|
|
2425
2508
|
this.MAX_RETRIES = 5;
|
|
2426
2509
|
this.BASE_RETRY_DELAY = 2e3;
|
|
@@ -2433,6 +2516,7 @@ var SyncEngine = class {
|
|
|
2433
2516
|
this.persistence = persistence;
|
|
2434
2517
|
this.store = store;
|
|
2435
2518
|
this.realtimeBridge = realtimeBridge || null;
|
|
2519
|
+
this.authentication = app.getAuthentication;
|
|
2436
2520
|
this.persistence.recoverSyncingMutations().catch(console.error);
|
|
2437
2521
|
if (typeof window !== "undefined") {
|
|
2438
2522
|
window.addEventListener("online", this.handleOnline);
|
|
@@ -2509,15 +2593,15 @@ var SyncEngine = class {
|
|
|
2509
2593
|
}
|
|
2510
2594
|
}
|
|
2511
2595
|
async syncCreate(mutation) {
|
|
2512
|
-
const res = await
|
|
2596
|
+
const res = await this.authentication.makeRequest({
|
|
2513
2597
|
method: "POST" /* POST */,
|
|
2514
2598
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
2515
|
-
headers: {
|
|
2599
|
+
headers: {},
|
|
2516
2600
|
body: {
|
|
2517
2601
|
collection: mutation.collection,
|
|
2518
2602
|
data: mutation.payload
|
|
2519
2603
|
}
|
|
2520
|
-
})
|
|
2604
|
+
});
|
|
2521
2605
|
if (!res?.success || !res.id)
|
|
2522
2606
|
return false;
|
|
2523
2607
|
const oldId = mutation.documentId;
|
|
@@ -2535,21 +2619,16 @@ var SyncEngine = class {
|
|
|
2535
2619
|
return true;
|
|
2536
2620
|
}
|
|
2537
2621
|
async syncUpdate(mutation) {
|
|
2538
|
-
const res = await
|
|
2622
|
+
const res = await this.authentication.makeRequest({
|
|
2539
2623
|
method: "POST" /* POST */,
|
|
2540
2624
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
2541
|
-
headers: {
|
|
2542
|
-
authorization: this.app.getConfig().token,
|
|
2543
|
-
"x-project": this.app.getConfig().project,
|
|
2544
|
-
user: JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
2545
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
2546
|
-
},
|
|
2625
|
+
headers: {},
|
|
2547
2626
|
body: {
|
|
2548
2627
|
collection: mutation.collection,
|
|
2549
2628
|
document: mutation.documentId,
|
|
2550
2629
|
data: mutation.payload
|
|
2551
2630
|
}
|
|
2552
|
-
})
|
|
2631
|
+
});
|
|
2553
2632
|
if (!res?.success)
|
|
2554
2633
|
return false;
|
|
2555
2634
|
const local = await this.persistence.upsertDoc({
|
|
@@ -2572,20 +2651,15 @@ var SyncEngine = class {
|
|
|
2572
2651
|
return true;
|
|
2573
2652
|
}
|
|
2574
2653
|
async syncDelete(mutation) {
|
|
2575
|
-
const res = await
|
|
2654
|
+
const res = await this.authentication.makeRequest({
|
|
2576
2655
|
method: "POST" /* POST */,
|
|
2577
2656
|
endpoint: `${this.app.getBaseUrl()}/db/delete`,
|
|
2578
|
-
headers: {
|
|
2579
|
-
authorization: this.app.getConfig().token,
|
|
2580
|
-
"x-project": this.app.getConfig().project,
|
|
2581
|
-
user: JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
2582
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
2583
|
-
},
|
|
2657
|
+
headers: {},
|
|
2584
2658
|
body: {
|
|
2585
2659
|
collection: mutation.collection,
|
|
2586
2660
|
document: mutation.documentId
|
|
2587
2661
|
}
|
|
2588
|
-
})
|
|
2662
|
+
});
|
|
2589
2663
|
if (!res?.success)
|
|
2590
2664
|
return false;
|
|
2591
2665
|
await this.persistence.markDeleted(mutation.collection, mutation.documentId, 0);
|
|
@@ -2684,13 +2758,11 @@ var StorageRef = class {
|
|
|
2684
2758
|
this.app = app;
|
|
2685
2759
|
}
|
|
2686
2760
|
async get(id) {
|
|
2687
|
-
const res = await
|
|
2761
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2688
2762
|
method: "GET" /* GET */,
|
|
2689
2763
|
endpoint: serverURI + `/storage/${id}`,
|
|
2690
|
-
headers: {
|
|
2691
|
-
|
|
2692
|
-
}
|
|
2693
|
-
}).sendRequest();
|
|
2764
|
+
headers: {}
|
|
2765
|
+
});
|
|
2694
2766
|
if (res.success) {
|
|
2695
2767
|
if (res.document === void 0)
|
|
2696
2768
|
return void 0;
|
|
@@ -2701,13 +2773,11 @@ var StorageRef = class {
|
|
|
2701
2773
|
}
|
|
2702
2774
|
}
|
|
2703
2775
|
async getMeta(id) {
|
|
2704
|
-
const res = await
|
|
2776
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2705
2777
|
method: "GET" /* GET */,
|
|
2706
2778
|
endpoint: serverURI + `/storage/${id}/info`,
|
|
2707
|
-
headers: {
|
|
2708
|
-
|
|
2709
|
-
}
|
|
2710
|
-
}).sendRequest();
|
|
2779
|
+
headers: {}
|
|
2780
|
+
});
|
|
2711
2781
|
if (res.success) {
|
|
2712
2782
|
if (res.document === void 0)
|
|
2713
2783
|
return void 0;
|
|
@@ -2721,13 +2791,13 @@ var StorageRef = class {
|
|
|
2721
2791
|
if (!srcFile) {
|
|
2722
2792
|
throw new Error("Invalid File");
|
|
2723
2793
|
}
|
|
2724
|
-
const res = await
|
|
2794
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2725
2795
|
method: "POST" /* POST */,
|
|
2726
2796
|
endpoint: serverURI + "/storage/file/upload",
|
|
2727
|
-
headers: {
|
|
2797
|
+
headers: {},
|
|
2728
2798
|
file: srcFile,
|
|
2729
2799
|
isMultipart: true
|
|
2730
|
-
})
|
|
2800
|
+
});
|
|
2731
2801
|
if (res.success) {
|
|
2732
2802
|
if (res.document === void 0)
|
|
2733
2803
|
return void 0;
|
|
@@ -2738,14 +2808,14 @@ var StorageRef = class {
|
|
|
2738
2808
|
}
|
|
2739
2809
|
}
|
|
2740
2810
|
async delete(id) {
|
|
2741
|
-
const res = await
|
|
2811
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2742
2812
|
method: "POST" /* POST */,
|
|
2743
2813
|
endpoint: serverURI + "/storage/file/delete",
|
|
2744
|
-
headers: {
|
|
2814
|
+
headers: {},
|
|
2745
2815
|
body: {
|
|
2746
2816
|
file_id: id
|
|
2747
2817
|
}
|
|
2748
|
-
})
|
|
2818
|
+
});
|
|
2749
2819
|
return res;
|
|
2750
2820
|
}
|
|
2751
2821
|
};
|