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 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
- #### Pendaftaran & Login Tradisional
225
+ #### Inisialisasi Auth & Realtime State Listener
226
226
  ```javascript
227
- // Registrasi User Baru
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
- const user = await app.auth().createUserWithEmailAndPassword(
230
- 'test@example.com',
231
- 'password123',
232
- 'Andi Wijaya'
233
- );
234
- console.log("Registrasi Berhasil:", user);
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
- const authSession = await app.auth().signInWithEmailAndPassword(
240
- 'test@example.com',
241
- 'password123'
242
- );
243
- console.log("Token Sesi JWT:", authSession.token);
244
- console.log("Data Akun Pengguna:", authSession.user);
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 Pengguna
278
+ // 1. Kirim OTP Ke Email (Berlaku 5 Menit, Hash Single-Use)
251
279
  const sendMyOtp = async () => {
252
- const res = await app.auth().sendOtp('test@example.com');
253
- console.log("Kode OTP berhasil terkirim ke email Anda.");
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
- const otpCode = '123456'; // Kode 6 digit yang dikirim ke email
259
- const authSession = await app.auth().signInWithOtp('test@example.com', otpCode, 'Andi Wijaya');
260
- console.log("Login OTP Berhasil! Sesi JWT Aktif:", authSession.token);
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
@@ -1,8 +1,9 @@
1
1
  import { AxiosInstance } from 'axios';
2
- import { NexaConfig, User, AuthResponse, DatabaseReference, StorageReference, UploadOptions, OfflineJob } from '../types/index';
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<AuthResponse>;
48
- createUserWithEmailAndPassword(email: string, password: string, name: string): Promise<User>;
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, name?: string): Promise<AuthResponse>;
54
+ signInWithOtp(email: string, otp: string): Promise<AuthSession>;
54
55
  signOut(): void;
55
- getCurrentUser(): User | null;
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<{
@@ -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, name) {
154
- const res = await this.authService.signInWithOtp(email, otp, name);
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
+ };
@@ -1,21 +1,53 @@
1
1
  import { AxiosInstance } from 'axios';
2
- import { User, AuthResponse } from './authTypes';
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
- signInWithEmailAndPassword(email: string, password: string): Promise<AuthResponse>;
12
- createUserWithEmailAndPassword(email: string, password: string, name: string): Promise<User>;
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, name?: string): Promise<AuthResponse>;
18
- signOut(): void;
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 NexaError_1 = require("../errors/NexaError");
5
+ const NexaAuthError_1 = require("./NexaAuthError");
6
6
  class Auth {
7
7
  constructor(projectId, client, initialToken) {
8
- this.token = null;
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
- this.token = initialToken || this.persistence.getSavedToken();
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.token;
107
+ return this.currentSession?.accessToken || null;
16
108
  }
17
109
  setToken(token) {
18
- this.token = token;
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 res = await this.client.post(`/api/project/${this.projectId}/auth/login`, { email, password });
23
- if (res.data.token) {
24
- this.token = res.data.token;
25
- this.persistence.saveSession(res.data.token, res.data.user);
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 (0, NexaError_1.toNexaError)(err);
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 createUserWithEmailAndPassword(email, password, name) {
245
+ async signInWithOtp(email, otp) {
34
246
  try {
35
- const res = await this.client.post(`/api/project/${this.projectId}/users`, { email, password, name });
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 (0, NexaError_1.toNexaError)(err);
268
+ throw new NexaAuthError_1.NexaAuthError('auth/user-not-found', 'Permintaan reset password gagal.');
40
269
  }
41
270
  }
42
- async sendOtp(email) {
271
+ async confirmPasswordReset(code, newPassword) {
43
272
  try {
44
- const res = await this.client.post(`/api/project/${this.projectId}/auth/otp/send`, { email });
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 (0, NexaError_1.toNexaError)(err);
278
+ throw new NexaAuthError_1.NexaAuthError('auth/invalid-credential', 'Kode reset password tidak valid.');
49
279
  }
50
280
  }
51
- async signInWithOtp(email, otp, name) {
281
+ async sendEmailVerification() {
282
+ const token = await this.getIdToken();
52
283
  try {
53
- const res = await this.client.post(`/api/project/${this.projectId}/auth/otp/login`, { email, otp, name });
54
- if (res.data.token) {
55
- this.token = res.data.token;
56
- this.persistence.saveSession(res.data.token, res.data.user);
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 (0, NexaError_1.toNexaError)(err);
291
+ throw new NexaAuthError_1.NexaAuthError('auth/unauthenticated', 'Gagal mengirim email verifikasi.');
62
292
  }
63
293
  }
64
- signOut() {
65
- this.token = null;
66
- this.persistence.clearSession();
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.persistence.getSavedUser();
382
+ return this.currentUser;
70
383
  }
71
384
  }
72
385
  exports.Auth = Auth;
@@ -0,0 +1,5 @@
1
+ export declare class NexaAuthError extends Error {
2
+ readonly code: string;
3
+ readonly status?: number;
4
+ constructor(code: string, message: string, status?: number);
5
+ }
@@ -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;
@@ -1 +1,38 @@
1
- export type { User, AuthResponse } from '../types/index';
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 { User } from './authTypes';
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
- saveSession(token: string, user: User): void;
6
- clearSession(): void;
6
+ getSession(): Promise<AuthSession | null>;
7
+ saveSession(session: AuthSession): Promise<void>;
8
+ clearSession(): Promise<void>;
7
9
  getSavedToken(): string | null;
8
- getSavedUser(): User | null;
10
+ getSavedUser(): AuthSession['user'] | null;
9
11
  }
@@ -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
- saveSession(token, user) {
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(`nexa_token_${this.projectId}`, token);
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(`nexa_token_${this.projectId}`);
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 !== 'undefined' && window.localStorage) {
22
- return localStorage.getItem(`nexa_token_${this.projectId}`);
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 !== 'undefined' && window.localStorage) {
28
- const userStr = localStorage.getItem(`nexa_user_${this.projectId}`);
29
- return userStr ? JSON.parse(userStr) : null;
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);
@@ -9,4 +9,9 @@ export declare class Storage {
9
9
  path: string;
10
10
  }>;
11
11
  getDownloadURL(path: string): Promise<string>;
12
+ deleteFile(path: string): Promise<{
13
+ success: boolean;
14
+ message?: string;
15
+ }>;
12
16
  }
17
+ export declare const getStorage: (app: any) => Storage;
@@ -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;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexabase-console",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "SDK Client resmi untuk NexaBase: Platform Sinkronisasi NoSQL, Realtime, File Storage, & Autentikasi Offline-First.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",