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.mjs
CHANGED
|
@@ -1,41 +1,89 @@
|
|
|
1
1
|
// src/authentication/Credentials.ts
|
|
2
2
|
var Credentials = class _Credentials {
|
|
3
|
-
constructor(uid, displayInfo, createdAt, updatedAt,
|
|
3
|
+
constructor(uid, displayInfo, data, verified, disabled, provider, createdAt, updatedAt, lastLogin) {
|
|
4
4
|
this.uid = uid;
|
|
5
5
|
this.displayInfo = displayInfo;
|
|
6
|
+
this.data = data;
|
|
7
|
+
this.verified = verified;
|
|
8
|
+
this.disabled = disabled;
|
|
9
|
+
this.provider = provider;
|
|
6
10
|
this.createdAt = createdAt;
|
|
7
11
|
this.updatedAt = updatedAt;
|
|
8
|
-
this.
|
|
9
|
-
this.logged = logged === true;
|
|
12
|
+
this.lastLogin = lastLogin;
|
|
10
13
|
}
|
|
11
14
|
static fromMap(map) {
|
|
12
|
-
const uid = map.id ?? map.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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(
|
|
20
41
|
uid,
|
|
21
|
-
|
|
42
|
+
displayInfo,
|
|
43
|
+
data,
|
|
44
|
+
verified,
|
|
45
|
+
disabled,
|
|
46
|
+
provider,
|
|
22
47
|
createdAt,
|
|
23
48
|
updatedAt,
|
|
24
|
-
|
|
25
|
-
logged
|
|
49
|
+
lastLogin
|
|
26
50
|
);
|
|
27
|
-
return
|
|
51
|
+
return output;
|
|
28
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.
|
|
29
57
|
toMap() {
|
|
30
58
|
return {
|
|
31
59
|
uid: this.uid,
|
|
32
60
|
displayInfo: this.displayInfo,
|
|
61
|
+
data: this.data,
|
|
62
|
+
verified: this.verified,
|
|
63
|
+
disabled: this.disabled,
|
|
64
|
+
provider: this.provider,
|
|
33
65
|
createdAt: this.createdAt,
|
|
34
66
|
updatedAt: this.updatedAt,
|
|
35
|
-
|
|
36
|
-
logged: this.logged
|
|
67
|
+
lastLogin: this.lastLogin
|
|
37
68
|
};
|
|
38
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
|
+
}
|
|
39
87
|
};
|
|
40
88
|
|
|
41
89
|
// src/utils/HttpsRequest.ts
|
|
@@ -68,11 +116,7 @@ var HttpsRequest = class {
|
|
|
68
116
|
headers: this.headers,
|
|
69
117
|
body: formData
|
|
70
118
|
});
|
|
71
|
-
|
|
72
|
-
const errorText = await response2.text().catch(() => "Network error");
|
|
73
|
-
throw new Error(`HTTP ${response2.status}: ${errorText}`);
|
|
74
|
-
}
|
|
75
|
-
return await response2.json().catch(() => ({}));
|
|
119
|
+
return response2;
|
|
76
120
|
}
|
|
77
121
|
const response = await fetch(this.endpoint, {
|
|
78
122
|
method: this.method,
|
|
@@ -82,11 +126,7 @@ var HttpsRequest = class {
|
|
|
82
126
|
},
|
|
83
127
|
body: this.method !== "GET" /* GET */ ? JSON.stringify(this.body) : void 0
|
|
84
128
|
});
|
|
85
|
-
|
|
86
|
-
const errorText = await response.text().catch(() => "Network error");
|
|
87
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
88
|
-
}
|
|
89
|
-
return await response.json().catch(() => ({}));
|
|
129
|
+
return response;
|
|
90
130
|
} catch (error) {
|
|
91
131
|
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
|
92
132
|
throw new Error("Network connection failed. Please check your internet connection.");
|
|
@@ -102,221 +142,292 @@ var HttpsRequest = class {
|
|
|
102
142
|
// src/authentication/Authentication.ts
|
|
103
143
|
var _Authentication = class _Authentication {
|
|
104
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 = [];
|
|
105
155
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const app = this.app?.getDatabase;
|
|
122
|
-
const data = await app?.collection("authentication").query.where({
|
|
123
|
-
key: "email",
|
|
124
|
-
op: "===",
|
|
125
|
-
value: email
|
|
126
|
-
}).get();
|
|
127
|
-
if (data.length > 0) {
|
|
128
|
-
throw new Error("Email Already In Use.");
|
|
129
|
-
}
|
|
130
|
-
const userRef = await app?.collection("authentication").add({
|
|
131
|
-
email,
|
|
132
|
-
password,
|
|
133
|
-
logged: true,
|
|
134
|
-
lastLogged: Date.now(),
|
|
135
|
-
displayInfo: {
|
|
136
|
-
firstname: email.split("@")[0],
|
|
137
|
-
lastname: "",
|
|
138
|
-
surname: "",
|
|
139
|
-
profile: "./logo.png",
|
|
140
|
-
email,
|
|
141
|
-
phone: ""
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
if (!userRef?.id || userRef?.id === "") {
|
|
145
|
-
throw new Error("Something went wrong try Again.");
|
|
146
|
-
}
|
|
147
|
-
this.eUser = Credentials.fromMap(userRef);
|
|
148
|
-
this.saveCredentials(this.eUser);
|
|
149
|
-
return Credentials.fromMap(userRef.toMap());
|
|
150
|
-
};
|
|
151
|
-
this.signInWithEmailAndPassword = async ({
|
|
152
|
-
email,
|
|
153
|
-
password
|
|
154
|
-
}) => {
|
|
155
|
-
const app = this.app?.getDatabase;
|
|
156
|
-
const data = await app?.collection("authentication").query.where({
|
|
157
|
-
key: "email",
|
|
158
|
-
op: "===",
|
|
159
|
-
value: email
|
|
160
|
-
}).where({
|
|
161
|
-
key: "password",
|
|
162
|
-
op: "===",
|
|
163
|
-
value: password
|
|
164
|
-
}).get();
|
|
165
|
-
console.log("Auth Data:", data);
|
|
166
|
-
if (data === void 0) {
|
|
167
|
-
throw new Error("Auth Failed.");
|
|
168
|
-
}
|
|
169
|
-
if (data?.length === 0) {
|
|
170
|
-
throw new Error("User Not Found.");
|
|
156
|
+
// ─── Sign up ──────────────────────────────────────────────────────────────
|
|
157
|
+
this.createUserWithEmailAndPassword = async (data) => {
|
|
158
|
+
this._saveSession(null);
|
|
159
|
+
const r = await new HttpsRequest({
|
|
160
|
+
method: "POST" /* POST */,
|
|
161
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/signup`,
|
|
162
|
+
// ← /auth/signup
|
|
163
|
+
headers: this.buildProjectHeaders(),
|
|
164
|
+
body: data
|
|
165
|
+
}).sendRequest();
|
|
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);
|
|
171
171
|
}
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
lastLogged: Date.now()
|
|
176
|
-
});
|
|
177
|
-
this.eUser = Credentials.fromMap(_data);
|
|
178
|
-
this.saveCredentials(this.eUser);
|
|
179
|
-
return Credentials.fromMap(_data);
|
|
172
|
+
const creds = Credentials.fromMap(res.result);
|
|
173
|
+
this._saveSession(creds, res.accessToken ?? null);
|
|
174
|
+
return creds;
|
|
180
175
|
};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
176
|
+
// ─── Sign in ──────────────────────────────────────────────────────────────
|
|
177
|
+
this.signInWithEmailAndPassword = async ({ email, password }) => {
|
|
178
|
+
const r = await new HttpsRequest({
|
|
179
|
+
method: "POST" /* POST */,
|
|
180
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/login`,
|
|
181
|
+
headers: this.buildProjectHeaders(),
|
|
182
|
+
body: { email, password }
|
|
183
|
+
}).sendRequest();
|
|
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);
|
|
189
189
|
}
|
|
190
|
-
|
|
191
|
-
this.
|
|
190
|
+
const creds = Credentials.fromMap(res.result);
|
|
191
|
+
this._saveSession(creds, res.accessToken ?? null);
|
|
192
|
+
return creds;
|
|
192
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.
|
|
193
199
|
this.signOut = async () => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
};
|
|
205
|
-
this.resetPassword = async () => {
|
|
206
|
-
const app = this.app?.getDatabase;
|
|
207
|
-
const luid = this.currentUser();
|
|
208
|
-
const payload = {
|
|
209
|
-
uid: luid?.uid,
|
|
210
|
-
ttl: 3
|
|
211
|
-
//minutes
|
|
212
|
-
};
|
|
213
|
-
const id = await app?.collection("_auth_tokens").add(payload);
|
|
214
|
-
if (id) {
|
|
215
|
-
return `https://auth.edmaxlabs.com/reset/${id}`;
|
|
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);
|
|
216
210
|
}
|
|
217
|
-
throw new Error("Failed to request reset tokens. Try Again");
|
|
218
211
|
};
|
|
219
|
-
|
|
220
|
-
|
|
212
|
+
// ─── Delete user ──────────────────────────────────────────────────────────
|
|
213
|
+
this.deleteUser = async () => {
|
|
214
|
+
if (!this.eUser)
|
|
215
|
+
throw new Error("No user signed in");
|
|
216
|
+
const res = await this.makeRequest({
|
|
221
217
|
method: "POST" /* POST */,
|
|
222
|
-
endpoint: this.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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);
|
|
224
|
+
};
|
|
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({
|
|
232
|
+
method: "POST" /* POST */,
|
|
233
|
+
endpoint: `${this.app?.getBaseUrl()}/auth/password/reset-request`,
|
|
234
|
+
headers: this.buildProjectHeaders(),
|
|
235
|
+
body: { email }
|
|
231
236
|
}).sendRequest();
|
|
232
|
-
return res;
|
|
233
237
|
};
|
|
234
238
|
if (_Authentication.instance)
|
|
235
239
|
return _Authentication.instance;
|
|
236
|
-
this.
|
|
237
|
-
this.app = new EdmaxLabs({
|
|
238
|
-
token: "auth",
|
|
239
|
-
project: "698457c2f7448550b9e166c4"
|
|
240
|
-
});
|
|
240
|
+
this.app = EdmaxLabs.instance;
|
|
241
241
|
_Authentication.instance = this;
|
|
242
|
-
this.
|
|
243
|
-
}
|
|
244
|
-
restoreSession() {
|
|
245
|
-
const saved = this.currentUser();
|
|
246
|
-
if (saved) {
|
|
247
|
-
this.eUser = saved;
|
|
248
|
-
this.emitValue("creds", saved);
|
|
249
|
-
}
|
|
242
|
+
this._restoreSession();
|
|
250
243
|
}
|
|
251
|
-
// Better singleton
|
|
252
244
|
static getInstance() {
|
|
253
|
-
if (!_Authentication.instance)
|
|
254
|
-
|
|
255
|
-
}
|
|
245
|
+
if (!_Authentication.instance)
|
|
246
|
+
new _Authentication();
|
|
256
247
|
return _Authentication.instance;
|
|
257
248
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
onValue(key, callback) {
|
|
266
|
-
const listeners = this.eventListeners.get(key) || [];
|
|
267
|
-
listeners.push(callback);
|
|
268
|
-
this.eventListeners.set(key, listeners);
|
|
269
|
-
return () => {
|
|
270
|
-
const filtered = listeners.filter((cb) => cb !== callback);
|
|
271
|
-
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"
|
|
272
255
|
};
|
|
273
256
|
}
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
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;
|
|
278
298
|
try {
|
|
279
|
-
|
|
299
|
+
const creds = Credentials.fromMap(JSON.parse(raw));
|
|
300
|
+
this.eUser = creds;
|
|
301
|
+
this._emit("creds", creds);
|
|
280
302
|
} catch {
|
|
281
303
|
localStorage.removeItem("eauth");
|
|
282
|
-
return null;
|
|
283
304
|
}
|
|
284
305
|
}
|
|
285
|
-
|
|
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) {
|
|
286
311
|
if (!credentials) {
|
|
287
312
|
localStorage.removeItem("eauth");
|
|
313
|
+
localStorage.removeItem("user_token");
|
|
288
314
|
this.eUser = null;
|
|
289
|
-
this.
|
|
315
|
+
this.accessToken = null;
|
|
316
|
+
this._emit("creds", null);
|
|
290
317
|
return;
|
|
291
318
|
}
|
|
292
319
|
localStorage.setItem("eauth", JSON.stringify(credentials.toMap()));
|
|
293
320
|
this.eUser = credentials;
|
|
294
|
-
|
|
321
|
+
if (accessToken !== void 0) {
|
|
322
|
+
localStorage.setItem("user_token", accessToken ?? "");
|
|
323
|
+
this.accessToken = accessToken ?? null;
|
|
324
|
+
}
|
|
325
|
+
this._emit("creds", credentials);
|
|
326
|
+
}
|
|
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 = [];
|
|
295
373
|
}
|
|
296
|
-
|
|
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).
|
|
297
410
|
authState({
|
|
298
411
|
onChange,
|
|
299
412
|
onSignOut,
|
|
300
413
|
onDeleted
|
|
301
414
|
}) {
|
|
302
|
-
let
|
|
415
|
+
let userDocUnsub;
|
|
303
416
|
const handleCredsChange = (creds) => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
userDocUnsubscribe = void 0;
|
|
307
|
-
}
|
|
417
|
+
userDocUnsub?.();
|
|
418
|
+
userDocUnsub = void 0;
|
|
308
419
|
if (!creds) {
|
|
309
420
|
this.eUser = null;
|
|
310
421
|
onSignOut?.();
|
|
311
422
|
return;
|
|
312
423
|
}
|
|
313
|
-
const userRef = this.app?.getDatabase.collection("
|
|
424
|
+
const userRef = this.app?.getDatabase.collection("__users").doc(creds.uid);
|
|
314
425
|
if (!userRef)
|
|
315
426
|
return;
|
|
316
|
-
|
|
427
|
+
userDocUnsub = userRef.onSnapshot(
|
|
317
428
|
(snapshot, change) => {
|
|
318
429
|
if (change === "delete") {
|
|
319
|
-
this.
|
|
430
|
+
this._saveSession(null);
|
|
320
431
|
onSignOut?.();
|
|
321
432
|
onDeleted?.();
|
|
322
433
|
return;
|
|
@@ -324,33 +435,49 @@ var _Authentication = class _Authentication {
|
|
|
324
435
|
if (change === "insert" || change === "update") {
|
|
325
436
|
if (!snapshot)
|
|
326
437
|
return;
|
|
327
|
-
if (snapshot.data.
|
|
328
|
-
this.
|
|
438
|
+
if (snapshot.data.disabled === true) {
|
|
439
|
+
this._saveSession(null);
|
|
329
440
|
onSignOut?.();
|
|
330
441
|
return;
|
|
331
442
|
}
|
|
332
443
|
const newCreds = Credentials.fromMap(snapshot.toMap());
|
|
333
|
-
if (
|
|
444
|
+
if (JSON.stringify(newCreds.toMap()) !== JSON.stringify(this.eUser?.toMap())) {
|
|
334
445
|
this.eUser = newCreds;
|
|
335
|
-
this.
|
|
446
|
+
this._saveSession(newCreds);
|
|
336
447
|
onChange(newCreds);
|
|
337
448
|
}
|
|
338
449
|
}
|
|
339
450
|
}
|
|
340
451
|
);
|
|
341
452
|
};
|
|
342
|
-
const
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
handleCredsChange(initialUser);
|
|
453
|
+
const initial = this.currentUser();
|
|
454
|
+
if (initial)
|
|
455
|
+
onChange(initial);
|
|
456
|
+
handleCredsChange(initial);
|
|
348
457
|
const credsUnsub = this.onValue("creds", handleCredsChange);
|
|
349
458
|
return () => {
|
|
350
459
|
credsUnsub();
|
|
351
|
-
|
|
460
|
+
userDocUnsub?.();
|
|
352
461
|
};
|
|
353
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
|
+
}
|
|
354
481
|
};
|
|
355
482
|
_Authentication.instance = null;
|
|
356
483
|
var Authentication = _Authentication;
|
|
@@ -362,7 +489,7 @@ var DocumentSnapshot = class _DocumentSnapshot {
|
|
|
362
489
|
this.data = doc;
|
|
363
490
|
}
|
|
364
491
|
static fromMap(map) {
|
|
365
|
-
const id = map.
|
|
492
|
+
const id = map._id ?? map.id ?? null;
|
|
366
493
|
const document2 = map.document ?? map.documents ?? map.data ?? map;
|
|
367
494
|
return new _DocumentSnapshot(id, document2);
|
|
368
495
|
}
|
|
@@ -424,16 +551,12 @@ var DocumentRef = class {
|
|
|
424
551
|
try {
|
|
425
552
|
validateDocumentData(data, "DocumentRef.update");
|
|
426
553
|
if (!this.persistence) {
|
|
427
|
-
const res = await
|
|
554
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
428
555
|
method: "POST" /* POST */,
|
|
429
556
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
430
|
-
headers: {
|
|
431
|
-
authorization: this.app.getConfig().token,
|
|
432
|
-
"x-project": this.app.getConfig().project,
|
|
433
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
434
|
-
},
|
|
557
|
+
headers: {},
|
|
435
558
|
body: { collection: this.collection, id: this.id, data }
|
|
436
|
-
})
|
|
559
|
+
});
|
|
437
560
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
438
561
|
}
|
|
439
562
|
const old = await this.persistence.getDoc(this.collection, this.id);
|
|
@@ -471,16 +594,12 @@ var DocumentRef = class {
|
|
|
471
594
|
async set(data) {
|
|
472
595
|
validateDocumentData(data, "DocumentRef.set");
|
|
473
596
|
if (!this.persistence) {
|
|
474
|
-
const res = await
|
|
597
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
475
598
|
method: "POST" /* POST */,
|
|
476
599
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
477
|
-
headers: {
|
|
478
|
-
authorization: this.app.getConfig().token,
|
|
479
|
-
"x-project": this.app.getConfig().project,
|
|
480
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
481
|
-
},
|
|
600
|
+
headers: {},
|
|
482
601
|
body: { collection: this.collection, data: { ...data, id: this.id } }
|
|
483
|
-
})
|
|
602
|
+
});
|
|
484
603
|
return res?.success ? DocumentSnapshot.fromMap({ ...data, id: this.id }) : null;
|
|
485
604
|
}
|
|
486
605
|
const existing = await this.persistence.getDoc(this.collection, this.id);
|
|
@@ -528,16 +647,12 @@ var DocumentRef = class {
|
|
|
528
647
|
this.syncEngine?.flush().catch(console.error);
|
|
529
648
|
return true;
|
|
530
649
|
}
|
|
531
|
-
const res = await
|
|
650
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
532
651
|
method: "POST" /* POST */,
|
|
533
652
|
endpoint: `${this.app.getBaseUrl()}/db/delete`,
|
|
534
|
-
headers: {
|
|
535
|
-
authorization: this.app.getConfig().token,
|
|
536
|
-
"x-project": this.app.getConfig().project,
|
|
537
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
538
|
-
},
|
|
653
|
+
headers: {},
|
|
539
654
|
body: { collection: this.collection, id: this.id }
|
|
540
|
-
})
|
|
655
|
+
});
|
|
541
656
|
return !!res?.success;
|
|
542
657
|
}
|
|
543
658
|
onSnapshot(callback) {
|
|
@@ -562,20 +677,17 @@ var DocumentRef = class {
|
|
|
562
677
|
}
|
|
563
678
|
async fetchRemoteSnapshot() {
|
|
564
679
|
try {
|
|
565
|
-
const res = await
|
|
680
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
566
681
|
method: "POST" /* POST */,
|
|
567
682
|
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
568
|
-
headers: {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
single: true
|
|
577
|
-
}
|
|
578
|
-
}).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
|
+
}
|
|
579
691
|
if (!res?.success || !res.document)
|
|
580
692
|
return null;
|
|
581
693
|
const snapshot = DocumentSnapshot.fromMap(res.document);
|
|
@@ -584,7 +696,7 @@ var DocumentRef = class {
|
|
|
584
696
|
}
|
|
585
697
|
return snapshot;
|
|
586
698
|
} catch (error) {
|
|
587
|
-
console.error(`[DocumentRef]
|
|
699
|
+
console.error(`[DocumentRef]:`, error);
|
|
588
700
|
return null;
|
|
589
701
|
}
|
|
590
702
|
}
|
|
@@ -686,9 +798,8 @@ var Query = class {
|
|
|
686
798
|
const persistence = this.app.offline().persistence;
|
|
687
799
|
if (this.filter.length > 0 && persistence) {
|
|
688
800
|
const local = await this.getLocalFilteredSnapshots();
|
|
689
|
-
if (typeof navigator === "undefined" || !navigator.onLine)
|
|
801
|
+
if (typeof navigator === "undefined" || !navigator.onLine)
|
|
690
802
|
return local;
|
|
691
|
-
}
|
|
692
803
|
return this.refreshFromRemote();
|
|
693
804
|
}
|
|
694
805
|
if (persistence) {
|
|
@@ -702,17 +813,12 @@ var Query = class {
|
|
|
702
813
|
return this.refreshFromRemote();
|
|
703
814
|
}
|
|
704
815
|
async update(data) {
|
|
705
|
-
|
|
706
|
-
return new HttpsRequest({
|
|
816
|
+
return await this.app.getAuthentication.makeRequest({
|
|
707
817
|
method: "POST" /* POST */,
|
|
708
818
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
709
|
-
headers: {
|
|
710
|
-
body: {
|
|
711
|
-
|
|
712
|
-
filter: genfilter,
|
|
713
|
-
data
|
|
714
|
-
}
|
|
715
|
-
}).sendRequest();
|
|
819
|
+
headers: {},
|
|
820
|
+
body: { collection: this.collection, filter: this.buildFilter(), data }
|
|
821
|
+
});
|
|
716
822
|
}
|
|
717
823
|
onSnapshot(callback) {
|
|
718
824
|
const genfilter = this.buildFilter();
|
|
@@ -842,32 +948,22 @@ var Query = class {
|
|
|
842
948
|
}
|
|
843
949
|
async refreshFromRemote() {
|
|
844
950
|
try {
|
|
845
|
-
const
|
|
846
|
-
const res = await new HttpsRequest({
|
|
951
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
847
952
|
method: "POST" /* POST */,
|
|
848
953
|
endpoint: `${this.app.getBaseUrl()}/db/read`,
|
|
849
|
-
headers: {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
},
|
|
854
|
-
body: {
|
|
855
|
-
collection: this.collection,
|
|
856
|
-
filter: genfilter
|
|
857
|
-
}
|
|
858
|
-
}).sendRequest();
|
|
859
|
-
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))
|
|
860
958
|
return [];
|
|
861
|
-
}
|
|
862
959
|
const snapshots = res.documents.map((d) => {
|
|
863
960
|
d.id = d.id ?? d._id;
|
|
864
961
|
delete d._id;
|
|
865
962
|
return DocumentSnapshot.fromMap(d);
|
|
866
963
|
});
|
|
867
964
|
const persistence = this.app.offline().persistence;
|
|
868
|
-
if (!persistence)
|
|
965
|
+
if (!persistence)
|
|
869
966
|
return snapshots;
|
|
870
|
-
}
|
|
871
967
|
for (const snapshot of snapshots) {
|
|
872
968
|
await persistence.applyRemoteDoc(this.collection, snapshot.data);
|
|
873
969
|
}
|
|
@@ -918,13 +1014,12 @@ var Query = class {
|
|
|
918
1014
|
newIndex: childChange.newIndex,
|
|
919
1015
|
previousDoc: childChange.previousDoc
|
|
920
1016
|
};
|
|
921
|
-
if (childChange.type === "added")
|
|
1017
|
+
if (childChange.type === "added")
|
|
922
1018
|
callbacks.onChildAdded?.(childChange.doc, context);
|
|
923
|
-
|
|
1019
|
+
else if (childChange.type === "modified")
|
|
924
1020
|
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
925
|
-
|
|
1021
|
+
else if (childChange.type === "removed")
|
|
926
1022
|
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
927
|
-
}
|
|
928
1023
|
});
|
|
929
1024
|
},
|
|
930
1025
|
this.buildFilter()
|
|
@@ -1001,19 +1096,12 @@ var CollectionRef = class {
|
|
|
1001
1096
|
this.syncEngine?.flush().catch(console.error);
|
|
1002
1097
|
return snap;
|
|
1003
1098
|
}
|
|
1004
|
-
const res = await
|
|
1099
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1005
1100
|
method: "POST" /* POST */,
|
|
1006
1101
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
1007
|
-
headers: {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
user: JSON.stringify(this.authentication.currentUser()?.toMap())
|
|
1011
|
-
},
|
|
1012
|
-
body: {
|
|
1013
|
-
collection: this.collection,
|
|
1014
|
-
data: { ...data, id: "" }
|
|
1015
|
-
}
|
|
1016
|
-
}).sendRequest();
|
|
1102
|
+
headers: {},
|
|
1103
|
+
body: { collection: this.collection, data: { ...data } }
|
|
1104
|
+
});
|
|
1017
1105
|
if (!res?.success || !res.document)
|
|
1018
1106
|
return null;
|
|
1019
1107
|
return DocumentSnapshot.fromMap(res.document);
|
|
@@ -1062,23 +1150,19 @@ var CollectionRef = class {
|
|
|
1062
1150
|
}
|
|
1063
1151
|
async refreshFromRemote() {
|
|
1064
1152
|
try {
|
|
1065
|
-
const res = await
|
|
1153
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1066
1154
|
method: "POST" /* POST */,
|
|
1067
1155
|
endpoint: `${serverURI}/db/read`,
|
|
1068
|
-
headers: {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
body: {
|
|
1075
|
-
collection: this.collection,
|
|
1076
|
-
filter: {}
|
|
1077
|
-
}
|
|
1078
|
-
}).sendRequest();
|
|
1079
|
-
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);
|
|
1080
1162
|
return [];
|
|
1081
1163
|
}
|
|
1164
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
1165
|
+
return [];
|
|
1082
1166
|
const normalized = res.documents.map((raw) => {
|
|
1083
1167
|
const doc = { ...raw };
|
|
1084
1168
|
doc.id = doc.id ?? doc._id;
|
|
@@ -1093,36 +1177,32 @@ var CollectionRef = class {
|
|
|
1093
1177
|
}
|
|
1094
1178
|
return normalized.map((doc) => DocumentSnapshot.fromMap(doc));
|
|
1095
1179
|
} catch (error) {
|
|
1096
|
-
console.error(
|
|
1180
|
+
console.error(`[CollectionRef]:`, error);
|
|
1097
1181
|
return [];
|
|
1098
1182
|
}
|
|
1099
1183
|
}
|
|
1100
1184
|
async fetchRemoteSnapshots() {
|
|
1101
1185
|
try {
|
|
1102
|
-
const res = await
|
|
1186
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1103
1187
|
method: "POST" /* POST */,
|
|
1104
1188
|
endpoint: `${serverURI}/db/read`,
|
|
1105
|
-
headers: {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
body: {
|
|
1112
|
-
collection: this.collection,
|
|
1113
|
-
filter: {}
|
|
1114
|
-
}
|
|
1115
|
-
}).sendRequest();
|
|
1116
|
-
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);
|
|
1117
1195
|
return [];
|
|
1118
1196
|
}
|
|
1197
|
+
if (!res?.success || !Array.isArray(res.documents))
|
|
1198
|
+
return [];
|
|
1119
1199
|
return res.documents.map((d) => {
|
|
1120
1200
|
d.id = d.id ?? d._id;
|
|
1121
1201
|
delete d._id;
|
|
1122
1202
|
return DocumentSnapshot.fromMap(d);
|
|
1123
1203
|
});
|
|
1124
1204
|
} catch (error) {
|
|
1125
|
-
console.error(
|
|
1205
|
+
console.error(`[CollectionRef]:`, error);
|
|
1126
1206
|
return [];
|
|
1127
1207
|
}
|
|
1128
1208
|
}
|
|
@@ -1161,20 +1241,18 @@ var CollectionRef = class {
|
|
|
1161
1241
|
newIndex: childChange.newIndex,
|
|
1162
1242
|
previousDoc: childChange.previousDoc
|
|
1163
1243
|
};
|
|
1164
|
-
if (childChange.type === "added")
|
|
1244
|
+
if (childChange.type === "added")
|
|
1165
1245
|
callbacks.onChildAdded?.(childChange.doc, context);
|
|
1166
|
-
|
|
1246
|
+
else if (childChange.type === "modified")
|
|
1167
1247
|
callbacks.onChildUpdated?.(childChange.doc, context);
|
|
1168
|
-
|
|
1248
|
+
else if (childChange.type === "removed")
|
|
1169
1249
|
callbacks.onChildRemoved?.(childChange.doc, context);
|
|
1170
|
-
}
|
|
1171
1250
|
});
|
|
1172
1251
|
});
|
|
1173
1252
|
}
|
|
1174
1253
|
emitInitialChildEvents(snapshots, callbacks) {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
const context = {
|
|
1254
|
+
diffSnapshots([], snapshots).forEach((change) => {
|
|
1255
|
+
callbacks.onChildAdded?.(change.doc, {
|
|
1178
1256
|
snapshots,
|
|
1179
1257
|
source: "initial",
|
|
1180
1258
|
changedDocId: change.doc.id,
|
|
@@ -1182,8 +1260,7 @@ var CollectionRef = class {
|
|
|
1182
1260
|
oldIndex: change.oldIndex,
|
|
1183
1261
|
newIndex: change.newIndex,
|
|
1184
1262
|
previousDoc: change.previousDoc
|
|
1185
|
-
};
|
|
1186
|
-
callbacks.onChildAdded?.(change.doc, context);
|
|
1263
|
+
});
|
|
1187
1264
|
});
|
|
1188
1265
|
}
|
|
1189
1266
|
};
|
|
@@ -1195,38 +1272,19 @@ var Batch = class {
|
|
|
1195
1272
|
this.app = app;
|
|
1196
1273
|
}
|
|
1197
1274
|
set(docRef, data) {
|
|
1198
|
-
this.ops.push({
|
|
1199
|
-
op: "set",
|
|
1200
|
-
collection: docRef.collection,
|
|
1201
|
-
id: docRef.id,
|
|
1202
|
-
data
|
|
1203
|
-
});
|
|
1275
|
+
this.ops.push({ op: "set", collection: docRef.collection, id: docRef.id, data });
|
|
1204
1276
|
return this;
|
|
1205
1277
|
}
|
|
1206
1278
|
create(docRef, data) {
|
|
1207
|
-
this.ops.push({
|
|
1208
|
-
op: "create",
|
|
1209
|
-
collection: docRef.collection,
|
|
1210
|
-
id: docRef.id,
|
|
1211
|
-
data
|
|
1212
|
-
});
|
|
1279
|
+
this.ops.push({ op: "create", collection: docRef.collection, id: docRef.id, data });
|
|
1213
1280
|
return this;
|
|
1214
1281
|
}
|
|
1215
1282
|
update(docRef, data) {
|
|
1216
|
-
this.ops.push({
|
|
1217
|
-
op: "update",
|
|
1218
|
-
collection: docRef.collection,
|
|
1219
|
-
id: docRef.id,
|
|
1220
|
-
data
|
|
1221
|
-
});
|
|
1283
|
+
this.ops.push({ op: "update", collection: docRef.collection, id: docRef.id, data });
|
|
1222
1284
|
return this;
|
|
1223
1285
|
}
|
|
1224
1286
|
delete(docRef) {
|
|
1225
|
-
this.ops.push({
|
|
1226
|
-
op: "delete",
|
|
1227
|
-
collection: docRef.collection,
|
|
1228
|
-
id: docRef.id
|
|
1229
|
-
});
|
|
1287
|
+
this.ops.push({ op: "delete", collection: docRef.collection, id: docRef.id });
|
|
1230
1288
|
return this;
|
|
1231
1289
|
}
|
|
1232
1290
|
async commit() {
|
|
@@ -1275,16 +1333,12 @@ var Batch = class {
|
|
|
1275
1333
|
syncEngine?.flush().catch(console.error);
|
|
1276
1334
|
return { success: true, results };
|
|
1277
1335
|
}
|
|
1278
|
-
const res = await
|
|
1336
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1279
1337
|
method: "POST" /* POST */,
|
|
1280
1338
|
endpoint: `${serverURI}/db/batch`,
|
|
1281
|
-
headers: {
|
|
1282
|
-
authorization: this.app.getConfig().token,
|
|
1283
|
-
"x-project": this.app.getConfig().project,
|
|
1284
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
1285
|
-
},
|
|
1339
|
+
headers: {},
|
|
1286
1340
|
body: { ops: this.ops }
|
|
1287
|
-
})
|
|
1341
|
+
});
|
|
1288
1342
|
if (res?.error) {
|
|
1289
1343
|
throw new Error(res.error.message || "Batch commit failed");
|
|
1290
1344
|
}
|
|
@@ -1331,12 +1385,12 @@ var Database = class {
|
|
|
1331
1385
|
}
|
|
1332
1386
|
};
|
|
1333
1387
|
await transactionFn(tx);
|
|
1334
|
-
const res = await
|
|
1388
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1335
1389
|
method: "POST" /* POST */,
|
|
1336
1390
|
endpoint: `${this.app.getBaseUrl()}/db/transaction`,
|
|
1337
|
-
headers: {
|
|
1391
|
+
headers: {},
|
|
1338
1392
|
body: { ops: tx.ops }
|
|
1339
|
-
})
|
|
1393
|
+
});
|
|
1340
1394
|
if (res?.error) {
|
|
1341
1395
|
throw new Error(res.error.message || "Transaction failed");
|
|
1342
1396
|
}
|
|
@@ -1363,6 +1417,14 @@ var Database = class {
|
|
|
1363
1417
|
}
|
|
1364
1418
|
};
|
|
1365
1419
|
|
|
1420
|
+
// src/core/RequestHeaders.ts
|
|
1421
|
+
function socketAuth(app) {
|
|
1422
|
+
const base = {
|
|
1423
|
+
...app.getAuthentication.getHeaders()
|
|
1424
|
+
};
|
|
1425
|
+
return base;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1366
1428
|
// src/database/Realtime.ts
|
|
1367
1429
|
import { io } from "socket.io-client";
|
|
1368
1430
|
|
|
@@ -1372,7 +1434,7 @@ function normalizePayload(payload) {
|
|
|
1372
1434
|
if (!raw)
|
|
1373
1435
|
return null;
|
|
1374
1436
|
const doc = { ...raw };
|
|
1375
|
-
doc.id = doc.id ?? raw?.id ??
|
|
1437
|
+
doc.id = raw?._id ?? doc.id ?? raw?.id ?? payload?._id ?? payload?.id;
|
|
1376
1438
|
delete doc._id;
|
|
1377
1439
|
return {
|
|
1378
1440
|
change: payload?.change ?? raw?.change ?? null,
|
|
@@ -1397,11 +1459,7 @@ var Realtime = class {
|
|
|
1397
1459
|
this.socket = io(this.app.getSocketUrl(), {
|
|
1398
1460
|
transports: ["websocket"],
|
|
1399
1461
|
auth: {
|
|
1400
|
-
|
|
1401
|
-
"x-project": this.app.getConfig().project,
|
|
1402
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
1403
|
-
user: this.app.getAuthentication.currentUser()
|
|
1404
|
-
// Pass user info for personalized permissions
|
|
1462
|
+
...socketAuth(this.app)
|
|
1405
1463
|
},
|
|
1406
1464
|
autoConnect: true,
|
|
1407
1465
|
reconnection: true,
|
|
@@ -1445,19 +1503,19 @@ var Realtime = class {
|
|
|
1445
1503
|
/**
|
|
1446
1504
|
* Low-level collection subscription (used by RealtimeBridge)
|
|
1447
1505
|
*/
|
|
1506
|
+
// Realtime.ts - subscribeToCollectionRaw
|
|
1448
1507
|
subscribeToCollectionRaw(collection, callback, filter = {}) {
|
|
1449
1508
|
this.connect();
|
|
1450
|
-
const lid = collection
|
|
1509
|
+
const lid = `${collection}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1451
1510
|
const handlers = [];
|
|
1452
|
-
this.socket.emit("subscribe", { collection, filter });
|
|
1511
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1453
1512
|
this.events.forEach((event) => {
|
|
1454
1513
|
const channel = `${lid}-${event}`;
|
|
1455
1514
|
const fn = (payload) => {
|
|
1456
1515
|
const normalized = normalizePayload(payload);
|
|
1457
1516
|
if (!normalized) {
|
|
1458
|
-
if (event === "delete")
|
|
1517
|
+
if (event === "delete")
|
|
1459
1518
|
callback(payload, "delete");
|
|
1460
|
-
}
|
|
1461
1519
|
return;
|
|
1462
1520
|
}
|
|
1463
1521
|
callback(normalized.data, event);
|
|
@@ -1473,10 +1531,10 @@ var Realtime = class {
|
|
|
1473
1531
|
*/
|
|
1474
1532
|
subscribeToDocumentRaw(collection, id, callback) {
|
|
1475
1533
|
this.connect();
|
|
1476
|
-
const lid = collection
|
|
1534
|
+
const lid = `${collection}_${id}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
1477
1535
|
const filter = { _id: id };
|
|
1478
1536
|
const handlers = [];
|
|
1479
|
-
this.socket.emit("subscribe", { collection, filter });
|
|
1537
|
+
this.socket.emit("subscribe", { collection, filter, lid });
|
|
1480
1538
|
this.events.forEach((event) => {
|
|
1481
1539
|
const channel = `${lid}-${event}`;
|
|
1482
1540
|
const fn = (payload) => {
|
|
@@ -1536,12 +1594,12 @@ var Functions = class {
|
|
|
1536
1594
|
}
|
|
1537
1595
|
async call(functionName, data) {
|
|
1538
1596
|
try {
|
|
1539
|
-
const res = await
|
|
1597
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1540
1598
|
method: "POST" /* POST */,
|
|
1541
1599
|
endpoint: serverURI + "/functions/call/" + functionName,
|
|
1542
|
-
headers: {
|
|
1600
|
+
headers: {},
|
|
1543
1601
|
body: data
|
|
1544
|
-
})
|
|
1602
|
+
});
|
|
1545
1603
|
return res;
|
|
1546
1604
|
} catch (err) {
|
|
1547
1605
|
throw {
|
|
@@ -1559,15 +1617,15 @@ var Hosting = class {
|
|
|
1559
1617
|
}
|
|
1560
1618
|
async createSubdomain(name) {
|
|
1561
1619
|
try {
|
|
1562
|
-
const res = await
|
|
1620
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
1563
1621
|
method: "POST" /* POST */,
|
|
1564
1622
|
endpoint: this.app.getBaseUrl() + "/hosting/register",
|
|
1565
|
-
headers: {
|
|
1623
|
+
headers: {},
|
|
1566
1624
|
body: {
|
|
1567
1625
|
hostname: name,
|
|
1568
1626
|
project: this.app.getConfig().project
|
|
1569
1627
|
}
|
|
1570
|
-
})
|
|
1628
|
+
});
|
|
1571
1629
|
return res;
|
|
1572
1630
|
} catch (err) {
|
|
1573
1631
|
throw {
|
|
@@ -2413,6 +2471,7 @@ var SyncEngine = class {
|
|
|
2413
2471
|
constructor(app, persistence, store, realtimeBridge) {
|
|
2414
2472
|
this.realtimeBridge = null;
|
|
2415
2473
|
this.syncing = false;
|
|
2474
|
+
// Use ReturnType<typeof setTimeout> for cross-env timeout type (works in browser and Node)
|
|
2416
2475
|
this.retryTimeout = null;
|
|
2417
2476
|
this.MAX_RETRIES = 5;
|
|
2418
2477
|
this.BASE_RETRY_DELAY = 2e3;
|
|
@@ -2425,6 +2484,7 @@ var SyncEngine = class {
|
|
|
2425
2484
|
this.persistence = persistence;
|
|
2426
2485
|
this.store = store;
|
|
2427
2486
|
this.realtimeBridge = realtimeBridge || null;
|
|
2487
|
+
this.authentication = app.getAuthentication;
|
|
2428
2488
|
this.persistence.recoverSyncingMutations().catch(console.error);
|
|
2429
2489
|
if (typeof window !== "undefined") {
|
|
2430
2490
|
window.addEventListener("online", this.handleOnline);
|
|
@@ -2501,15 +2561,15 @@ var SyncEngine = class {
|
|
|
2501
2561
|
}
|
|
2502
2562
|
}
|
|
2503
2563
|
async syncCreate(mutation) {
|
|
2504
|
-
const res = await
|
|
2564
|
+
const res = await this.authentication.makeRequest({
|
|
2505
2565
|
method: "POST" /* POST */,
|
|
2506
2566
|
endpoint: `${this.app.getBaseUrl()}/db/create`,
|
|
2507
|
-
headers: {
|
|
2567
|
+
headers: {},
|
|
2508
2568
|
body: {
|
|
2509
2569
|
collection: mutation.collection,
|
|
2510
2570
|
data: mutation.payload
|
|
2511
2571
|
}
|
|
2512
|
-
})
|
|
2572
|
+
});
|
|
2513
2573
|
if (!res?.success || !res.id)
|
|
2514
2574
|
return false;
|
|
2515
2575
|
const oldId = mutation.documentId;
|
|
@@ -2527,21 +2587,16 @@ var SyncEngine = class {
|
|
|
2527
2587
|
return true;
|
|
2528
2588
|
}
|
|
2529
2589
|
async syncUpdate(mutation) {
|
|
2530
|
-
const res = await
|
|
2590
|
+
const res = await this.authentication.makeRequest({
|
|
2531
2591
|
method: "POST" /* POST */,
|
|
2532
2592
|
endpoint: `${this.app.getBaseUrl()}/db/update`,
|
|
2533
|
-
headers: {
|
|
2534
|
-
authorization: this.app.getConfig().token,
|
|
2535
|
-
"x-project": this.app.getConfig().project,
|
|
2536
|
-
user: JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
2537
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
2538
|
-
},
|
|
2593
|
+
headers: {},
|
|
2539
2594
|
body: {
|
|
2540
2595
|
collection: mutation.collection,
|
|
2541
2596
|
document: mutation.documentId,
|
|
2542
2597
|
data: mutation.payload
|
|
2543
2598
|
}
|
|
2544
|
-
})
|
|
2599
|
+
});
|
|
2545
2600
|
if (!res?.success)
|
|
2546
2601
|
return false;
|
|
2547
2602
|
const local = await this.persistence.upsertDoc({
|
|
@@ -2564,20 +2619,15 @@ var SyncEngine = class {
|
|
|
2564
2619
|
return true;
|
|
2565
2620
|
}
|
|
2566
2621
|
async syncDelete(mutation) {
|
|
2567
|
-
const res = await
|
|
2622
|
+
const res = await this.authentication.makeRequest({
|
|
2568
2623
|
method: "POST" /* POST */,
|
|
2569
2624
|
endpoint: `${this.app.getBaseUrl()}/db/delete`,
|
|
2570
|
-
headers: {
|
|
2571
|
-
authorization: this.app.getConfig().token,
|
|
2572
|
-
"x-project": this.app.getConfig().project,
|
|
2573
|
-
user: JSON.stringify(this.app.getAuthentication.currentUser()?.toMap()),
|
|
2574
|
-
"x-user": JSON.stringify(this.app.getAuthentication.currentUser()?.toMap())
|
|
2575
|
-
},
|
|
2625
|
+
headers: {},
|
|
2576
2626
|
body: {
|
|
2577
2627
|
collection: mutation.collection,
|
|
2578
2628
|
document: mutation.documentId
|
|
2579
2629
|
}
|
|
2580
|
-
})
|
|
2630
|
+
});
|
|
2581
2631
|
if (!res?.success)
|
|
2582
2632
|
return false;
|
|
2583
2633
|
await this.persistence.markDeleted(mutation.collection, mutation.documentId, 0);
|
|
@@ -2676,13 +2726,11 @@ var StorageRef = class {
|
|
|
2676
2726
|
this.app = app;
|
|
2677
2727
|
}
|
|
2678
2728
|
async get(id) {
|
|
2679
|
-
const res = await
|
|
2729
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2680
2730
|
method: "GET" /* GET */,
|
|
2681
2731
|
endpoint: serverURI + `/storage/${id}`,
|
|
2682
|
-
headers: {
|
|
2683
|
-
|
|
2684
|
-
}
|
|
2685
|
-
}).sendRequest();
|
|
2732
|
+
headers: {}
|
|
2733
|
+
});
|
|
2686
2734
|
if (res.success) {
|
|
2687
2735
|
if (res.document === void 0)
|
|
2688
2736
|
return void 0;
|
|
@@ -2693,13 +2741,11 @@ var StorageRef = class {
|
|
|
2693
2741
|
}
|
|
2694
2742
|
}
|
|
2695
2743
|
async getMeta(id) {
|
|
2696
|
-
const res = await
|
|
2744
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2697
2745
|
method: "GET" /* GET */,
|
|
2698
2746
|
endpoint: serverURI + `/storage/${id}/info`,
|
|
2699
|
-
headers: {
|
|
2700
|
-
|
|
2701
|
-
}
|
|
2702
|
-
}).sendRequest();
|
|
2747
|
+
headers: {}
|
|
2748
|
+
});
|
|
2703
2749
|
if (res.success) {
|
|
2704
2750
|
if (res.document === void 0)
|
|
2705
2751
|
return void 0;
|
|
@@ -2713,13 +2759,13 @@ var StorageRef = class {
|
|
|
2713
2759
|
if (!srcFile) {
|
|
2714
2760
|
throw new Error("Invalid File");
|
|
2715
2761
|
}
|
|
2716
|
-
const res = await
|
|
2762
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2717
2763
|
method: "POST" /* POST */,
|
|
2718
2764
|
endpoint: serverURI + "/storage/file/upload",
|
|
2719
|
-
headers: {
|
|
2765
|
+
headers: {},
|
|
2720
2766
|
file: srcFile,
|
|
2721
2767
|
isMultipart: true
|
|
2722
|
-
})
|
|
2768
|
+
});
|
|
2723
2769
|
if (res.success) {
|
|
2724
2770
|
if (res.document === void 0)
|
|
2725
2771
|
return void 0;
|
|
@@ -2730,14 +2776,14 @@ var StorageRef = class {
|
|
|
2730
2776
|
}
|
|
2731
2777
|
}
|
|
2732
2778
|
async delete(id) {
|
|
2733
|
-
const res = await
|
|
2779
|
+
const res = await this.app.getAuthentication.makeRequest({
|
|
2734
2780
|
method: "POST" /* POST */,
|
|
2735
2781
|
endpoint: serverURI + "/storage/file/delete",
|
|
2736
|
-
headers: {
|
|
2782
|
+
headers: {},
|
|
2737
2783
|
body: {
|
|
2738
2784
|
file_id: id
|
|
2739
2785
|
}
|
|
2740
|
-
})
|
|
2786
|
+
});
|
|
2741
2787
|
return res;
|
|
2742
2788
|
}
|
|
2743
2789
|
};
|