edmaxlabs-core 2.7.2 → 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 +451 -405
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -25
- package/dist/index.d.ts +98 -25
- package/dist/index.mjs +451 -405
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,42 +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, createdAt, updatedAt,
|
|
35
|
+
constructor(uid, displayInfo, data, verified, disabled, provider, createdAt, updatedAt, lastLogin) {
|
|
36
36
|
this.uid = uid;
|
|
37
37
|
this.displayInfo = displayInfo;
|
|
38
|
+
this.data = data;
|
|
39
|
+
this.verified = verified;
|
|
40
|
+
this.disabled = disabled;
|
|
41
|
+
this.provider = provider;
|
|
38
42
|
this.createdAt = createdAt;
|
|
39
43
|
this.updatedAt = updatedAt;
|
|
40
|
-
this.
|
|
41
|
-
this.logged = logged === true;
|
|
44
|
+
this.lastLogin = lastLogin;
|
|
42
45
|
}
|
|
43
46
|
static fromMap(map) {
|
|
44
|
-
const uid = map.id ?? map.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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(
|
|
52
73
|
uid,
|
|
53
|
-
|
|
74
|
+
displayInfo,
|
|
75
|
+
data,
|
|
76
|
+
verified,
|
|
77
|
+
disabled,
|
|
78
|
+
provider,
|
|
54
79
|
createdAt,
|
|
55
80
|
updatedAt,
|
|
56
|
-
|
|
57
|
-
logged
|
|
81
|
+
lastLogin
|
|
58
82
|
);
|
|
59
|
-
return
|
|
83
|
+
return output;
|
|
60
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.
|
|
61
89
|
toMap() {
|
|
62
90
|
return {
|
|
63
91
|
uid: this.uid,
|
|
64
92
|
displayInfo: this.displayInfo,
|
|
93
|
+
data: this.data,
|
|
94
|
+
verified: this.verified,
|
|
95
|
+
disabled: this.disabled,
|
|
96
|
+
provider: this.provider,
|
|
65
97
|
createdAt: this.createdAt,
|
|
66
98
|
updatedAt: this.updatedAt,
|
|
67
|
-
|
|
68
|
-
logged: this.logged
|
|
99
|
+
lastLogin: this.lastLogin
|
|
69
100
|
};
|
|
70
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
|
+
}
|
|
71
119
|
};
|
|
72
120
|
|
|
73
121
|
// src/utils/HttpsRequest.ts
|
|
@@ -100,11 +148,7 @@ var HttpsRequest = class {
|
|
|
100
148
|
headers: this.headers,
|
|
101
149
|
body: formData
|
|
102
150
|
});
|
|
103
|
-
|
|
104
|
-
const errorText = await response2.text().catch(() => "Network error");
|
|
105
|
-
throw new Error(`HTTP ${response2.status}: ${errorText}`);
|
|
106
|
-
}
|
|
107
|
-
return await response2.json().catch(() => ({}));
|
|
151
|
+
return response2;
|
|
108
152
|
}
|
|
109
153
|
const response = await fetch(this.endpoint, {
|
|
110
154
|
method: this.method,
|
|
@@ -114,11 +158,7 @@ var HttpsRequest = class {
|
|
|
114
158
|
},
|
|
115
159
|
body: this.method !== "GET" /* GET */ ? JSON.stringify(this.body) : void 0
|
|
116
160
|
});
|
|
117
|
-
|
|
118
|
-
const errorText = await response.text().catch(() => "Network error");
|
|
119
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
120
|
-
}
|
|
121
|
-
return await response.json().catch(() => ({}));
|
|
161
|
+
return response;
|
|
122
162
|
} catch (error) {
|
|
123
163
|
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
|
124
164
|
throw new Error("Network connection failed. Please check your internet connection.");
|
|
@@ -134,221 +174,292 @@ var HttpsRequest = class {
|
|
|
134
174
|
// src/authentication/Authentication.ts
|
|
135
175
|
var _Authentication = class _Authentication {
|
|
136
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 = [];
|
|
137
187
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
138
|
-
|
|
139
|
-
this.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const app = this.app?.getDatabase;
|
|
154
|
-
const data = await app?.collection("authentication").query.where({
|
|
155
|
-
key: "email",
|
|
156
|
-
op: "===",
|
|
157
|
-
value: email
|
|
158
|
-
}).get();
|
|
159
|
-
if (data.length > 0) {
|
|
160
|
-
throw new Error("Email Already In Use.");
|
|
161
|
-
}
|
|
162
|
-
const userRef = await app?.collection("authentication").add({
|
|
163
|
-
email,
|
|
164
|
-
password,
|
|
165
|
-
logged: true,
|
|
166
|
-
lastLogged: Date.now(),
|
|
167
|
-
displayInfo: {
|
|
168
|
-
firstname: email.split("@")[0],
|
|
169
|
-
lastname: "",
|
|
170
|
-
surname: "",
|
|
171
|
-
profile: "./logo.png",
|
|
172
|
-
email,
|
|
173
|
-
phone: ""
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
if (!userRef?.id || userRef?.id === "") {
|
|
177
|
-
throw new Error("Something went wrong try Again.");
|
|
178
|
-
}
|
|
179
|
-
this.eUser = Credentials.fromMap(userRef);
|
|
180
|
-
this.saveCredentials(this.eUser);
|
|
181
|
-
return Credentials.fromMap(userRef.toMap());
|
|
182
|
-
};
|
|
183
|
-
this.signInWithEmailAndPassword = async ({
|
|
184
|
-
email,
|
|
185
|
-
password
|
|
186
|
-
}) => {
|
|
187
|
-
const app = this.app?.getDatabase;
|
|
188
|
-
const data = await app?.collection("authentication").query.where({
|
|
189
|
-
key: "email",
|
|
190
|
-
op: "===",
|
|
191
|
-
value: email
|
|
192
|
-
}).where({
|
|
193
|
-
key: "password",
|
|
194
|
-
op: "===",
|
|
195
|
-
value: password
|
|
196
|
-
}).get();
|
|
197
|
-
console.log("Auth Data:", data);
|
|
198
|
-
if (data === void 0) {
|
|
199
|
-
throw new Error("Auth Failed.");
|
|
200
|
-
}
|
|
201
|
-
if (data?.length === 0) {
|
|
202
|
-
throw new Error("User Not Found.");
|
|
188
|
+
// ─── Sign up ──────────────────────────────────────────────────────────────
|
|
189
|
+
this.createUserWithEmailAndPassword = async (data) => {
|
|
190
|
+
this._saveSession(null);
|
|
191
|
+
const r = await new HttpsRequest({
|
|
192
|
+
method: "POST" /* POST */,
|
|
193
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/signup`,
|
|
194
|
+
// ← /auth/signup
|
|
195
|
+
headers: this.buildProjectHeaders(),
|
|
196
|
+
body: data
|
|
197
|
+
}).sendRequest();
|
|
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);
|
|
203
203
|
}
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
lastLogged: Date.now()
|
|
208
|
-
});
|
|
209
|
-
this.eUser = Credentials.fromMap(_data);
|
|
210
|
-
this.saveCredentials(this.eUser);
|
|
211
|
-
return Credentials.fromMap(_data);
|
|
204
|
+
const creds = Credentials.fromMap(res.result);
|
|
205
|
+
this._saveSession(creds, res.accessToken ?? null);
|
|
206
|
+
return creds;
|
|
212
207
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
208
|
+
// ─── Sign in ──────────────────────────────────────────────────────────────
|
|
209
|
+
this.signInWithEmailAndPassword = async ({ email, password }) => {
|
|
210
|
+
const r = await new HttpsRequest({
|
|
211
|
+
method: "POST" /* POST */,
|
|
212
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/login`,
|
|
213
|
+
headers: this.buildProjectHeaders(),
|
|
214
|
+
body: { email, password }
|
|
215
|
+
}).sendRequest();
|
|
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);
|
|
221
221
|
}
|
|
222
|
-
|
|
223
|
-
this.
|
|
222
|
+
const creds = Credentials.fromMap(res.result);
|
|
223
|
+
this._saveSession(creds, res.accessToken ?? null);
|
|
224
|
+
return creds;
|
|
224
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.
|
|
225
231
|
this.signOut = async () => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
};
|
|
237
|
-
this.resetPassword = async () => {
|
|
238
|
-
const app = this.app?.getDatabase;
|
|
239
|
-
const luid = this.currentUser();
|
|
240
|
-
const payload = {
|
|
241
|
-
uid: luid?.uid,
|
|
242
|
-
ttl: 3
|
|
243
|
-
//minutes
|
|
244
|
-
};
|
|
245
|
-
const id = await app?.collection("_auth_tokens").add(payload);
|
|
246
|
-
if (id) {
|
|
247
|
-
return `https://auth.edmaxlabs.com/reset/${id}`;
|
|
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);
|
|
248
242
|
}
|
|
249
|
-
throw new Error("Failed to request reset tokens. Try Again");
|
|
250
243
|
};
|
|
251
|
-
|
|
252
|
-
|
|
244
|
+
// ─── Delete user ──────────────────────────────────────────────────────────
|
|
245
|
+
this.deleteUser = async () => {
|
|
246
|
+
if (!this.eUser)
|
|
247
|
+
throw new Error("No user signed in");
|
|
248
|
+
const res = await this.makeRequest({
|
|
253
249
|
method: "POST" /* POST */,
|
|
254
|
-
endpoint: this.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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);
|
|
256
|
+
};
|
|
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({
|
|
264
|
+
method: "POST" /* POST */,
|
|
265
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/password/reset-request`,
|
|
266
|
+
headers: this.buildProjectHeaders(),
|
|
267
|
+
body: { email }
|
|
263
268
|
}).sendRequest();
|
|
264
|
-
return res;
|
|
265
269
|
};
|
|
266
270
|
if (_Authentication.instance)
|
|
267
271
|
return _Authentication.instance;
|
|
268
|
-
this.
|
|
269
|
-
this.app = new EdmaxLabs({
|
|
270
|
-
token: "auth",
|
|
271
|
-
project: "698457c2f7448550b9e166c4"
|
|
272
|
-
});
|
|
272
|
+
this.app = EdmaxLabs.instance;
|
|
273
273
|
_Authentication.instance = this;
|
|
274
|
-
this.
|
|
275
|
-
}
|
|
276
|
-
restoreSession() {
|
|
277
|
-
const saved = this.currentUser();
|
|
278
|
-
if (saved) {
|
|
279
|
-
this.eUser = saved;
|
|
280
|
-
this.emitValue("creds", saved);
|
|
281
|
-
}
|
|
274
|
+
this._restoreSession();
|
|
282
275
|
}
|
|
283
|
-
// Better singleton
|
|
284
276
|
static getInstance() {
|
|
285
|
-
if (!_Authentication.instance)
|
|
286
|
-
|
|
287
|
-
}
|
|
277
|
+
if (!_Authentication.instance)
|
|
278
|
+
new _Authentication();
|
|
288
279
|
return _Authentication.instance;
|
|
289
280
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
onValue(key, callback) {
|
|
298
|
-
const listeners = this.eventListeners.get(key) || [];
|
|
299
|
-
listeners.push(callback);
|
|
300
|
-
this.eventListeners.set(key, listeners);
|
|
301
|
-
return () => {
|
|
302
|
-
const filtered = listeners.filter((cb) => cb !== callback);
|
|
303
|
-
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"
|
|
304
287
|
};
|
|
305
288
|
}
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
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;
|
|
310
330
|
try {
|
|
311
|
-
|
|
331
|
+
const creds = Credentials.fromMap(JSON.parse(raw));
|
|
332
|
+
this.eUser = creds;
|
|
333
|
+
this._emit("creds", creds);
|
|
312
334
|
} catch {
|
|
313
335
|
localStorage.removeItem("eauth");
|
|
314
|
-
return null;
|
|
315
336
|
}
|
|
316
337
|
}
|
|
317
|
-
|
|
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) {
|
|
318
343
|
if (!credentials) {
|
|
319
344
|
localStorage.removeItem("eauth");
|
|
345
|
+
localStorage.removeItem("user_token");
|
|
320
346
|
this.eUser = null;
|
|
321
|
-
this.
|
|
347
|
+
this.accessToken = null;
|
|
348
|
+
this._emit("creds", null);
|
|
322
349
|
return;
|
|
323
350
|
}
|
|
324
351
|
localStorage.setItem("eauth", JSON.stringify(credentials.toMap()));
|
|
325
352
|
this.eUser = credentials;
|
|
326
|
-
|
|
353
|
+
if (accessToken !== void 0) {
|
|
354
|
+
localStorage.setItem("user_token", accessToken ?? "");
|
|
355
|
+
this.accessToken = accessToken ?? null;
|
|
356
|
+
}
|
|
357
|
+
this._emit("creds", credentials);
|
|
358
|
+
}
|
|
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 = [];
|
|
327
405
|
}
|
|
328
|
-
|
|
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).
|
|
329
442
|
authState({
|
|
330
443
|
onChange,
|
|
331
444
|
onSignOut,
|
|
332
445
|
onDeleted
|
|
333
446
|
}) {
|
|
334
|
-
let
|
|
447
|
+
let userDocUnsub;
|
|
335
448
|
const handleCredsChange = (creds) => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
userDocUnsubscribe = void 0;
|
|
339
|
-
}
|
|
449
|
+
userDocUnsub?.();
|
|
450
|
+
userDocUnsub = void 0;
|
|
340
451
|
if (!creds) {
|
|
341
452
|
this.eUser = null;
|
|
342
453
|
onSignOut?.();
|
|
343
454
|
return;
|
|
344
455
|
}
|
|
345
|
-
const userRef = this.app?.getDatabase.collection("
|
|
456
|
+
const userRef = this.app?.getDatabase.collection("__users").doc(creds.uid);
|
|
346
457
|
if (!userRef)
|
|
347
458
|
return;
|
|
348
|
-
|
|
459
|
+
userDocUnsub = userRef.onSnapshot(
|
|
349
460
|
(snapshot, change) => {
|
|
350
461
|
if (change === "delete") {
|
|
351
|
-
this.
|
|
462
|
+
this._saveSession(null);
|
|
352
463
|
onSignOut?.();
|
|
353
464
|
onDeleted?.();
|
|
354
465
|
return;
|
|
@@ -356,33 +467,49 @@ var _Authentication = class _Authentication {
|
|
|
356
467
|
if (change === "insert" || change === "update") {
|
|
357
468
|
if (!snapshot)
|
|
358
469
|
return;
|
|
359
|
-
if (snapshot.data.
|
|
360
|
-
this.
|
|
470
|
+
if (snapshot.data.disabled === true) {
|
|
471
|
+
this._saveSession(null);
|
|
361
472
|
onSignOut?.();
|
|
362
473
|
return;
|
|
363
474
|
}
|
|
364
475
|
const newCreds = Credentials.fromMap(snapshot.toMap());
|
|
365
|
-
if (
|
|
476
|
+
if (JSON.stringify(newCreds.toMap()) !== JSON.stringify(this.eUser?.toMap())) {
|
|
366
477
|
this.eUser = newCreds;
|
|
367
|
-
this.
|
|
478
|
+
this._saveSession(newCreds);
|
|
368
479
|
onChange(newCreds);
|
|
369
480
|
}
|
|
370
481
|
}
|
|
371
482
|
}
|
|
372
483
|
);
|
|
373
484
|
};
|
|
374
|
-
const
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
handleCredsChange(initialUser);
|
|
485
|
+
const initial = this.currentUser();
|
|
486
|
+
if (initial)
|
|
487
|
+
onChange(initial);
|
|
488
|
+
handleCredsChange(initial);
|
|
380
489
|
const credsUnsub = this.onValue("creds", handleCredsChange);
|
|
381
490
|
return () => {
|
|
382
491
|
credsUnsub();
|
|
383
|
-
|
|
492
|
+
userDocUnsub?.();
|
|
384
493
|
};
|
|
385
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
|
+
}
|
|
386
513
|
};
|
|
387
514
|
_Authentication.instance = null;
|
|
388
515
|
var Authentication = _Authentication;
|
|
@@ -394,7 +521,7 @@ var DocumentSnapshot = class _DocumentSnapshot {
|
|
|
394
521
|
this.data = doc;
|
|
395
522
|
}
|
|
396
523
|
static fromMap(map) {
|
|
397
|
-
const id = map.
|
|
524
|
+
const id = map._id ?? map.id ?? null;
|
|
398
525
|
const document2 = map.document ?? map.documents ?? map.data ?? map;
|
|
399
526
|
return new _DocumentSnapshot(id, document2);
|
|
400
527
|
}
|
|
@@ -456,16 +583,12 @@ var DocumentRef = class {
|
|
|
456
583
|
try {
|
|
457
584
|
validateDocumentData(data, "DocumentRef.update");
|
|
458
585
|
if (!this.persistence) {
|
|
459
|
-
const res = await
|
|
586
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
460
587
|
method: "POST" /* POST */,
|
|
461
588
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
462
|
-
headers: {
|
|
463
|
-
authorization: this.app.getConfig().token,
|
|
464
|
-
"x-project": this.app.getConfig().project,
|
|
465
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
466
|
-
},
|
|
589
|
+
headers: {},
|
|
467
590
|
body: { collection: this.collection, id: this.id, data }
|
|
468
|
-
})
|
|
591
|
+
});
|
|
469
592
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
470
593
|
}
|
|
471
594
|
const old = await this.persistence.getDoc(this.collection, this.id);
|
|
@@ -503,16 +626,12 @@ var DocumentRef = class {
|
|
|
503
626
|
async set(data) {
|
|
504
627
|
validateDocumentData(data, "DocumentRef.set");
|
|
505
628
|
if (!this.persistence) {
|
|
506
|
-
const res = await
|
|
629
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
507
630
|
method: "POST" /* POST */,
|
|
508
631
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
509
|
-
headers: {
|
|
510
|
-
authorization: this.app.getConfig().token,
|
|
511
|
-
"x-project": this.app.getConfig().project,
|
|
512
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
513
|
-
},
|
|
632
|
+
headers: {},
|
|
514
633
|
body: { collection: this.collection, data: { ...data, id: this.id } }
|
|
515
|
-
})
|
|
634
|
+
});
|
|
516
635
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
517
636
|
}
|
|
518
637
|
const existing = await this.persistence.getDoc(this.collection, this.id);
|
|
@@ -560,16 +679,12 @@ var DocumentRef = class {
|
|
|
560
679
|
this.syncEngine?.flush().catch(console.error);
|
|
561
680
|
return true;
|
|
562
681
|
}
|
|
563
|
-
const res = await
|
|
682
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
564
683
|
method: "POST" /* POST */,
|
|
565
684
|
endpoint: `${this.app.getBaseUrl()}/db/delete`,
|
|
566
|
-
headers: {
|
|
567
|
-
authorization: this.app.getConfig().token,
|
|
568
|
-
"x-project": this.app.getConfig().project,
|
|
569
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
570
|
-
},
|
|
685
|
+
headers: {},
|
|
571
686
|
body: { collection: this.collection, id: this.id }
|
|
572
|
-
})
|
|
687
|
+
});
|
|
573
688
|
return !!res?.success;
|
|
574
689
|
}
|
|
575
690
|
onSnapshot(callback) {
|
|
@@ -594,20 +709,17 @@ var DocumentRef = class {
|
|
|
594
709
|
}
|
|
595
710
|
async fetchRemoteSnapshot() {
|
|
596
711
|
try {
|
|
597
|
-
const res = await
|
|
712
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
598
713
|
method: "POST" /* POST */,
|
|
599
714
|
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
600
|
-
headers: {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
single: true
|
|
609
|
-
}
|
|
610
|
-
}).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
|
+
}
|
|
611
723
|
if (!res?.success || !res.document)
|
|
612
724
|
return null;
|
|
613
725
|
const snapshot = DocumentSnapshot.fromMap(res.document);
|
|
@@ -616,7 +728,7 @@ var DocumentRef = class {
|
|
|
616
728
|
}
|
|
617
729
|
return snapshot;
|
|
618
730
|
} catch (error) {
|
|
619
|
-
console.error(`[DocumentRef]
|
|
731
|
+
console.error(`[DocumentRef]:`, error);
|
|
620
732
|
return null;
|
|
621
733
|
}
|
|
622
734
|
}
|
|
@@ -718,9 +830,8 @@ var Query = class {
|
|
|
718
830
|
const persistence = this.app.offline().persistence;
|
|
719
831
|
if (this.filter.length > 0 && persistence) {
|
|
720
832
|
const local = await this.getLocalFilteredSnapshots();
|
|
721
|
-
if (typeof navigator === "undefined" || !navigator.onLine)
|
|
833
|
+
if (typeof navigator === "undefined" || !navigator.onLine)
|
|
722
834
|
return local;
|
|
723
|
-
}
|
|
724
835
|
return this.refreshFromRemote();
|
|
725
836
|
}
|
|
726
837
|
if (persistence) {
|
|
@@ -734,17 +845,12 @@ var Query = class {
|
|
|
734
845
|
return this.refreshFromRemote();
|
|
735
846
|
}
|
|
736
847
|
async update(data) {
|
|
737
|
-
|
|
738
|
-
return new HttpsRequest({
|
|
848
|
+
return await this.app.getAuthentication.makeRequest({
|
|
739
849
|
method: "POST" /* POST */,
|
|
740
850
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
741
|
-
headers: {
|
|
742
|
-
body: {
|
|
743
|
-
|
|
744
|
-
filter: genfilter,
|
|
745
|
-
data
|
|
746
|
-
}
|
|
747
|
-
}).sendRequest();
|
|
851
|
+
headers: {},
|
|
852
|
+
body: { collection: this.collection, filter: this.buildFilter(), data }
|
|
853
|
+
});
|
|
748
854
|
}
|
|
749
855
|
onSnapshot(callback) {
|
|
750
856
|
const genfilter = this.buildFilter();
|
|
@@ -874,32 +980,22 @@ var Query = class {
|
|
|
874
980
|
}
|
|
875
981
|
async refreshFromRemote() {
|
|
876
982
|
try {
|
|
877
|
-
const
|
|
878
|
-
const res = await new HttpsRequest({
|
|
983
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
879
984
|
method: "POST" /* POST */,
|
|
880
985
|
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
881
|
-
headers: {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
},
|
|
886
|
-
body: {
|
|
887
|
-
collection: this.collection,
|
|
888
|
-
filter: genfilter
|
|
889
|
-
}
|
|
890
|
-
}).sendRequest();
|
|
891
|
-
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))
|
|
892
990
|
return [];
|
|
893
|
-
}
|
|
894
991
|
const snapshots = res.documents.map((d) => {
|
|
895
992
|
d.id = d.id ?? d._id;
|
|
896
993
|
delete d._id;
|
|
897
994
|
return DocumentSnapshot.fromMap(d);
|
|
898
995
|
});
|
|
899
996
|
const persistence = this.app.offline().persistence;
|
|
900
|
-
if (!persistence)
|
|
997
|
+
if (!persistence)
|
|
901
998
|
return snapshots;
|
|
902
|
-
}
|
|
903
999
|
for (const snapshot of snapshots) {
|
|
904
1000
|
await persistence.applyRemoteDoc(this.collection, snapshot.data);
|
|
905
1001
|
}
|
|
@@ -950,13 +1046,12 @@ var Query = class {
|
|
|
950
1046
|
newIndex: childChange.newIndex,
|
|
951
1047
|
previousDoc: childChange.previousDoc
|
|
952
1048
|
};
|
|
953
|
-
if (childChange.type === "added")
|
|
1049
|
+
if (childChange.type === "added")
|
|
954
1050
|
callbacks.onChildAdded?.(childChange.doc, context);
|
|
955
|
-
|
|
1051
|
+
else if (childChange.type === "modified")
|
|
956
1052
|
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
957
|
-
|
|
1053
|
+
else if (childChange.type === "removed")
|
|
958
1054
|
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
959
|
-
}
|
|
960
1055
|
});
|
|
961
1056
|
},
|
|
962
1057
|
this.buildFilter()
|
|
@@ -1033,19 +1128,12 @@ var CollectionRef = class {
|
|
|
1033
1128
|
this.syncEngine?.flush().catch(console.error);
|
|
1034
1129
|
return snap;
|
|
1035
1130
|
}
|
|
1036
|
-
const res = await
|
|
1131
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1037
1132
|
method: "POST" /* POST */,
|
|
1038
1133
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
1039
|
-
headers: {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
user: JSON.stringify(this.authentication.currentUser()?.toMap())
|
|
1043
|
-
},
|
|
1044
|
-
body: {
|
|
1045
|
-
collection: this.collection,
|
|
1046
|
-
data: { ...data, id: "" }
|
|
1047
|
-
}
|
|
1048
|
-
}).sendRequest();
|
|
1134
|
+
headers: {},
|
|
1135
|
+
body: { collection: this.collection, data: { ...data } }
|
|
1136
|
+
});
|
|
1049
1137
|
if (!res?.success || !res.document)
|
|
1050
1138
|
return null;
|
|
1051
1139
|
return DocumentSnapshot.fromMap(res.document);
|
|
@@ -1094,23 +1182,19 @@ var CollectionRef = class {
|
|
|
1094
1182
|
}
|
|
1095
1183
|
async refreshFromRemote() {
|
|
1096
1184
|
try {
|
|
1097
|
-
const res = await
|
|
1185
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1098
1186
|
method: "POST" /* POST */,
|
|
1099
1187
|
endpoint: `${serverURI}/db/read`,
|
|
1100
|
-
headers: {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
body: {
|
|
1107
|
-
collection: this.collection,
|
|
1108
|
-
filter: {}
|
|
1109
|
-
}
|
|
1110
|
-
}).sendRequest();
|
|
1111
|
-
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);
|
|
1112
1194
|
return [];
|
|
1113
1195
|
}
|
|
1196
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
1197
|
+
return [];
|
|
1114
1198
|
const normalized = res.documents.map((raw) => {
|
|
1115
1199
|
const doc = { ...raw };
|
|
1116
1200
|
doc.id = doc.id ?? doc._id;
|
|
@@ -1125,36 +1209,32 @@ var CollectionRef = class {
|
|
|
1125
1209
|
}
|
|
1126
1210
|
return normalized.map((doc) => DocumentSnapshot.fromMap(doc));
|
|
1127
1211
|
} catch (error) {
|
|
1128
|
-
console.error(
|
|
1212
|
+
console.error(`[CollectionRef]:`, error);
|
|
1129
1213
|
return [];
|
|
1130
1214
|
}
|
|
1131
1215
|
}
|
|
1132
1216
|
async fetchRemoteSnapshots() {
|
|
1133
1217
|
try {
|
|
1134
|
-
const res = await
|
|
1218
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1135
1219
|
method: "POST" /* POST */,
|
|
1136
1220
|
endpoint: `${serverURI}/db/read`,
|
|
1137
|
-
headers: {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
body: {
|
|
1144
|
-
collection: this.collection,
|
|
1145
|
-
filter: {}
|
|
1146
|
-
}
|
|
1147
|
-
}).sendRequest();
|
|
1148
|
-
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);
|
|
1149
1227
|
return [];
|
|
1150
1228
|
}
|
|
1229
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
1230
|
+
return [];
|
|
1151
1231
|
return res.documents.map((d) => {
|
|
1152
1232
|
d.id = d.id ?? d._id;
|
|
1153
1233
|
delete d._id;
|
|
1154
1234
|
return DocumentSnapshot.fromMap(d);
|
|
1155
1235
|
});
|
|
1156
1236
|
} catch (error) {
|
|
1157
|
-
console.error(
|
|
1237
|
+
console.error(`[CollectionRef]:`, error);
|
|
1158
1238
|
return [];
|
|
1159
1239
|
}
|
|
1160
1240
|
}
|
|
@@ -1193,20 +1273,18 @@ var CollectionRef = class {
|
|
|
1193
1273
|
newIndex: childChange.newIndex,
|
|
1194
1274
|
previousDoc: childChange.previousDoc
|
|
1195
1275
|
};
|
|
1196
|
-
if (childChange.type === "added")
|
|
1276
|
+
if (childChange.type === "added")
|
|
1197
1277
|
callbacks.onChildAdded?.(childChange.doc, context);
|
|
1198
|
-
|
|
1278
|
+
else if (childChange.type === "modified")
|
|
1199
1279
|
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
1200
|
-
|
|
1280
|
+
else if (childChange.type === "removed")
|
|
1201
1281
|
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
1202
|
-
}
|
|
1203
1282
|
});
|
|
1204
1283
|
});
|
|
1205
1284
|
}
|
|
1206
1285
|
emitInitialChildEvents(snapshots, callbacks) {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
const context = {
|
|
1286
|
+
diffSnapshots([], snapshots).forEach((change) => {
|
|
1287
|
+
callbacks.onChildAdded?.(change.doc, {
|
|
1210
1288
|
snapshots,
|
|
1211
1289
|
source: "initial",
|
|
1212
1290
|
changedDocId: change.doc.id,
|
|
@@ -1214,8 +1292,7 @@ var CollectionRef = class {
|
|
|
1214
1292
|
oldIndex: change.oldIndex,
|
|
1215
1293
|
newIndex: change.newIndex,
|
|
1216
1294
|
previousDoc: change.previousDoc
|
|
1217
|
-
};
|
|
1218
|
-
callbacks.onChildAdded?.(change.doc, context);
|
|
1295
|
+
});
|
|
1219
1296
|
});
|
|
1220
1297
|
}
|
|
1221
1298
|
};
|
|
@@ -1227,38 +1304,19 @@ var Batch = class {
|
|
|
1227
1304
|
this.app = app;
|
|
1228
1305
|
}
|
|
1229
1306
|
set(docRef, data) {
|
|
1230
|
-
this.ops.push({
|
|
1231
|
-
op: "set",
|
|
1232
|
-
collection: docRef.collection,
|
|
1233
|
-
id: docRef.id,
|
|
1234
|
-
data
|
|
1235
|
-
});
|
|
1307
|
+
this.ops.push({ op: "set", collection: docRef.collection, id: docRef.id, data });
|
|
1236
1308
|
return this;
|
|
1237
1309
|
}
|
|
1238
1310
|
create(docRef, data) {
|
|
1239
|
-
this.ops.push({
|
|
1240
|
-
op: "create",
|
|
1241
|
-
collection: docRef.collection,
|
|
1242
|
-
id: docRef.id,
|
|
1243
|
-
data
|
|
1244
|
-
});
|
|
1311
|
+
this.ops.push({ op: "create", collection: docRef.collection, id: docRef.id, data });
|
|
1245
1312
|
return this;
|
|
1246
1313
|
}
|
|
1247
1314
|
update(docRef, data) {
|
|
1248
|
-
this.ops.push({
|
|
1249
|
-
op: "update",
|
|
1250
|
-
collection: docRef.collection,
|
|
1251
|
-
id: docRef.id,
|
|
1252
|
-
data
|
|
1253
|
-
});
|
|
1315
|
+
this.ops.push({ op: "update", collection: docRef.collection, id: docRef.id, data });
|
|
1254
1316
|
return this;
|
|
1255
1317
|
}
|
|
1256
1318
|
delete(docRef) {
|
|
1257
|
-
this.ops.push({
|
|
1258
|
-
op: "delete",
|
|
1259
|
-
collection: docRef.collection,
|
|
1260
|
-
id: docRef.id
|
|
1261
|
-
});
|
|
1319
|
+
this.ops.push({ op: "delete", collection: docRef.collection, id: docRef.id });
|
|
1262
1320
|
return this;
|
|
1263
1321
|
}
|
|
1264
1322
|
async commit() {
|
|
@@ -1307,16 +1365,12 @@ var Batch = class {
|
|
|
1307
1365
|
syncEngine?.flush().catch(console.error);
|
|
1308
1366
|
return { success: true, results };
|
|
1309
1367
|
}
|
|
1310
|
-
const res = await
|
|
1368
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1311
1369
|
method: "POST" /* POST */,
|
|
1312
1370
|
endpoint: `${serverURI}/db/batch`,
|
|
1313
|
-
headers: {
|
|
1314
|
-
authorization: this.app.getConfig().token,
|
|
1315
|
-
"x-project": this.app.getConfig().project,
|
|
1316
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
1317
|
-
},
|
|
1371
|
+
headers: {},
|
|
1318
1372
|
body: { ops: this.ops }
|
|
1319
|
-
})
|
|
1373
|
+
});
|
|
1320
1374
|
if (res?.error) {
|
|
1321
1375
|
throw new Error(res.error.message || "Batch commit failed");
|
|
1322
1376
|
}
|
|
@@ -1363,12 +1417,12 @@ var Database = class {
|
|
|
1363
1417
|
}
|
|
1364
1418
|
};
|
|
1365
1419
|
await transactionFn(tx);
|
|
1366
|
-
const res = await
|
|
1420
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1367
1421
|
method: "POST" /* POST */,
|
|
1368
1422
|
endpoint: `${this.app.getBaseUrl()}/db/transaction`,
|
|
1369
|
-
headers: {
|
|
1423
|
+
headers: {},
|
|
1370
1424
|
body: { ops: tx.ops }
|
|
1371
|
-
})
|
|
1425
|
+
});
|
|
1372
1426
|
if (res?.error) {
|
|
1373
1427
|
throw new Error(res.error.message || "Transaction failed");
|
|
1374
1428
|
}
|
|
@@ -1395,6 +1449,14 @@ var Database = class {
|
|
|
1395
1449
|
}
|
|
1396
1450
|
};
|
|
1397
1451
|
|
|
1452
|
+
// src/core/RequestHeaders.ts
|
|
1453
|
+
function socketAuth(app) {
|
|
1454
|
+
const base = {
|
|
1455
|
+
...app.getAuthentication.getHeaders()
|
|
1456
|
+
};
|
|
1457
|
+
return base;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1398
1460
|
// src/database/Realtime.ts
|
|
1399
1461
|
var import_socket = require("socket.io-client");
|
|
1400
1462
|
|
|
@@ -1404,7 +1466,7 @@ function normalizePayload(payload) {
|
|
|
1404
1466
|
if (!raw)
|
|
1405
1467
|
return null;
|
|
1406
1468
|
const doc = { ...raw };
|
|
1407
|
-
doc.id = doc.id ?? raw?.id ??
|
|
1469
|
+
doc.id = raw?._id ?? doc.id ?? raw?.id ?? payload?._id ?? payload?.id;
|
|
1408
1470
|
delete doc._id;
|
|
1409
1471
|
return {
|
|
1410
1472
|
change: payload?.change ?? raw?.change ?? null,
|
|
@@ -1429,11 +1491,7 @@ var Realtime = class {
|
|
|
1429
1491
|
this.socket = (0, import_socket.io)(this.app.getSocketUrl(), {
|
|
1430
1492
|
transports: ["websocket"],
|
|
1431
1493
|
auth: {
|
|
1432
|
-
|
|
1433
|
-
"x-project": this.app.getConfig().project,
|
|
1434
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
1435
|
-
user: this.app.getAuthentication.currentUser()
|
|
1436
|
-
// Pass user info for personalized permissions
|
|
1494
|
+
...socketAuth(this.app)
|
|
1437
1495
|
},
|
|
1438
1496
|
autoConnect: true,
|
|
1439
1497
|
reconnection: true,
|
|
@@ -1477,19 +1535,19 @@ var Realtime = class {
|
|
|
1477
1535
|
/**
|
|
1478
1536
|
* Low-level collection subscription (used by RealtimeBridge)
|
|
1479
1537
|
*/
|
|
1538
|
+
// Realtime.ts - subscribeToCollectionRaw
|
|
1480
1539
|
subscribeToCollectionRaw(collection, callback, filter = {}) {
|
|
1481
1540
|
this.connect();
|
|
1482
|
-
const lid = collection
|
|
1541
|
+
const lid = `${collection}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1483
1542
|
const handlers = [];
|
|
1484
|
-
this.socket.emit("subscribe", { collection, filter });
|
|
1543
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1485
1544
|
this.events.forEach((event) => {
|
|
1486
1545
|
const channel = `${lid}-${event}`;
|
|
1487
1546
|
const fn = (payload) => {
|
|
1488
1547
|
const normalized = normalizePayload(payload);
|
|
1489
1548
|
if (!normalized) {
|
|
1490
|
-
if (event === "delete")
|
|
1549
|
+
if (event === "delete")
|
|
1491
1550
|
callback(payload, "delete");
|
|
1492
|
-
}
|
|
1493
1551
|
return;
|
|
1494
1552
|
}
|
|
1495
1553
|
callback(normalized.data, event);
|
|
@@ -1505,10 +1563,10 @@ var Realtime = class {
|
|
|
1505
1563
|
*/
|
|
1506
1564
|
subscribeToDocumentRaw(collection, id, callback) {
|
|
1507
1565
|
this.connect();
|
|
1508
|
-
const lid = collection
|
|
1566
|
+
const lid = `${collection}_${id}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1509
1567
|
const filter = { _id: id };
|
|
1510
1568
|
const handlers = [];
|
|
1511
|
-
this.socket.emit("subscribe", { collection, filter });
|
|
1569
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1512
1570
|
this.events.forEach((event) => {
|
|
1513
1571
|
const channel = `${lid}-${event}`;
|
|
1514
1572
|
const fn = (payload) => {
|
|
@@ -1568,12 +1626,12 @@ var Functions = class {
|
|
|
1568
1626
|
}
|
|
1569
1627
|
async call(functionName, data) {
|
|
1570
1628
|
try {
|
|
1571
|
-
const res = await
|
|
1629
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1572
1630
|
method: "POST" /* POST */,
|
|
1573
1631
|
endpoint: serverURI + "/functions/call/" + functionName,
|
|
1574
|
-
headers: {
|
|
1632
|
+
headers: {},
|
|
1575
1633
|
body: data
|
|
1576
|
-
})
|
|
1634
|
+
});
|
|
1577
1635
|
return res;
|
|
1578
1636
|
} catch (err) {
|
|
1579
1637
|
throw {
|
|
@@ -1591,15 +1649,15 @@ var Hosting = class {
|
|
|
1591
1649
|
}
|
|
1592
1650
|
async createSubdomain(name) {
|
|
1593
1651
|
try {
|
|
1594
|
-
const res = await
|
|
1652
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1595
1653
|
method: "POST" /* POST */,
|
|
1596
1654
|
endpoint: this.app.getBaseUrl() + "/hosting/register",
|
|
1597
|
-
headers: {
|
|
1655
|
+
headers: {},
|
|
1598
1656
|
body: {
|
|
1599
1657
|
hostname: name,
|
|
1600
1658
|
project: this.app.getConfig().project
|
|
1601
1659
|
}
|
|
1602
|
-
})
|
|
1660
|
+
});
|
|
1603
1661
|
return res;
|
|
1604
1662
|
} catch (err) {
|
|
1605
1663
|
throw {
|
|
@@ -2445,6 +2503,7 @@ var SyncEngine = class {
|
|
|
2445
2503
|
constructor(app, persistence, store, realtimeBridge) {
|
|
2446
2504
|
this.realtimeBridge = null;
|
|
2447
2505
|
this.syncing = false;
|
|
2506
|
+
// Use ReturnType<typeof setTimeout> for cross-env timeout type (works in browser and Node)
|
|
2448
2507
|
this.retryTimeout = null;
|
|
2449
2508
|
this.MAX_RETRIES = 5;
|
|
2450
2509
|
this.BASE_RETRY_DELAY = 2e3;
|
|
@@ -2457,6 +2516,7 @@ var SyncEngine = class {
|
|
|
2457
2516
|
this.persistence = persistence;
|
|
2458
2517
|
this.store = store;
|
|
2459
2518
|
this.realtimeBridge = realtimeBridge || null;
|
|
2519
|
+
this.authentication = app.getAuthentication;
|
|
2460
2520
|
this.persistence.recoverSyncingMutations().catch(console.error);
|
|
2461
2521
|
if (typeof window !== "undefined") {
|
|
2462
2522
|
window.addEventListener("online", this.handleOnline);
|
|
@@ -2533,15 +2593,15 @@ var SyncEngine = class {
|
|
|
2533
2593
|
}
|
|
2534
2594
|
}
|
|
2535
2595
|
async syncCreate(mutation) {
|
|
2536
|
-
const res = await
|
|
2596
|
+
const res = await this.authentication.makeRequest({
|
|
2537
2597
|
method: "POST" /* POST */,
|
|
2538
2598
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
2539
|
-
headers: {
|
|
2599
|
+
headers: {},
|
|
2540
2600
|
body: {
|
|
2541
2601
|
collection: mutation.collection,
|
|
2542
2602
|
data: mutation.payload
|
|
2543
2603
|
}
|
|
2544
|
-
})
|
|
2604
|
+
});
|
|
2545
2605
|
if (!res?.success || !res.id)
|
|
2546
2606
|
return false;
|
|
2547
2607
|
const oldId = mutation.documentId;
|
|
@@ -2559,21 +2619,16 @@ var SyncEngine = class {
|
|
|
2559
2619
|
return true;
|
|
2560
2620
|
}
|
|
2561
2621
|
async syncUpdate(mutation) {
|
|
2562
|
-
const res = await
|
|
2622
|
+
const res = await this.authentication.makeRequest({
|
|
2563
2623
|
method: "POST" /* POST */,
|
|
2564
2624
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
2565
|
-
headers: {
|
|
2566
|
-
authorization: this.app.getConfig().token,
|
|
2567
|
-
"x-project": this.app.getConfig().project,
|
|
2568
|
-
user: JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
2569
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
2570
|
-
},
|
|
2625
|
+
headers: {},
|
|
2571
2626
|
body: {
|
|
2572
2627
|
collection: mutation.collection,
|
|
2573
2628
|
document: mutation.documentId,
|
|
2574
2629
|
data: mutation.payload
|
|
2575
2630
|
}
|
|
2576
|
-
})
|
|
2631
|
+
});
|
|
2577
2632
|
if (!res?.success)
|
|
2578
2633
|
return false;
|
|
2579
2634
|
const local = await this.persistence.upsertDoc({
|
|
@@ -2596,20 +2651,15 @@ var SyncEngine = class {
|
|
|
2596
2651
|
return true;
|
|
2597
2652
|
}
|
|
2598
2653
|
async syncDelete(mutation) {
|
|
2599
|
-
const res = await
|
|
2654
|
+
const res = await this.authentication.makeRequest({
|
|
2600
2655
|
method: "POST" /* POST */,
|
|
2601
2656
|
endpoint: `${this.app.getBaseUrl()}/db/delete`,
|
|
2602
|
-
headers: {
|
|
2603
|
-
authorization: this.app.getConfig().token,
|
|
2604
|
-
"x-project": this.app.getConfig().project,
|
|
2605
|
-
user: JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
2606
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
2607
|
-
},
|
|
2657
|
+
headers: {},
|
|
2608
2658
|
body: {
|
|
2609
2659
|
collection: mutation.collection,
|
|
2610
2660
|
document: mutation.documentId
|
|
2611
2661
|
}
|
|
2612
|
-
})
|
|
2662
|
+
});
|
|
2613
2663
|
if (!res?.success)
|
|
2614
2664
|
return false;
|
|
2615
2665
|
await this.persistence.markDeleted(mutation.collection, mutation.documentId, 0);
|
|
@@ -2708,13 +2758,11 @@ var StorageRef = class {
|
|
|
2708
2758
|
this.app = app;
|
|
2709
2759
|
}
|
|
2710
2760
|
async get(id) {
|
|
2711
|
-
const res = await
|
|
2761
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2712
2762
|
method: "GET" /* GET */,
|
|
2713
2763
|
endpoint: serverURI + `/storage/${id}`,
|
|
2714
|
-
headers: {
|
|
2715
|
-
|
|
2716
|
-
}
|
|
2717
|
-
}).sendRequest();
|
|
2764
|
+
headers: {}
|
|
2765
|
+
});
|
|
2718
2766
|
if (res.success) {
|
|
2719
2767
|
if (res.document === void 0)
|
|
2720
2768
|
return void 0;
|
|
@@ -2725,13 +2773,11 @@ var StorageRef = class {
|
|
|
2725
2773
|
}
|
|
2726
2774
|
}
|
|
2727
2775
|
async getMeta(id) {
|
|
2728
|
-
const res = await
|
|
2776
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2729
2777
|
method: "GET" /* GET */,
|
|
2730
2778
|
endpoint: serverURI + `/storage/${id}/info`,
|
|
2731
|
-
headers: {
|
|
2732
|
-
|
|
2733
|
-
}
|
|
2734
|
-
}).sendRequest();
|
|
2779
|
+
headers: {}
|
|
2780
|
+
});
|
|
2735
2781
|
if (res.success) {
|
|
2736
2782
|
if (res.document === void 0)
|
|
2737
2783
|
return void 0;
|
|
@@ -2745,13 +2791,13 @@ var StorageRef = class {
|
|
|
2745
2791
|
if (!srcFile) {
|
|
2746
2792
|
throw new Error("Invalid File");
|
|
2747
2793
|
}
|
|
2748
|
-
const res = await
|
|
2794
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2749
2795
|
method: "POST" /* POST */,
|
|
2750
2796
|
endpoint: serverURI + "/storage/file/upload",
|
|
2751
|
-
headers: {
|
|
2797
|
+
headers: {},
|
|
2752
2798
|
file: srcFile,
|
|
2753
2799
|
isMultipart: true
|
|
2754
|
-
})
|
|
2800
|
+
});
|
|
2755
2801
|
if (res.success) {
|
|
2756
2802
|
if (res.document === void 0)
|
|
2757
2803
|
return void 0;
|
|
@@ -2762,14 +2808,14 @@ var StorageRef = class {
|
|
|
2762
2808
|
}
|
|
2763
2809
|
}
|
|
2764
2810
|
async delete(id) {
|
|
2765
|
-
const res = await
|
|
2811
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2766
2812
|
method: "POST" /* POST */,
|
|
2767
2813
|
endpoint: serverURI + "/storage/file/delete",
|
|
2768
|
-
headers: {
|
|
2814
|
+
headers: {},
|
|
2769
2815
|
body: {
|
|
2770
2816
|
file_id: id
|
|
2771
2817
|
}
|
|
2772
|
-
})
|
|
2818
|
+
});
|
|
2773
2819
|
return res;
|
|
2774
2820
|
}
|
|
2775
2821
|
};
|