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.mjs
CHANGED
|
@@ -1,39 +1,89 @@
|
|
|
1
1
|
// src/authentication/Credentials.ts
|
|
2
2
|
var Credentials = class _Credentials {
|
|
3
|
-
constructor(uid, displayInfo, data, verified, createdAt, updatedAt) {
|
|
3
|
+
constructor(uid, displayInfo, data, verified, disabled, provider, createdAt, updatedAt, lastLogin) {
|
|
4
4
|
this.uid = uid;
|
|
5
5
|
this.displayInfo = displayInfo;
|
|
6
6
|
this.data = data;
|
|
7
7
|
this.verified = verified;
|
|
8
|
+
this.disabled = disabled;
|
|
9
|
+
this.provider = provider;
|
|
8
10
|
this.createdAt = createdAt;
|
|
9
11
|
this.updatedAt = updatedAt;
|
|
12
|
+
this.lastLogin = lastLogin;
|
|
10
13
|
}
|
|
11
14
|
static fromMap(map) {
|
|
12
|
-
const uid = map.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const uid = map.uid ?? map.id ?? map._id ?? null;
|
|
16
|
+
let displayInfo;
|
|
17
|
+
let data;
|
|
18
|
+
let createdAt;
|
|
19
|
+
let updatedAt;
|
|
20
|
+
let lastLogin;
|
|
21
|
+
let verified;
|
|
22
|
+
let disabled;
|
|
23
|
+
let provider;
|
|
24
|
+
const inner = map;
|
|
25
|
+
displayInfo = inner.displayInfo ?? {
|
|
26
|
+
firstname: "",
|
|
27
|
+
lastname: "",
|
|
28
|
+
surname: "",
|
|
29
|
+
profile: "",
|
|
30
|
+
email: "",
|
|
31
|
+
phone: ""
|
|
32
|
+
};
|
|
33
|
+
data = inner.data ?? {};
|
|
34
|
+
createdAt = inner.createdAt ?? map.createdAt ?? null;
|
|
35
|
+
updatedAt = inner.updatedAt ?? map.updatedAt ?? null;
|
|
36
|
+
lastLogin = inner.lastLogin ?? map.lastLogin ?? null;
|
|
37
|
+
verified = inner.verified ?? map.verified ?? false;
|
|
38
|
+
disabled = inner.disabled ?? map.disabled ?? false;
|
|
39
|
+
provider = inner.provider ?? map.provider ?? "EAuth";
|
|
40
|
+
const output = new _Credentials(
|
|
18
41
|
uid,
|
|
19
42
|
displayInfo,
|
|
20
|
-
data
|
|
21
|
-
|
|
43
|
+
data,
|
|
44
|
+
verified,
|
|
45
|
+
disabled,
|
|
46
|
+
provider,
|
|
22
47
|
createdAt,
|
|
23
|
-
updatedAt
|
|
48
|
+
updatedAt,
|
|
49
|
+
lastLogin
|
|
24
50
|
);
|
|
25
|
-
return
|
|
51
|
+
return output;
|
|
26
52
|
}
|
|
53
|
+
// toMap() produces the nested shape — used by:
|
|
54
|
+
// - localStorage.setItem("eauth", JSON.stringify(creds.toMap()))
|
|
55
|
+
// - authState snapshot listener: Credentials.fromMap(snapshot.toMap())
|
|
56
|
+
// Keep this stable so existing persisted sessions restore correctly.
|
|
27
57
|
toMap() {
|
|
28
58
|
return {
|
|
29
59
|
uid: this.uid,
|
|
30
60
|
displayInfo: this.displayInfo,
|
|
31
61
|
data: this.data,
|
|
32
62
|
verified: this.verified,
|
|
63
|
+
disabled: this.disabled,
|
|
64
|
+
provider: this.provider,
|
|
33
65
|
createdAt: this.createdAt,
|
|
34
|
-
updatedAt: this.updatedAt
|
|
66
|
+
updatedAt: this.updatedAt,
|
|
67
|
+
lastLogin: this.lastLogin
|
|
35
68
|
};
|
|
36
69
|
}
|
|
70
|
+
// ── Convenience getters ───────────────────────────────────────────────────
|
|
71
|
+
// So callers don't have to drill into displayInfo everywhere.
|
|
72
|
+
get email() {
|
|
73
|
+
return this.displayInfo.email ?? "";
|
|
74
|
+
}
|
|
75
|
+
get firstname() {
|
|
76
|
+
return this.displayInfo.firstname ?? "";
|
|
77
|
+
}
|
|
78
|
+
get lastname() {
|
|
79
|
+
return this.displayInfo.lastname ?? "";
|
|
80
|
+
}
|
|
81
|
+
get displayName() {
|
|
82
|
+
return `${this.firstname} ${this.lastname}`.trim() || this.email;
|
|
83
|
+
}
|
|
84
|
+
get photoUrl() {
|
|
85
|
+
return this.displayInfo.profile ?? "";
|
|
86
|
+
}
|
|
37
87
|
};
|
|
38
88
|
|
|
39
89
|
// src/utils/HttpsRequest.ts
|
|
@@ -66,11 +116,7 @@ var HttpsRequest = class {
|
|
|
66
116
|
headers: this.headers,
|
|
67
117
|
body: formData
|
|
68
118
|
});
|
|
69
|
-
|
|
70
|
-
const errorText = await response2.text().catch(() => "Network error");
|
|
71
|
-
throw new Error(`HTTP ${response2.status}: ${errorText}`);
|
|
72
|
-
}
|
|
73
|
-
return await response2.json().catch(() => ({}));
|
|
119
|
+
return response2;
|
|
74
120
|
}
|
|
75
121
|
const response = await fetch(this.endpoint, {
|
|
76
122
|
method: this.method,
|
|
@@ -80,11 +126,7 @@ var HttpsRequest = class {
|
|
|
80
126
|
},
|
|
81
127
|
body: this.method !== "GET" /* GET */ ? JSON.stringify(this.body) : void 0
|
|
82
128
|
});
|
|
83
|
-
|
|
84
|
-
const errorText = await response.text().catch(() => "Network error");
|
|
85
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
86
|
-
}
|
|
87
|
-
return await response.json().catch(() => ({}));
|
|
129
|
+
return response;
|
|
88
130
|
} catch (error) {
|
|
89
131
|
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
|
90
132
|
throw new Error("Network connection failed. Please check your internet connection.");
|
|
@@ -100,187 +142,280 @@ var HttpsRequest = class {
|
|
|
100
142
|
// src/authentication/Authentication.ts
|
|
101
143
|
var _Authentication = class _Authentication {
|
|
102
144
|
constructor() {
|
|
145
|
+
// accessToken lives in MEMORY ONLY — never written to localStorage or logged.
|
|
146
|
+
// Reason: localStorage is readable by any JS on the page (XSS).
|
|
147
|
+
// On page reload it starts null; the first authenticated request that gets
|
|
148
|
+
// 401 "Access token expired" triggers _refreshAccessToken() automatically.
|
|
149
|
+
this.accessToken = null;
|
|
150
|
+
// The user SHAPE (no tokens, no passwordHash) is safe to persist.
|
|
151
|
+
// Used to restore who is signed in across page reloads without a network call.
|
|
152
|
+
this.eUser = null;
|
|
153
|
+
this.isRefreshing = false;
|
|
154
|
+
this.refreshQueue = [];
|
|
103
155
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return true;
|
|
109
|
-
if (!a || !b)
|
|
110
|
-
return false;
|
|
111
|
-
return JSON.stringify(a.toMap()) === JSON.stringify(b.toMap());
|
|
112
|
-
};
|
|
113
|
-
this.createUserWithEmailAndPassword = async ({
|
|
114
|
-
email,
|
|
115
|
-
password
|
|
116
|
-
}) => {
|
|
117
|
-
this.eUser = void 0;
|
|
118
|
-
this.saveCredentials(null);
|
|
119
|
-
const res = await new HttpsRequest({
|
|
156
|
+
// ─── Sign up ──────────────────────────────────────────────────────────────
|
|
157
|
+
this.createUserWithEmailAndPassword = async (data) => {
|
|
158
|
+
this._saveSession(null);
|
|
159
|
+
const r = await new HttpsRequest({
|
|
120
160
|
method: "POST" /* POST */,
|
|
121
|
-
endpoint: `${this.app?.getBaseUrl()}/auth/
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
password,
|
|
126
|
-
project: this.client?.getConfig().project
|
|
127
|
-
}
|
|
161
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/signup`,
|
|
162
|
+
// ← /auth/signup
|
|
163
|
+
headers: this.buildProjectHeaders(),
|
|
164
|
+
body: data
|
|
128
165
|
}).sendRequest();
|
|
129
|
-
|
|
130
|
-
|
|
166
|
+
const res = await r.json().catch(() => ({}));
|
|
167
|
+
const error = res?.error ?? null;
|
|
168
|
+
if (error) {
|
|
169
|
+
console.error(`[Authentication] Registration failed :`, error);
|
|
170
|
+
throw new Error(error);
|
|
131
171
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const userRef = res.result;
|
|
136
|
-
this.eUser = Credentials.fromMap(userRef);
|
|
137
|
-
this.saveCredentials(this.eUser);
|
|
138
|
-
return Credentials.fromMap(userRef.toMap());
|
|
172
|
+
const creds = Credentials.fromMap(res.result);
|
|
173
|
+
this._saveSession(creds, res.accessToken ?? null);
|
|
174
|
+
return creds;
|
|
139
175
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}) => {
|
|
144
|
-
const res = await new HttpsRequest({
|
|
176
|
+
// ─── Sign in ──────────────────────────────────────────────────────────────
|
|
177
|
+
this.signInWithEmailAndPassword = async ({ email, password }) => {
|
|
178
|
+
const r = await new HttpsRequest({
|
|
145
179
|
method: "POST" /* POST */,
|
|
146
180
|
endpoint: `${this.app?.getBaseUrl()}/auth/login`,
|
|
147
|
-
headers:
|
|
148
|
-
body: {
|
|
149
|
-
email,
|
|
150
|
-
password,
|
|
151
|
-
project: this.client?.getConfig().project
|
|
152
|
-
}
|
|
181
|
+
headers: this.buildProjectHeaders(),
|
|
182
|
+
body: { email, password }
|
|
153
183
|
}).sendRequest();
|
|
154
|
-
|
|
155
|
-
|
|
184
|
+
const res = await r.json().catch(() => ({}));
|
|
185
|
+
const error = res?.error ?? null;
|
|
186
|
+
if (error) {
|
|
187
|
+
console.error(`[Authentication] Login failed :`, error);
|
|
188
|
+
throw new Error(error);
|
|
156
189
|
}
|
|
157
|
-
|
|
158
|
-
|
|
190
|
+
const creds = Credentials.fromMap(res.result);
|
|
191
|
+
this._saveSession(creds, res.accessToken ?? null);
|
|
192
|
+
return creds;
|
|
193
|
+
};
|
|
194
|
+
// ─── Sign out ─────────────────────────────────────────────────────────────
|
|
195
|
+
//
|
|
196
|
+
// Server call bumps tokenVersion — instantly invalidates all existing
|
|
197
|
+
// access tokens for this user without waiting for the 1h expiry.
|
|
198
|
+
// Local state is always cleared even if the server call fails.
|
|
199
|
+
this.signOut = async () => {
|
|
200
|
+
try {
|
|
201
|
+
await new HttpsRequest({
|
|
202
|
+
method: "POST" /* POST */,
|
|
203
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/signout`,
|
|
204
|
+
headers: this.buildUserHeaders(),
|
|
205
|
+
body: {}
|
|
206
|
+
}).sendRequest();
|
|
207
|
+
} catch {
|
|
208
|
+
} finally {
|
|
209
|
+
this._saveSession(null);
|
|
159
210
|
}
|
|
160
|
-
const _data = res.result;
|
|
161
|
-
this.eUser = Credentials.fromMap(_data);
|
|
162
|
-
this.saveCredentials(this.eUser);
|
|
163
|
-
return Credentials.fromMap(_data);
|
|
164
211
|
};
|
|
212
|
+
// ─── Delete user ──────────────────────────────────────────────────────────
|
|
165
213
|
this.deleteUser = async () => {
|
|
166
|
-
if (!this.eUser)
|
|
167
|
-
throw new Error("No
|
|
168
|
-
|
|
169
|
-
const res = await new HttpsRequest({
|
|
214
|
+
if (!this.eUser)
|
|
215
|
+
throw new Error("No user signed in");
|
|
216
|
+
const res = await this.makeRequest({
|
|
170
217
|
method: "POST" /* POST */,
|
|
171
|
-
endpoint: `${this.app?.getBaseUrl()}/auth/delete`,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}).sendRequest();
|
|
178
|
-
if (!res.status) {
|
|
179
|
-
throw new Error("Something went wrong. Try Again.");
|
|
180
|
-
}
|
|
181
|
-
if (res.status === false) {
|
|
182
|
-
throw new Error(res.error ?? "Authentication Failed");
|
|
183
|
-
}
|
|
184
|
-
this.eUser = void 0;
|
|
185
|
-
this.saveCredentials(null);
|
|
218
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/users/delete`,
|
|
219
|
+
body: { userId: this.eUser.uid }
|
|
220
|
+
});
|
|
221
|
+
if (!res.success)
|
|
222
|
+
throw new Error(res.error ?? "Delete failed");
|
|
223
|
+
this._saveSession(null);
|
|
186
224
|
};
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
225
|
+
// ─── Password reset ───────────────────────────────────────────────────────
|
|
226
|
+
//
|
|
227
|
+
// Takes email — NOT uid. Never expose uid in client-controlled reset flows.
|
|
228
|
+
// Server always responds with success regardless of whether the email exists
|
|
229
|
+
// (prevents email enumeration attacks).
|
|
230
|
+
this.resetPassword = async ({ email }) => {
|
|
231
|
+
await new HttpsRequest({
|
|
191
232
|
method: "POST" /* POST */,
|
|
192
|
-
endpoint: `${this.app?.getBaseUrl()}/auth/reset`,
|
|
193
|
-
headers:
|
|
194
|
-
body: {
|
|
195
|
-
uid: luid,
|
|
196
|
-
project: this.client?.getConfig().project
|
|
197
|
-
}
|
|
233
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/password/reset-request`,
|
|
234
|
+
headers: this.buildProjectHeaders(),
|
|
235
|
+
body: { email }
|
|
198
236
|
}).sendRequest();
|
|
199
|
-
if (!res.status) {
|
|
200
|
-
throw new Error("Something went wrong. Try Again.");
|
|
201
|
-
}
|
|
202
|
-
if (res.status === false) {
|
|
203
|
-
throw new Error(res.error ?? "Authentication Failed");
|
|
204
|
-
}
|
|
205
|
-
const url = res.url;
|
|
206
|
-
return url;
|
|
207
|
-
};
|
|
208
|
-
this.signOut = async () => {
|
|
209
|
-
this.eUser = void 0;
|
|
210
|
-
this.saveCredentials(null);
|
|
211
|
-
return;
|
|
212
237
|
};
|
|
213
238
|
if (_Authentication.instance)
|
|
214
239
|
return _Authentication.instance;
|
|
215
|
-
this.client = EdmaxLabs.instance;
|
|
216
240
|
this.app = EdmaxLabs.instance;
|
|
217
241
|
_Authentication.instance = this;
|
|
218
|
-
this.
|
|
219
|
-
}
|
|
220
|
-
restoreSession() {
|
|
221
|
-
const saved = this.currentUser();
|
|
222
|
-
if (saved) {
|
|
223
|
-
this.eUser = saved;
|
|
224
|
-
this.emitValue("creds", saved);
|
|
225
|
-
}
|
|
242
|
+
this._restoreSession();
|
|
226
243
|
}
|
|
227
|
-
// Better singleton
|
|
228
244
|
static getInstance() {
|
|
229
|
-
if (!_Authentication.instance)
|
|
230
|
-
|
|
231
|
-
}
|
|
245
|
+
if (!_Authentication.instance)
|
|
246
|
+
new _Authentication();
|
|
232
247
|
return _Authentication.instance;
|
|
233
248
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
onValue(key, callback) {
|
|
242
|
-
const listeners = this.eventListeners.get(key) || [];
|
|
243
|
-
listeners.push(callback);
|
|
244
|
-
this.eventListeners.set(key, listeners);
|
|
245
|
-
return () => {
|
|
246
|
-
const filtered = listeners.filter((cb) => cb !== callback);
|
|
247
|
-
this.eventListeners.set(key, filtered);
|
|
249
|
+
// ─── Header builders ──────────────────────────────────────────────────────
|
|
250
|
+
buildProjectHeaders() {
|
|
251
|
+
return {
|
|
252
|
+
"Authorization": `${this.app?.getConfig().token ?? ""}`,
|
|
253
|
+
"X-Project": `${this.app?.getConfig().project ?? ""}`,
|
|
254
|
+
"Content-Type": "application/json"
|
|
248
255
|
};
|
|
249
256
|
}
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
buildUserHeaders() {
|
|
258
|
+
const headers = this.buildProjectHeaders();
|
|
259
|
+
const userToken = localStorage.getItem("user_token");
|
|
260
|
+
if (userToken) {
|
|
261
|
+
headers["X-User"] = `${userToken}`;
|
|
262
|
+
}
|
|
263
|
+
return headers;
|
|
264
|
+
}
|
|
265
|
+
/** Exposed so database/RequestHeaders.ts can read current headers */
|
|
266
|
+
getHeaders() {
|
|
267
|
+
return this.buildUserHeaders();
|
|
268
|
+
}
|
|
269
|
+
getUserToken() {
|
|
270
|
+
const userToken = localStorage.getItem("user_token");
|
|
271
|
+
if (userToken) {
|
|
272
|
+
return userToken;
|
|
273
|
+
}
|
|
274
|
+
return "";
|
|
275
|
+
}
|
|
276
|
+
// ─── Session persistence ──────────────────────────────────────────────────
|
|
277
|
+
//
|
|
278
|
+
// WHAT IS STORED:
|
|
279
|
+
// localStorage["eauth"] — Credentials shape (uid, email, displayName …)
|
|
280
|
+
// Safe to persist. No tokens. No secrets.
|
|
281
|
+
//
|
|
282
|
+
// WHAT IS NOT STORED:
|
|
283
|
+
// accessToken — memory only. Lost on page reload.
|
|
284
|
+
// Restored transparently via auto-refresh on first
|
|
285
|
+
// authenticated request.
|
|
286
|
+
// refreshToken — never touches JS at all. Lives in an httpOnly
|
|
287
|
+
// cookie the server sets. The browser sends it
|
|
288
|
+
// automatically on POST /auth/refresh.
|
|
289
|
+
/** Synchronous. Reads only from localStorage — no network call. */
|
|
290
|
+
_restoreSession() {
|
|
291
|
+
const raw = localStorage.getItem("eauth");
|
|
292
|
+
const userToken = localStorage.getItem("user_token");
|
|
293
|
+
if (userToken) {
|
|
294
|
+
this.accessToken = userToken;
|
|
295
|
+
}
|
|
296
|
+
if (!raw)
|
|
297
|
+
return;
|
|
254
298
|
try {
|
|
255
|
-
|
|
299
|
+
const creds = Credentials.fromMap(JSON.parse(raw));
|
|
300
|
+
this.eUser = creds;
|
|
301
|
+
this._emit("creds", creds);
|
|
256
302
|
} catch {
|
|
257
303
|
localStorage.removeItem("eauth");
|
|
258
|
-
return null;
|
|
259
304
|
}
|
|
260
305
|
}
|
|
261
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Persist the user shape and update the in-memory access token.
|
|
308
|
+
* Pass credentials=null to fully clear the session (sign out).
|
|
309
|
+
*/
|
|
310
|
+
_saveSession(credentials, accessToken) {
|
|
262
311
|
if (!credentials) {
|
|
263
312
|
localStorage.removeItem("eauth");
|
|
313
|
+
localStorage.removeItem("user_token");
|
|
264
314
|
this.eUser = null;
|
|
265
|
-
this.
|
|
315
|
+
this.accessToken = null;
|
|
316
|
+
this._emit("creds", null);
|
|
266
317
|
return;
|
|
267
318
|
}
|
|
268
319
|
localStorage.setItem("eauth", JSON.stringify(credentials.toMap()));
|
|
269
320
|
this.eUser = credentials;
|
|
270
|
-
|
|
321
|
+
if (accessToken !== void 0) {
|
|
322
|
+
localStorage.setItem("user_token", accessToken ?? "");
|
|
323
|
+
this.accessToken = accessToken ?? null;
|
|
324
|
+
}
|
|
325
|
+
this._emit("creds", credentials);
|
|
271
326
|
}
|
|
272
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Returns the locally cached user — no network call.
|
|
329
|
+
* Returns null if nobody is signed in.
|
|
330
|
+
*/
|
|
331
|
+
currentUser() {
|
|
332
|
+
return this.eUser;
|
|
333
|
+
}
|
|
334
|
+
// ─── Auto-refresh ─────────────────────────────────────────────────────────
|
|
335
|
+
//
|
|
336
|
+
// When the access token expires (1h), the next request gets 401.
|
|
337
|
+
// makeRequest() catches this, calls _refreshAccessToken() once, then retries.
|
|
338
|
+
//
|
|
339
|
+
// The queue pattern prevents N parallel requests from triggering N refreshes.
|
|
340
|
+
// Only the first caller refreshes; the rest wait and get the same new token.
|
|
341
|
+
async _refreshAccessToken() {
|
|
342
|
+
if (this.isRefreshing) {
|
|
343
|
+
return new Promise((resolve) => this.refreshQueue.push(resolve));
|
|
344
|
+
}
|
|
345
|
+
this.isRefreshing = true;
|
|
346
|
+
try {
|
|
347
|
+
const r = await new HttpsRequest({
|
|
348
|
+
method: "POST" /* POST */,
|
|
349
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/refresh`,
|
|
350
|
+
headers: this.buildProjectHeaders(),
|
|
351
|
+
body: {}
|
|
352
|
+
}).sendRequest();
|
|
353
|
+
const res = await r.json().catch(() => ({}));
|
|
354
|
+
if (!res.success || !res.accessToken) {
|
|
355
|
+
this._saveSession(null);
|
|
356
|
+
this._drainQueue(null);
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
this.accessToken = res.accessToken;
|
|
360
|
+
this._drainQueue(res.accessToken);
|
|
361
|
+
return res.accessToken;
|
|
362
|
+
} catch {
|
|
363
|
+
this._saveSession(null);
|
|
364
|
+
this._drainQueue(null);
|
|
365
|
+
return null;
|
|
366
|
+
} finally {
|
|
367
|
+
this.isRefreshing = false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
_drainQueue(token) {
|
|
371
|
+
this.refreshQueue.forEach((resolve) => resolve(token));
|
|
372
|
+
this.refreshQueue = [];
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Central request method — all auth HTTP calls go through here.
|
|
376
|
+
* Handles the access-token-expired → refresh → retry cycle automatically.
|
|
377
|
+
*/
|
|
378
|
+
async makeRequest(options) {
|
|
379
|
+
const res = await new HttpsRequest({
|
|
380
|
+
method: options.method,
|
|
381
|
+
endpoint: options.endpoint,
|
|
382
|
+
headers: {
|
|
383
|
+
...options.headers,
|
|
384
|
+
...this.getHeaders()
|
|
385
|
+
},
|
|
386
|
+
body: options.body ?? {},
|
|
387
|
+
file: options.file,
|
|
388
|
+
isMultipart: options.isMultipart ?? !!options.file
|
|
389
|
+
}).sendRequest();
|
|
390
|
+
const body = await res.json().catch(() => ({}));
|
|
391
|
+
const error = body.error ?? "";
|
|
392
|
+
if (!options.isRetry && res.status === 401 && typeof error === "string" && (error.toLowerCase() === "access token expired" || error.toLowerCase() === "invalid access token")) {
|
|
393
|
+
const newToken = await this._refreshAccessToken();
|
|
394
|
+
if (!newToken)
|
|
395
|
+
throw new Error("Session expired. Please sign in again.");
|
|
396
|
+
console.log("[Auth] Retrying request with new access token");
|
|
397
|
+
return this.makeRequest({ ...options, headers: this.buildUserHeaders(), isRetry: true });
|
|
398
|
+
}
|
|
399
|
+
return body;
|
|
400
|
+
}
|
|
401
|
+
// ─── Auth state listener ──────────────────────────────────────────────────
|
|
402
|
+
//
|
|
403
|
+
// Synchronous setup — fires onChange immediately with the current user
|
|
404
|
+
// (from localStorage restore), then streams changes as they happen.
|
|
405
|
+
//
|
|
406
|
+
// Also subscribes to __users via onSnapshot — if the user is disabled
|
|
407
|
+
// server-side, the client signs out in real time without a page reload.
|
|
408
|
+
//
|
|
409
|
+
// Returns an unsubscribe function (no Promise — matches Firebase's API shape).
|
|
273
410
|
authState({
|
|
274
411
|
onChange,
|
|
275
412
|
onSignOut,
|
|
276
413
|
onDeleted
|
|
277
414
|
}) {
|
|
278
|
-
let
|
|
415
|
+
let userDocUnsub;
|
|
279
416
|
const handleCredsChange = (creds) => {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
userDocUnsubscribe = void 0;
|
|
283
|
-
}
|
|
417
|
+
userDocUnsub?.();
|
|
418
|
+
userDocUnsub = void 0;
|
|
284
419
|
if (!creds) {
|
|
285
420
|
this.eUser = null;
|
|
286
421
|
onSignOut?.();
|
|
@@ -289,10 +424,10 @@ var _Authentication = class _Authentication {
|
|
|
289
424
|
const userRef = this.app?.getDatabase.collection("__users").doc(creds.uid);
|
|
290
425
|
if (!userRef)
|
|
291
426
|
return;
|
|
292
|
-
|
|
427
|
+
userDocUnsub = userRef.onSnapshot(
|
|
293
428
|
(snapshot, change) => {
|
|
294
429
|
if (change === "delete") {
|
|
295
|
-
this.
|
|
430
|
+
this._saveSession(null);
|
|
296
431
|
onSignOut?.();
|
|
297
432
|
onDeleted?.();
|
|
298
433
|
return;
|
|
@@ -300,33 +435,49 @@ var _Authentication = class _Authentication {
|
|
|
300
435
|
if (change === "insert" || change === "update") {
|
|
301
436
|
if (!snapshot)
|
|
302
437
|
return;
|
|
303
|
-
if (snapshot.data.
|
|
304
|
-
this.
|
|
438
|
+
if (snapshot.data.disabled === true) {
|
|
439
|
+
this._saveSession(null);
|
|
305
440
|
onSignOut?.();
|
|
306
441
|
return;
|
|
307
442
|
}
|
|
308
443
|
const newCreds = Credentials.fromMap(snapshot.toMap());
|
|
309
|
-
if (
|
|
444
|
+
if (JSON.stringify(newCreds.toMap()) !== JSON.stringify(this.eUser?.toMap())) {
|
|
310
445
|
this.eUser = newCreds;
|
|
311
|
-
this.
|
|
446
|
+
this._saveSession(newCreds);
|
|
312
447
|
onChange(newCreds);
|
|
313
448
|
}
|
|
314
449
|
}
|
|
315
450
|
}
|
|
316
451
|
);
|
|
317
452
|
};
|
|
318
|
-
const
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
handleCredsChange(initialUser);
|
|
453
|
+
const initial = this.currentUser();
|
|
454
|
+
if (initial)
|
|
455
|
+
onChange(initial);
|
|
456
|
+
handleCredsChange(initial);
|
|
324
457
|
const credsUnsub = this.onValue("creds", handleCredsChange);
|
|
325
458
|
return () => {
|
|
326
459
|
credsUnsub();
|
|
327
|
-
|
|
460
|
+
userDocUnsub?.();
|
|
328
461
|
};
|
|
329
462
|
}
|
|
463
|
+
// ─── Event system ─────────────────────────────────────────────────────────
|
|
464
|
+
_emit(key, value) {
|
|
465
|
+
(this.eventListeners.get(key) ?? []).forEach((l) => l(value));
|
|
466
|
+
}
|
|
467
|
+
onValue(key, callback) {
|
|
468
|
+
const listeners = this.eventListeners.get(key) ?? [];
|
|
469
|
+
listeners.push(callback);
|
|
470
|
+
this.eventListeners.set(key, listeners);
|
|
471
|
+
return () => {
|
|
472
|
+
this.eventListeners.set(key, listeners.filter((cb) => cb !== callback));
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
// ─── Dispose ──────────────────────────────────────────────────────────────
|
|
476
|
+
dispose() {
|
|
477
|
+
this._saveSession(null);
|
|
478
|
+
this.eventListeners.clear();
|
|
479
|
+
_Authentication.instance = null;
|
|
480
|
+
}
|
|
330
481
|
};
|
|
331
482
|
_Authentication.instance = null;
|
|
332
483
|
var Authentication = _Authentication;
|
|
@@ -338,7 +489,7 @@ var DocumentSnapshot = class _DocumentSnapshot {
|
|
|
338
489
|
this.data = doc;
|
|
339
490
|
}
|
|
340
491
|
static fromMap(map) {
|
|
341
|
-
const id = map.
|
|
492
|
+
const id = map._id ?? map.id ?? null;
|
|
342
493
|
const document2 = map.document ?? map.documents ?? map.data ?? map;
|
|
343
494
|
return new _DocumentSnapshot(id, document2);
|
|
344
495
|
}
|
|
@@ -400,16 +551,12 @@ var DocumentRef = class {
|
|
|
400
551
|
try {
|
|
401
552
|
validateDocumentData(data, "DocumentRef.update");
|
|
402
553
|
if (!this.persistence) {
|
|
403
|
-
const res = await
|
|
554
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
404
555
|
method: "POST" /* POST */,
|
|
405
556
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
406
|
-
headers: {
|
|
407
|
-
authorization: this.app.getConfig().token,
|
|
408
|
-
"x-project": this.app.getConfig().project,
|
|
409
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
410
|
-
},
|
|
557
|
+
headers: {},
|
|
411
558
|
body: { collection: this.collection, id: this.id, data }
|
|
412
|
-
})
|
|
559
|
+
});
|
|
413
560
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
414
561
|
}
|
|
415
562
|
const old = await this.persistence.getDoc(this.collection, this.id);
|
|
@@ -447,16 +594,12 @@ var DocumentRef = class {
|
|
|
447
594
|
async set(data) {
|
|
448
595
|
validateDocumentData(data, "DocumentRef.set");
|
|
449
596
|
if (!this.persistence) {
|
|
450
|
-
const res = await
|
|
597
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
451
598
|
method: "POST" /* POST */,
|
|
452
599
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
453
|
-
headers: {
|
|
454
|
-
authorization: this.app.getConfig().token,
|
|
455
|
-
"x-project": this.app.getConfig().project,
|
|
456
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
457
|
-
},
|
|
600
|
+
headers: {},
|
|
458
601
|
body: { collection: this.collection, data: { ...data, id: this.id } }
|
|
459
|
-
})
|
|
602
|
+
});
|
|
460
603
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
461
604
|
}
|
|
462
605
|
const existing = await this.persistence.getDoc(this.collection, this.id);
|
|
@@ -504,16 +647,12 @@ var DocumentRef = class {
|
|
|
504
647
|
this.syncEngine?.flush().catch(console.error);
|
|
505
648
|
return true;
|
|
506
649
|
}
|
|
507
|
-
const res = await
|
|
650
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
508
651
|
method: "POST" /* POST */,
|
|
509
652
|
endpoint: `${this.app.getBaseUrl()}/db/delete`,
|
|
510
|
-
headers: {
|
|
511
|
-
authorization: this.app.getConfig().token,
|
|
512
|
-
"x-project": this.app.getConfig().project,
|
|
513
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
514
|
-
},
|
|
653
|
+
headers: {},
|
|
515
654
|
body: { collection: this.collection, id: this.id }
|
|
516
|
-
})
|
|
655
|
+
});
|
|
517
656
|
return !!res?.success;
|
|
518
657
|
}
|
|
519
658
|
onSnapshot(callback) {
|
|
@@ -538,20 +677,17 @@ var DocumentRef = class {
|
|
|
538
677
|
}
|
|
539
678
|
async fetchRemoteSnapshot() {
|
|
540
679
|
try {
|
|
541
|
-
const res = await
|
|
680
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
542
681
|
method: "POST" /* POST */,
|
|
543
682
|
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
544
|
-
headers: {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
single: true
|
|
553
|
-
}
|
|
554
|
-
}).sendRequest();
|
|
683
|
+
headers: {},
|
|
684
|
+
body: { collection: this.collection, id: this.id, single: true }
|
|
685
|
+
});
|
|
686
|
+
const error = res?.error ?? null;
|
|
687
|
+
if (error) {
|
|
688
|
+
console.error(`[DocumentRef]:`, error);
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
555
691
|
if (!res?.success || !res.document)
|
|
556
692
|
return null;
|
|
557
693
|
const snapshot = DocumentSnapshot.fromMap(res.document);
|
|
@@ -560,7 +696,7 @@ var DocumentRef = class {
|
|
|
560
696
|
}
|
|
561
697
|
return snapshot;
|
|
562
698
|
} catch (error) {
|
|
563
|
-
console.error(`[DocumentRef]
|
|
699
|
+
console.error(`[DocumentRef]:`, error);
|
|
564
700
|
return null;
|
|
565
701
|
}
|
|
566
702
|
}
|
|
@@ -662,9 +798,8 @@ var Query = class {
|
|
|
662
798
|
const persistence = this.app.offline().persistence;
|
|
663
799
|
if (this.filter.length > 0 && persistence) {
|
|
664
800
|
const local = await this.getLocalFilteredSnapshots();
|
|
665
|
-
if (typeof navigator === "undefined" || !navigator.onLine)
|
|
801
|
+
if (typeof navigator === "undefined" || !navigator.onLine)
|
|
666
802
|
return local;
|
|
667
|
-
}
|
|
668
803
|
return this.refreshFromRemote();
|
|
669
804
|
}
|
|
670
805
|
if (persistence) {
|
|
@@ -678,17 +813,12 @@ var Query = class {
|
|
|
678
813
|
return this.refreshFromRemote();
|
|
679
814
|
}
|
|
680
815
|
async update(data) {
|
|
681
|
-
|
|
682
|
-
return new HttpsRequest({
|
|
816
|
+
return await this.app.getAuthentication.makeRequest({
|
|
683
817
|
method: "POST" /* POST */,
|
|
684
818
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
685
|
-
headers: {
|
|
686
|
-
body: {
|
|
687
|
-
|
|
688
|
-
filter: genfilter,
|
|
689
|
-
data
|
|
690
|
-
}
|
|
691
|
-
}).sendRequest();
|
|
819
|
+
headers: {},
|
|
820
|
+
body: { collection: this.collection, filter: this.buildFilter(), data }
|
|
821
|
+
});
|
|
692
822
|
}
|
|
693
823
|
onSnapshot(callback) {
|
|
694
824
|
const genfilter = this.buildFilter();
|
|
@@ -818,32 +948,22 @@ var Query = class {
|
|
|
818
948
|
}
|
|
819
949
|
async refreshFromRemote() {
|
|
820
950
|
try {
|
|
821
|
-
const
|
|
822
|
-
const res = await new HttpsRequest({
|
|
951
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
823
952
|
method: "POST" /* POST */,
|
|
824
953
|
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
825
|
-
headers: {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
},
|
|
830
|
-
body: {
|
|
831
|
-
collection: this.collection,
|
|
832
|
-
filter: genfilter
|
|
833
|
-
}
|
|
834
|
-
}).sendRequest();
|
|
835
|
-
if (!res?.success || !Array.isArray(res.documents)) {
|
|
954
|
+
headers: {},
|
|
955
|
+
body: { collection: this.collection, filter: this.buildFilter() }
|
|
956
|
+
});
|
|
957
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
836
958
|
return [];
|
|
837
|
-
}
|
|
838
959
|
const snapshots = res.documents.map((d) => {
|
|
839
960
|
d.id = d.id ?? d._id;
|
|
840
961
|
delete d._id;
|
|
841
962
|
return DocumentSnapshot.fromMap(d);
|
|
842
963
|
});
|
|
843
964
|
const persistence = this.app.offline().persistence;
|
|
844
|
-
if (!persistence)
|
|
965
|
+
if (!persistence)
|
|
845
966
|
return snapshots;
|
|
846
|
-
}
|
|
847
967
|
for (const snapshot of snapshots) {
|
|
848
968
|
await persistence.applyRemoteDoc(this.collection, snapshot.data);
|
|
849
969
|
}
|
|
@@ -894,13 +1014,12 @@ var Query = class {
|
|
|
894
1014
|
newIndex: childChange.newIndex,
|
|
895
1015
|
previousDoc: childChange.previousDoc
|
|
896
1016
|
};
|
|
897
|
-
if (childChange.type === "added")
|
|
1017
|
+
if (childChange.type === "added")
|
|
898
1018
|
callbacks.onChildAdded?.(childChange.doc, context);
|
|
899
|
-
|
|
1019
|
+
else if (childChange.type === "modified")
|
|
900
1020
|
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
901
|
-
|
|
1021
|
+
else if (childChange.type === "removed")
|
|
902
1022
|
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
903
|
-
}
|
|
904
1023
|
});
|
|
905
1024
|
},
|
|
906
1025
|
this.buildFilter()
|
|
@@ -977,19 +1096,12 @@ var CollectionRef = class {
|
|
|
977
1096
|
this.syncEngine?.flush().catch(console.error);
|
|
978
1097
|
return snap;
|
|
979
1098
|
}
|
|
980
|
-
const res = await
|
|
1099
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
981
1100
|
method: "POST" /* POST */,
|
|
982
1101
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
983
|
-
headers: {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
user: JSON.stringify(this.authentication.currentUser()?.toMap())
|
|
987
|
-
},
|
|
988
|
-
body: {
|
|
989
|
-
collection: this.collection,
|
|
990
|
-
data: { ...data, id: "" }
|
|
991
|
-
}
|
|
992
|
-
}).sendRequest();
|
|
1102
|
+
headers: {},
|
|
1103
|
+
body: { collection: this.collection, data: { ...data } }
|
|
1104
|
+
});
|
|
993
1105
|
if (!res?.success || !res.document)
|
|
994
1106
|
return null;
|
|
995
1107
|
return DocumentSnapshot.fromMap(res.document);
|
|
@@ -1038,23 +1150,19 @@ var CollectionRef = class {
|
|
|
1038
1150
|
}
|
|
1039
1151
|
async refreshFromRemote() {
|
|
1040
1152
|
try {
|
|
1041
|
-
const res = await
|
|
1153
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1042
1154
|
method: "POST" /* POST */,
|
|
1043
1155
|
endpoint: `${serverURI}/db/read`,
|
|
1044
|
-
headers: {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
body: {
|
|
1051
|
-
collection: this.collection,
|
|
1052
|
-
filter: {}
|
|
1053
|
-
}
|
|
1054
|
-
}).sendRequest();
|
|
1055
|
-
if (!res?.success || !Array.isArray(res.documents)) {
|
|
1156
|
+
headers: {},
|
|
1157
|
+
body: { collection: this.collection, filter: {} }
|
|
1158
|
+
});
|
|
1159
|
+
const error = res?.error ?? null;
|
|
1160
|
+
if (error) {
|
|
1161
|
+
console.error(`[CollectionRef]:`, error);
|
|
1056
1162
|
return [];
|
|
1057
1163
|
}
|
|
1164
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
1165
|
+
return [];
|
|
1058
1166
|
const normalized = res.documents.map((raw) => {
|
|
1059
1167
|
const doc = { ...raw };
|
|
1060
1168
|
doc.id = doc.id ?? doc._id;
|
|
@@ -1069,36 +1177,32 @@ var CollectionRef = class {
|
|
|
1069
1177
|
}
|
|
1070
1178
|
return normalized.map((doc) => DocumentSnapshot.fromMap(doc));
|
|
1071
1179
|
} catch (error) {
|
|
1072
|
-
console.error(
|
|
1180
|
+
console.error(`[CollectionRef]:`, error);
|
|
1073
1181
|
return [];
|
|
1074
1182
|
}
|
|
1075
1183
|
}
|
|
1076
1184
|
async fetchRemoteSnapshots() {
|
|
1077
1185
|
try {
|
|
1078
|
-
const res = await
|
|
1186
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1079
1187
|
method: "POST" /* POST */,
|
|
1080
1188
|
endpoint: `${serverURI}/db/read`,
|
|
1081
|
-
headers: {
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
body: {
|
|
1088
|
-
collection: this.collection,
|
|
1089
|
-
filter: {}
|
|
1090
|
-
}
|
|
1091
|
-
}).sendRequest();
|
|
1092
|
-
if (!res?.success || !Array.isArray(res.documents)) {
|
|
1189
|
+
headers: {},
|
|
1190
|
+
body: { collection: this.collection, filter: {} }
|
|
1191
|
+
});
|
|
1192
|
+
const error = res?.error ?? null;
|
|
1193
|
+
if (error) {
|
|
1194
|
+
console.error(`[CollectionRef]:`, error);
|
|
1093
1195
|
return [];
|
|
1094
1196
|
}
|
|
1197
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
1198
|
+
return [];
|
|
1095
1199
|
return res.documents.map((d) => {
|
|
1096
1200
|
d.id = d.id ?? d._id;
|
|
1097
1201
|
delete d._id;
|
|
1098
1202
|
return DocumentSnapshot.fromMap(d);
|
|
1099
1203
|
});
|
|
1100
1204
|
} catch (error) {
|
|
1101
|
-
console.error(
|
|
1205
|
+
console.error(`[CollectionRef]:`, error);
|
|
1102
1206
|
return [];
|
|
1103
1207
|
}
|
|
1104
1208
|
}
|
|
@@ -1137,20 +1241,18 @@ var CollectionRef = class {
|
|
|
1137
1241
|
newIndex: childChange.newIndex,
|
|
1138
1242
|
previousDoc: childChange.previousDoc
|
|
1139
1243
|
};
|
|
1140
|
-
if (childChange.type === "added")
|
|
1244
|
+
if (childChange.type === "added")
|
|
1141
1245
|
callbacks.onChildAdded?.(childChange.doc, context);
|
|
1142
|
-
|
|
1246
|
+
else if (childChange.type === "modified")
|
|
1143
1247
|
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
1144
|
-
|
|
1248
|
+
else if (childChange.type === "removed")
|
|
1145
1249
|
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
1146
|
-
}
|
|
1147
1250
|
});
|
|
1148
1251
|
});
|
|
1149
1252
|
}
|
|
1150
1253
|
emitInitialChildEvents(snapshots, callbacks) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
const context = {
|
|
1254
|
+
diffSnapshots([], snapshots).forEach((change) => {
|
|
1255
|
+
callbacks.onChildAdded?.(change.doc, {
|
|
1154
1256
|
snapshots,
|
|
1155
1257
|
source: "initial",
|
|
1156
1258
|
changedDocId: change.doc.id,
|
|
@@ -1158,8 +1260,7 @@ var CollectionRef = class {
|
|
|
1158
1260
|
oldIndex: change.oldIndex,
|
|
1159
1261
|
newIndex: change.newIndex,
|
|
1160
1262
|
previousDoc: change.previousDoc
|
|
1161
|
-
};
|
|
1162
|
-
callbacks.onChildAdded?.(change.doc, context);
|
|
1263
|
+
});
|
|
1163
1264
|
});
|
|
1164
1265
|
}
|
|
1165
1266
|
};
|
|
@@ -1171,38 +1272,19 @@ var Batch = class {
|
|
|
1171
1272
|
this.app = app;
|
|
1172
1273
|
}
|
|
1173
1274
|
set(docRef, data) {
|
|
1174
|
-
this.ops.push({
|
|
1175
|
-
op: "set",
|
|
1176
|
-
collection: docRef.collection,
|
|
1177
|
-
id: docRef.id,
|
|
1178
|
-
data
|
|
1179
|
-
});
|
|
1275
|
+
this.ops.push({ op: "set", collection: docRef.collection, id: docRef.id, data });
|
|
1180
1276
|
return this;
|
|
1181
1277
|
}
|
|
1182
1278
|
create(docRef, data) {
|
|
1183
|
-
this.ops.push({
|
|
1184
|
-
op: "create",
|
|
1185
|
-
collection: docRef.collection,
|
|
1186
|
-
id: docRef.id,
|
|
1187
|
-
data
|
|
1188
|
-
});
|
|
1279
|
+
this.ops.push({ op: "create", collection: docRef.collection, id: docRef.id, data });
|
|
1189
1280
|
return this;
|
|
1190
1281
|
}
|
|
1191
1282
|
update(docRef, data) {
|
|
1192
|
-
this.ops.push({
|
|
1193
|
-
op: "update",
|
|
1194
|
-
collection: docRef.collection,
|
|
1195
|
-
id: docRef.id,
|
|
1196
|
-
data
|
|
1197
|
-
});
|
|
1283
|
+
this.ops.push({ op: "update", collection: docRef.collection, id: docRef.id, data });
|
|
1198
1284
|
return this;
|
|
1199
1285
|
}
|
|
1200
1286
|
delete(docRef) {
|
|
1201
|
-
this.ops.push({
|
|
1202
|
-
op: "delete",
|
|
1203
|
-
collection: docRef.collection,
|
|
1204
|
-
id: docRef.id
|
|
1205
|
-
});
|
|
1287
|
+
this.ops.push({ op: "delete", collection: docRef.collection, id: docRef.id });
|
|
1206
1288
|
return this;
|
|
1207
1289
|
}
|
|
1208
1290
|
async commit() {
|
|
@@ -1251,16 +1333,12 @@ var Batch = class {
|
|
|
1251
1333
|
syncEngine?.flush().catch(console.error);
|
|
1252
1334
|
return { success: true, results };
|
|
1253
1335
|
}
|
|
1254
|
-
const res = await
|
|
1336
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1255
1337
|
method: "POST" /* POST */,
|
|
1256
1338
|
endpoint: `${serverURI}/db/batch`,
|
|
1257
|
-
headers: {
|
|
1258
|
-
authorization: this.app.getConfig().token,
|
|
1259
|
-
"x-project": this.app.getConfig().project,
|
|
1260
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
1261
|
-
},
|
|
1339
|
+
headers: {},
|
|
1262
1340
|
body: { ops: this.ops }
|
|
1263
|
-
})
|
|
1341
|
+
});
|
|
1264
1342
|
if (res?.error) {
|
|
1265
1343
|
throw new Error(res.error.message || "Batch commit failed");
|
|
1266
1344
|
}
|
|
@@ -1307,12 +1385,12 @@ var Database = class {
|
|
|
1307
1385
|
}
|
|
1308
1386
|
};
|
|
1309
1387
|
await transactionFn(tx);
|
|
1310
|
-
const res = await
|
|
1388
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1311
1389
|
method: "POST" /* POST */,
|
|
1312
1390
|
endpoint: `${this.app.getBaseUrl()}/db/transaction`,
|
|
1313
|
-
headers: {
|
|
1391
|
+
headers: {},
|
|
1314
1392
|
body: { ops: tx.ops }
|
|
1315
|
-
})
|
|
1393
|
+
});
|
|
1316
1394
|
if (res?.error) {
|
|
1317
1395
|
throw new Error(res.error.message || "Transaction failed");
|
|
1318
1396
|
}
|
|
@@ -1339,6 +1417,14 @@ var Database = class {
|
|
|
1339
1417
|
}
|
|
1340
1418
|
};
|
|
1341
1419
|
|
|
1420
|
+
// src/core/RequestHeaders.ts
|
|
1421
|
+
function socketAuth(app) {
|
|
1422
|
+
const base = {
|
|
1423
|
+
...app.getAuthentication.getHeaders()
|
|
1424
|
+
};
|
|
1425
|
+
return base;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1342
1428
|
// src/database/Realtime.ts
|
|
1343
1429
|
import { io } from "socket.io-client";
|
|
1344
1430
|
|
|
@@ -1348,7 +1434,7 @@ function normalizePayload(payload) {
|
|
|
1348
1434
|
if (!raw)
|
|
1349
1435
|
return null;
|
|
1350
1436
|
const doc = { ...raw };
|
|
1351
|
-
doc.id = doc.id ?? raw?.id ??
|
|
1437
|
+
doc.id = raw?._id ?? doc.id ?? raw?.id ?? payload?._id ?? payload?.id;
|
|
1352
1438
|
delete doc._id;
|
|
1353
1439
|
return {
|
|
1354
1440
|
change: payload?.change ?? raw?.change ?? null,
|
|
@@ -1373,11 +1459,7 @@ var Realtime = class {
|
|
|
1373
1459
|
this.socket = io(this.app.getSocketUrl(), {
|
|
1374
1460
|
transports: ["websocket"],
|
|
1375
1461
|
auth: {
|
|
1376
|
-
|
|
1377
|
-
"x-project": this.app.getConfig().project,
|
|
1378
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
1379
|
-
user: this.app.getAuthentication.currentUser()
|
|
1380
|
-
// Pass user info for personalized permissions
|
|
1462
|
+
...socketAuth(this.app)
|
|
1381
1463
|
},
|
|
1382
1464
|
autoConnect: true,
|
|
1383
1465
|
reconnection: true,
|
|
@@ -1421,19 +1503,19 @@ var Realtime = class {
|
|
|
1421
1503
|
/**
|
|
1422
1504
|
* Low-level collection subscription (used by RealtimeBridge)
|
|
1423
1505
|
*/
|
|
1506
|
+
// Realtime.ts - subscribeToCollectionRaw
|
|
1424
1507
|
subscribeToCollectionRaw(collection, callback, filter = {}) {
|
|
1425
1508
|
this.connect();
|
|
1426
|
-
const lid = collection
|
|
1509
|
+
const lid = `${collection}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1427
1510
|
const handlers = [];
|
|
1428
|
-
this.socket.emit("subscribe", { collection, filter });
|
|
1511
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1429
1512
|
this.events.forEach((event) => {
|
|
1430
1513
|
const channel = `${lid}-${event}`;
|
|
1431
1514
|
const fn = (payload) => {
|
|
1432
1515
|
const normalized = normalizePayload(payload);
|
|
1433
1516
|
if (!normalized) {
|
|
1434
|
-
if (event === "delete")
|
|
1517
|
+
if (event === "delete")
|
|
1435
1518
|
callback(payload, "delete");
|
|
1436
|
-
}
|
|
1437
1519
|
return;
|
|
1438
1520
|
}
|
|
1439
1521
|
callback(normalized.data, event);
|
|
@@ -1449,10 +1531,10 @@ var Realtime = class {
|
|
|
1449
1531
|
*/
|
|
1450
1532
|
subscribeToDocumentRaw(collection, id, callback) {
|
|
1451
1533
|
this.connect();
|
|
1452
|
-
const lid = collection
|
|
1534
|
+
const lid = `${collection}_${id}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1453
1535
|
const filter = { _id: id };
|
|
1454
1536
|
const handlers = [];
|
|
1455
|
-
this.socket.emit("subscribe", { collection, filter });
|
|
1537
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1456
1538
|
this.events.forEach((event) => {
|
|
1457
1539
|
const channel = `${lid}-${event}`;
|
|
1458
1540
|
const fn = (payload) => {
|
|
@@ -1512,12 +1594,12 @@ var Functions = class {
|
|
|
1512
1594
|
}
|
|
1513
1595
|
async call(functionName, data) {
|
|
1514
1596
|
try {
|
|
1515
|
-
const res = await
|
|
1597
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1516
1598
|
method: "POST" /* POST */,
|
|
1517
1599
|
endpoint: serverURI + "/functions/call/" + functionName,
|
|
1518
|
-
headers: {
|
|
1600
|
+
headers: {},
|
|
1519
1601
|
body: data
|
|
1520
|
-
})
|
|
1602
|
+
});
|
|
1521
1603
|
return res;
|
|
1522
1604
|
} catch (err) {
|
|
1523
1605
|
throw {
|
|
@@ -1535,15 +1617,15 @@ var Hosting = class {
|
|
|
1535
1617
|
}
|
|
1536
1618
|
async createSubdomain(name) {
|
|
1537
1619
|
try {
|
|
1538
|
-
const res = await
|
|
1620
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1539
1621
|
method: "POST" /* POST */,
|
|
1540
1622
|
endpoint: this.app.getBaseUrl() + "/hosting/register",
|
|
1541
|
-
headers: {
|
|
1623
|
+
headers: {},
|
|
1542
1624
|
body: {
|
|
1543
1625
|
hostname: name,
|
|
1544
1626
|
project: this.app.getConfig().project
|
|
1545
1627
|
}
|
|
1546
|
-
})
|
|
1628
|
+
});
|
|
1547
1629
|
return res;
|
|
1548
1630
|
} catch (err) {
|
|
1549
1631
|
throw {
|
|
@@ -2389,6 +2471,7 @@ var SyncEngine = class {
|
|
|
2389
2471
|
constructor(app, persistence, store, realtimeBridge) {
|
|
2390
2472
|
this.realtimeBridge = null;
|
|
2391
2473
|
this.syncing = false;
|
|
2474
|
+
// Use ReturnType<typeof setTimeout> for cross-env timeout type (works in browser and Node)
|
|
2392
2475
|
this.retryTimeout = null;
|
|
2393
2476
|
this.MAX_RETRIES = 5;
|
|
2394
2477
|
this.BASE_RETRY_DELAY = 2e3;
|
|
@@ -2401,6 +2484,7 @@ var SyncEngine = class {
|
|
|
2401
2484
|
this.persistence = persistence;
|
|
2402
2485
|
this.store = store;
|
|
2403
2486
|
this.realtimeBridge = realtimeBridge || null;
|
|
2487
|
+
this.authentication = app.getAuthentication;
|
|
2404
2488
|
this.persistence.recoverSyncingMutations().catch(console.error);
|
|
2405
2489
|
if (typeof window !== "undefined") {
|
|
2406
2490
|
window.addEventListener("online", this.handleOnline);
|
|
@@ -2477,15 +2561,15 @@ var SyncEngine = class {
|
|
|
2477
2561
|
}
|
|
2478
2562
|
}
|
|
2479
2563
|
async syncCreate(mutation) {
|
|
2480
|
-
const res = await
|
|
2564
|
+
const res = await this.authentication.makeRequest({
|
|
2481
2565
|
method: "POST" /* POST */,
|
|
2482
2566
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
2483
|
-
headers: {
|
|
2567
|
+
headers: {},
|
|
2484
2568
|
body: {
|
|
2485
2569
|
collection: mutation.collection,
|
|
2486
2570
|
data: mutation.payload
|
|
2487
2571
|
}
|
|
2488
|
-
})
|
|
2572
|
+
});
|
|
2489
2573
|
if (!res?.success || !res.id)
|
|
2490
2574
|
return false;
|
|
2491
2575
|
const oldId = mutation.documentId;
|
|
@@ -2503,21 +2587,16 @@ var SyncEngine = class {
|
|
|
2503
2587
|
return true;
|
|
2504
2588
|
}
|
|
2505
2589
|
async syncUpdate(mutation) {
|
|
2506
|
-
const res = await
|
|
2590
|
+
const res = await this.authentication.makeRequest({
|
|
2507
2591
|
method: "POST" /* POST */,
|
|
2508
2592
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
2509
|
-
headers: {
|
|
2510
|
-
authorization: this.app.getConfig().token,
|
|
2511
|
-
"x-project": this.app.getConfig().project,
|
|
2512
|
-
user: JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
2513
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
2514
|
-
},
|
|
2593
|
+
headers: {},
|
|
2515
2594
|
body: {
|
|
2516
2595
|
collection: mutation.collection,
|
|
2517
2596
|
document: mutation.documentId,
|
|
2518
2597
|
data: mutation.payload
|
|
2519
2598
|
}
|
|
2520
|
-
})
|
|
2599
|
+
});
|
|
2521
2600
|
if (!res?.success)
|
|
2522
2601
|
return false;
|
|
2523
2602
|
const local = await this.persistence.upsertDoc({
|
|
@@ -2540,20 +2619,15 @@ var SyncEngine = class {
|
|
|
2540
2619
|
return true;
|
|
2541
2620
|
}
|
|
2542
2621
|
async syncDelete(mutation) {
|
|
2543
|
-
const res = await
|
|
2622
|
+
const res = await this.authentication.makeRequest({
|
|
2544
2623
|
method: "POST" /* POST */,
|
|
2545
2624
|
endpoint: `${this.app.getBaseUrl()}/db/delete`,
|
|
2546
|
-
headers: {
|
|
2547
|
-
authorization: this.app.getConfig().token,
|
|
2548
|
-
"x-project": this.app.getConfig().project,
|
|
2549
|
-
user: JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
2550
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
2551
|
-
},
|
|
2625
|
+
headers: {},
|
|
2552
2626
|
body: {
|
|
2553
2627
|
collection: mutation.collection,
|
|
2554
2628
|
document: mutation.documentId
|
|
2555
2629
|
}
|
|
2556
|
-
})
|
|
2630
|
+
});
|
|
2557
2631
|
if (!res?.success)
|
|
2558
2632
|
return false;
|
|
2559
2633
|
await this.persistence.markDeleted(mutation.collection, mutation.documentId, 0);
|
|
@@ -2652,13 +2726,11 @@ var StorageRef = class {
|
|
|
2652
2726
|
this.app = app;
|
|
2653
2727
|
}
|
|
2654
2728
|
async get(id) {
|
|
2655
|
-
const res = await
|
|
2729
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2656
2730
|
method: "GET" /* GET */,
|
|
2657
2731
|
endpoint: serverURI + `/storage/${id}`,
|
|
2658
|
-
headers: {
|
|
2659
|
-
|
|
2660
|
-
}
|
|
2661
|
-
}).sendRequest();
|
|
2732
|
+
headers: {}
|
|
2733
|
+
});
|
|
2662
2734
|
if (res.success) {
|
|
2663
2735
|
if (res.document === void 0)
|
|
2664
2736
|
return void 0;
|
|
@@ -2669,13 +2741,11 @@ var StorageRef = class {
|
|
|
2669
2741
|
}
|
|
2670
2742
|
}
|
|
2671
2743
|
async getMeta(id) {
|
|
2672
|
-
const res = await
|
|
2744
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2673
2745
|
method: "GET" /* GET */,
|
|
2674
2746
|
endpoint: serverURI + `/storage/${id}/info`,
|
|
2675
|
-
headers: {
|
|
2676
|
-
|
|
2677
|
-
}
|
|
2678
|
-
}).sendRequest();
|
|
2747
|
+
headers: {}
|
|
2748
|
+
});
|
|
2679
2749
|
if (res.success) {
|
|
2680
2750
|
if (res.document === void 0)
|
|
2681
2751
|
return void 0;
|
|
@@ -2689,13 +2759,13 @@ var StorageRef = class {
|
|
|
2689
2759
|
if (!srcFile) {
|
|
2690
2760
|
throw new Error("Invalid File");
|
|
2691
2761
|
}
|
|
2692
|
-
const res = await
|
|
2762
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2693
2763
|
method: "POST" /* POST */,
|
|
2694
2764
|
endpoint: serverURI + "/storage/file/upload",
|
|
2695
|
-
headers: {
|
|
2765
|
+
headers: {},
|
|
2696
2766
|
file: srcFile,
|
|
2697
2767
|
isMultipart: true
|
|
2698
|
-
})
|
|
2768
|
+
});
|
|
2699
2769
|
if (res.success) {
|
|
2700
2770
|
if (res.document === void 0)
|
|
2701
2771
|
return void 0;
|
|
@@ -2706,14 +2776,14 @@ var StorageRef = class {
|
|
|
2706
2776
|
}
|
|
2707
2777
|
}
|
|
2708
2778
|
async delete(id) {
|
|
2709
|
-
const res = await
|
|
2779
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2710
2780
|
method: "POST" /* POST */,
|
|
2711
2781
|
endpoint: serverURI + "/storage/file/delete",
|
|
2712
|
-
headers: {
|
|
2782
|
+
headers: {},
|
|
2713
2783
|
body: {
|
|
2714
2784
|
file_id: id
|
|
2715
2785
|
}
|
|
2716
|
-
})
|
|
2786
|
+
});
|
|
2717
2787
|
return res;
|
|
2718
2788
|
}
|
|
2719
2789
|
};
|