nexabase-console 2.0.1 → 2.0.2
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 +96 -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/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 +1 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -220,47 +220,120 @@ async function potongStokAman(productId, variantId, qtyBeli) {
|
|
|
220
220
|
|
|
221
221
|
---
|
|
222
222
|
|
|
223
|
-
### 3. Autentikasi Pengguna & OTP Tanpa Sandi
|
|
223
|
+
### 3. Autentikasi Pengguna & OTP Tanpa Sandi (SDK v2.0.0)
|
|
224
224
|
|
|
225
|
-
####
|
|
225
|
+
#### Inisialisasi Auth & Realtime State Listener
|
|
226
226
|
```javascript
|
|
227
|
-
|
|
227
|
+
import { getAuth, NexaAuthError } from 'nexabase-console';
|
|
228
|
+
|
|
229
|
+
const auth = getAuth(app);
|
|
230
|
+
|
|
231
|
+
// Realtime Listener & Sync Sesi Multi-Tab via StorageEvent
|
|
232
|
+
const unsubscribe = auth.onAuthStateChanged((user) => {
|
|
233
|
+
if (user) {
|
|
234
|
+
console.log("User terautentikasi:", user.uid, "| Email:", user.email);
|
|
235
|
+
} else {
|
|
236
|
+
console.log("User ter-logout / belum login");
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Pendaftaran & Login Email/Password
|
|
242
|
+
```javascript
|
|
243
|
+
// 1. Registrasi Akun Baru
|
|
228
244
|
const register = async () => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
245
|
+
try {
|
|
246
|
+
const session = await auth.createUserWithEmailAndPassword(
|
|
247
|
+
'budi@example.com',
|
|
248
|
+
'Password123!',
|
|
249
|
+
'Budi Santoso'
|
|
250
|
+
);
|
|
251
|
+
console.log("Registrasi Berhasil! UID:", session.user.uid);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
if (err instanceof NexaAuthError && err.code === 'auth/email-already-in-use') {
|
|
254
|
+
console.error("Email sudah terdaftar.");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
235
257
|
};
|
|
236
258
|
|
|
237
|
-
// Login User
|
|
259
|
+
// 2. Login User (Dukungan Automatic Single-Flight Token Refresh)
|
|
238
260
|
const login = async () => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
261
|
+
try {
|
|
262
|
+
const session = await auth.signInWithEmailAndPassword(
|
|
263
|
+
'budi@example.com',
|
|
264
|
+
'Password123!'
|
|
265
|
+
);
|
|
266
|
+
console.log("Access Token JWT:", session.accessToken);
|
|
267
|
+
console.log("Refresh Token:", session.refreshToken);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
if (err instanceof NexaAuthError && err.code === 'auth/invalid-credential') {
|
|
270
|
+
console.error("Email atau password salah.");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
245
273
|
};
|
|
246
274
|
```
|
|
247
275
|
|
|
248
|
-
#### Login Passwordless dengan OTP (Email)
|
|
276
|
+
#### Login Passwordless dengan OTP (Email 6-Digit)
|
|
249
277
|
```javascript
|
|
250
|
-
// 1. Kirim OTP Ke Email
|
|
278
|
+
// 1. Kirim OTP Ke Email (Berlaku 5 Menit, Hash Single-Use)
|
|
251
279
|
const sendMyOtp = async () => {
|
|
252
|
-
const res = await
|
|
253
|
-
console.log("Kode OTP berhasil terkirim
|
|
280
|
+
const res = await auth.sendOtp('budi@example.com');
|
|
281
|
+
console.log("Kode OTP berhasil terkirim:", res.message);
|
|
254
282
|
};
|
|
255
283
|
|
|
256
284
|
// 2. Verifikasi OTP dari Input Pengguna
|
|
257
285
|
const verifyMyOtp = async () => {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
286
|
+
try {
|
|
287
|
+
const otpCode = '123456'; // Kode 6 digit yang dikirim ke email
|
|
288
|
+
const session = await auth.signInWithOtp('budi@example.com', otpCode);
|
|
289
|
+
console.log("Login OTP Berhasil! Welcome:", session.user.email);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if (err instanceof NexaAuthError) {
|
|
292
|
+
if (err.code === 'auth/invalid-otp') console.error("OTP tidak valid.");
|
|
293
|
+
if (err.code === 'auth/expired-otp') console.error("OTP kedaluwarsa.");
|
|
294
|
+
if (err.code === 'auth/too-many-otp-attempts') console.error("Terlalu banyak percobaan.");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
261
297
|
};
|
|
262
298
|
```
|
|
263
299
|
|
|
300
|
+
#### Mengambil ID Token untuk Request API / Server Verification
|
|
301
|
+
```javascript
|
|
302
|
+
// Mengambil token JWT (dengan auto-refresh jika hampir expired)
|
|
303
|
+
const idToken = await auth.getIdToken(/* forceRefresh */ false);
|
|
304
|
+
|
|
305
|
+
// Mengambil rincian klaim token (exp, authTime, claims)
|
|
306
|
+
const tokenResult = await auth.getIdTokenResult();
|
|
307
|
+
console.log("Token Kedaluwarsa:", tokenResult.expirationTime);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Update Profil, Reset Password & Sign Out
|
|
311
|
+
```javascript
|
|
312
|
+
// Update Profil
|
|
313
|
+
await auth.updateProfile({ name: 'Budi Santoso, M.T.', photoURL: 'https://example.com/photo.jpg' });
|
|
314
|
+
|
|
315
|
+
// Reset Password Email
|
|
316
|
+
await auth.sendPasswordResetEmail('budi@example.com');
|
|
317
|
+
|
|
318
|
+
// Revoke All Sessions
|
|
319
|
+
await auth.revokeAllSessions();
|
|
320
|
+
|
|
321
|
+
// === Opsi Logout Akun (Sign Out) ===
|
|
322
|
+
// 1. Method Instance (Rekomendasi)
|
|
323
|
+
await auth.signOut();
|
|
324
|
+
|
|
325
|
+
// 2. Fungsi Standar Modular
|
|
326
|
+
import { signOut } from 'nexabase-console';
|
|
327
|
+
await signOut(auth);
|
|
328
|
+
|
|
329
|
+
// 3. Namespace Helper
|
|
330
|
+
import { NexabaseAuth } from 'nexabase-console';
|
|
331
|
+
await NexabaseAuth.signOut(auth);
|
|
332
|
+
|
|
333
|
+
// 4. Shortcut dari objek App
|
|
334
|
+
await app.signOut();
|
|
335
|
+
```
|
|
336
|
+
|
|
264
337
|
---
|
|
265
338
|
|
|
266
339
|
### 4. File Storage API
|
package/dist/app/NexaApp.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
|
-
import { NexaConfig,
|
|
2
|
+
import { NexaConfig, DatabaseReference, StorageReference, UploadOptions, OfflineJob } from '../types/index';
|
|
3
3
|
import { HttpClient } from '../transport/HttpClient';
|
|
4
4
|
import { SSEClient } from '../transport/SSEClient';
|
|
5
5
|
import { Auth } from '../auth/Auth';
|
|
6
|
+
import { User as AuthUser, AuthSession } from '../auth/authTypes';
|
|
6
7
|
import { IndexedDB } from '../persistence/IndexedDB';
|
|
7
8
|
import { LocalStore } from '../persistence/LocalStore';
|
|
8
9
|
import { MutationQueue } from '../persistence/MutationQueue';
|
|
@@ -44,15 +45,15 @@ export declare class NexaApp {
|
|
|
44
45
|
_ensureFirestoreSSE(): void;
|
|
45
46
|
_closeFirestoreSSEIfIdle(): void;
|
|
46
47
|
_notifySnapshotCallbacks(path: string, actionType: string, payload: any): void;
|
|
47
|
-
signInWithEmailAndPassword(email: string, password: string): Promise<
|
|
48
|
-
createUserWithEmailAndPassword(email: string, password: string, name
|
|
48
|
+
signInWithEmailAndPassword(email: string, password: string): Promise<AuthSession>;
|
|
49
|
+
createUserWithEmailAndPassword(email: string, password: string, name?: string): Promise<AuthSession>;
|
|
49
50
|
sendOtp(email: string): Promise<{
|
|
50
51
|
success: boolean;
|
|
51
52
|
message: string;
|
|
52
53
|
}>;
|
|
53
|
-
signInWithOtp(email: string, otp: string
|
|
54
|
+
signInWithOtp(email: string, otp: string): Promise<AuthSession>;
|
|
54
55
|
signOut(): void;
|
|
55
|
-
getCurrentUser():
|
|
56
|
+
getCurrentUser(): AuthUser | null;
|
|
56
57
|
ref(path: string): DatabaseReference;
|
|
57
58
|
storageRef(path: string): StorageReference;
|
|
58
59
|
uploadFile(path: string, file: File | Blob, options?: UploadOptions): Promise<{
|
package/dist/app/NexaApp.js
CHANGED
|
@@ -150,8 +150,8 @@ class NexaApp {
|
|
|
150
150
|
async sendOtp(email) {
|
|
151
151
|
return this.authService.sendOtp(email);
|
|
152
152
|
}
|
|
153
|
-
async signInWithOtp(email, otp
|
|
154
|
-
const res = await this.authService.signInWithOtp(email, otp
|
|
153
|
+
async signInWithOtp(email, otp) {
|
|
154
|
+
const res = await this.authService.signInWithOtp(email, otp);
|
|
155
155
|
this.token = this.authService.getToken();
|
|
156
156
|
return res;
|
|
157
157
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { NexaApp } from './NexaApp';
|
|
2
2
|
import { NexaConfig } from '../types/index';
|
|
3
|
+
import { Auth } from '../auth/Auth';
|
|
3
4
|
export declare const NexaBase: {
|
|
4
5
|
init: (config: NexaConfig) => NexaApp;
|
|
5
6
|
initializeApp: (config: NexaConfig) => NexaApp;
|
|
6
7
|
};
|
|
7
8
|
export declare const initializeApp: (config: NexaConfig) => NexaApp;
|
|
8
9
|
export declare const getFirestore: (app: NexaApp) => NexaApp;
|
|
10
|
+
export declare const getAuth: (app: NexaApp) => Auth;
|
|
11
|
+
export declare const signOut: (auth: Auth) => Promise<void>;
|
|
12
|
+
export declare const NexabaseAuth: {
|
|
13
|
+
signOut: (auth: Auth) => Promise<void>;
|
|
14
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFirestore = exports.initializeApp = exports.NexaBase = void 0;
|
|
3
|
+
exports.NexabaseAuth = exports.signOut = exports.getAuth = exports.getFirestore = exports.initializeApp = exports.NexaBase = void 0;
|
|
4
4
|
const NexaApp_1 = require("./NexaApp");
|
|
5
5
|
exports.NexaBase = {
|
|
6
6
|
init: (config) => {
|
|
@@ -25,3 +25,16 @@ const getFirestore = (app) => {
|
|
|
25
25
|
return app;
|
|
26
26
|
};
|
|
27
27
|
exports.getFirestore = getFirestore;
|
|
28
|
+
const getAuth = (app) => {
|
|
29
|
+
return app.authService;
|
|
30
|
+
};
|
|
31
|
+
exports.getAuth = getAuth;
|
|
32
|
+
const signOut = (auth) => {
|
|
33
|
+
return auth.signOut();
|
|
34
|
+
};
|
|
35
|
+
exports.signOut = signOut;
|
|
36
|
+
exports.NexabaseAuth = {
|
|
37
|
+
signOut: (auth) => {
|
|
38
|
+
return auth.signOut();
|
|
39
|
+
}
|
|
40
|
+
};
|
package/dist/auth/Auth.d.ts
CHANGED
|
@@ -1,21 +1,53 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
|
-
import { User,
|
|
2
|
+
import { User, AuthSession, IdTokenResult, UserProfileUpdate, AuthStateCallback } from './authTypes';
|
|
3
3
|
export declare class Auth {
|
|
4
4
|
private projectId;
|
|
5
5
|
private client;
|
|
6
|
-
private token;
|
|
7
6
|
private persistence;
|
|
7
|
+
private emulatorUrl;
|
|
8
|
+
private currentSession;
|
|
9
|
+
private listeners;
|
|
10
|
+
private refreshPromise;
|
|
11
|
+
private refreshTimer;
|
|
8
12
|
constructor(projectId: string, client: AxiosInstance, initialToken: string | null);
|
|
13
|
+
connectAuthEmulator(url: string): void;
|
|
14
|
+
get emulator(): string | null;
|
|
15
|
+
private initSession;
|
|
16
|
+
onAuthStateChanged(callback: AuthStateCallback): () => void;
|
|
17
|
+
private notifyListeners;
|
|
18
|
+
private handleStorageChange;
|
|
19
|
+
get currentUser(): User | null;
|
|
9
20
|
getToken(): string | null;
|
|
10
21
|
setToken(token: string | null): void;
|
|
11
|
-
|
|
12
|
-
|
|
22
|
+
getIdToken(forceRefresh?: boolean): Promise<string>;
|
|
23
|
+
getIdTokenResult(forceRefresh?: boolean): Promise<IdTokenResult>;
|
|
24
|
+
private parseJwtClaims;
|
|
25
|
+
private refreshAccessTokenDeduplicated;
|
|
26
|
+
private refreshAccessToken;
|
|
27
|
+
private scheduleAutoRefresh;
|
|
28
|
+
private clearAutoRefresh;
|
|
29
|
+
createUserWithEmailAndPassword(email: string, password: string, name?: string): Promise<AuthSession>;
|
|
30
|
+
signInWithEmailAndPassword(email: string, password: string): Promise<AuthSession>;
|
|
13
31
|
sendOtp(email: string): Promise<{
|
|
14
32
|
success: boolean;
|
|
15
33
|
message: string;
|
|
16
34
|
}>;
|
|
17
|
-
signInWithOtp(email: string, otp: string
|
|
18
|
-
|
|
35
|
+
signInWithOtp(email: string, otp: string): Promise<AuthSession>;
|
|
36
|
+
sendPasswordResetEmail(email: string): Promise<{
|
|
37
|
+
success: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
confirmPasswordReset(code: string, newPassword: string): Promise<{
|
|
40
|
+
success: boolean;
|
|
41
|
+
}>;
|
|
42
|
+
sendEmailVerification(): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
updateProfile(update: UserProfileUpdate): Promise<User>;
|
|
46
|
+
updatePassword(newPassword: string): Promise<void>;
|
|
47
|
+
deleteUser(): Promise<void>;
|
|
48
|
+
revokeSession(sessionId?: string): Promise<void>;
|
|
49
|
+
revokeAllSessions(): Promise<void>;
|
|
50
|
+
signOut(): Promise<void>;
|
|
19
51
|
getCurrentUser(): User | null;
|
|
20
52
|
}
|
|
21
53
|
export { Auth as AuthService };
|
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;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nexaSDK = exports.NexaSDKFacade = void 0;
|
|
4
|
+
exports.createNexaSDK = createNexaSDK;
|
|
5
|
+
const initializeApp_1 = require("../app/initializeApp");
|
|
6
|
+
/**
|
|
7
|
+
* Universal NexaBase Client SDK Facade (Domain & Server-Authoritative Logic)
|
|
8
|
+
* Decouples client components & stores from raw primitives like getDocs/runTransaction.
|
|
9
|
+
*/
|
|
10
|
+
class NexaSDKFacade {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.appInstance = null;
|
|
13
|
+
this.customBaseURL = '';
|
|
14
|
+
/* =========================================================================
|
|
15
|
+
* 1. PRODUCTS & INVENTORY DOMAIN
|
|
16
|
+
* ========================================================================= */
|
|
17
|
+
this.products = {
|
|
18
|
+
/**
|
|
19
|
+
* Atomically adjust product stock in server with audit log trail
|
|
20
|
+
*/
|
|
21
|
+
adjustStock: async (params) => {
|
|
22
|
+
return this.call('adjustProductStock', params);
|
|
23
|
+
},
|
|
24
|
+
/**
|
|
25
|
+
* Quick stock deduction helper
|
|
26
|
+
*/
|
|
27
|
+
deductStock: async (productId, qty, reason = 'sale') => {
|
|
28
|
+
return this.call('adjustProductStock', { productId, delta: -Math.abs(qty), reason });
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* Quick stock addition helper
|
|
32
|
+
*/
|
|
33
|
+
addStock: async (productId, qty, reason = 'restock') => {
|
|
34
|
+
return this.call('adjustProductStock', { productId, delta: Math.abs(qty), reason });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/* =========================================================================
|
|
38
|
+
* 2. ORDERS & WAREHOUSE DOMAIN
|
|
39
|
+
* ========================================================================= */
|
|
40
|
+
this.orders = {
|
|
41
|
+
/**
|
|
42
|
+
* Scan tracking number (resi), update shipping status & auto-deduct stock atomically
|
|
43
|
+
*/
|
|
44
|
+
scanResi: async (params) => {
|
|
45
|
+
return this.call('scanResiOrder', params);
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* Cancel an order & automatically return deducted stock to inventory
|
|
49
|
+
*/
|
|
50
|
+
cancelOrder: async (params) => {
|
|
51
|
+
return this.call('cancelOrder', params);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/* =========================================================================
|
|
55
|
+
* 3. FINANCIAL & TRANSACTION DOMAIN
|
|
56
|
+
* ========================================================================= */
|
|
57
|
+
this.finance = {
|
|
58
|
+
/**
|
|
59
|
+
* Atomic balance transfer between users with ledger record
|
|
60
|
+
*/
|
|
61
|
+
transferBalance: async (params) => {
|
|
62
|
+
return this.call('transferBalance', params);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/* =========================================================================
|
|
66
|
+
* 4. GENERAL PURPOSE UTILITIES
|
|
67
|
+
* ========================================================================= */
|
|
68
|
+
this.utils = {
|
|
69
|
+
/**
|
|
70
|
+
* Atomic field incrementer on any Firestore document
|
|
71
|
+
*/
|
|
72
|
+
increment: async (params) => {
|
|
73
|
+
return this.call('incrementCounter', params);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
if (options?.app)
|
|
77
|
+
this.appInstance = options.app;
|
|
78
|
+
if (options?.baseURL)
|
|
79
|
+
this.customBaseURL = options.baseURL;
|
|
80
|
+
}
|
|
81
|
+
getApp() {
|
|
82
|
+
if (this.appInstance)
|
|
83
|
+
return this.appInstance;
|
|
84
|
+
return (0, initializeApp_1.getApp)();
|
|
85
|
+
}
|
|
86
|
+
getBaseURL() {
|
|
87
|
+
if (this.customBaseURL)
|
|
88
|
+
return this.customBaseURL;
|
|
89
|
+
if (typeof window !== 'undefined')
|
|
90
|
+
return window.location.origin;
|
|
91
|
+
return 'http://localhost:3000';
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Universal Server-Authoritative Cloud Function Caller
|
|
95
|
+
*/
|
|
96
|
+
async call(functionName, data = {}) {
|
|
97
|
+
const app = this.getApp();
|
|
98
|
+
const projectId = app.options.projectId;
|
|
99
|
+
const apiKey = app.options.apiKey;
|
|
100
|
+
const url = `${this.getBaseURL()}/api/functions/${projectId}/${functionName}`;
|
|
101
|
+
const headers = {
|
|
102
|
+
'Content-Type': 'application/json'
|
|
103
|
+
};
|
|
104
|
+
if (apiKey) {
|
|
105
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
106
|
+
}
|
|
107
|
+
const response = await fetch(url, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers,
|
|
110
|
+
body: JSON.stringify(data)
|
|
111
|
+
});
|
|
112
|
+
const result = await response.json();
|
|
113
|
+
if (!response.ok || result.success === false) {
|
|
114
|
+
throw new Error(result.error || result.message || `Failed to execute function "${functionName}"`);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.NexaSDKFacade = NexaSDKFacade;
|
|
120
|
+
/**
|
|
121
|
+
* Singleton Default Facade Export
|
|
122
|
+
*/
|
|
123
|
+
exports.nexaSDK = new NexaSDKFacade();
|
|
124
|
+
/**
|
|
125
|
+
* Factory function to create custom facade instance with specific NexaApp
|
|
126
|
+
*/
|
|
127
|
+
function createNexaSDK(app, options) {
|
|
128
|
+
return new NexaSDKFacade({ app, baseURL: options?.baseURL });
|
|
129
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './app/NexaApp';
|
|
|
3
3
|
export * from './auth/Auth';
|
|
4
4
|
export * from './auth/authTypes';
|
|
5
5
|
export * from './auth/persistence';
|
|
6
|
+
export * from './auth/NexaAuthError';
|
|
6
7
|
export * from './firestore/Firestore';
|
|
7
8
|
export * from './firestore/DocumentReference';
|
|
8
9
|
export * from './firestore/CollectionReference';
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ __exportStar(require("./app/NexaApp"), exports);
|
|
|
21
21
|
__exportStar(require("./auth/Auth"), exports);
|
|
22
22
|
__exportStar(require("./auth/authTypes"), exports);
|
|
23
23
|
__exportStar(require("./auth/persistence"), exports);
|
|
24
|
+
__exportStar(require("./auth/NexaAuthError"), exports);
|
|
24
25
|
// Firestore
|
|
25
26
|
__exportStar(require("./firestore/Firestore"), exports);
|
|
26
27
|
__exportStar(require("./firestore/DocumentReference"), exports);
|
package/dist/storage/Storage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Storage = void 0;
|
|
3
|
+
exports.getStorage = exports.Storage = void 0;
|
|
4
4
|
const UploadTask_1 = require("./UploadTask");
|
|
5
5
|
const NexaError_1 = require("../errors/NexaError");
|
|
6
6
|
class Storage {
|
|
@@ -37,5 +37,21 @@ class Storage {
|
|
|
37
37
|
throw (0, NexaError_1.toNexaError)(err);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
async deleteFile(path) {
|
|
41
|
+
try {
|
|
42
|
+
const res = await this.client.delete(`/api/project/${this.projectId}/storage/file?path=${encodeURIComponent(path)}`);
|
|
43
|
+
return res.data;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw (0, NexaError_1.toNexaError)(err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
exports.Storage = Storage;
|
|
51
|
+
const getStorage = (app) => {
|
|
52
|
+
if (app && typeof app.storage === 'function') {
|
|
53
|
+
return app.storage();
|
|
54
|
+
}
|
|
55
|
+
throw new Error('Invalid NexaApp instance provided to getStorage()');
|
|
56
|
+
};
|
|
57
|
+
exports.getStorage = getStorage;
|
|
@@ -9,5 +9,23 @@ export declare class StorageReferenceImpl implements IStorageReference {
|
|
|
9
9
|
path: string;
|
|
10
10
|
}>;
|
|
11
11
|
getDownloadURL(): Promise<string>;
|
|
12
|
+
delete(): Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
message?: string;
|
|
15
|
+
}>;
|
|
12
16
|
}
|
|
13
17
|
export declare const storageRef: (storage: Storage, path: string) => IStorageReference;
|
|
18
|
+
export declare const ref: (storage: Storage, path: string) => IStorageReference;
|
|
19
|
+
export declare const uploadBytes: (storageRef: IStorageReference, file: File | Blob, options?: UploadOptions) => Promise<{
|
|
20
|
+
url: string;
|
|
21
|
+
path: string;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const uploadBytesResumable: (storageRef: IStorageReference, file: File | Blob, options?: UploadOptions) => Promise<{
|
|
24
|
+
url: string;
|
|
25
|
+
path: string;
|
|
26
|
+
}>;
|
|
27
|
+
export declare const getDownloadURL: (storageRef: IStorageReference) => Promise<string>;
|
|
28
|
+
export declare const deleteObject: (storageRef: IStorageReference) => Promise<{
|
|
29
|
+
success: boolean;
|
|
30
|
+
message?: string;
|
|
31
|
+
}>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.storageRef = exports.StorageReferenceImpl = void 0;
|
|
3
|
+
exports.deleteObject = exports.getDownloadURL = exports.uploadBytesResumable = exports.uploadBytes = exports.ref = exports.storageRef = exports.StorageReferenceImpl = void 0;
|
|
4
4
|
class StorageReferenceImpl {
|
|
5
5
|
constructor(path, storage) {
|
|
6
6
|
this.path = path;
|
|
@@ -12,9 +12,41 @@ class StorageReferenceImpl {
|
|
|
12
12
|
async getDownloadURL() {
|
|
13
13
|
return this.storage.getDownloadURL(this.path);
|
|
14
14
|
}
|
|
15
|
+
async delete() {
|
|
16
|
+
return this.storage.deleteFile(this.path);
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
exports.StorageReferenceImpl = StorageReferenceImpl;
|
|
17
20
|
const storageRef = (storage, path) => {
|
|
18
21
|
return new StorageReferenceImpl(path, storage);
|
|
19
22
|
};
|
|
20
23
|
exports.storageRef = storageRef;
|
|
24
|
+
const ref = (storage, path) => {
|
|
25
|
+
return new StorageReferenceImpl(path, storage);
|
|
26
|
+
};
|
|
27
|
+
exports.ref = ref;
|
|
28
|
+
const uploadBytes = async (storageRef, file, options) => {
|
|
29
|
+
if (storageRef.upload) {
|
|
30
|
+
return storageRef.upload(file, options);
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Invalid storage reference');
|
|
33
|
+
};
|
|
34
|
+
exports.uploadBytes = uploadBytes;
|
|
35
|
+
const uploadBytesResumable = (storageRef, file, options) => {
|
|
36
|
+
return (0, exports.uploadBytes)(storageRef, file, options);
|
|
37
|
+
};
|
|
38
|
+
exports.uploadBytesResumable = uploadBytesResumable;
|
|
39
|
+
const getDownloadURL = async (storageRef) => {
|
|
40
|
+
if (storageRef.getDownloadURL) {
|
|
41
|
+
return storageRef.getDownloadURL();
|
|
42
|
+
}
|
|
43
|
+
throw new Error('Invalid storage reference');
|
|
44
|
+
};
|
|
45
|
+
exports.getDownloadURL = getDownloadURL;
|
|
46
|
+
const deleteObject = async (storageRef) => {
|
|
47
|
+
if (storageRef.delete) {
|
|
48
|
+
return storageRef.delete();
|
|
49
|
+
}
|
|
50
|
+
throw new Error('Invalid storage reference');
|
|
51
|
+
};
|
|
52
|
+
exports.deleteObject = deleteObject;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NexaApp } from '../app/NexaApp';
|
|
2
2
|
import type { FieldPath } from '../firestore/FieldPath';
|
|
3
|
+
import type { User } from '../auth/authTypes';
|
|
3
4
|
export interface NexaConfig {
|
|
4
5
|
projectId: string;
|
|
5
6
|
apiKey?: string;
|
|
@@ -7,14 +8,6 @@ export interface NexaConfig {
|
|
|
7
8
|
enablePersistence?: boolean;
|
|
8
9
|
cacheStrategy?: 'network-first' | 'cache-first';
|
|
9
10
|
}
|
|
10
|
-
export interface User {
|
|
11
|
-
id: string;
|
|
12
|
-
email: string;
|
|
13
|
-
name?: string;
|
|
14
|
-
role?: string;
|
|
15
|
-
createdAt?: string;
|
|
16
|
-
[key: string]: any;
|
|
17
|
-
}
|
|
18
11
|
export interface AuthResponse {
|
|
19
12
|
token: string;
|
|
20
13
|
user: User;
|
package/package.json
CHANGED