nexabase-console 2.0.1 → 2.0.3
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/README.md +130 -23
- package/dist/app/NexaApp.d.ts +6 -5
- package/dist/app/NexaApp.js +2 -2
- package/dist/app/initializeApp.d.ts +6 -0
- package/dist/app/initializeApp.js +14 -1
- package/dist/auth/Auth.d.ts +38 -6
- package/dist/auth/Auth.js +341 -28
- package/dist/auth/NexaAuthError.d.ts +5 -0
- package/dist/auth/NexaAuthError.js +15 -0
- package/dist/auth/authTypes.d.ts +38 -1
- package/dist/auth/persistence.d.ts +7 -5
- package/dist/auth/persistence.js +42 -13
- package/dist/facade/nexaSDK.d.ts +93 -0
- package/dist/facade/nexaSDK.js +129 -0
- package/dist/firestore/Query.d.ts +9 -1
- package/dist/firestore/Query.js +62 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/storage/Storage.d.ts +5 -0
- package/dist/storage/Storage.js +17 -1
- package/dist/storage/StorageReference.d.ts +18 -0
- package/dist/storage/StorageReference.js +33 -1
- package/dist/types/index.d.ts +17 -8
- package/package.json +1 -1
package/dist/auth/Auth.js
CHANGED
|
@@ -2,71 +2,384 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AuthService = exports.Auth = void 0;
|
|
4
4
|
const persistence_1 = require("./persistence");
|
|
5
|
-
const
|
|
5
|
+
const NexaAuthError_1 = require("./NexaAuthError");
|
|
6
6
|
class Auth {
|
|
7
7
|
constructor(projectId, client, initialToken) {
|
|
8
|
-
this.
|
|
8
|
+
this.emulatorUrl = null;
|
|
9
|
+
this.currentSession = null;
|
|
10
|
+
this.listeners = new Set();
|
|
11
|
+
// Single concurrent token refresh mutex / request deduplication
|
|
12
|
+
this.refreshPromise = null;
|
|
13
|
+
this.refreshTimer = null;
|
|
9
14
|
this.projectId = projectId;
|
|
10
15
|
this.client = client;
|
|
11
16
|
this.persistence = new persistence_1.AuthPersistence(projectId);
|
|
12
|
-
|
|
17
|
+
// Initial session restoration
|
|
18
|
+
this.initSession().catch((err) => {
|
|
19
|
+
console.warn('⚡ Session restore failed:', err);
|
|
20
|
+
});
|
|
21
|
+
// Multi-tab auth state synchronization via StorageEvent
|
|
22
|
+
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
23
|
+
window.addEventListener('storage', (event) => {
|
|
24
|
+
if (event.key === `nexa_session_${this.projectId}`) {
|
|
25
|
+
this.handleStorageChange(event.newValue);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// --- Emulator Integration ---
|
|
31
|
+
connectAuthEmulator(url) {
|
|
32
|
+
this.emulatorUrl = url;
|
|
33
|
+
console.log(`🔌 Auth connected to emulator at ${url}`);
|
|
34
|
+
}
|
|
35
|
+
get emulator() {
|
|
36
|
+
return this.emulatorUrl;
|
|
37
|
+
}
|
|
38
|
+
// --- Initial Session Restore ---
|
|
39
|
+
async initSession() {
|
|
40
|
+
const saved = await this.persistence.getSession();
|
|
41
|
+
if (!saved) {
|
|
42
|
+
this.notifyListeners(null);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
// Check if access token is valid or close to expiration (buffer 60 seconds)
|
|
47
|
+
if (saved.accessTokenExpiresAt > now + 60000) {
|
|
48
|
+
this.currentSession = saved;
|
|
49
|
+
this.scheduleAutoRefresh();
|
|
50
|
+
this.notifyListeners(saved.user);
|
|
51
|
+
}
|
|
52
|
+
else if (saved.refreshTokenExpiresAt > now) {
|
|
53
|
+
// Access token expired, attempt refresh
|
|
54
|
+
try {
|
|
55
|
+
await this.refreshAccessToken(saved.refreshToken);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
await this.signOut();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Refresh token expired
|
|
63
|
+
await this.signOut();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// --- Auth State Listeners ---
|
|
67
|
+
onAuthStateChanged(callback) {
|
|
68
|
+
this.listeners.add(callback);
|
|
69
|
+
// Fire callback immediately with current state
|
|
70
|
+
callback(this.currentUser);
|
|
71
|
+
return () => {
|
|
72
|
+
this.listeners.delete(callback);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
notifyListeners(user) {
|
|
76
|
+
this.listeners.forEach((cb) => {
|
|
77
|
+
try {
|
|
78
|
+
cb(user);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
console.error('Error in auth listener:', e);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async handleStorageChange(newValue) {
|
|
86
|
+
if (!newValue) {
|
|
87
|
+
this.currentSession = null;
|
|
88
|
+
this.clearAutoRefresh();
|
|
89
|
+
this.notifyListeners(null);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(newValue);
|
|
94
|
+
this.currentSession = parsed;
|
|
95
|
+
this.scheduleAutoRefresh();
|
|
96
|
+
this.notifyListeners(parsed.user);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
await this.signOut();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// --- User Getters ---
|
|
103
|
+
get currentUser() {
|
|
104
|
+
return this.currentSession?.user || null;
|
|
13
105
|
}
|
|
14
106
|
getToken() {
|
|
15
|
-
return this.
|
|
107
|
+
return this.currentSession?.accessToken || null;
|
|
16
108
|
}
|
|
17
109
|
setToken(token) {
|
|
18
|
-
this.
|
|
110
|
+
if (this.currentSession) {
|
|
111
|
+
this.currentSession.accessToken = token || '';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// --- Single Flight Token Refresh (Mutex / Request Deduplication) ---
|
|
115
|
+
async getIdToken(forceRefresh = false) {
|
|
116
|
+
if (!this.currentSession) {
|
|
117
|
+
throw new NexaAuthError_1.NexaAuthError('auth/unauthenticated', 'User belum login.');
|
|
118
|
+
}
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
const isExpired = this.currentSession.accessTokenExpiresAt <= now + 60000;
|
|
121
|
+
if (forceRefresh || isExpired) {
|
|
122
|
+
const session = await this.refreshAccessTokenDeduplicated();
|
|
123
|
+
return session.accessToken;
|
|
124
|
+
}
|
|
125
|
+
return this.currentSession.accessToken;
|
|
126
|
+
}
|
|
127
|
+
async getIdTokenResult(forceRefresh = false) {
|
|
128
|
+
const token = await this.getIdToken(forceRefresh);
|
|
129
|
+
const claims = this.parseJwtClaims(token);
|
|
130
|
+
return {
|
|
131
|
+
token,
|
|
132
|
+
expirationTime: new Date(claims.exp * 1000).toISOString(),
|
|
133
|
+
authTime: new Date(claims.auth_time * 1000).toISOString(),
|
|
134
|
+
issuedAtTime: new Date(claims.iat * 1000).toISOString(),
|
|
135
|
+
signInProvider: claims.provider_id || 'password',
|
|
136
|
+
claims
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
parseJwtClaims(token) {
|
|
140
|
+
try {
|
|
141
|
+
const base64Url = token.split('.')[1];
|
|
142
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
143
|
+
const jsonPayload = decodeURIComponent(atob(base64)
|
|
144
|
+
.split('')
|
|
145
|
+
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
146
|
+
.join(''));
|
|
147
|
+
return JSON.parse(jsonPayload);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Deduplicated token refresh (Concurrent Request Safety)
|
|
154
|
+
refreshAccessTokenDeduplicated() {
|
|
155
|
+
if (this.refreshPromise) {
|
|
156
|
+
return this.refreshPromise; // Return active in-flight request
|
|
157
|
+
}
|
|
158
|
+
if (!this.currentSession?.refreshToken) {
|
|
159
|
+
return Promise.reject(new NexaAuthError_1.NexaAuthError('auth/invalid-refresh-token', 'Refresh token tidak ditemukan.'));
|
|
160
|
+
}
|
|
161
|
+
this.refreshPromise = this.refreshAccessToken(this.currentSession.refreshToken)
|
|
162
|
+
.finally(() => {
|
|
163
|
+
this.refreshPromise = null;
|
|
164
|
+
});
|
|
165
|
+
return this.refreshPromise;
|
|
166
|
+
}
|
|
167
|
+
async refreshAccessToken(refreshToken) {
|
|
168
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/token/refresh`;
|
|
169
|
+
try {
|
|
170
|
+
const res = await this.client.post(endpoint, { refreshToken });
|
|
171
|
+
const session = res.data;
|
|
172
|
+
this.currentSession = session;
|
|
173
|
+
await this.persistence.saveSession(session);
|
|
174
|
+
this.scheduleAutoRefresh();
|
|
175
|
+
this.notifyListeners(session.user);
|
|
176
|
+
return session;
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
await this.signOut();
|
|
180
|
+
throw new NexaAuthError_1.NexaAuthError(err.response?.data?.code || 'auth/session-expired', err.response?.data?.message || 'Sesi telah berakhir, silakan login kembali.', err.response?.status || 401);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
scheduleAutoRefresh() {
|
|
184
|
+
this.clearAutoRefresh();
|
|
185
|
+
if (!this.currentSession)
|
|
186
|
+
return;
|
|
187
|
+
const expiresAt = this.currentSession.accessTokenExpiresAt;
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
// Refresh 2 minutes before expiry
|
|
190
|
+
const delay = Math.max(1000, expiresAt - now - 120000);
|
|
191
|
+
this.refreshTimer = setTimeout(() => {
|
|
192
|
+
this.refreshAccessTokenDeduplicated().catch((err) => {
|
|
193
|
+
console.warn('⚠️ Auto token refresh failed:', err);
|
|
194
|
+
});
|
|
195
|
+
}, delay);
|
|
196
|
+
}
|
|
197
|
+
clearAutoRefresh() {
|
|
198
|
+
if (this.refreshTimer) {
|
|
199
|
+
clearTimeout(this.refreshTimer);
|
|
200
|
+
this.refreshTimer = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// --- Email & Password Auth ---
|
|
204
|
+
async createUserWithEmailAndPassword(email, password, name) {
|
|
205
|
+
try {
|
|
206
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/register`;
|
|
207
|
+
const res = await this.client.post(endpoint, { email, password, name });
|
|
208
|
+
const session = res.data;
|
|
209
|
+
this.currentSession = session;
|
|
210
|
+
await this.persistence.saveSession(session);
|
|
211
|
+
this.scheduleAutoRefresh();
|
|
212
|
+
this.notifyListeners(session.user);
|
|
213
|
+
return session;
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
throw new NexaAuthError_1.NexaAuthError(err.response?.data?.code || 'auth/email-already-in-use', err.response?.data?.message || 'Gagal mendaftar pengguna baru.', err.response?.status || 400);
|
|
217
|
+
}
|
|
19
218
|
}
|
|
20
219
|
async signInWithEmailAndPassword(email, password) {
|
|
21
220
|
try {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
221
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/login`;
|
|
222
|
+
const res = await this.client.post(endpoint, { email, password });
|
|
223
|
+
const session = res.data;
|
|
224
|
+
this.currentSession = session;
|
|
225
|
+
await this.persistence.saveSession(session);
|
|
226
|
+
this.scheduleAutoRefresh();
|
|
227
|
+
this.notifyListeners(session.user);
|
|
228
|
+
return session;
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
throw new NexaAuthError_1.NexaAuthError(err.response?.data?.code || 'auth/invalid-credential', err.response?.data?.message || 'Email atau password salah.', err.response?.status || 401);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// --- OTP Auth ---
|
|
235
|
+
async sendOtp(email) {
|
|
236
|
+
try {
|
|
237
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/otp/send`;
|
|
238
|
+
const res = await this.client.post(endpoint, { email });
|
|
27
239
|
return res.data;
|
|
28
240
|
}
|
|
29
241
|
catch (err) {
|
|
30
|
-
throw (
|
|
242
|
+
throw new NexaAuthError_1.NexaAuthError(err.response?.data?.code || 'auth/otp-rate-limit', err.response?.data?.message || 'Gagal mengirim OTP.', err.response?.status || 429);
|
|
31
243
|
}
|
|
32
244
|
}
|
|
33
|
-
async
|
|
245
|
+
async signInWithOtp(email, otp) {
|
|
34
246
|
try {
|
|
35
|
-
const
|
|
247
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/otp/login`;
|
|
248
|
+
const res = await this.client.post(endpoint, { email, otp });
|
|
249
|
+
const session = res.data;
|
|
250
|
+
this.currentSession = session;
|
|
251
|
+
await this.persistence.saveSession(session);
|
|
252
|
+
this.scheduleAutoRefresh();
|
|
253
|
+
this.notifyListeners(session.user);
|
|
254
|
+
return session;
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
throw new NexaAuthError_1.NexaAuthError(err.response?.data?.code || 'auth/invalid-otp', err.response?.data?.message || 'Kode OTP tidak valid atau telah kedaluwarsa.', err.response?.status || 400);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// --- Password Reset & Verification ---
|
|
261
|
+
async sendPasswordResetEmail(email) {
|
|
262
|
+
try {
|
|
263
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/password-reset/send`;
|
|
264
|
+
const res = await this.client.post(endpoint, { email });
|
|
36
265
|
return res.data;
|
|
37
266
|
}
|
|
38
267
|
catch (err) {
|
|
39
|
-
throw (
|
|
268
|
+
throw new NexaAuthError_1.NexaAuthError('auth/user-not-found', 'Permintaan reset password gagal.');
|
|
40
269
|
}
|
|
41
270
|
}
|
|
42
|
-
async
|
|
271
|
+
async confirmPasswordReset(code, newPassword) {
|
|
43
272
|
try {
|
|
44
|
-
const
|
|
273
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/password-reset/confirm`;
|
|
274
|
+
const res = await this.client.post(endpoint, { code, newPassword });
|
|
45
275
|
return res.data;
|
|
46
276
|
}
|
|
47
277
|
catch (err) {
|
|
48
|
-
throw (
|
|
278
|
+
throw new NexaAuthError_1.NexaAuthError('auth/invalid-credential', 'Kode reset password tidak valid.');
|
|
49
279
|
}
|
|
50
280
|
}
|
|
51
|
-
async
|
|
281
|
+
async sendEmailVerification() {
|
|
282
|
+
const token = await this.getIdToken();
|
|
52
283
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
284
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/email/send-verification`;
|
|
285
|
+
const res = await this.client.post(endpoint, {}, {
|
|
286
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
287
|
+
});
|
|
58
288
|
return res.data;
|
|
59
289
|
}
|
|
60
290
|
catch (err) {
|
|
61
|
-
throw (
|
|
291
|
+
throw new NexaAuthError_1.NexaAuthError('auth/unauthenticated', 'Gagal mengirim email verifikasi.');
|
|
62
292
|
}
|
|
63
293
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.
|
|
294
|
+
// --- User Profile & Account Management ---
|
|
295
|
+
async updateProfile(update) {
|
|
296
|
+
const token = await this.getIdToken();
|
|
297
|
+
try {
|
|
298
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/profile`;
|
|
299
|
+
const res = await this.client.patch(endpoint, update, {
|
|
300
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
301
|
+
});
|
|
302
|
+
const updatedUser = res.data;
|
|
303
|
+
if (this.currentSession) {
|
|
304
|
+
this.currentSession.user = updatedUser;
|
|
305
|
+
await this.persistence.saveSession(this.currentSession);
|
|
306
|
+
this.notifyListeners(updatedUser);
|
|
307
|
+
}
|
|
308
|
+
return updatedUser;
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
throw new NexaAuthError_1.NexaAuthError('auth/invalid-argument', 'Gagal memperbarui profil.');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async updatePassword(newPassword) {
|
|
315
|
+
const token = await this.getIdToken();
|
|
316
|
+
try {
|
|
317
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/password/update`;
|
|
318
|
+
await this.client.post(endpoint, { newPassword }, {
|
|
319
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
throw new NexaAuthError_1.NexaAuthError(err.response?.data?.code || 'auth/requires-recent-login', 'Gagal memperbarui password. Silakan re-autentikasi.', err.response?.status || 401);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async deleteUser() {
|
|
327
|
+
const token = await this.getIdToken();
|
|
328
|
+
try {
|
|
329
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/user/delete`;
|
|
330
|
+
await this.client.delete(endpoint, {
|
|
331
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
332
|
+
});
|
|
333
|
+
await this.signOut();
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
throw new NexaAuthError_1.NexaAuthError('auth/requires-recent-login', 'Gagal menghapus akun.');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// --- Session Revocation & Sign Out ---
|
|
340
|
+
async revokeSession(sessionId) {
|
|
341
|
+
const token = await this.getIdToken();
|
|
342
|
+
try {
|
|
343
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/session/revoke`;
|
|
344
|
+
await this.client.post(endpoint, { sessionId }, {
|
|
345
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
throw new NexaAuthError_1.NexaAuthError('auth/invalid-argument', 'Gagal mereduksi sesi.');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async revokeAllSessions() {
|
|
353
|
+
const token = await this.getIdToken();
|
|
354
|
+
try {
|
|
355
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/session/revoke-all`;
|
|
356
|
+
await this.client.post(endpoint, {}, {
|
|
357
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
358
|
+
});
|
|
359
|
+
await this.signOut();
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
throw new NexaAuthError_1.NexaAuthError('auth/invalid-argument', 'Gagal mereduksi seluruh sesi.');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async signOut() {
|
|
366
|
+
this.clearAutoRefresh();
|
|
367
|
+
const oldSession = this.currentSession;
|
|
368
|
+
this.currentSession = null;
|
|
369
|
+
if (oldSession) {
|
|
370
|
+
try {
|
|
371
|
+
const endpoint = this.emulatorUrl || `/api/project/${this.projectId}/auth/logout`;
|
|
372
|
+
await this.client.post(endpoint, { refreshToken: oldSession.refreshToken });
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Non-blocking logout network failure
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
await this.persistence.clearSession();
|
|
379
|
+
this.notifyListeners(null);
|
|
67
380
|
}
|
|
68
381
|
getCurrentUser() {
|
|
69
|
-
return this.
|
|
382
|
+
return this.currentUser;
|
|
70
383
|
}
|
|
71
384
|
}
|
|
72
385
|
exports.Auth = Auth;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NexaAuthError = void 0;
|
|
4
|
+
class NexaAuthError extends Error {
|
|
5
|
+
constructor(code, message, status) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'NexaAuthError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.status = status;
|
|
10
|
+
if (typeof Error.captureStackTrace === 'function') {
|
|
11
|
+
Error.captureStackTrace(this, NexaAuthError);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.NexaAuthError = NexaAuthError;
|
package/dist/auth/authTypes.d.ts
CHANGED
|
@@ -1 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
import { Timestamp } from '../firestore/Timestamp';
|
|
2
|
+
export interface User {
|
|
3
|
+
uid: string;
|
|
4
|
+
email: string | null;
|
|
5
|
+
emailVerified: boolean;
|
|
6
|
+
name: string | null;
|
|
7
|
+
photoURL: string | null;
|
|
8
|
+
phoneNumber: string | null;
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
createdAt: Timestamp | string;
|
|
11
|
+
updatedAt: Timestamp | string;
|
|
12
|
+
lastLoginAt: Timestamp | string;
|
|
13
|
+
}
|
|
14
|
+
export interface AuthSession {
|
|
15
|
+
accessToken: string;
|
|
16
|
+
refreshToken: string;
|
|
17
|
+
accessTokenExpiresAt: number;
|
|
18
|
+
refreshTokenExpiresAt: number;
|
|
19
|
+
user: User;
|
|
20
|
+
}
|
|
21
|
+
export interface IdTokenResult {
|
|
22
|
+
token: string;
|
|
23
|
+
expirationTime: string;
|
|
24
|
+
authTime: string;
|
|
25
|
+
issuedAtTime: string;
|
|
26
|
+
signInProvider: string | null;
|
|
27
|
+
claims: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface UserProfileUpdate {
|
|
30
|
+
name?: string | null;
|
|
31
|
+
photoURL?: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface IAuthPersistence {
|
|
34
|
+
getSession(): Promise<AuthSession | null>;
|
|
35
|
+
saveSession(session: AuthSession): Promise<void>;
|
|
36
|
+
clearSession(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export type AuthStateCallback = (user: User | null) => void;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare class AuthPersistence {
|
|
1
|
+
import { AuthSession, IAuthPersistence } from './authTypes';
|
|
2
|
+
export declare class AuthPersistence implements IAuthPersistence {
|
|
3
3
|
private projectId;
|
|
4
|
+
private storageKey;
|
|
4
5
|
constructor(projectId: string);
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
getSession(): Promise<AuthSession | null>;
|
|
7
|
+
saveSession(session: AuthSession): Promise<void>;
|
|
8
|
+
clearSession(): Promise<void>;
|
|
7
9
|
getSavedToken(): string | null;
|
|
8
|
-
getSavedUser():
|
|
10
|
+
getSavedUser(): AuthSession['user'] | null;
|
|
9
11
|
}
|
package/dist/auth/persistence.js
CHANGED
|
@@ -4,31 +4,60 @@ exports.AuthPersistence = void 0;
|
|
|
4
4
|
class AuthPersistence {
|
|
5
5
|
constructor(projectId) {
|
|
6
6
|
this.projectId = projectId;
|
|
7
|
+
this.storageKey = `nexa_session_${projectId}`;
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
+
async getSession() {
|
|
10
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const item = localStorage.getItem(this.storageKey);
|
|
15
|
+
if (!item)
|
|
16
|
+
return null;
|
|
17
|
+
return JSON.parse(item);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async saveSession(session) {
|
|
9
24
|
if (typeof window !== 'undefined' && window.localStorage) {
|
|
10
|
-
localStorage.setItem(
|
|
11
|
-
localStorage.setItem(`nexa_user_${this.projectId}`, JSON.stringify(user));
|
|
25
|
+
localStorage.setItem(this.storageKey, JSON.stringify(session));
|
|
12
26
|
}
|
|
13
27
|
}
|
|
14
|
-
clearSession() {
|
|
28
|
+
async clearSession() {
|
|
15
29
|
if (typeof window !== 'undefined' && window.localStorage) {
|
|
16
|
-
localStorage.removeItem(
|
|
17
|
-
localStorage.removeItem(`nexa_user_${this.projectId}`);
|
|
30
|
+
localStorage.removeItem(this.storageKey);
|
|
18
31
|
}
|
|
19
32
|
}
|
|
33
|
+
// Helper backward compatibility
|
|
20
34
|
getSavedToken() {
|
|
21
|
-
if (typeof window
|
|
22
|
-
return
|
|
35
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
const session = localStorage.getItem(this.storageKey);
|
|
39
|
+
if (!session)
|
|
40
|
+
return null;
|
|
41
|
+
const parsed = JSON.parse(session);
|
|
42
|
+
return parsed.accessToken || null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
23
46
|
}
|
|
24
|
-
return null;
|
|
25
47
|
}
|
|
26
48
|
getSavedUser() {
|
|
27
|
-
if (typeof window
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
50
|
+
return null;
|
|
51
|
+
try {
|
|
52
|
+
const session = localStorage.getItem(this.storageKey);
|
|
53
|
+
if (!session)
|
|
54
|
+
return null;
|
|
55
|
+
const parsed = JSON.parse(session);
|
|
56
|
+
return parsed.user || null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
30
60
|
}
|
|
31
|
-
return null;
|
|
32
61
|
}
|
|
33
62
|
}
|
|
34
63
|
exports.AuthPersistence = AuthPersistence;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { NexaApp } from '../app/initializeApp';
|
|
2
|
+
export interface NexaSDKOptions {
|
|
3
|
+
app?: NexaApp;
|
|
4
|
+
baseURL?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface AdjustStockParams {
|
|
7
|
+
productId: string;
|
|
8
|
+
delta: number;
|
|
9
|
+
reason?: string;
|
|
10
|
+
referenceId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ScanResiParams {
|
|
13
|
+
noResi: string;
|
|
14
|
+
orderId?: string;
|
|
15
|
+
courier?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CancelOrderParams {
|
|
18
|
+
orderId: string;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface TransferParams {
|
|
22
|
+
fromUserId: string;
|
|
23
|
+
toUserId: string;
|
|
24
|
+
amount: number;
|
|
25
|
+
currency?: string;
|
|
26
|
+
note?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface IncrementCounterParams {
|
|
29
|
+
docPath: string;
|
|
30
|
+
field: string;
|
|
31
|
+
amount?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Universal NexaBase Client SDK Facade (Domain & Server-Authoritative Logic)
|
|
35
|
+
* Decouples client components & stores from raw primitives like getDocs/runTransaction.
|
|
36
|
+
*/
|
|
37
|
+
export declare class NexaSDKFacade {
|
|
38
|
+
private appInstance;
|
|
39
|
+
private customBaseURL;
|
|
40
|
+
constructor(options?: NexaSDKOptions);
|
|
41
|
+
private getApp;
|
|
42
|
+
private getBaseURL;
|
|
43
|
+
/**
|
|
44
|
+
* Universal Server-Authoritative Cloud Function Caller
|
|
45
|
+
*/
|
|
46
|
+
call<T = any>(functionName: string, data?: any): Promise<T>;
|
|
47
|
+
products: {
|
|
48
|
+
/**
|
|
49
|
+
* Atomically adjust product stock in server with audit log trail
|
|
50
|
+
*/
|
|
51
|
+
adjustStock: (params: AdjustStockParams) => Promise<any>;
|
|
52
|
+
/**
|
|
53
|
+
* Quick stock deduction helper
|
|
54
|
+
*/
|
|
55
|
+
deductStock: (productId: string, qty: number, reason?: string) => Promise<any>;
|
|
56
|
+
/**
|
|
57
|
+
* Quick stock addition helper
|
|
58
|
+
*/
|
|
59
|
+
addStock: (productId: string, qty: number, reason?: string) => Promise<any>;
|
|
60
|
+
};
|
|
61
|
+
orders: {
|
|
62
|
+
/**
|
|
63
|
+
* Scan tracking number (resi), update shipping status & auto-deduct stock atomically
|
|
64
|
+
*/
|
|
65
|
+
scanResi: (params: ScanResiParams) => Promise<any>;
|
|
66
|
+
/**
|
|
67
|
+
* Cancel an order & automatically return deducted stock to inventory
|
|
68
|
+
*/
|
|
69
|
+
cancelOrder: (params: CancelOrderParams) => Promise<any>;
|
|
70
|
+
};
|
|
71
|
+
finance: {
|
|
72
|
+
/**
|
|
73
|
+
* Atomic balance transfer between users with ledger record
|
|
74
|
+
*/
|
|
75
|
+
transferBalance: (params: TransferParams) => Promise<any>;
|
|
76
|
+
};
|
|
77
|
+
utils: {
|
|
78
|
+
/**
|
|
79
|
+
* Atomic field incrementer on any Firestore document
|
|
80
|
+
*/
|
|
81
|
+
increment: (params: IncrementCounterParams) => Promise<any>;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Singleton Default Facade Export
|
|
86
|
+
*/
|
|
87
|
+
export declare const nexaSDK: NexaSDKFacade;
|
|
88
|
+
/**
|
|
89
|
+
* Factory function to create custom facade instance with specific NexaApp
|
|
90
|
+
*/
|
|
91
|
+
export declare function createNexaSDK(app?: NexaApp, options?: {
|
|
92
|
+
baseURL?: string;
|
|
93
|
+
}): NexaSDKFacade;
|