@codixus/client 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +39 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -1
- package/dist/push-expo/index.cjs +23 -0
- package/dist/push-expo/index.cjs.map +1 -1
- package/dist/push-expo/index.js +23 -0
- package/dist/push-expo/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
CodixusError: () => import_shared.CodixusError,
|
|
36
36
|
Collection: () => Collection,
|
|
37
37
|
ErrorCodes: () => import_shared.ErrorCodes,
|
|
38
|
+
EventManager: () => EventManager,
|
|
38
39
|
setCryptoProvider: () => setCryptoProvider
|
|
39
40
|
});
|
|
40
41
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -481,11 +482,47 @@ var PushManager = class {
|
|
|
481
482
|
}
|
|
482
483
|
};
|
|
483
484
|
|
|
485
|
+
// src/events/event-manager.ts
|
|
486
|
+
var EVENT_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
487
|
+
var EventManager = class {
|
|
488
|
+
constructor(api, auth) {
|
|
489
|
+
this.api = api;
|
|
490
|
+
this.auth = auth;
|
|
491
|
+
}
|
|
492
|
+
api;
|
|
493
|
+
auth;
|
|
494
|
+
async track(name, properties, options) {
|
|
495
|
+
if (!EVENT_NAME_RE.test(name)) {
|
|
496
|
+
throw new Error("Invalid event name");
|
|
497
|
+
}
|
|
498
|
+
if (options?.idempotencyKey !== void 0 && (options.idempotencyKey.length === 0 || options.idempotencyKey.length > 200)) {
|
|
499
|
+
throw new Error("Invalid idempotency key");
|
|
500
|
+
}
|
|
501
|
+
if (options?.occurredAt !== void 0 && Number.isNaN(options.occurredAt.getTime())) {
|
|
502
|
+
throw new Error("Invalid occurredAt");
|
|
503
|
+
}
|
|
504
|
+
if (!await this.auth.getToken()) {
|
|
505
|
+
throw new Error("Not authenticated");
|
|
506
|
+
}
|
|
507
|
+
const payload = { name };
|
|
508
|
+
if (properties !== void 0) payload.properties = properties;
|
|
509
|
+
if (options?.idempotencyKey !== void 0) {
|
|
510
|
+
payload.idempotencyKey = options.idempotencyKey;
|
|
511
|
+
}
|
|
512
|
+
if (options?.occurredAt !== void 0) {
|
|
513
|
+
payload.occurredAt = options.occurredAt.toISOString();
|
|
514
|
+
}
|
|
515
|
+
const response = await this.api.post("/events", payload);
|
|
516
|
+
return response.data.data;
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
484
520
|
// src/index.ts
|
|
485
521
|
var import_shared = require("@codixus/shared");
|
|
486
522
|
var CodixusClient = class {
|
|
487
523
|
auth;
|
|
488
524
|
api;
|
|
525
|
+
events;
|
|
489
526
|
push;
|
|
490
527
|
constructor(config) {
|
|
491
528
|
const storageKey = config.storageKey ?? "codixus";
|
|
@@ -496,6 +533,7 @@ var CodixusClient = class {
|
|
|
496
533
|
});
|
|
497
534
|
this.api = new ApiClient(config.apiUrl, this.auth, config.appSecret);
|
|
498
535
|
this.auth._setApi(this.api);
|
|
536
|
+
this.events = new EventManager(this.api, this.auth);
|
|
499
537
|
if (config.push) {
|
|
500
538
|
this.push = new PushManager(config.push, this.api, this.auth);
|
|
501
539
|
}
|
|
@@ -524,6 +562,7 @@ var CodixusClient = class {
|
|
|
524
562
|
CodixusError,
|
|
525
563
|
Collection,
|
|
526
564
|
ErrorCodes,
|
|
565
|
+
EventManager,
|
|
527
566
|
setCryptoProvider
|
|
528
567
|
});
|
|
529
568
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/auth/auth-manager.ts","../src/api/api-client.ts","../src/db/collection.ts","../src/push/push-manager.ts"],"sourcesContent":["import {\n AuthManager,\n type StorageAdapter,\n type SignInParams,\n type AuthState,\n type DeviceIdProvider,\n type AutoSignInConfig,\n} from \"./auth/auth-manager.js\";\nimport { ApiClient } from \"./api/api-client.js\";\nimport { Collection, type FindOptions } from \"./db/collection.js\";\nimport { PushManager } from \"./push/push-manager.js\";\nimport type { PushAdapter } from \"./push/types.js\";\n\nexport interface CodixusClientConfig {\n apiUrl: string;\n storageKey?: string;\n storage?: StorageAdapter | null;\n appSecret?: string;\n deviceIdProvider?: DeviceIdProvider | null;\n autoSignIn?: AutoSignInConfig | null;\n push?: PushAdapter;\n}\n\nexport class CodixusClient {\n readonly auth: AuthManager;\n readonly api: ApiClient;\n readonly push?: PushManager;\n\n constructor(config: CodixusClientConfig) {\n const storageKey = config.storageKey ?? \"codixus\";\n const storage = config.storage ?? null;\n\n this.auth = new AuthManager(storage, storageKey, {\n deviceIdProvider: config.deviceIdProvider ?? null,\n autoSignIn: config.autoSignIn ?? null,\n });\n this.api = new ApiClient(config.apiUrl, this.auth, config.appSecret);\n this.auth._setApi(this.api);\n\n if (config.push) {\n this.push = new PushManager(config.push, this.api, this.auth);\n }\n }\n\n /**\n * Create a typed collection accessor.\n * Maps to auto REST routes on server: codixus.rest(Model)\n *\n * @example\n * interface Product { _id: string; name: string; price: number; }\n * const products = codixus.collection<Product>(\"products\");\n * const all = await products.find(); // Product[]\n * const one = await products.findById(\"abc\"); // Product | null\n */\n collection<T>(name: string): Collection<T> {\n return new Collection<T>(this.api, `/api/${name}`);\n }\n\n async init(): Promise<void> {\n await this.auth.init();\n }\n}\n\nexport {\n type StorageAdapter,\n type SignInParams,\n type AuthState,\n type DeviceIdProvider,\n type AutoSignInConfig,\n} from \"./auth/auth-manager.js\";\nexport { ApiClient, setCryptoProvider, type CryptoProvider } from \"./api/api-client.js\";\nexport { Collection, type FindOptions } from \"./db/collection.js\";\nexport { type PushAdapter, type PushAdapterToken } from \"./push/types.js\";\n\n// Re-export shared types\nexport {\n type AuthUser,\n type TokenPair,\n type DeviceInfo,\n type DeviceMetadata,\n type AuthTokenRequest,\n type AuthTokenResponse,\n type ApiResponse,\n CodixusError,\n ErrorCodes,\n} from \"@codixus/shared\";\n","import type {\n AuthTokenResponse,\n DeviceInfo,\n DeviceMetadata,\n} from \"@codixus/shared\";\nimport type { ApiClient } from \"../api/api-client.js\";\n\nexport interface StorageAdapter {\n getItem(key: string): Promise<string | null> | string | null;\n setItem(key: string, value: string): Promise<void> | void;\n removeItem(key: string): Promise<void> | void;\n}\n\n// Host-injected source for a stable, OS-level device identifier (IDFV on\n// iOS, ANDROID_ID on Android, etc.). The SDK calls this when it needs to\n// obtain a deviceId and has nothing in storage. Returning null lets the\n// SDK fall back to a generated UUID. Keeping this an interface (instead\n// of importing expo-application) keeps the SDK platform-agnostic.\nexport interface DeviceIdProvider {\n getDeviceId(): Promise<string | null> | string | null;\n}\n\n// Optional auto signIn driven by the SDK on init(). When provided, after\n// init() restores from storage and the user is still unauthenticated, the\n// SDK calls signIn with the host-supplied device info/metadata. This\n// removes the need for every host app to wire its own boot signIn.\nexport interface AutoSignInConfig {\n enabled: boolean;\n getDeviceInfo(): DeviceInfo | Promise<DeviceInfo>;\n getMetadata?(): DeviceMetadata | Promise<DeviceMetadata>;\n onError?(error: unknown): void;\n}\n\nexport interface SignInParams {\n deviceId?: string;\n deviceInfo: DeviceInfo;\n metadata?: DeviceMetadata;\n}\n\nexport interface AuthState {\n isAuthenticated: boolean;\n isLoading: boolean;\n user: Record<string, unknown> | null;\n}\n\ntype AuthStateListener = (state: AuthState) => void;\n\nexport class AuthManager {\n private accessToken: string | null = null;\n private refreshTokenValue: string | null = null;\n private user: Record<string, unknown> | null = null;\n private listeners: Set<AuthStateListener> = new Set();\n private _isLoading = true;\n private storageKey: string;\n private api!: ApiClient;\n private deviceId: string | null = null;\n private deviceIdProvider: DeviceIdProvider | null;\n private autoSignInConfig: AutoSignInConfig | null;\n private initPromise: Promise<void> | null = null;\n private signInPromise: Promise<{ user: Record<string, unknown>; isNewUser: boolean }> | null = null;\n\n constructor(\n private storage: StorageAdapter | null,\n storageKey: string,\n options?: {\n deviceIdProvider?: DeviceIdProvider | null;\n autoSignIn?: AutoSignInConfig | null;\n },\n ) {\n this.storageKey = storageKey;\n this.deviceIdProvider = options?.deviceIdProvider ?? null;\n this.autoSignInConfig = options?.autoSignIn ?? null;\n }\n\n /** @internal */\n _setApi(api: ApiClient): void {\n this.api = api;\n }\n\n async init(): Promise<void> {\n // Idempotent: concurrent or repeat callers (e.g. host module-load\n // init plus the React provider's useEffect init) share a single run\n // so autoSignIn does not fire twice and race against itself on the\n // backend. On rejection we clear the cached promise so a retry can\n // run — caching a rejected promise would brick init() for the rest\n // of the app lifetime.\n if (this.initPromise) return this.initPromise;\n this.initPromise = this._init().catch((err) => {\n this.initPromise = null;\n throw err;\n });\n return this.initPromise;\n }\n\n private async _init(): Promise<void> {\n if (this.storage) {\n try {\n const storedRefresh = await this.storage.getItem(\n `${this.storageKey}_refresh_token`,\n );\n const storedUser = await this.storage.getItem(\n `${this.storageKey}_user`,\n );\n const storedDeviceId = await this.storage.getItem(\n `${this.storageKey}_device_id`,\n );\n\n if (storedRefresh) {\n this.refreshTokenValue = storedRefresh;\n }\n if (storedUser) {\n try {\n this.user = JSON.parse(storedUser);\n } catch {\n // Corrupted user data in storage, ignore\n }\n }\n if (storedDeviceId) {\n this.deviceId = storedDeviceId;\n }\n } catch (error) {\n console.warn(\"[CodixusClient] Failed to restore auth state from storage:\", error);\n }\n }\n\n // Auto signIn: if the host app opted in and we have no live session yet\n // (cold start, app data cleared, fresh install), reach out to the\n // backend with the device-bound identifier so the existing user is\n // restored before any UI hits an authenticated route.\n if (\n this.autoSignInConfig?.enabled &&\n !this.refreshTokenValue\n ) {\n try {\n const deviceInfo = await this.autoSignInConfig.getDeviceInfo();\n const metadata = this.autoSignInConfig.getMetadata\n ? await this.autoSignInConfig.getMetadata()\n : undefined;\n await this.signIn({ deviceInfo, metadata });\n } catch (error) {\n this.autoSignInConfig.onError?.(error);\n }\n }\n\n this._isLoading = false;\n this.notify();\n }\n\n async signIn(\n params: SignInParams,\n ): Promise<{ user: Record<string, unknown>; isNewUser: boolean }> {\n // Coalesce concurrent signIn calls (e.g. autoSignIn racing a manual\n // signIn from a UI handler) onto a single network request.\n if (this.signInPromise) return this.signInPromise;\n this.signInPromise = this._signIn(params).finally(() => {\n this.signInPromise = null;\n });\n return this.signInPromise;\n }\n\n private async _signIn(\n params: SignInParams,\n ): Promise<{ user: Record<string, unknown>; isNewUser: boolean }> {\n const providerId = this.deviceIdProvider\n ? await this.deviceIdProvider.getDeviceId()\n : null;\n const deviceId =\n params.deviceId ??\n this.deviceId ??\n providerId ??\n this.generateDeviceId();\n\n const response = await this.api.post<AuthTokenResponse>(\"/auth/token\", {\n deviceId,\n deviceInfo: params.deviceInfo,\n metadata: params.metadata,\n });\n\n const data = response.data;\n this.accessToken = data.accessToken;\n this.refreshTokenValue = data.refreshToken;\n this.user = data.user;\n this.deviceId = deviceId;\n\n // Persist. Storage failures (full disk, locked keychain post-restore,\n // EncryptedSharedPreferences key invalidation) must not strand the\n // in-memory session — the user is signed in for this run; the next\n // cold start will simply re-trigger autoSignIn.\n if (this.storage) {\n try {\n await this.storage.setItem(\n `${this.storageKey}_refresh_token`,\n data.refreshToken,\n );\n await this.storage.setItem(\n `${this.storageKey}_user`,\n JSON.stringify(data.user),\n );\n await this.storage.setItem(`${this.storageKey}_device_id`, deviceId);\n } catch (error) {\n console.warn(\"[CodixusClient] Failed to persist auth state:\", error);\n }\n }\n\n this.notify();\n return { user: data.user, isNewUser: data.isNewUser };\n }\n\n async getToken(): Promise<string | null> {\n if (this.accessToken) return this.accessToken;\n\n // Try refresh\n if (this.refreshTokenValue) {\n try {\n await this.forceRefresh();\n return this.accessToken;\n } catch {\n return null;\n }\n }\n\n return null;\n }\n\n async forceRefresh(): Promise<string | null> {\n if (!this.refreshTokenValue) return null;\n\n try {\n const response = await this.api.post<{\n success: boolean;\n accessToken: string;\n refreshToken: string;\n }>(\"/auth/refresh\", {\n refreshToken: this.refreshTokenValue,\n });\n\n this.accessToken = response.data.accessToken;\n this.refreshTokenValue = response.data.refreshToken;\n\n if (this.storage) {\n await this.storage.setItem(\n `${this.storageKey}_refresh_token`,\n response.data.refreshToken,\n );\n }\n\n return this.accessToken;\n } catch {\n // Refresh failed, clear auth state\n await this.signOut();\n return null;\n }\n }\n\n async signOut(): Promise<void> {\n // Notify server\n if (this.refreshTokenValue) {\n try {\n await this.api.post(\"/auth/logout\", {\n refreshToken: this.refreshTokenValue,\n });\n } catch {}\n }\n\n this.accessToken = null;\n this.refreshTokenValue = null;\n this.user = null;\n // Reset guards so a fresh signIn flow (or a re-init triggered by a\n // host that wants to re-authenticate after sign-out) can run. The\n // signInPromise is also cleared by its own .finally, but a signOut\n // racing against an in-flight signIn shouldn't keep the stale guard.\n this.initPromise = null;\n this.signInPromise = null;\n\n if (this.storage) {\n await this.storage.removeItem(`${this.storageKey}_refresh_token`);\n await this.storage.removeItem(`${this.storageKey}_user`);\n await this.storage.removeItem(`${this.storageKey}_device_id`);\n }\n\n this.notify();\n }\n\n getState(): AuthState {\n return {\n isAuthenticated: this.accessToken !== null || this.refreshTokenValue !== null,\n isLoading: this._isLoading,\n user: this.user,\n };\n }\n\n getDeviceId(): string | null {\n return this.deviceId;\n }\n\n onAuthStateChange(listener: AuthStateListener): () => void {\n this.listeners.add(listener);\n // Call immediately with current state\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** @internal - used by API interceptor */\n _getAccessToken(): string | null {\n return this.accessToken;\n }\n\n /** @internal - called by API interceptor after refresh */\n _setAccessToken(token: string): void {\n this.accessToken = token;\n }\n\n private notify(): void {\n const state = this.getState();\n for (const listener of this.listeners) {\n listener(state);\n }\n }\n\n private generateDeviceId(): string {\n const bytes = new Uint8Array(16);\n if (typeof globalThis.crypto?.getRandomValues === \"function\") {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return \"dev-\" + Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n }\n}\n","import axios, {\n type AxiosInstance,\n type AxiosRequestConfig,\n type AxiosResponse,\n} from \"axios\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\n\n// Crypto functions — injected from outside to avoid platform-specific imports\nexport interface CryptoProvider {\n sha256(data: string): Promise<string>;\n hmacSha256(secret: string, data: string): Promise<string>;\n randomUUID(): string;\n}\n\n// Default: no-op (signing disabled if no crypto provider)\nlet _crypto: CryptoProvider | null = null;\n\nexport function setCryptoProvider(provider: CryptoProvider): void {\n _crypto = provider;\n}\n\nfunction generateNonce(): string {\n if (_crypto) return _crypto.randomUUID();\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n const bytes = new Uint8Array(16);\n if (typeof globalThis.crypto?.getRandomValues === \"function\") {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport class ApiClient {\n readonly instance: AxiosInstance;\n private refreshPromise: Promise<string | null> | null = null;\n private appSecret: string | undefined;\n\n constructor(\n baseURL: string,\n private authManager: AuthManager,\n appSecret?: string,\n ) {\n this.appSecret = appSecret;\n\n this.instance = axios.create({\n baseURL,\n headers: { \"Content-Type\": \"application/json\" },\n });\n\n // Request interceptor: add Bearer token + signing\n this.instance.interceptors.request.use(async (config) => {\n // Skip auth for auth endpoints\n if (!config.url?.includes(\"/auth/\")) {\n const token = this.authManager._getAccessToken();\n if (token) {\n config.headers.Authorization = `Bearer ${token}`;\n }\n }\n\n // Request signing (only if crypto provider is set)\n if (this.appSecret && _crypto) {\n const timestamp = Date.now().toString();\n const nonce = _crypto.randomUUID();\n const body =\n config.data && typeof config.data === \"object\"\n ? JSON.stringify(config.data)\n : (config.data ?? \"\");\n const bodyHash = body ? await _crypto.sha256(body) : \"\";\n const method = (config.method ?? \"GET\").toUpperCase();\n const url = config.url ?? \"/\";\n const payload = [method, url, timestamp, nonce, bodyHash].join(\"\\n\");\n const signature = await _crypto.hmacSha256(this.appSecret, payload);\n\n config.headers[\"X-Codixus-Timestamp\"] = timestamp;\n config.headers[\"X-Codixus-Nonce\"] = nonce;\n config.headers[\"X-Codixus-Signature\"] = signature;\n }\n\n return config;\n });\n\n // Response interceptor: handle 401 with token refresh\n this.instance.interceptors.response.use(\n (response) => response,\n async (error) => {\n if (error.response?.status === 401) {\n const originalRequest = error.config;\n if (originalRequest && !originalRequest._retry) {\n originalRequest._retry = true;\n\n try {\n if (!this.refreshPromise) {\n this.refreshPromise = this.authManager\n .forceRefresh()\n .finally(() => {\n this.refreshPromise = null;\n });\n }\n\n const newToken = await this.refreshPromise;\n if (newToken) {\n originalRequest.headers.Authorization = `Bearer ${newToken}`;\n return this.instance(originalRequest);\n }\n } catch (refreshError) {\n console.warn(\"[CodixusClient] Token refresh failed:\", refreshError);\n }\n }\n }\n return Promise.reject(error);\n },\n );\n }\n\n async get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.get<T>(url, config);\n }\n\n async post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.post<T>(url, data, config);\n }\n\n async patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.patch<T>(url, data, config);\n }\n\n async delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.delete<T>(url, config);\n }\n\n async put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.put<T>(url, data, config);\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\n\nexport interface FindOptions {\n sort?: Record<string, 1 | -1>;\n limit?: number;\n skip?: number;\n cursor?: string | number;\n}\n\nexport class Collection<T = Record<string, unknown>> {\n constructor(\n private api: ApiClient,\n private basePath: string,\n ) {}\n\n async find(\n filter?: Record<string, unknown>,\n options?: FindOptions,\n ): Promise<T[]> {\n const params = new URLSearchParams();\n if (filter && Object.keys(filter).length > 0) {\n params.set(\"filter\", JSON.stringify(filter));\n }\n if (options?.sort) params.set(\"sort\", JSON.stringify(options.sort));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.skip) params.set(\"skip\", String(options.skip));\n if (options?.cursor) params.set(\"cursor\", String(options.cursor));\n\n const qs = params.toString();\n const url = qs ? `${this.basePath}?${qs}` : this.basePath;\n const { data } = await this.api.get<{ success: boolean; data: T[]; nextCursor?: string | null }>(url);\n return data.data;\n }\n\n async findWithCursor(\n filter?: Record<string, unknown>,\n options?: FindOptions,\n ): Promise<{ data: T[]; nextCursor: string | number | null }> {\n const params = new URLSearchParams();\n if (filter && Object.keys(filter).length > 0) {\n params.set(\"filter\", JSON.stringify(filter));\n }\n if (options?.sort) params.set(\"sort\", JSON.stringify(options.sort));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.cursor) params.set(\"cursor\", String(options.cursor));\n\n const qs = params.toString();\n const url = qs ? `${this.basePath}?${qs}` : this.basePath;\n const { data } = await this.api.get<{ success: boolean; data: T[]; nextCursor: string | number | null }>(url);\n return { data: data.data, nextCursor: data.nextCursor };\n }\n\n async findById(id: string): Promise<T | null> {\n try {\n const { data } = await this.api.get<{ success: boolean; data: T }>(\n `${this.basePath}/${id}`,\n );\n return data.data;\n } catch (error: any) {\n if (error.response?.status === 404) return null;\n throw error;\n }\n }\n\n async create(doc: Partial<T> & Record<string, unknown>): Promise<T> {\n const { data } = await this.api.post<{ success: boolean; data: T }>(\n this.basePath,\n doc,\n );\n return data.data;\n }\n\n async updateById(\n id: string,\n update: Record<string, unknown>,\n ): Promise<T> {\n const { data } = await this.api.patch<{ success: boolean; data: T }>(\n `${this.basePath}/${id}`,\n { update },\n );\n return data.data;\n }\n\n async deleteById(id: string): Promise<void> {\n await this.api.delete(`${this.basePath}/${id}`);\n }\n\n async count(filter?: Record<string, unknown>): Promise<number> {\n const { data } = await this.api.post<{ success: boolean; count: number }>(\n `${this.basePath}/count`,\n { filter: filter ?? {} },\n );\n return data.count;\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\nimport type { PushAdapter } from \"./types.js\";\n\nfunction extractDeliveryId(\n payload: Record<string, unknown>,\n): string | undefined {\n if (typeof payload.deliveryId === \"string\" && payload.deliveryId) {\n return payload.deliveryId;\n }\n const data = payload.data;\n if (data && typeof data === \"object\" && !Array.isArray(data)) {\n const nested = (data as Record<string, unknown>).deliveryId;\n if (typeof nested === \"string\" && nested) {\n return nested;\n }\n }\n return undefined;\n}\n\nexport class PushManager {\n constructor(\n private adapter: PushAdapter,\n private api: ApiClient,\n private auth: AuthManager,\n ) {}\n\n async register(): Promise<void> {\n const authToken = await this.auth.getToken();\n if (!authToken) {\n throw new Error(\"Not authenticated\");\n }\n\n const adapterToken = await this.adapter.getToken();\n if (!adapterToken) {\n return;\n }\n\n const body: Record<string, unknown> = {\n token: adapterToken.token,\n provider: adapterToken.provider,\n platform: adapterToken.platform,\n };\n\n try {\n body.permissionStatus = await this.adapter.getPermissionStatus();\n } catch {\n // omit permissionStatus, still POST\n }\n\n await this.api.post(\"/push/register\", body);\n }\n\n async unregister(): Promise<void> {\n const authToken = await this.auth.getToken();\n if (!authToken) {\n throw new Error(\"Not authenticated\");\n }\n\n await this.api.post(\"/push/unregister\", {});\n }\n\n async requestPermission(): Promise<boolean> {\n return this.adapter.requestPermission();\n }\n\n async getPermissionStatus(): Promise<\n \"granted\" | \"denied\" | \"undetermined\"\n > {\n return this.adapter.getPermissionStatus();\n }\n\n addNotificationOpenedListener(\n handler: (payload: Record<string, unknown>) => void,\n ): () => void {\n return this.adapter.addNotificationOpenedListener((payload) => {\n handler(payload);\n const deliveryId = extractDeliveryId(payload);\n if (deliveryId) {\n void this.reportOpen(deliveryId);\n }\n });\n }\n\n addNotificationReceivedListener(\n handler: (payload: Record<string, unknown>) => void,\n ): () => void {\n return this.adapter.addNotificationReceivedListener(handler);\n }\n\n private async reportOpen(deliveryId: string): Promise<void> {\n try {\n const authToken = await this.auth.getToken();\n if (!authToken) return;\n await this.api.post(\"/push/open\", { deliveryId });\n } catch {\n // fire-and-forget: swallow 404, 401, 5xx, network errors\n }\n }\n}\n"],"mappings":"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","names":["axios"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/auth/auth-manager.ts","../src/api/api-client.ts","../src/db/collection.ts","../src/push/push-manager.ts","../src/events/event-manager.ts"],"sourcesContent":["import {\n AuthManager,\n type StorageAdapter,\n type SignInParams,\n type AuthState,\n type DeviceIdProvider,\n type AutoSignInConfig,\n} from \"./auth/auth-manager.js\";\nimport { ApiClient } from \"./api/api-client.js\";\nimport { Collection, type FindOptions } from \"./db/collection.js\";\nimport { PushManager } from \"./push/push-manager.js\";\nimport type { PushAdapter } from \"./push/types.js\";\nimport {\n EventManager,\n type EventProperties,\n type EventTrackOptions,\n type EventTrackResult,\n} from \"./events/event-manager.js\";\n\nexport interface CodixusClientConfig {\n apiUrl: string;\n storageKey?: string;\n storage?: StorageAdapter | null;\n appSecret?: string;\n deviceIdProvider?: DeviceIdProvider | null;\n autoSignIn?: AutoSignInConfig | null;\n push?: PushAdapter;\n}\n\nexport class CodixusClient {\n readonly auth: AuthManager;\n readonly api: ApiClient;\n readonly events: EventManager;\n readonly push?: PushManager;\n\n constructor(config: CodixusClientConfig) {\n const storageKey = config.storageKey ?? \"codixus\";\n const storage = config.storage ?? null;\n\n this.auth = new AuthManager(storage, storageKey, {\n deviceIdProvider: config.deviceIdProvider ?? null,\n autoSignIn: config.autoSignIn ?? null,\n });\n this.api = new ApiClient(config.apiUrl, this.auth, config.appSecret);\n this.auth._setApi(this.api);\n this.events = new EventManager(this.api, this.auth);\n\n if (config.push) {\n this.push = new PushManager(config.push, this.api, this.auth);\n }\n }\n\n /**\n * Create a typed collection accessor.\n * Maps to auto REST routes on server: codixus.rest(Model)\n *\n * @example\n * interface Product { _id: string; name: string; price: number; }\n * const products = codixus.collection<Product>(\"products\");\n * const all = await products.find(); // Product[]\n * const one = await products.findById(\"abc\"); // Product | null\n */\n collection<T>(name: string): Collection<T> {\n return new Collection<T>(this.api, `/api/${name}`);\n }\n\n async init(): Promise<void> {\n await this.auth.init();\n }\n}\n\nexport {\n type StorageAdapter,\n type SignInParams,\n type AuthState,\n type DeviceIdProvider,\n type AutoSignInConfig,\n} from \"./auth/auth-manager.js\";\nexport { ApiClient, setCryptoProvider, type CryptoProvider } from \"./api/api-client.js\";\nexport { Collection, type FindOptions } from \"./db/collection.js\";\nexport { type PushAdapter, type PushAdapterToken } from \"./push/types.js\";\nexport {\n EventManager,\n type EventProperties,\n type EventTrackOptions,\n type EventTrackResult,\n} from \"./events/event-manager.js\";\n\n// Re-export shared types\nexport {\n type AuthUser,\n type TokenPair,\n type DeviceInfo,\n type DeviceMetadata,\n type AuthTokenRequest,\n type AuthTokenResponse,\n type ApiResponse,\n CodixusError,\n ErrorCodes,\n} from \"@codixus/shared\";\n","import type {\n AuthTokenResponse,\n DeviceInfo,\n DeviceMetadata,\n} from \"@codixus/shared\";\nimport type { ApiClient } from \"../api/api-client.js\";\n\nexport interface StorageAdapter {\n getItem(key: string): Promise<string | null> | string | null;\n setItem(key: string, value: string): Promise<void> | void;\n removeItem(key: string): Promise<void> | void;\n}\n\n// Host-injected source for a stable, OS-level device identifier (IDFV on\n// iOS, ANDROID_ID on Android, etc.). The SDK calls this when it needs to\n// obtain a deviceId and has nothing in storage. Returning null lets the\n// SDK fall back to a generated UUID. Keeping this an interface (instead\n// of importing expo-application) keeps the SDK platform-agnostic.\nexport interface DeviceIdProvider {\n getDeviceId(): Promise<string | null> | string | null;\n}\n\n// Optional auto signIn driven by the SDK on init(). When provided, after\n// init() restores from storage and the user is still unauthenticated, the\n// SDK calls signIn with the host-supplied device info/metadata. This\n// removes the need for every host app to wire its own boot signIn.\nexport interface AutoSignInConfig {\n enabled: boolean;\n getDeviceInfo(): DeviceInfo | Promise<DeviceInfo>;\n getMetadata?(): DeviceMetadata | Promise<DeviceMetadata>;\n onError?(error: unknown): void;\n}\n\nexport interface SignInParams {\n deviceId?: string;\n deviceInfo: DeviceInfo;\n metadata?: DeviceMetadata;\n}\n\nexport interface AuthState {\n isAuthenticated: boolean;\n isLoading: boolean;\n user: Record<string, unknown> | null;\n}\n\ntype AuthStateListener = (state: AuthState) => void;\n\nexport class AuthManager {\n private accessToken: string | null = null;\n private refreshTokenValue: string | null = null;\n private user: Record<string, unknown> | null = null;\n private listeners: Set<AuthStateListener> = new Set();\n private _isLoading = true;\n private storageKey: string;\n private api!: ApiClient;\n private deviceId: string | null = null;\n private deviceIdProvider: DeviceIdProvider | null;\n private autoSignInConfig: AutoSignInConfig | null;\n private initPromise: Promise<void> | null = null;\n private signInPromise: Promise<{ user: Record<string, unknown>; isNewUser: boolean }> | null = null;\n\n constructor(\n private storage: StorageAdapter | null,\n storageKey: string,\n options?: {\n deviceIdProvider?: DeviceIdProvider | null;\n autoSignIn?: AutoSignInConfig | null;\n },\n ) {\n this.storageKey = storageKey;\n this.deviceIdProvider = options?.deviceIdProvider ?? null;\n this.autoSignInConfig = options?.autoSignIn ?? null;\n }\n\n /** @internal */\n _setApi(api: ApiClient): void {\n this.api = api;\n }\n\n async init(): Promise<void> {\n // Idempotent: concurrent or repeat callers (e.g. host module-load\n // init plus the React provider's useEffect init) share a single run\n // so autoSignIn does not fire twice and race against itself on the\n // backend. On rejection we clear the cached promise so a retry can\n // run — caching a rejected promise would brick init() for the rest\n // of the app lifetime.\n if (this.initPromise) return this.initPromise;\n this.initPromise = this._init().catch((err) => {\n this.initPromise = null;\n throw err;\n });\n return this.initPromise;\n }\n\n private async _init(): Promise<void> {\n if (this.storage) {\n try {\n const storedRefresh = await this.storage.getItem(\n `${this.storageKey}_refresh_token`,\n );\n const storedUser = await this.storage.getItem(\n `${this.storageKey}_user`,\n );\n const storedDeviceId = await this.storage.getItem(\n `${this.storageKey}_device_id`,\n );\n\n if (storedRefresh) {\n this.refreshTokenValue = storedRefresh;\n }\n if (storedUser) {\n try {\n this.user = JSON.parse(storedUser);\n } catch {\n // Corrupted user data in storage, ignore\n }\n }\n if (storedDeviceId) {\n this.deviceId = storedDeviceId;\n }\n } catch (error) {\n console.warn(\"[CodixusClient] Failed to restore auth state from storage:\", error);\n }\n }\n\n // Auto signIn: if the host app opted in and we have no live session yet\n // (cold start, app data cleared, fresh install), reach out to the\n // backend with the device-bound identifier so the existing user is\n // restored before any UI hits an authenticated route.\n if (\n this.autoSignInConfig?.enabled &&\n !this.refreshTokenValue\n ) {\n try {\n const deviceInfo = await this.autoSignInConfig.getDeviceInfo();\n const metadata = this.autoSignInConfig.getMetadata\n ? await this.autoSignInConfig.getMetadata()\n : undefined;\n await this.signIn({ deviceInfo, metadata });\n } catch (error) {\n this.autoSignInConfig.onError?.(error);\n }\n }\n\n this._isLoading = false;\n this.notify();\n }\n\n async signIn(\n params: SignInParams,\n ): Promise<{ user: Record<string, unknown>; isNewUser: boolean }> {\n // Coalesce concurrent signIn calls (e.g. autoSignIn racing a manual\n // signIn from a UI handler) onto a single network request.\n if (this.signInPromise) return this.signInPromise;\n this.signInPromise = this._signIn(params).finally(() => {\n this.signInPromise = null;\n });\n return this.signInPromise;\n }\n\n private async _signIn(\n params: SignInParams,\n ): Promise<{ user: Record<string, unknown>; isNewUser: boolean }> {\n const providerId = this.deviceIdProvider\n ? await this.deviceIdProvider.getDeviceId()\n : null;\n const deviceId =\n params.deviceId ??\n this.deviceId ??\n providerId ??\n this.generateDeviceId();\n\n const response = await this.api.post<AuthTokenResponse>(\"/auth/token\", {\n deviceId,\n deviceInfo: params.deviceInfo,\n metadata: params.metadata,\n });\n\n const data = response.data;\n this.accessToken = data.accessToken;\n this.refreshTokenValue = data.refreshToken;\n this.user = data.user;\n this.deviceId = deviceId;\n\n // Persist. Storage failures (full disk, locked keychain post-restore,\n // EncryptedSharedPreferences key invalidation) must not strand the\n // in-memory session — the user is signed in for this run; the next\n // cold start will simply re-trigger autoSignIn.\n if (this.storage) {\n try {\n await this.storage.setItem(\n `${this.storageKey}_refresh_token`,\n data.refreshToken,\n );\n await this.storage.setItem(\n `${this.storageKey}_user`,\n JSON.stringify(data.user),\n );\n await this.storage.setItem(`${this.storageKey}_device_id`, deviceId);\n } catch (error) {\n console.warn(\"[CodixusClient] Failed to persist auth state:\", error);\n }\n }\n\n this.notify();\n return { user: data.user, isNewUser: data.isNewUser };\n }\n\n async getToken(): Promise<string | null> {\n if (this.accessToken) return this.accessToken;\n\n // Try refresh\n if (this.refreshTokenValue) {\n try {\n await this.forceRefresh();\n return this.accessToken;\n } catch {\n return null;\n }\n }\n\n return null;\n }\n\n async forceRefresh(): Promise<string | null> {\n if (!this.refreshTokenValue) return null;\n\n try {\n const response = await this.api.post<{\n success: boolean;\n accessToken: string;\n refreshToken: string;\n }>(\"/auth/refresh\", {\n refreshToken: this.refreshTokenValue,\n });\n\n this.accessToken = response.data.accessToken;\n this.refreshTokenValue = response.data.refreshToken;\n\n if (this.storage) {\n await this.storage.setItem(\n `${this.storageKey}_refresh_token`,\n response.data.refreshToken,\n );\n }\n\n return this.accessToken;\n } catch {\n // Refresh failed, clear auth state\n await this.signOut();\n return null;\n }\n }\n\n async signOut(): Promise<void> {\n // Notify server\n if (this.refreshTokenValue) {\n try {\n await this.api.post(\"/auth/logout\", {\n refreshToken: this.refreshTokenValue,\n });\n } catch {}\n }\n\n this.accessToken = null;\n this.refreshTokenValue = null;\n this.user = null;\n // Reset guards so a fresh signIn flow (or a re-init triggered by a\n // host that wants to re-authenticate after sign-out) can run. The\n // signInPromise is also cleared by its own .finally, but a signOut\n // racing against an in-flight signIn shouldn't keep the stale guard.\n this.initPromise = null;\n this.signInPromise = null;\n\n if (this.storage) {\n await this.storage.removeItem(`${this.storageKey}_refresh_token`);\n await this.storage.removeItem(`${this.storageKey}_user`);\n await this.storage.removeItem(`${this.storageKey}_device_id`);\n }\n\n this.notify();\n }\n\n getState(): AuthState {\n return {\n isAuthenticated: this.accessToken !== null || this.refreshTokenValue !== null,\n isLoading: this._isLoading,\n user: this.user,\n };\n }\n\n getDeviceId(): string | null {\n return this.deviceId;\n }\n\n onAuthStateChange(listener: AuthStateListener): () => void {\n this.listeners.add(listener);\n // Call immediately with current state\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** @internal - used by API interceptor */\n _getAccessToken(): string | null {\n return this.accessToken;\n }\n\n /** @internal - called by API interceptor after refresh */\n _setAccessToken(token: string): void {\n this.accessToken = token;\n }\n\n private notify(): void {\n const state = this.getState();\n for (const listener of this.listeners) {\n listener(state);\n }\n }\n\n private generateDeviceId(): string {\n const bytes = new Uint8Array(16);\n if (typeof globalThis.crypto?.getRandomValues === \"function\") {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return \"dev-\" + Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n }\n}\n","import axios, {\n type AxiosInstance,\n type AxiosRequestConfig,\n type AxiosResponse,\n} from \"axios\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\n\n// Crypto functions — injected from outside to avoid platform-specific imports\nexport interface CryptoProvider {\n sha256(data: string): Promise<string>;\n hmacSha256(secret: string, data: string): Promise<string>;\n randomUUID(): string;\n}\n\n// Default: no-op (signing disabled if no crypto provider)\nlet _crypto: CryptoProvider | null = null;\n\nexport function setCryptoProvider(provider: CryptoProvider): void {\n _crypto = provider;\n}\n\nfunction generateNonce(): string {\n if (_crypto) return _crypto.randomUUID();\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n const bytes = new Uint8Array(16);\n if (typeof globalThis.crypto?.getRandomValues === \"function\") {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport class ApiClient {\n readonly instance: AxiosInstance;\n private refreshPromise: Promise<string | null> | null = null;\n private appSecret: string | undefined;\n\n constructor(\n baseURL: string,\n private authManager: AuthManager,\n appSecret?: string,\n ) {\n this.appSecret = appSecret;\n\n this.instance = axios.create({\n baseURL,\n headers: { \"Content-Type\": \"application/json\" },\n });\n\n // Request interceptor: add Bearer token + signing\n this.instance.interceptors.request.use(async (config) => {\n // Skip auth for auth endpoints\n if (!config.url?.includes(\"/auth/\")) {\n const token = this.authManager._getAccessToken();\n if (token) {\n config.headers.Authorization = `Bearer ${token}`;\n }\n }\n\n // Request signing (only if crypto provider is set)\n if (this.appSecret && _crypto) {\n const timestamp = Date.now().toString();\n const nonce = _crypto.randomUUID();\n const body =\n config.data && typeof config.data === \"object\"\n ? JSON.stringify(config.data)\n : (config.data ?? \"\");\n const bodyHash = body ? await _crypto.sha256(body) : \"\";\n const method = (config.method ?? \"GET\").toUpperCase();\n const url = config.url ?? \"/\";\n const payload = [method, url, timestamp, nonce, bodyHash].join(\"\\n\");\n const signature = await _crypto.hmacSha256(this.appSecret, payload);\n\n config.headers[\"X-Codixus-Timestamp\"] = timestamp;\n config.headers[\"X-Codixus-Nonce\"] = nonce;\n config.headers[\"X-Codixus-Signature\"] = signature;\n }\n\n return config;\n });\n\n // Response interceptor: handle 401 with token refresh\n this.instance.interceptors.response.use(\n (response) => response,\n async (error) => {\n if (error.response?.status === 401) {\n const originalRequest = error.config;\n if (originalRequest && !originalRequest._retry) {\n originalRequest._retry = true;\n\n try {\n if (!this.refreshPromise) {\n this.refreshPromise = this.authManager\n .forceRefresh()\n .finally(() => {\n this.refreshPromise = null;\n });\n }\n\n const newToken = await this.refreshPromise;\n if (newToken) {\n originalRequest.headers.Authorization = `Bearer ${newToken}`;\n return this.instance(originalRequest);\n }\n } catch (refreshError) {\n console.warn(\"[CodixusClient] Token refresh failed:\", refreshError);\n }\n }\n }\n return Promise.reject(error);\n },\n );\n }\n\n async get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.get<T>(url, config);\n }\n\n async post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.post<T>(url, data, config);\n }\n\n async patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.patch<T>(url, data, config);\n }\n\n async delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.delete<T>(url, config);\n }\n\n async put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.put<T>(url, data, config);\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\n\nexport interface FindOptions {\n sort?: Record<string, 1 | -1>;\n limit?: number;\n skip?: number;\n cursor?: string | number;\n}\n\nexport class Collection<T = Record<string, unknown>> {\n constructor(\n private api: ApiClient,\n private basePath: string,\n ) {}\n\n async find(\n filter?: Record<string, unknown>,\n options?: FindOptions,\n ): Promise<T[]> {\n const params = new URLSearchParams();\n if (filter && Object.keys(filter).length > 0) {\n params.set(\"filter\", JSON.stringify(filter));\n }\n if (options?.sort) params.set(\"sort\", JSON.stringify(options.sort));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.skip) params.set(\"skip\", String(options.skip));\n if (options?.cursor) params.set(\"cursor\", String(options.cursor));\n\n const qs = params.toString();\n const url = qs ? `${this.basePath}?${qs}` : this.basePath;\n const { data } = await this.api.get<{ success: boolean; data: T[]; nextCursor?: string | null }>(url);\n return data.data;\n }\n\n async findWithCursor(\n filter?: Record<string, unknown>,\n options?: FindOptions,\n ): Promise<{ data: T[]; nextCursor: string | number | null }> {\n const params = new URLSearchParams();\n if (filter && Object.keys(filter).length > 0) {\n params.set(\"filter\", JSON.stringify(filter));\n }\n if (options?.sort) params.set(\"sort\", JSON.stringify(options.sort));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.cursor) params.set(\"cursor\", String(options.cursor));\n\n const qs = params.toString();\n const url = qs ? `${this.basePath}?${qs}` : this.basePath;\n const { data } = await this.api.get<{ success: boolean; data: T[]; nextCursor: string | number | null }>(url);\n return { data: data.data, nextCursor: data.nextCursor };\n }\n\n async findById(id: string): Promise<T | null> {\n try {\n const { data } = await this.api.get<{ success: boolean; data: T }>(\n `${this.basePath}/${id}`,\n );\n return data.data;\n } catch (error: any) {\n if (error.response?.status === 404) return null;\n throw error;\n }\n }\n\n async create(doc: Partial<T> & Record<string, unknown>): Promise<T> {\n const { data } = await this.api.post<{ success: boolean; data: T }>(\n this.basePath,\n doc,\n );\n return data.data;\n }\n\n async updateById(\n id: string,\n update: Record<string, unknown>,\n ): Promise<T> {\n const { data } = await this.api.patch<{ success: boolean; data: T }>(\n `${this.basePath}/${id}`,\n { update },\n );\n return data.data;\n }\n\n async deleteById(id: string): Promise<void> {\n await this.api.delete(`${this.basePath}/${id}`);\n }\n\n async count(filter?: Record<string, unknown>): Promise<number> {\n const { data } = await this.api.post<{ success: boolean; count: number }>(\n `${this.basePath}/count`,\n { filter: filter ?? {} },\n );\n return data.count;\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\nimport type { PushAdapter } from \"./types.js\";\n\nfunction extractDeliveryId(\n payload: Record<string, unknown>,\n): string | undefined {\n if (typeof payload.deliveryId === \"string\" && payload.deliveryId) {\n return payload.deliveryId;\n }\n const data = payload.data;\n if (data && typeof data === \"object\" && !Array.isArray(data)) {\n const nested = (data as Record<string, unknown>).deliveryId;\n if (typeof nested === \"string\" && nested) {\n return nested;\n }\n }\n return undefined;\n}\n\nexport class PushManager {\n constructor(\n private adapter: PushAdapter,\n private api: ApiClient,\n private auth: AuthManager,\n ) {}\n\n async register(): Promise<void> {\n const authToken = await this.auth.getToken();\n if (!authToken) {\n throw new Error(\"Not authenticated\");\n }\n\n const adapterToken = await this.adapter.getToken();\n if (!adapterToken) {\n return;\n }\n\n const body: Record<string, unknown> = {\n token: adapterToken.token,\n provider: adapterToken.provider,\n platform: adapterToken.platform,\n };\n\n try {\n body.permissionStatus = await this.adapter.getPermissionStatus();\n } catch {\n // omit permissionStatus, still POST\n }\n\n await this.api.post(\"/push/register\", body);\n }\n\n async unregister(): Promise<void> {\n const authToken = await this.auth.getToken();\n if (!authToken) {\n throw new Error(\"Not authenticated\");\n }\n\n await this.api.post(\"/push/unregister\", {});\n }\n\n async requestPermission(): Promise<boolean> {\n return this.adapter.requestPermission();\n }\n\n async getPermissionStatus(): Promise<\n \"granted\" | \"denied\" | \"undetermined\"\n > {\n return this.adapter.getPermissionStatus();\n }\n\n addNotificationOpenedListener(\n handler: (payload: Record<string, unknown>) => void,\n ): () => void {\n return this.adapter.addNotificationOpenedListener((payload) => {\n handler(payload);\n const deliveryId = extractDeliveryId(payload);\n if (deliveryId) {\n void this.reportOpen(deliveryId);\n }\n });\n }\n\n addNotificationReceivedListener(\n handler: (payload: Record<string, unknown>) => void,\n ): () => void {\n return this.adapter.addNotificationReceivedListener(handler);\n }\n\n private async reportOpen(deliveryId: string): Promise<void> {\n try {\n const authToken = await this.auth.getToken();\n if (!authToken) return;\n await this.api.post(\"/push/open\", { deliveryId });\n } catch {\n // fire-and-forget: swallow 404, 401, 5xx, network errors\n }\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\n\nconst EVENT_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;\n\nexport type EventProperties = Record<string, unknown>;\n\nexport type EventTrackOptions = {\n idempotencyKey?: string;\n occurredAt?: Date;\n};\n\nexport type EventTrackResult = {\n created: boolean;\n eventId?: string;\n ignored?: \"USER_NOT_FOUND\";\n};\n\nexport class EventManager {\n constructor(\n private api: ApiClient,\n private auth: AuthManager,\n ) {}\n\n async track(\n name: string,\n properties?: EventProperties,\n options?: EventTrackOptions,\n ): Promise<EventTrackResult> {\n if (!EVENT_NAME_RE.test(name)) {\n throw new Error(\"Invalid event name\");\n }\n if (\n options?.idempotencyKey !== undefined &&\n (options.idempotencyKey.length === 0 || options.idempotencyKey.length > 200)\n ) {\n throw new Error(\"Invalid idempotency key\");\n }\n if (\n options?.occurredAt !== undefined &&\n Number.isNaN(options.occurredAt.getTime())\n ) {\n throw new Error(\"Invalid occurredAt\");\n }\n if (!(await this.auth.getToken())) {\n throw new Error(\"Not authenticated\");\n }\n\n const payload: Record<string, unknown> = { name };\n if (properties !== undefined) payload.properties = properties;\n if (options?.idempotencyKey !== undefined) {\n payload.idempotencyKey = options.idempotencyKey;\n }\n if (options?.occurredAt !== undefined) {\n payload.occurredAt = options.occurredAt.toISOString();\n }\n\n const response = await this.api.post<{\n success: true;\n data: EventTrackResult;\n }>(\"/events\", payload);\n return response.data.data;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC+CO,IAAM,cAAN,MAAkB;AAAA,EAcvB,YACU,SACR,YACA,SAIA;AANQ;AAOR,SAAK,aAAa;AAClB,SAAK,mBAAmB,SAAS,oBAAoB;AACrD,SAAK,mBAAmB,SAAS,cAAc;AAAA,EACjD;AAAA,EAVU;AAAA,EAdF,cAA6B;AAAA,EAC7B,oBAAmC;AAAA,EACnC,OAAuC;AAAA,EACvC,YAAoC,oBAAI,IAAI;AAAA,EAC5C,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,WAA0B;AAAA,EAC1B;AAAA,EACA;AAAA,EACA,cAAoC;AAAA,EACpC,gBAAuF;AAAA;AAAA,EAgB/F,QAAQ,KAAsB;AAC5B,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,MAAM,OAAsB;AAO1B,QAAI,KAAK,YAAa,QAAO,KAAK;AAClC,SAAK,cAAc,KAAK,MAAM,EAAE,MAAM,CAAC,QAAQ;AAC7C,WAAK,cAAc;AACnB,YAAM;AAAA,IACR,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,QAAuB;AACnC,QAAI,KAAK,SAAS;AAChB,UAAI;AACF,cAAM,gBAAgB,MAAM,KAAK,QAAQ;AAAA,UACvC,GAAG,KAAK,UAAU;AAAA,QACpB;AACA,cAAM,aAAa,MAAM,KAAK,QAAQ;AAAA,UACpC,GAAG,KAAK,UAAU;AAAA,QACpB;AACA,cAAM,iBAAiB,MAAM,KAAK,QAAQ;AAAA,UACxC,GAAG,KAAK,UAAU;AAAA,QACpB;AAEA,YAAI,eAAe;AACjB,eAAK,oBAAoB;AAAA,QAC3B;AACA,YAAI,YAAY;AACd,cAAI;AACF,iBAAK,OAAO,KAAK,MAAM,UAAU;AAAA,UACnC,QAAQ;AAAA,UAER;AAAA,QACF;AACA,YAAI,gBAAgB;AAClB,eAAK,WAAW;AAAA,QAClB;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,KAAK,8DAA8D,KAAK;AAAA,MAClF;AAAA,IACF;AAMA,QACE,KAAK,kBAAkB,WACvB,CAAC,KAAK,mBACN;AACA,UAAI;AACF,cAAM,aAAa,MAAM,KAAK,iBAAiB,cAAc;AAC7D,cAAM,WAAW,KAAK,iBAAiB,cACnC,MAAM,KAAK,iBAAiB,YAAY,IACxC;AACJ,cAAM,KAAK,OAAO,EAAE,YAAY,SAAS,CAAC;AAAA,MAC5C,SAAS,OAAO;AACd,aAAK,iBAAiB,UAAU,KAAK;AAAA,MACvC;AAAA,IACF;AAEA,SAAK,aAAa;AAClB,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,MAAM,OACJ,QACgE;AAGhE,QAAI,KAAK,cAAe,QAAO,KAAK;AACpC,SAAK,gBAAgB,KAAK,QAAQ,MAAM,EAAE,QAAQ,MAAM;AACtD,WAAK,gBAAgB;AAAA,IACvB,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,QACZ,QACgE;AAChE,UAAM,aAAa,KAAK,mBACpB,MAAM,KAAK,iBAAiB,YAAY,IACxC;AACJ,UAAM,WACJ,OAAO,YACP,KAAK,YACL,cACA,KAAK,iBAAiB;AAExB,UAAM,WAAW,MAAM,KAAK,IAAI,KAAwB,eAAe;AAAA,MACrE;AAAA,MACA,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,IACnB,CAAC;AAED,UAAM,OAAO,SAAS;AACtB,SAAK,cAAc,KAAK;AACxB,SAAK,oBAAoB,KAAK;AAC9B,SAAK,OAAO,KAAK;AACjB,SAAK,WAAW;AAMhB,QAAI,KAAK,SAAS;AAChB,UAAI;AACF,cAAM,KAAK,QAAQ;AAAA,UACjB,GAAG,KAAK,UAAU;AAAA,UAClB,KAAK;AAAA,QACP;AACA,cAAM,KAAK,QAAQ;AAAA,UACjB,GAAG,KAAK,UAAU;AAAA,UAClB,KAAK,UAAU,KAAK,IAAI;AAAA,QAC1B;AACA,cAAM,KAAK,QAAQ,QAAQ,GAAG,KAAK,UAAU,cAAc,QAAQ;AAAA,MACrE,SAAS,OAAO;AACd,gBAAQ,KAAK,iDAAiD,KAAK;AAAA,MACrE;AAAA,IACF;AAEA,SAAK,OAAO;AACZ,WAAO,EAAE,MAAM,KAAK,MAAM,WAAW,KAAK,UAAU;AAAA,EACtD;AAAA,EAEA,MAAM,WAAmC;AACvC,QAAI,KAAK,YAAa,QAAO,KAAK;AAGlC,QAAI,KAAK,mBAAmB;AAC1B,UAAI;AACF,cAAM,KAAK,aAAa;AACxB,eAAO,KAAK;AAAA,MACd,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAuC;AAC3C,QAAI,CAAC,KAAK,kBAAmB,QAAO;AAEpC,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,IAAI,KAI7B,iBAAiB;AAAA,QAClB,cAAc,KAAK;AAAA,MACrB,CAAC;AAED,WAAK,cAAc,SAAS,KAAK;AACjC,WAAK,oBAAoB,SAAS,KAAK;AAEvC,UAAI,KAAK,SAAS;AAChB,cAAM,KAAK,QAAQ;AAAA,UACjB,GAAG,KAAK,UAAU;AAAA,UAClB,SAAS,KAAK;AAAA,QAChB;AAAA,MACF;AAEA,aAAO,KAAK;AAAA,IACd,QAAQ;AAEN,YAAM,KAAK,QAAQ;AACnB,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAE7B,QAAI,KAAK,mBAAmB;AAC1B,UAAI;AACF,cAAM,KAAK,IAAI,KAAK,gBAAgB;AAAA,UAClC,cAAc,KAAK;AAAA,QACrB,CAAC;AAAA,MACH,QAAQ;AAAA,MAAC;AAAA,IACX;AAEA,SAAK,cAAc;AACnB,SAAK,oBAAoB;AACzB,SAAK,OAAO;AAKZ,SAAK,cAAc;AACnB,SAAK,gBAAgB;AAErB,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,WAAW,GAAG,KAAK,UAAU,gBAAgB;AAChE,YAAM,KAAK,QAAQ,WAAW,GAAG,KAAK,UAAU,OAAO;AACvD,YAAM,KAAK,QAAQ,WAAW,GAAG,KAAK,UAAU,YAAY;AAAA,IAC9D;AAEA,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,WAAsB;AACpB,WAAO;AAAA,MACL,iBAAiB,KAAK,gBAAgB,QAAQ,KAAK,sBAAsB;AAAA,MACzE,WAAW,KAAK;AAAA,MAChB,MAAM,KAAK;AAAA,IACb;AAAA,EACF;AAAA,EAEA,cAA6B;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,kBAAkB,UAAyC;AACzD,SAAK,UAAU,IAAI,QAAQ;AAE3B,aAAS,KAAK,SAAS,CAAC;AACxB,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA;AAAA,EAGA,kBAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,gBAAgB,OAAqB;AACnC,SAAK,cAAc;AAAA,EACrB;AAAA,EAEQ,SAAe;AACrB,UAAM,QAAQ,KAAK,SAAS;AAC5B,eAAW,YAAY,KAAK,WAAW;AACrC,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AAAA,EAEQ,mBAA2B;AACjC,UAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,QAAI,OAAO,WAAW,QAAQ,oBAAoB,YAAY;AAC5D,iBAAW,OAAO,gBAAgB,KAAK;AAAA,IACzC,OAAO;AACL,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,cAAM,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAAA,MAC3C;AAAA,IACF;AACA,WAAO,SAAS,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAAA,EACnF;AACF;;;AC5UA,mBAIO;AAWP,IAAI,UAAiC;AAE9B,SAAS,kBAAkB,UAAgC;AAChE,YAAU;AACZ;AAkBO,IAAM,YAAN,MAAgB;AAAA,EAKrB,YACE,SACQ,aACR,WACA;AAFQ;AAGR,SAAK,YAAY;AAEjB,SAAK,WAAW,aAAAA,QAAM,OAAO;AAAA,MAC3B;AAAA,MACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AAGD,SAAK,SAAS,aAAa,QAAQ,IAAI,OAAO,WAAW;AAEvD,UAAI,CAAC,OAAO,KAAK,SAAS,QAAQ,GAAG;AACnC,cAAM,QAAQ,KAAK,YAAY,gBAAgB;AAC/C,YAAI,OAAO;AACT,iBAAO,QAAQ,gBAAgB,UAAU,KAAK;AAAA,QAChD;AAAA,MACF;AAGA,UAAI,KAAK,aAAa,SAAS;AAC7B,cAAM,YAAY,KAAK,IAAI,EAAE,SAAS;AACtC,cAAM,QAAQ,QAAQ,WAAW;AACjC,cAAM,OACJ,OAAO,QAAQ,OAAO,OAAO,SAAS,WAClC,KAAK,UAAU,OAAO,IAAI,IACzB,OAAO,QAAQ;AACtB,cAAM,WAAW,OAAO,MAAM,QAAQ,OAAO,IAAI,IAAI;AACrD,cAAM,UAAU,OAAO,UAAU,OAAO,YAAY;AACpD,cAAM,MAAM,OAAO,OAAO;AAC1B,cAAM,UAAU,CAAC,QAAQ,KAAK,WAAW,OAAO,QAAQ,EAAE,KAAK,IAAI;AACnE,cAAM,YAAY,MAAM,QAAQ,WAAW,KAAK,WAAW,OAAO;AAElE,eAAO,QAAQ,qBAAqB,IAAI;AACxC,eAAO,QAAQ,iBAAiB,IAAI;AACpC,eAAO,QAAQ,qBAAqB,IAAI;AAAA,MAC1C;AAEA,aAAO;AAAA,IACT,CAAC;AAGD,SAAK,SAAS,aAAa,SAAS;AAAA,MAClC,CAAC,aAAa;AAAA,MACd,OAAO,UAAU;AACf,YAAI,MAAM,UAAU,WAAW,KAAK;AAClC,gBAAM,kBAAkB,MAAM;AAC9B,cAAI,mBAAmB,CAAC,gBAAgB,QAAQ;AAC9C,4BAAgB,SAAS;AAEzB,gBAAI;AACF,kBAAI,CAAC,KAAK,gBAAgB;AACxB,qBAAK,iBAAiB,KAAK,YACxB,aAAa,EACb,QAAQ,MAAM;AACb,uBAAK,iBAAiB;AAAA,gBACxB,CAAC;AAAA,cACL;AAEA,oBAAM,WAAW,MAAM,KAAK;AAC5B,kBAAI,UAAU;AACZ,gCAAgB,QAAQ,gBAAgB,UAAU,QAAQ;AAC1D,uBAAO,KAAK,SAAS,eAAe;AAAA,cACtC;AAAA,YACF,SAAS,cAAc;AACrB,sBAAQ,KAAK,yCAAyC,YAAY;AAAA,YACpE;AAAA,UACF;AAAA,QACF;AACA,eAAO,QAAQ,OAAO,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAzEU;AAAA,EAND;AAAA,EACD,iBAAgD;AAAA,EAChD;AAAA,EA+ER,MAAM,IAAiB,KAAa,QAAwD;AAC1F,WAAO,KAAK,SAAS,IAAO,KAAK,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,KAAkB,KAAa,MAAgB,QAAwD;AAC3G,WAAO,KAAK,SAAS,KAAQ,KAAK,MAAM,MAAM;AAAA,EAChD;AAAA,EAEA,MAAM,MAAmB,KAAa,MAAgB,QAAwD;AAC5G,WAAO,KAAK,SAAS,MAAS,KAAK,MAAM,MAAM;AAAA,EACjD;AAAA,EAEA,MAAM,OAAoB,KAAa,QAAwD;AAC7F,WAAO,KAAK,SAAS,OAAU,KAAK,MAAM;AAAA,EAC5C;AAAA,EAEA,MAAM,IAAiB,KAAa,MAAgB,QAAwD;AAC1G,WAAO,KAAK,SAAS,IAAO,KAAK,MAAM,MAAM;AAAA,EAC/C;AACF;;;ACjIO,IAAM,aAAN,MAA8C;AAAA,EACnD,YACU,KACA,UACR;AAFQ;AACA;AAAA,EACP;AAAA,EAFO;AAAA,EACA;AAAA,EAGV,MAAM,KACJ,QACA,SACc;AACd,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,UAAU,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAC5C,aAAO,IAAI,UAAU,KAAK,UAAU,MAAM,CAAC;AAAA,IAC7C;AACA,QAAI,SAAS,KAAM,QAAO,IAAI,QAAQ,KAAK,UAAU,QAAQ,IAAI,CAAC;AAClE,QAAI,SAAS,MAAO,QAAO,IAAI,SAAS,OAAO,QAAQ,KAAK,CAAC;AAC7D,QAAI,SAAS,KAAM,QAAO,IAAI,QAAQ,OAAO,QAAQ,IAAI,CAAC;AAC1D,QAAI,SAAS,OAAQ,QAAO,IAAI,UAAU,OAAO,QAAQ,MAAM,CAAC;AAEhE,UAAM,KAAK,OAAO,SAAS;AAC3B,UAAM,MAAM,KAAK,GAAG,KAAK,QAAQ,IAAI,EAAE,KAAK,KAAK;AACjD,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAI,IAAiE,GAAG;AACpG,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,eACJ,QACA,SAC4D;AAC5D,UAAM,SAAS,IAAI,gBAAgB;AACnC,QAAI,UAAU,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAC5C,aAAO,IAAI,UAAU,KAAK,UAAU,MAAM,CAAC;AAAA,IAC7C;AACA,QAAI,SAAS,KAAM,QAAO,IAAI,QAAQ,KAAK,UAAU,QAAQ,IAAI,CAAC;AAClE,QAAI,SAAS,MAAO,QAAO,IAAI,SAAS,OAAO,QAAQ,KAAK,CAAC;AAC7D,QAAI,SAAS,OAAQ,QAAO,IAAI,UAAU,OAAO,QAAQ,MAAM,CAAC;AAEhE,UAAM,KAAK,OAAO,SAAS;AAC3B,UAAM,MAAM,KAAK,GAAG,KAAK,QAAQ,IAAI,EAAE,KAAK,KAAK;AACjD,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAI,IAAyE,GAAG;AAC5G,WAAO,EAAE,MAAM,KAAK,MAAM,YAAY,KAAK,WAAW;AAAA,EACxD;AAAA,EAEA,MAAM,SAAS,IAA+B;AAC5C,QAAI;AACF,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAI;AAAA,QAC9B,GAAG,KAAK,QAAQ,IAAI,EAAE;AAAA,MACxB;AACA,aAAO,KAAK;AAAA,IACd,SAAS,OAAY;AACnB,UAAI,MAAM,UAAU,WAAW,IAAK,QAAO;AAC3C,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAAuD;AAClE,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAI;AAAA,MAC9B,KAAK;AAAA,MACL;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,WACJ,IACA,QACY;AACZ,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAI;AAAA,MAC9B,GAAG,KAAK,QAAQ,IAAI,EAAE;AAAA,MACtB,EAAE,OAAO;AAAA,IACX;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,WAAW,IAA2B;AAC1C,UAAM,KAAK,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,EAAE,EAAE;AAAA,EAChD;AAAA,EAEA,MAAM,MAAM,QAAmD;AAC7D,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,IAAI;AAAA,MAC9B,GAAG,KAAK,QAAQ;AAAA,MAChB,EAAE,QAAQ,UAAU,CAAC,EAAE;AAAA,IACzB;AACA,WAAO,KAAK;AAAA,EACd;AACF;;;AC1FA,SAAS,kBACP,SACoB;AACpB,MAAI,OAAO,QAAQ,eAAe,YAAY,QAAQ,YAAY;AAChE,WAAO,QAAQ;AAAA,EACjB;AACA,QAAM,OAAO,QAAQ;AACrB,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;AAC5D,UAAM,SAAU,KAAiC;AACjD,QAAI,OAAO,WAAW,YAAY,QAAQ;AACxC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,cAAN,MAAkB;AAAA,EACvB,YACU,SACA,KACA,MACR;AAHQ;AACA;AACA;AAAA,EACP;AAAA,EAHO;AAAA,EACA;AAAA,EACA;AAAA,EAGV,MAAM,WAA0B;AAC9B,UAAM,YAAY,MAAM,KAAK,KAAK,SAAS;AAC3C,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACrC;AAEA,UAAM,eAAe,MAAM,KAAK,QAAQ,SAAS;AACjD,QAAI,CAAC,cAAc;AACjB;AAAA,IACF;AAEA,UAAM,OAAgC;AAAA,MACpC,OAAO,aAAa;AAAA,MACpB,UAAU,aAAa;AAAA,MACvB,UAAU,aAAa;AAAA,IACzB;AAEA,QAAI;AACF,WAAK,mBAAmB,MAAM,KAAK,QAAQ,oBAAoB;AAAA,IACjE,QAAQ;AAAA,IAER;AAEA,UAAM,KAAK,IAAI,KAAK,kBAAkB,IAAI;AAAA,EAC5C;AAAA,EAEA,MAAM,aAA4B;AAChC,UAAM,YAAY,MAAM,KAAK,KAAK,SAAS;AAC3C,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACrC;AAEA,UAAM,KAAK,IAAI,KAAK,oBAAoB,CAAC,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAM,oBAAsC;AAC1C,WAAO,KAAK,QAAQ,kBAAkB;AAAA,EACxC;AAAA,EAEA,MAAM,sBAEJ;AACA,WAAO,KAAK,QAAQ,oBAAoB;AAAA,EAC1C;AAAA,EAEA,8BACE,SACY;AACZ,WAAO,KAAK,QAAQ,8BAA8B,CAAC,YAAY;AAC7D,cAAQ,OAAO;AACf,YAAM,aAAa,kBAAkB,OAAO;AAC5C,UAAI,YAAY;AACd,aAAK,KAAK,WAAW,UAAU;AAAA,MACjC;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,gCACE,SACY;AACZ,WAAO,KAAK,QAAQ,gCAAgC,OAAO;AAAA,EAC7D;AAAA,EAEA,MAAc,WAAW,YAAmC;AAC1D,QAAI;AACF,YAAM,YAAY,MAAM,KAAK,KAAK,SAAS;AAC3C,UAAI,CAAC,UAAW;AAChB,YAAM,KAAK,IAAI,KAAK,cAAc,EAAE,WAAW,CAAC;AAAA,IAClD,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AChGA,IAAM,gBAAgB;AAef,IAAM,eAAN,MAAmB;AAAA,EACxB,YACU,KACA,MACR;AAFQ;AACA;AAAA,EACP;AAAA,EAFO;AAAA,EACA;AAAA,EAGV,MAAM,MACJ,MACA,YACA,SAC2B;AAC3B,QAAI,CAAC,cAAc,KAAK,IAAI,GAAG;AAC7B,YAAM,IAAI,MAAM,oBAAoB;AAAA,IACtC;AACA,QACE,SAAS,mBAAmB,WAC3B,QAAQ,eAAe,WAAW,KAAK,QAAQ,eAAe,SAAS,MACxE;AACA,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,QACE,SAAS,eAAe,UACxB,OAAO,MAAM,QAAQ,WAAW,QAAQ,CAAC,GACzC;AACA,YAAM,IAAI,MAAM,oBAAoB;AAAA,IACtC;AACA,QAAI,CAAE,MAAM,KAAK,KAAK,SAAS,GAAI;AACjC,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACrC;AAEA,UAAM,UAAmC,EAAE,KAAK;AAChD,QAAI,eAAe,OAAW,SAAQ,aAAa;AACnD,QAAI,SAAS,mBAAmB,QAAW;AACzC,cAAQ,iBAAiB,QAAQ;AAAA,IACnC;AACA,QAAI,SAAS,eAAe,QAAW;AACrC,cAAQ,aAAa,QAAQ,WAAW,YAAY;AAAA,IACtD;AAEA,UAAM,WAAW,MAAM,KAAK,IAAI,KAG7B,WAAW,OAAO;AACrB,WAAO,SAAS,KAAK;AAAA,EACvB;AACF;;;AL0BA,oBAUO;AAtEA,IAAM,gBAAN,MAAoB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAA6B;AACvC,UAAM,aAAa,OAAO,cAAc;AACxC,UAAM,UAAU,OAAO,WAAW;AAElC,SAAK,OAAO,IAAI,YAAY,SAAS,YAAY;AAAA,MAC/C,kBAAkB,OAAO,oBAAoB;AAAA,MAC7C,YAAY,OAAO,cAAc;AAAA,IACnC,CAAC;AACD,SAAK,MAAM,IAAI,UAAU,OAAO,QAAQ,KAAK,MAAM,OAAO,SAAS;AACnE,SAAK,KAAK,QAAQ,KAAK,GAAG;AAC1B,SAAK,SAAS,IAAI,aAAa,KAAK,KAAK,KAAK,IAAI;AAElD,QAAI,OAAO,MAAM;AACf,WAAK,OAAO,IAAI,YAAY,OAAO,MAAM,KAAK,KAAK,KAAK,IAAI;AAAA,IAC9D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,WAAc,MAA6B;AACzC,WAAO,IAAI,WAAc,KAAK,KAAK,QAAQ,IAAI,EAAE;AAAA,EACnD;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,KAAK,KAAK,KAAK;AAAA,EACvB;AACF;","names":["axios"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -125,6 +125,23 @@ declare class PushManager {
|
|
|
125
125
|
private reportOpen;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
type EventProperties = Record<string, unknown>;
|
|
129
|
+
type EventTrackOptions = {
|
|
130
|
+
idempotencyKey?: string;
|
|
131
|
+
occurredAt?: Date;
|
|
132
|
+
};
|
|
133
|
+
type EventTrackResult = {
|
|
134
|
+
created: boolean;
|
|
135
|
+
eventId?: string;
|
|
136
|
+
ignored?: "USER_NOT_FOUND";
|
|
137
|
+
};
|
|
138
|
+
declare class EventManager {
|
|
139
|
+
private api;
|
|
140
|
+
private auth;
|
|
141
|
+
constructor(api: ApiClient, auth: AuthManager);
|
|
142
|
+
track(name: string, properties?: EventProperties, options?: EventTrackOptions): Promise<EventTrackResult>;
|
|
143
|
+
}
|
|
144
|
+
|
|
128
145
|
interface CodixusClientConfig {
|
|
129
146
|
apiUrl: string;
|
|
130
147
|
storageKey?: string;
|
|
@@ -137,6 +154,7 @@ interface CodixusClientConfig {
|
|
|
137
154
|
declare class CodixusClient {
|
|
138
155
|
readonly auth: AuthManager;
|
|
139
156
|
readonly api: ApiClient;
|
|
157
|
+
readonly events: EventManager;
|
|
140
158
|
readonly push?: PushManager;
|
|
141
159
|
constructor(config: CodixusClientConfig);
|
|
142
160
|
/**
|
|
@@ -153,4 +171,4 @@ declare class CodixusClient {
|
|
|
153
171
|
init(): Promise<void>;
|
|
154
172
|
}
|
|
155
173
|
|
|
156
|
-
export { ApiClient, type AuthState, type AutoSignInConfig, CodixusClient, type CodixusClientConfig, Collection, type CryptoProvider, type DeviceIdProvider, type FindOptions, PushAdapter, type SignInParams, type StorageAdapter, setCryptoProvider };
|
|
174
|
+
export { ApiClient, type AuthState, type AutoSignInConfig, CodixusClient, type CodixusClientConfig, Collection, type CryptoProvider, type DeviceIdProvider, EventManager, type EventProperties, type EventTrackOptions, type EventTrackResult, type FindOptions, PushAdapter, type SignInParams, type StorageAdapter, setCryptoProvider };
|
package/dist/index.d.ts
CHANGED
|
@@ -125,6 +125,23 @@ declare class PushManager {
|
|
|
125
125
|
private reportOpen;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
type EventProperties = Record<string, unknown>;
|
|
129
|
+
type EventTrackOptions = {
|
|
130
|
+
idempotencyKey?: string;
|
|
131
|
+
occurredAt?: Date;
|
|
132
|
+
};
|
|
133
|
+
type EventTrackResult = {
|
|
134
|
+
created: boolean;
|
|
135
|
+
eventId?: string;
|
|
136
|
+
ignored?: "USER_NOT_FOUND";
|
|
137
|
+
};
|
|
138
|
+
declare class EventManager {
|
|
139
|
+
private api;
|
|
140
|
+
private auth;
|
|
141
|
+
constructor(api: ApiClient, auth: AuthManager);
|
|
142
|
+
track(name: string, properties?: EventProperties, options?: EventTrackOptions): Promise<EventTrackResult>;
|
|
143
|
+
}
|
|
144
|
+
|
|
128
145
|
interface CodixusClientConfig {
|
|
129
146
|
apiUrl: string;
|
|
130
147
|
storageKey?: string;
|
|
@@ -137,6 +154,7 @@ interface CodixusClientConfig {
|
|
|
137
154
|
declare class CodixusClient {
|
|
138
155
|
readonly auth: AuthManager;
|
|
139
156
|
readonly api: ApiClient;
|
|
157
|
+
readonly events: EventManager;
|
|
140
158
|
readonly push?: PushManager;
|
|
141
159
|
constructor(config: CodixusClientConfig);
|
|
142
160
|
/**
|
|
@@ -153,4 +171,4 @@ declare class CodixusClient {
|
|
|
153
171
|
init(): Promise<void>;
|
|
154
172
|
}
|
|
155
173
|
|
|
156
|
-
export { ApiClient, type AuthState, type AutoSignInConfig, CodixusClient, type CodixusClientConfig, Collection, type CryptoProvider, type DeviceIdProvider, type FindOptions, PushAdapter, type SignInParams, type StorageAdapter, setCryptoProvider };
|
|
174
|
+
export { ApiClient, type AuthState, type AutoSignInConfig, CodixusClient, type CodixusClientConfig, Collection, type CryptoProvider, type DeviceIdProvider, EventManager, type EventProperties, type EventTrackOptions, type EventTrackResult, type FindOptions, PushAdapter, type SignInParams, type StorageAdapter, setCryptoProvider };
|
package/dist/index.js
CHANGED
|
@@ -440,6 +440,41 @@ var PushManager = class {
|
|
|
440
440
|
}
|
|
441
441
|
};
|
|
442
442
|
|
|
443
|
+
// src/events/event-manager.ts
|
|
444
|
+
var EVENT_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
445
|
+
var EventManager = class {
|
|
446
|
+
constructor(api, auth) {
|
|
447
|
+
this.api = api;
|
|
448
|
+
this.auth = auth;
|
|
449
|
+
}
|
|
450
|
+
api;
|
|
451
|
+
auth;
|
|
452
|
+
async track(name, properties, options) {
|
|
453
|
+
if (!EVENT_NAME_RE.test(name)) {
|
|
454
|
+
throw new Error("Invalid event name");
|
|
455
|
+
}
|
|
456
|
+
if (options?.idempotencyKey !== void 0 && (options.idempotencyKey.length === 0 || options.idempotencyKey.length > 200)) {
|
|
457
|
+
throw new Error("Invalid idempotency key");
|
|
458
|
+
}
|
|
459
|
+
if (options?.occurredAt !== void 0 && Number.isNaN(options.occurredAt.getTime())) {
|
|
460
|
+
throw new Error("Invalid occurredAt");
|
|
461
|
+
}
|
|
462
|
+
if (!await this.auth.getToken()) {
|
|
463
|
+
throw new Error("Not authenticated");
|
|
464
|
+
}
|
|
465
|
+
const payload = { name };
|
|
466
|
+
if (properties !== void 0) payload.properties = properties;
|
|
467
|
+
if (options?.idempotencyKey !== void 0) {
|
|
468
|
+
payload.idempotencyKey = options.idempotencyKey;
|
|
469
|
+
}
|
|
470
|
+
if (options?.occurredAt !== void 0) {
|
|
471
|
+
payload.occurredAt = options.occurredAt.toISOString();
|
|
472
|
+
}
|
|
473
|
+
const response = await this.api.post("/events", payload);
|
|
474
|
+
return response.data.data;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
443
478
|
// src/index.ts
|
|
444
479
|
import {
|
|
445
480
|
CodixusError,
|
|
@@ -448,6 +483,7 @@ import {
|
|
|
448
483
|
var CodixusClient = class {
|
|
449
484
|
auth;
|
|
450
485
|
api;
|
|
486
|
+
events;
|
|
451
487
|
push;
|
|
452
488
|
constructor(config) {
|
|
453
489
|
const storageKey = config.storageKey ?? "codixus";
|
|
@@ -458,6 +494,7 @@ var CodixusClient = class {
|
|
|
458
494
|
});
|
|
459
495
|
this.api = new ApiClient(config.apiUrl, this.auth, config.appSecret);
|
|
460
496
|
this.auth._setApi(this.api);
|
|
497
|
+
this.events = new EventManager(this.api, this.auth);
|
|
461
498
|
if (config.push) {
|
|
462
499
|
this.push = new PushManager(config.push, this.api, this.auth);
|
|
463
500
|
}
|
|
@@ -485,6 +522,7 @@ export {
|
|
|
485
522
|
CodixusError,
|
|
486
523
|
Collection,
|
|
487
524
|
ErrorCodes,
|
|
525
|
+
EventManager,
|
|
488
526
|
setCryptoProvider
|
|
489
527
|
};
|
|
490
528
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth/auth-manager.ts","../src/api/api-client.ts","../src/db/collection.ts","../src/push/push-manager.ts","../src/index.ts"],"sourcesContent":["import type {\n AuthTokenResponse,\n DeviceInfo,\n DeviceMetadata,\n} from \"@codixus/shared\";\nimport type { ApiClient } from \"../api/api-client.js\";\n\nexport interface StorageAdapter {\n getItem(key: string): Promise<string | null> | string | null;\n setItem(key: string, value: string): Promise<void> | void;\n removeItem(key: string): Promise<void> | void;\n}\n\n// Host-injected source for a stable, OS-level device identifier (IDFV on\n// iOS, ANDROID_ID on Android, etc.). The SDK calls this when it needs to\n// obtain a deviceId and has nothing in storage. Returning null lets the\n// SDK fall back to a generated UUID. Keeping this an interface (instead\n// of importing expo-application) keeps the SDK platform-agnostic.\nexport interface DeviceIdProvider {\n getDeviceId(): Promise<string | null> | string | null;\n}\n\n// Optional auto signIn driven by the SDK on init(). When provided, after\n// init() restores from storage and the user is still unauthenticated, the\n// SDK calls signIn with the host-supplied device info/metadata. This\n// removes the need for every host app to wire its own boot signIn.\nexport interface AutoSignInConfig {\n enabled: boolean;\n getDeviceInfo(): DeviceInfo | Promise<DeviceInfo>;\n getMetadata?(): DeviceMetadata | Promise<DeviceMetadata>;\n onError?(error: unknown): void;\n}\n\nexport interface SignInParams {\n deviceId?: string;\n deviceInfo: DeviceInfo;\n metadata?: DeviceMetadata;\n}\n\nexport interface AuthState {\n isAuthenticated: boolean;\n isLoading: boolean;\n user: Record<string, unknown> | null;\n}\n\ntype AuthStateListener = (state: AuthState) => void;\n\nexport class AuthManager {\n private accessToken: string | null = null;\n private refreshTokenValue: string | null = null;\n private user: Record<string, unknown> | null = null;\n private listeners: Set<AuthStateListener> = new Set();\n private _isLoading = true;\n private storageKey: string;\n private api!: ApiClient;\n private deviceId: string | null = null;\n private deviceIdProvider: DeviceIdProvider | null;\n private autoSignInConfig: AutoSignInConfig | null;\n private initPromise: Promise<void> | null = null;\n private signInPromise: Promise<{ user: Record<string, unknown>; isNewUser: boolean }> | null = null;\n\n constructor(\n private storage: StorageAdapter | null,\n storageKey: string,\n options?: {\n deviceIdProvider?: DeviceIdProvider | null;\n autoSignIn?: AutoSignInConfig | null;\n },\n ) {\n this.storageKey = storageKey;\n this.deviceIdProvider = options?.deviceIdProvider ?? null;\n this.autoSignInConfig = options?.autoSignIn ?? null;\n }\n\n /** @internal */\n _setApi(api: ApiClient): void {\n this.api = api;\n }\n\n async init(): Promise<void> {\n // Idempotent: concurrent or repeat callers (e.g. host module-load\n // init plus the React provider's useEffect init) share a single run\n // so autoSignIn does not fire twice and race against itself on the\n // backend. On rejection we clear the cached promise so a retry can\n // run — caching a rejected promise would brick init() for the rest\n // of the app lifetime.\n if (this.initPromise) return this.initPromise;\n this.initPromise = this._init().catch((err) => {\n this.initPromise = null;\n throw err;\n });\n return this.initPromise;\n }\n\n private async _init(): Promise<void> {\n if (this.storage) {\n try {\n const storedRefresh = await this.storage.getItem(\n `${this.storageKey}_refresh_token`,\n );\n const storedUser = await this.storage.getItem(\n `${this.storageKey}_user`,\n );\n const storedDeviceId = await this.storage.getItem(\n `${this.storageKey}_device_id`,\n );\n\n if (storedRefresh) {\n this.refreshTokenValue = storedRefresh;\n }\n if (storedUser) {\n try {\n this.user = JSON.parse(storedUser);\n } catch {\n // Corrupted user data in storage, ignore\n }\n }\n if (storedDeviceId) {\n this.deviceId = storedDeviceId;\n }\n } catch (error) {\n console.warn(\"[CodixusClient] Failed to restore auth state from storage:\", error);\n }\n }\n\n // Auto signIn: if the host app opted in and we have no live session yet\n // (cold start, app data cleared, fresh install), reach out to the\n // backend with the device-bound identifier so the existing user is\n // restored before any UI hits an authenticated route.\n if (\n this.autoSignInConfig?.enabled &&\n !this.refreshTokenValue\n ) {\n try {\n const deviceInfo = await this.autoSignInConfig.getDeviceInfo();\n const metadata = this.autoSignInConfig.getMetadata\n ? await this.autoSignInConfig.getMetadata()\n : undefined;\n await this.signIn({ deviceInfo, metadata });\n } catch (error) {\n this.autoSignInConfig.onError?.(error);\n }\n }\n\n this._isLoading = false;\n this.notify();\n }\n\n async signIn(\n params: SignInParams,\n ): Promise<{ user: Record<string, unknown>; isNewUser: boolean }> {\n // Coalesce concurrent signIn calls (e.g. autoSignIn racing a manual\n // signIn from a UI handler) onto a single network request.\n if (this.signInPromise) return this.signInPromise;\n this.signInPromise = this._signIn(params).finally(() => {\n this.signInPromise = null;\n });\n return this.signInPromise;\n }\n\n private async _signIn(\n params: SignInParams,\n ): Promise<{ user: Record<string, unknown>; isNewUser: boolean }> {\n const providerId = this.deviceIdProvider\n ? await this.deviceIdProvider.getDeviceId()\n : null;\n const deviceId =\n params.deviceId ??\n this.deviceId ??\n providerId ??\n this.generateDeviceId();\n\n const response = await this.api.post<AuthTokenResponse>(\"/auth/token\", {\n deviceId,\n deviceInfo: params.deviceInfo,\n metadata: params.metadata,\n });\n\n const data = response.data;\n this.accessToken = data.accessToken;\n this.refreshTokenValue = data.refreshToken;\n this.user = data.user;\n this.deviceId = deviceId;\n\n // Persist. Storage failures (full disk, locked keychain post-restore,\n // EncryptedSharedPreferences key invalidation) must not strand the\n // in-memory session — the user is signed in for this run; the next\n // cold start will simply re-trigger autoSignIn.\n if (this.storage) {\n try {\n await this.storage.setItem(\n `${this.storageKey}_refresh_token`,\n data.refreshToken,\n );\n await this.storage.setItem(\n `${this.storageKey}_user`,\n JSON.stringify(data.user),\n );\n await this.storage.setItem(`${this.storageKey}_device_id`, deviceId);\n } catch (error) {\n console.warn(\"[CodixusClient] Failed to persist auth state:\", error);\n }\n }\n\n this.notify();\n return { user: data.user, isNewUser: data.isNewUser };\n }\n\n async getToken(): Promise<string | null> {\n if (this.accessToken) return this.accessToken;\n\n // Try refresh\n if (this.refreshTokenValue) {\n try {\n await this.forceRefresh();\n return this.accessToken;\n } catch {\n return null;\n }\n }\n\n return null;\n }\n\n async forceRefresh(): Promise<string | null> {\n if (!this.refreshTokenValue) return null;\n\n try {\n const response = await this.api.post<{\n success: boolean;\n accessToken: string;\n refreshToken: string;\n }>(\"/auth/refresh\", {\n refreshToken: this.refreshTokenValue,\n });\n\n this.accessToken = response.data.accessToken;\n this.refreshTokenValue = response.data.refreshToken;\n\n if (this.storage) {\n await this.storage.setItem(\n `${this.storageKey}_refresh_token`,\n response.data.refreshToken,\n );\n }\n\n return this.accessToken;\n } catch {\n // Refresh failed, clear auth state\n await this.signOut();\n return null;\n }\n }\n\n async signOut(): Promise<void> {\n // Notify server\n if (this.refreshTokenValue) {\n try {\n await this.api.post(\"/auth/logout\", {\n refreshToken: this.refreshTokenValue,\n });\n } catch {}\n }\n\n this.accessToken = null;\n this.refreshTokenValue = null;\n this.user = null;\n // Reset guards so a fresh signIn flow (or a re-init triggered by a\n // host that wants to re-authenticate after sign-out) can run. The\n // signInPromise is also cleared by its own .finally, but a signOut\n // racing against an in-flight signIn shouldn't keep the stale guard.\n this.initPromise = null;\n this.signInPromise = null;\n\n if (this.storage) {\n await this.storage.removeItem(`${this.storageKey}_refresh_token`);\n await this.storage.removeItem(`${this.storageKey}_user`);\n await this.storage.removeItem(`${this.storageKey}_device_id`);\n }\n\n this.notify();\n }\n\n getState(): AuthState {\n return {\n isAuthenticated: this.accessToken !== null || this.refreshTokenValue !== null,\n isLoading: this._isLoading,\n user: this.user,\n };\n }\n\n getDeviceId(): string | null {\n return this.deviceId;\n }\n\n onAuthStateChange(listener: AuthStateListener): () => void {\n this.listeners.add(listener);\n // Call immediately with current state\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** @internal - used by API interceptor */\n _getAccessToken(): string | null {\n return this.accessToken;\n }\n\n /** @internal - called by API interceptor after refresh */\n _setAccessToken(token: string): void {\n this.accessToken = token;\n }\n\n private notify(): void {\n const state = this.getState();\n for (const listener of this.listeners) {\n listener(state);\n }\n }\n\n private generateDeviceId(): string {\n const bytes = new Uint8Array(16);\n if (typeof globalThis.crypto?.getRandomValues === \"function\") {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return \"dev-\" + Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n }\n}\n","import axios, {\n type AxiosInstance,\n type AxiosRequestConfig,\n type AxiosResponse,\n} from \"axios\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\n\n// Crypto functions — injected from outside to avoid platform-specific imports\nexport interface CryptoProvider {\n sha256(data: string): Promise<string>;\n hmacSha256(secret: string, data: string): Promise<string>;\n randomUUID(): string;\n}\n\n// Default: no-op (signing disabled if no crypto provider)\nlet _crypto: CryptoProvider | null = null;\n\nexport function setCryptoProvider(provider: CryptoProvider): void {\n _crypto = provider;\n}\n\nfunction generateNonce(): string {\n if (_crypto) return _crypto.randomUUID();\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n const bytes = new Uint8Array(16);\n if (typeof globalThis.crypto?.getRandomValues === \"function\") {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport class ApiClient {\n readonly instance: AxiosInstance;\n private refreshPromise: Promise<string | null> | null = null;\n private appSecret: string | undefined;\n\n constructor(\n baseURL: string,\n private authManager: AuthManager,\n appSecret?: string,\n ) {\n this.appSecret = appSecret;\n\n this.instance = axios.create({\n baseURL,\n headers: { \"Content-Type\": \"application/json\" },\n });\n\n // Request interceptor: add Bearer token + signing\n this.instance.interceptors.request.use(async (config) => {\n // Skip auth for auth endpoints\n if (!config.url?.includes(\"/auth/\")) {\n const token = this.authManager._getAccessToken();\n if (token) {\n config.headers.Authorization = `Bearer ${token}`;\n }\n }\n\n // Request signing (only if crypto provider is set)\n if (this.appSecret && _crypto) {\n const timestamp = Date.now().toString();\n const nonce = _crypto.randomUUID();\n const body =\n config.data && typeof config.data === \"object\"\n ? JSON.stringify(config.data)\n : (config.data ?? \"\");\n const bodyHash = body ? await _crypto.sha256(body) : \"\";\n const method = (config.method ?? \"GET\").toUpperCase();\n const url = config.url ?? \"/\";\n const payload = [method, url, timestamp, nonce, bodyHash].join(\"\\n\");\n const signature = await _crypto.hmacSha256(this.appSecret, payload);\n\n config.headers[\"X-Codixus-Timestamp\"] = timestamp;\n config.headers[\"X-Codixus-Nonce\"] = nonce;\n config.headers[\"X-Codixus-Signature\"] = signature;\n }\n\n return config;\n });\n\n // Response interceptor: handle 401 with token refresh\n this.instance.interceptors.response.use(\n (response) => response,\n async (error) => {\n if (error.response?.status === 401) {\n const originalRequest = error.config;\n if (originalRequest && !originalRequest._retry) {\n originalRequest._retry = true;\n\n try {\n if (!this.refreshPromise) {\n this.refreshPromise = this.authManager\n .forceRefresh()\n .finally(() => {\n this.refreshPromise = null;\n });\n }\n\n const newToken = await this.refreshPromise;\n if (newToken) {\n originalRequest.headers.Authorization = `Bearer ${newToken}`;\n return this.instance(originalRequest);\n }\n } catch (refreshError) {\n console.warn(\"[CodixusClient] Token refresh failed:\", refreshError);\n }\n }\n }\n return Promise.reject(error);\n },\n );\n }\n\n async get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.get<T>(url, config);\n }\n\n async post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.post<T>(url, data, config);\n }\n\n async patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.patch<T>(url, data, config);\n }\n\n async delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.delete<T>(url, config);\n }\n\n async put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.put<T>(url, data, config);\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\n\nexport interface FindOptions {\n sort?: Record<string, 1 | -1>;\n limit?: number;\n skip?: number;\n cursor?: string | number;\n}\n\nexport class Collection<T = Record<string, unknown>> {\n constructor(\n private api: ApiClient,\n private basePath: string,\n ) {}\n\n async find(\n filter?: Record<string, unknown>,\n options?: FindOptions,\n ): Promise<T[]> {\n const params = new URLSearchParams();\n if (filter && Object.keys(filter).length > 0) {\n params.set(\"filter\", JSON.stringify(filter));\n }\n if (options?.sort) params.set(\"sort\", JSON.stringify(options.sort));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.skip) params.set(\"skip\", String(options.skip));\n if (options?.cursor) params.set(\"cursor\", String(options.cursor));\n\n const qs = params.toString();\n const url = qs ? `${this.basePath}?${qs}` : this.basePath;\n const { data } = await this.api.get<{ success: boolean; data: T[]; nextCursor?: string | null }>(url);\n return data.data;\n }\n\n async findWithCursor(\n filter?: Record<string, unknown>,\n options?: FindOptions,\n ): Promise<{ data: T[]; nextCursor: string | number | null }> {\n const params = new URLSearchParams();\n if (filter && Object.keys(filter).length > 0) {\n params.set(\"filter\", JSON.stringify(filter));\n }\n if (options?.sort) params.set(\"sort\", JSON.stringify(options.sort));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.cursor) params.set(\"cursor\", String(options.cursor));\n\n const qs = params.toString();\n const url = qs ? `${this.basePath}?${qs}` : this.basePath;\n const { data } = await this.api.get<{ success: boolean; data: T[]; nextCursor: string | number | null }>(url);\n return { data: data.data, nextCursor: data.nextCursor };\n }\n\n async findById(id: string): Promise<T | null> {\n try {\n const { data } = await this.api.get<{ success: boolean; data: T }>(\n `${this.basePath}/${id}`,\n );\n return data.data;\n } catch (error: any) {\n if (error.response?.status === 404) return null;\n throw error;\n }\n }\n\n async create(doc: Partial<T> & Record<string, unknown>): Promise<T> {\n const { data } = await this.api.post<{ success: boolean; data: T }>(\n this.basePath,\n doc,\n );\n return data.data;\n }\n\n async updateById(\n id: string,\n update: Record<string, unknown>,\n ): Promise<T> {\n const { data } = await this.api.patch<{ success: boolean; data: T }>(\n `${this.basePath}/${id}`,\n { update },\n );\n return data.data;\n }\n\n async deleteById(id: string): Promise<void> {\n await this.api.delete(`${this.basePath}/${id}`);\n }\n\n async count(filter?: Record<string, unknown>): Promise<number> {\n const { data } = await this.api.post<{ success: boolean; count: number }>(\n `${this.basePath}/count`,\n { filter: filter ?? {} },\n );\n return data.count;\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\nimport type { PushAdapter } from \"./types.js\";\n\nfunction extractDeliveryId(\n payload: Record<string, unknown>,\n): string | undefined {\n if (typeof payload.deliveryId === \"string\" && payload.deliveryId) {\n return payload.deliveryId;\n }\n const data = payload.data;\n if (data && typeof data === \"object\" && !Array.isArray(data)) {\n const nested = (data as Record<string, unknown>).deliveryId;\n if (typeof nested === \"string\" && nested) {\n return nested;\n }\n }\n return undefined;\n}\n\nexport class PushManager {\n constructor(\n private adapter: PushAdapter,\n private api: ApiClient,\n private auth: AuthManager,\n ) {}\n\n async register(): Promise<void> {\n const authToken = await this.auth.getToken();\n if (!authToken) {\n throw new Error(\"Not authenticated\");\n }\n\n const adapterToken = await this.adapter.getToken();\n if (!adapterToken) {\n return;\n }\n\n const body: Record<string, unknown> = {\n token: adapterToken.token,\n provider: adapterToken.provider,\n platform: adapterToken.platform,\n };\n\n try {\n body.permissionStatus = await this.adapter.getPermissionStatus();\n } catch {\n // omit permissionStatus, still POST\n }\n\n await this.api.post(\"/push/register\", body);\n }\n\n async unregister(): Promise<void> {\n const authToken = await this.auth.getToken();\n if (!authToken) {\n throw new Error(\"Not authenticated\");\n }\n\n await this.api.post(\"/push/unregister\", {});\n }\n\n async requestPermission(): Promise<boolean> {\n return this.adapter.requestPermission();\n }\n\n async getPermissionStatus(): Promise<\n \"granted\" | \"denied\" | \"undetermined\"\n > {\n return this.adapter.getPermissionStatus();\n }\n\n addNotificationOpenedListener(\n handler: (payload: Record<string, unknown>) => void,\n ): () => void {\n return this.adapter.addNotificationOpenedListener((payload) => {\n handler(payload);\n const deliveryId = extractDeliveryId(payload);\n if (deliveryId) {\n void this.reportOpen(deliveryId);\n }\n });\n }\n\n addNotificationReceivedListener(\n handler: (payload: Record<string, unknown>) => void,\n ): () => void {\n return this.adapter.addNotificationReceivedListener(handler);\n }\n\n private async reportOpen(deliveryId: string): Promise<void> {\n try {\n const authToken = await this.auth.getToken();\n if (!authToken) return;\n await this.api.post(\"/push/open\", { deliveryId });\n } catch {\n // fire-and-forget: swallow 404, 401, 5xx, network errors\n }\n }\n}\n","import {\n AuthManager,\n type StorageAdapter,\n type SignInParams,\n type AuthState,\n type DeviceIdProvider,\n type AutoSignInConfig,\n} from \"./auth/auth-manager.js\";\nimport { ApiClient } from \"./api/api-client.js\";\nimport { Collection, type FindOptions } from \"./db/collection.js\";\nimport { PushManager } from \"./push/push-manager.js\";\nimport type { PushAdapter } from \"./push/types.js\";\n\nexport interface CodixusClientConfig {\n apiUrl: string;\n storageKey?: string;\n storage?: StorageAdapter | null;\n appSecret?: string;\n deviceIdProvider?: DeviceIdProvider | null;\n autoSignIn?: AutoSignInConfig | null;\n push?: PushAdapter;\n}\n\nexport class CodixusClient {\n readonly auth: AuthManager;\n readonly api: ApiClient;\n readonly push?: PushManager;\n\n constructor(config: CodixusClientConfig) {\n const storageKey = config.storageKey ?? \"codixus\";\n const storage = config.storage ?? null;\n\n this.auth = new AuthManager(storage, storageKey, {\n deviceIdProvider: config.deviceIdProvider ?? null,\n autoSignIn: config.autoSignIn ?? null,\n });\n this.api = new ApiClient(config.apiUrl, this.auth, config.appSecret);\n this.auth._setApi(this.api);\n\n if (config.push) {\n this.push = new PushManager(config.push, this.api, this.auth);\n }\n }\n\n /**\n * Create a typed collection accessor.\n * Maps to auto REST routes on server: codixus.rest(Model)\n *\n * @example\n * interface Product { _id: string; name: string; price: number; }\n * const products = codixus.collection<Product>(\"products\");\n * const all = await products.find(); // Product[]\n * const one = await products.findById(\"abc\"); // Product | null\n */\n collection<T>(name: string): Collection<T> {\n return new Collection<T>(this.api, `/api/${name}`);\n }\n\n async init(): Promise<void> {\n await this.auth.init();\n }\n}\n\nexport {\n type StorageAdapter,\n type SignInParams,\n type AuthState,\n type DeviceIdProvider,\n type AutoSignInConfig,\n} from \"./auth/auth-manager.js\";\nexport { ApiClient, setCryptoProvider, type CryptoProvider } from \"./api/api-client.js\";\nexport { Collection, type FindOptions } from \"./db/collection.js\";\nexport { type PushAdapter, type PushAdapterToken } from \"./push/types.js\";\n\n// Re-export shared types\nexport {\n type AuthUser,\n type TokenPair,\n type DeviceInfo,\n type DeviceMetadata,\n type AuthTokenRequest,\n type AuthTokenResponse,\n type ApiResponse,\n CodixusError,\n ErrorCodes,\n} from \"@codixus/shared\";\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/auth/auth-manager.ts","../src/api/api-client.ts","../src/db/collection.ts","../src/push/push-manager.ts","../src/events/event-manager.ts","../src/index.ts"],"sourcesContent":["import type {\n AuthTokenResponse,\n DeviceInfo,\n DeviceMetadata,\n} from \"@codixus/shared\";\nimport type { ApiClient } from \"../api/api-client.js\";\n\nexport interface StorageAdapter {\n getItem(key: string): Promise<string | null> | string | null;\n setItem(key: string, value: string): Promise<void> | void;\n removeItem(key: string): Promise<void> | void;\n}\n\n// Host-injected source for a stable, OS-level device identifier (IDFV on\n// iOS, ANDROID_ID on Android, etc.). The SDK calls this when it needs to\n// obtain a deviceId and has nothing in storage. Returning null lets the\n// SDK fall back to a generated UUID. Keeping this an interface (instead\n// of importing expo-application) keeps the SDK platform-agnostic.\nexport interface DeviceIdProvider {\n getDeviceId(): Promise<string | null> | string | null;\n}\n\n// Optional auto signIn driven by the SDK on init(). When provided, after\n// init() restores from storage and the user is still unauthenticated, the\n// SDK calls signIn with the host-supplied device info/metadata. This\n// removes the need for every host app to wire its own boot signIn.\nexport interface AutoSignInConfig {\n enabled: boolean;\n getDeviceInfo(): DeviceInfo | Promise<DeviceInfo>;\n getMetadata?(): DeviceMetadata | Promise<DeviceMetadata>;\n onError?(error: unknown): void;\n}\n\nexport interface SignInParams {\n deviceId?: string;\n deviceInfo: DeviceInfo;\n metadata?: DeviceMetadata;\n}\n\nexport interface AuthState {\n isAuthenticated: boolean;\n isLoading: boolean;\n user: Record<string, unknown> | null;\n}\n\ntype AuthStateListener = (state: AuthState) => void;\n\nexport class AuthManager {\n private accessToken: string | null = null;\n private refreshTokenValue: string | null = null;\n private user: Record<string, unknown> | null = null;\n private listeners: Set<AuthStateListener> = new Set();\n private _isLoading = true;\n private storageKey: string;\n private api!: ApiClient;\n private deviceId: string | null = null;\n private deviceIdProvider: DeviceIdProvider | null;\n private autoSignInConfig: AutoSignInConfig | null;\n private initPromise: Promise<void> | null = null;\n private signInPromise: Promise<{ user: Record<string, unknown>; isNewUser: boolean }> | null = null;\n\n constructor(\n private storage: StorageAdapter | null,\n storageKey: string,\n options?: {\n deviceIdProvider?: DeviceIdProvider | null;\n autoSignIn?: AutoSignInConfig | null;\n },\n ) {\n this.storageKey = storageKey;\n this.deviceIdProvider = options?.deviceIdProvider ?? null;\n this.autoSignInConfig = options?.autoSignIn ?? null;\n }\n\n /** @internal */\n _setApi(api: ApiClient): void {\n this.api = api;\n }\n\n async init(): Promise<void> {\n // Idempotent: concurrent or repeat callers (e.g. host module-load\n // init plus the React provider's useEffect init) share a single run\n // so autoSignIn does not fire twice and race against itself on the\n // backend. On rejection we clear the cached promise so a retry can\n // run — caching a rejected promise would brick init() for the rest\n // of the app lifetime.\n if (this.initPromise) return this.initPromise;\n this.initPromise = this._init().catch((err) => {\n this.initPromise = null;\n throw err;\n });\n return this.initPromise;\n }\n\n private async _init(): Promise<void> {\n if (this.storage) {\n try {\n const storedRefresh = await this.storage.getItem(\n `${this.storageKey}_refresh_token`,\n );\n const storedUser = await this.storage.getItem(\n `${this.storageKey}_user`,\n );\n const storedDeviceId = await this.storage.getItem(\n `${this.storageKey}_device_id`,\n );\n\n if (storedRefresh) {\n this.refreshTokenValue = storedRefresh;\n }\n if (storedUser) {\n try {\n this.user = JSON.parse(storedUser);\n } catch {\n // Corrupted user data in storage, ignore\n }\n }\n if (storedDeviceId) {\n this.deviceId = storedDeviceId;\n }\n } catch (error) {\n console.warn(\"[CodixusClient] Failed to restore auth state from storage:\", error);\n }\n }\n\n // Auto signIn: if the host app opted in and we have no live session yet\n // (cold start, app data cleared, fresh install), reach out to the\n // backend with the device-bound identifier so the existing user is\n // restored before any UI hits an authenticated route.\n if (\n this.autoSignInConfig?.enabled &&\n !this.refreshTokenValue\n ) {\n try {\n const deviceInfo = await this.autoSignInConfig.getDeviceInfo();\n const metadata = this.autoSignInConfig.getMetadata\n ? await this.autoSignInConfig.getMetadata()\n : undefined;\n await this.signIn({ deviceInfo, metadata });\n } catch (error) {\n this.autoSignInConfig.onError?.(error);\n }\n }\n\n this._isLoading = false;\n this.notify();\n }\n\n async signIn(\n params: SignInParams,\n ): Promise<{ user: Record<string, unknown>; isNewUser: boolean }> {\n // Coalesce concurrent signIn calls (e.g. autoSignIn racing a manual\n // signIn from a UI handler) onto a single network request.\n if (this.signInPromise) return this.signInPromise;\n this.signInPromise = this._signIn(params).finally(() => {\n this.signInPromise = null;\n });\n return this.signInPromise;\n }\n\n private async _signIn(\n params: SignInParams,\n ): Promise<{ user: Record<string, unknown>; isNewUser: boolean }> {\n const providerId = this.deviceIdProvider\n ? await this.deviceIdProvider.getDeviceId()\n : null;\n const deviceId =\n params.deviceId ??\n this.deviceId ??\n providerId ??\n this.generateDeviceId();\n\n const response = await this.api.post<AuthTokenResponse>(\"/auth/token\", {\n deviceId,\n deviceInfo: params.deviceInfo,\n metadata: params.metadata,\n });\n\n const data = response.data;\n this.accessToken = data.accessToken;\n this.refreshTokenValue = data.refreshToken;\n this.user = data.user;\n this.deviceId = deviceId;\n\n // Persist. Storage failures (full disk, locked keychain post-restore,\n // EncryptedSharedPreferences key invalidation) must not strand the\n // in-memory session — the user is signed in for this run; the next\n // cold start will simply re-trigger autoSignIn.\n if (this.storage) {\n try {\n await this.storage.setItem(\n `${this.storageKey}_refresh_token`,\n data.refreshToken,\n );\n await this.storage.setItem(\n `${this.storageKey}_user`,\n JSON.stringify(data.user),\n );\n await this.storage.setItem(`${this.storageKey}_device_id`, deviceId);\n } catch (error) {\n console.warn(\"[CodixusClient] Failed to persist auth state:\", error);\n }\n }\n\n this.notify();\n return { user: data.user, isNewUser: data.isNewUser };\n }\n\n async getToken(): Promise<string | null> {\n if (this.accessToken) return this.accessToken;\n\n // Try refresh\n if (this.refreshTokenValue) {\n try {\n await this.forceRefresh();\n return this.accessToken;\n } catch {\n return null;\n }\n }\n\n return null;\n }\n\n async forceRefresh(): Promise<string | null> {\n if (!this.refreshTokenValue) return null;\n\n try {\n const response = await this.api.post<{\n success: boolean;\n accessToken: string;\n refreshToken: string;\n }>(\"/auth/refresh\", {\n refreshToken: this.refreshTokenValue,\n });\n\n this.accessToken = response.data.accessToken;\n this.refreshTokenValue = response.data.refreshToken;\n\n if (this.storage) {\n await this.storage.setItem(\n `${this.storageKey}_refresh_token`,\n response.data.refreshToken,\n );\n }\n\n return this.accessToken;\n } catch {\n // Refresh failed, clear auth state\n await this.signOut();\n return null;\n }\n }\n\n async signOut(): Promise<void> {\n // Notify server\n if (this.refreshTokenValue) {\n try {\n await this.api.post(\"/auth/logout\", {\n refreshToken: this.refreshTokenValue,\n });\n } catch {}\n }\n\n this.accessToken = null;\n this.refreshTokenValue = null;\n this.user = null;\n // Reset guards so a fresh signIn flow (or a re-init triggered by a\n // host that wants to re-authenticate after sign-out) can run. The\n // signInPromise is also cleared by its own .finally, but a signOut\n // racing against an in-flight signIn shouldn't keep the stale guard.\n this.initPromise = null;\n this.signInPromise = null;\n\n if (this.storage) {\n await this.storage.removeItem(`${this.storageKey}_refresh_token`);\n await this.storage.removeItem(`${this.storageKey}_user`);\n await this.storage.removeItem(`${this.storageKey}_device_id`);\n }\n\n this.notify();\n }\n\n getState(): AuthState {\n return {\n isAuthenticated: this.accessToken !== null || this.refreshTokenValue !== null,\n isLoading: this._isLoading,\n user: this.user,\n };\n }\n\n getDeviceId(): string | null {\n return this.deviceId;\n }\n\n onAuthStateChange(listener: AuthStateListener): () => void {\n this.listeners.add(listener);\n // Call immediately with current state\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** @internal - used by API interceptor */\n _getAccessToken(): string | null {\n return this.accessToken;\n }\n\n /** @internal - called by API interceptor after refresh */\n _setAccessToken(token: string): void {\n this.accessToken = token;\n }\n\n private notify(): void {\n const state = this.getState();\n for (const listener of this.listeners) {\n listener(state);\n }\n }\n\n private generateDeviceId(): string {\n const bytes = new Uint8Array(16);\n if (typeof globalThis.crypto?.getRandomValues === \"function\") {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return \"dev-\" + Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n }\n}\n","import axios, {\n type AxiosInstance,\n type AxiosRequestConfig,\n type AxiosResponse,\n} from \"axios\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\n\n// Crypto functions — injected from outside to avoid platform-specific imports\nexport interface CryptoProvider {\n sha256(data: string): Promise<string>;\n hmacSha256(secret: string, data: string): Promise<string>;\n randomUUID(): string;\n}\n\n// Default: no-op (signing disabled if no crypto provider)\nlet _crypto: CryptoProvider | null = null;\n\nexport function setCryptoProvider(provider: CryptoProvider): void {\n _crypto = provider;\n}\n\nfunction generateNonce(): string {\n if (_crypto) return _crypto.randomUUID();\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n const bytes = new Uint8Array(16);\n if (typeof globalThis.crypto?.getRandomValues === \"function\") {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport class ApiClient {\n readonly instance: AxiosInstance;\n private refreshPromise: Promise<string | null> | null = null;\n private appSecret: string | undefined;\n\n constructor(\n baseURL: string,\n private authManager: AuthManager,\n appSecret?: string,\n ) {\n this.appSecret = appSecret;\n\n this.instance = axios.create({\n baseURL,\n headers: { \"Content-Type\": \"application/json\" },\n });\n\n // Request interceptor: add Bearer token + signing\n this.instance.interceptors.request.use(async (config) => {\n // Skip auth for auth endpoints\n if (!config.url?.includes(\"/auth/\")) {\n const token = this.authManager._getAccessToken();\n if (token) {\n config.headers.Authorization = `Bearer ${token}`;\n }\n }\n\n // Request signing (only if crypto provider is set)\n if (this.appSecret && _crypto) {\n const timestamp = Date.now().toString();\n const nonce = _crypto.randomUUID();\n const body =\n config.data && typeof config.data === \"object\"\n ? JSON.stringify(config.data)\n : (config.data ?? \"\");\n const bodyHash = body ? await _crypto.sha256(body) : \"\";\n const method = (config.method ?? \"GET\").toUpperCase();\n const url = config.url ?? \"/\";\n const payload = [method, url, timestamp, nonce, bodyHash].join(\"\\n\");\n const signature = await _crypto.hmacSha256(this.appSecret, payload);\n\n config.headers[\"X-Codixus-Timestamp\"] = timestamp;\n config.headers[\"X-Codixus-Nonce\"] = nonce;\n config.headers[\"X-Codixus-Signature\"] = signature;\n }\n\n return config;\n });\n\n // Response interceptor: handle 401 with token refresh\n this.instance.interceptors.response.use(\n (response) => response,\n async (error) => {\n if (error.response?.status === 401) {\n const originalRequest = error.config;\n if (originalRequest && !originalRequest._retry) {\n originalRequest._retry = true;\n\n try {\n if (!this.refreshPromise) {\n this.refreshPromise = this.authManager\n .forceRefresh()\n .finally(() => {\n this.refreshPromise = null;\n });\n }\n\n const newToken = await this.refreshPromise;\n if (newToken) {\n originalRequest.headers.Authorization = `Bearer ${newToken}`;\n return this.instance(originalRequest);\n }\n } catch (refreshError) {\n console.warn(\"[CodixusClient] Token refresh failed:\", refreshError);\n }\n }\n }\n return Promise.reject(error);\n },\n );\n }\n\n async get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.get<T>(url, config);\n }\n\n async post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.post<T>(url, data, config);\n }\n\n async patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.patch<T>(url, data, config);\n }\n\n async delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.delete<T>(url, config);\n }\n\n async put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {\n return this.instance.put<T>(url, data, config);\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\n\nexport interface FindOptions {\n sort?: Record<string, 1 | -1>;\n limit?: number;\n skip?: number;\n cursor?: string | number;\n}\n\nexport class Collection<T = Record<string, unknown>> {\n constructor(\n private api: ApiClient,\n private basePath: string,\n ) {}\n\n async find(\n filter?: Record<string, unknown>,\n options?: FindOptions,\n ): Promise<T[]> {\n const params = new URLSearchParams();\n if (filter && Object.keys(filter).length > 0) {\n params.set(\"filter\", JSON.stringify(filter));\n }\n if (options?.sort) params.set(\"sort\", JSON.stringify(options.sort));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.skip) params.set(\"skip\", String(options.skip));\n if (options?.cursor) params.set(\"cursor\", String(options.cursor));\n\n const qs = params.toString();\n const url = qs ? `${this.basePath}?${qs}` : this.basePath;\n const { data } = await this.api.get<{ success: boolean; data: T[]; nextCursor?: string | null }>(url);\n return data.data;\n }\n\n async findWithCursor(\n filter?: Record<string, unknown>,\n options?: FindOptions,\n ): Promise<{ data: T[]; nextCursor: string | number | null }> {\n const params = new URLSearchParams();\n if (filter && Object.keys(filter).length > 0) {\n params.set(\"filter\", JSON.stringify(filter));\n }\n if (options?.sort) params.set(\"sort\", JSON.stringify(options.sort));\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.cursor) params.set(\"cursor\", String(options.cursor));\n\n const qs = params.toString();\n const url = qs ? `${this.basePath}?${qs}` : this.basePath;\n const { data } = await this.api.get<{ success: boolean; data: T[]; nextCursor: string | number | null }>(url);\n return { data: data.data, nextCursor: data.nextCursor };\n }\n\n async findById(id: string): Promise<T | null> {\n try {\n const { data } = await this.api.get<{ success: boolean; data: T }>(\n `${this.basePath}/${id}`,\n );\n return data.data;\n } catch (error: any) {\n if (error.response?.status === 404) return null;\n throw error;\n }\n }\n\n async create(doc: Partial<T> & Record<string, unknown>): Promise<T> {\n const { data } = await this.api.post<{ success: boolean; data: T }>(\n this.basePath,\n doc,\n );\n return data.data;\n }\n\n async updateById(\n id: string,\n update: Record<string, unknown>,\n ): Promise<T> {\n const { data } = await this.api.patch<{ success: boolean; data: T }>(\n `${this.basePath}/${id}`,\n { update },\n );\n return data.data;\n }\n\n async deleteById(id: string): Promise<void> {\n await this.api.delete(`${this.basePath}/${id}`);\n }\n\n async count(filter?: Record<string, unknown>): Promise<number> {\n const { data } = await this.api.post<{ success: boolean; count: number }>(\n `${this.basePath}/count`,\n { filter: filter ?? {} },\n );\n return data.count;\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\nimport type { PushAdapter } from \"./types.js\";\n\nfunction extractDeliveryId(\n payload: Record<string, unknown>,\n): string | undefined {\n if (typeof payload.deliveryId === \"string\" && payload.deliveryId) {\n return payload.deliveryId;\n }\n const data = payload.data;\n if (data && typeof data === \"object\" && !Array.isArray(data)) {\n const nested = (data as Record<string, unknown>).deliveryId;\n if (typeof nested === \"string\" && nested) {\n return nested;\n }\n }\n return undefined;\n}\n\nexport class PushManager {\n constructor(\n private adapter: PushAdapter,\n private api: ApiClient,\n private auth: AuthManager,\n ) {}\n\n async register(): Promise<void> {\n const authToken = await this.auth.getToken();\n if (!authToken) {\n throw new Error(\"Not authenticated\");\n }\n\n const adapterToken = await this.adapter.getToken();\n if (!adapterToken) {\n return;\n }\n\n const body: Record<string, unknown> = {\n token: adapterToken.token,\n provider: adapterToken.provider,\n platform: adapterToken.platform,\n };\n\n try {\n body.permissionStatus = await this.adapter.getPermissionStatus();\n } catch {\n // omit permissionStatus, still POST\n }\n\n await this.api.post(\"/push/register\", body);\n }\n\n async unregister(): Promise<void> {\n const authToken = await this.auth.getToken();\n if (!authToken) {\n throw new Error(\"Not authenticated\");\n }\n\n await this.api.post(\"/push/unregister\", {});\n }\n\n async requestPermission(): Promise<boolean> {\n return this.adapter.requestPermission();\n }\n\n async getPermissionStatus(): Promise<\n \"granted\" | \"denied\" | \"undetermined\"\n > {\n return this.adapter.getPermissionStatus();\n }\n\n addNotificationOpenedListener(\n handler: (payload: Record<string, unknown>) => void,\n ): () => void {\n return this.adapter.addNotificationOpenedListener((payload) => {\n handler(payload);\n const deliveryId = extractDeliveryId(payload);\n if (deliveryId) {\n void this.reportOpen(deliveryId);\n }\n });\n }\n\n addNotificationReceivedListener(\n handler: (payload: Record<string, unknown>) => void,\n ): () => void {\n return this.adapter.addNotificationReceivedListener(handler);\n }\n\n private async reportOpen(deliveryId: string): Promise<void> {\n try {\n const authToken = await this.auth.getToken();\n if (!authToken) return;\n await this.api.post(\"/push/open\", { deliveryId });\n } catch {\n // fire-and-forget: swallow 404, 401, 5xx, network errors\n }\n }\n}\n","import type { ApiClient } from \"../api/api-client.js\";\nimport type { AuthManager } from \"../auth/auth-manager.js\";\n\nconst EVENT_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;\n\nexport type EventProperties = Record<string, unknown>;\n\nexport type EventTrackOptions = {\n idempotencyKey?: string;\n occurredAt?: Date;\n};\n\nexport type EventTrackResult = {\n created: boolean;\n eventId?: string;\n ignored?: \"USER_NOT_FOUND\";\n};\n\nexport class EventManager {\n constructor(\n private api: ApiClient,\n private auth: AuthManager,\n ) {}\n\n async track(\n name: string,\n properties?: EventProperties,\n options?: EventTrackOptions,\n ): Promise<EventTrackResult> {\n if (!EVENT_NAME_RE.test(name)) {\n throw new Error(\"Invalid event name\");\n }\n if (\n options?.idempotencyKey !== undefined &&\n (options.idempotencyKey.length === 0 || options.idempotencyKey.length > 200)\n ) {\n throw new Error(\"Invalid idempotency key\");\n }\n if (\n options?.occurredAt !== undefined &&\n Number.isNaN(options.occurredAt.getTime())\n ) {\n throw new Error(\"Invalid occurredAt\");\n }\n if (!(await this.auth.getToken())) {\n throw new Error(\"Not authenticated\");\n }\n\n const payload: Record<string, unknown> = { name };\n if (properties !== undefined) payload.properties = properties;\n if (options?.idempotencyKey !== undefined) {\n payload.idempotencyKey = options.idempotencyKey;\n }\n if (options?.occurredAt !== undefined) {\n payload.occurredAt = options.occurredAt.toISOString();\n }\n\n const response = await this.api.post<{\n success: true;\n data: EventTrackResult;\n }>(\"/events\", payload);\n return response.data.data;\n }\n}\n","import {\n AuthManager,\n type StorageAdapter,\n type SignInParams,\n type AuthState,\n type DeviceIdProvider,\n type AutoSignInConfig,\n} from \"./auth/auth-manager.js\";\nimport { ApiClient } from \"./api/api-client.js\";\nimport { Collection, type FindOptions } from \"./db/collection.js\";\nimport { PushManager } from \"./push/push-manager.js\";\nimport type { PushAdapter } from \"./push/types.js\";\nimport {\n EventManager,\n type EventProperties,\n type EventTrackOptions,\n type EventTrackResult,\n} from \"./events/event-manager.js\";\n\nexport interface CodixusClientConfig {\n apiUrl: string;\n storageKey?: string;\n storage?: StorageAdapter | null;\n appSecret?: string;\n deviceIdProvider?: DeviceIdProvider | null;\n autoSignIn?: AutoSignInConfig | null;\n push?: PushAdapter;\n}\n\nexport class CodixusClient {\n readonly auth: AuthManager;\n readonly api: ApiClient;\n readonly events: EventManager;\n readonly push?: PushManager;\n\n constructor(config: CodixusClientConfig) {\n const storageKey = config.storageKey ?? \"codixus\";\n const storage = config.storage ?? null;\n\n this.auth = new AuthManager(storage, storageKey, {\n deviceIdProvider: config.deviceIdProvider ?? null,\n autoSignIn: config.autoSignIn ?? null,\n });\n this.api = new ApiClient(config.apiUrl, this.auth, config.appSecret);\n this.auth._setApi(this.api);\n this.events = new EventManager(this.api, this.auth);\n\n if (config.push) {\n this.push = new PushManager(config.push, this.api, this.auth);\n }\n }\n\n /**\n * Create a typed collection accessor.\n * Maps to auto REST routes on server: codixus.rest(Model)\n *\n * @example\n * interface Product { _id: string; name: string; price: number; }\n * const products = codixus.collection<Product>(\"products\");\n * const all = await products.find(); // Product[]\n * const one = await products.findById(\"abc\"); // Product | null\n */\n collection<T>(name: string): Collection<T> {\n return new Collection<T>(this.api, `/api/${name}`);\n }\n\n async init(): Promise<void> {\n await this.auth.init();\n }\n}\n\nexport {\n type StorageAdapter,\n type SignInParams,\n type AuthState,\n type DeviceIdProvider,\n type AutoSignInConfig,\n} from \"./auth/auth-manager.js\";\nexport { ApiClient, setCryptoProvider, type CryptoProvider } from \"./api/api-client.js\";\nexport { Collection, type FindOptions } from \"./db/collection.js\";\nexport { type PushAdapter, type PushAdapterToken } from \"./push/types.js\";\nexport {\n EventManager,\n type EventProperties,\n type EventTrackOptions,\n type EventTrackResult,\n} from \"./events/event-manager.js\";\n\n// Re-export shared types\nexport {\n type AuthUser,\n type TokenPair,\n type DeviceInfo,\n type DeviceMetadata,\n type AuthTokenRequest,\n type AuthTokenResponse,\n type ApiResponse,\n CodixusError,\n ErrorCodes,\n} from \"@codixus/shared\";\n"],"mappings":"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","names":[]}
|
package/dist/push-expo/index.cjs
CHANGED
|
@@ -35,6 +35,16 @@ __export(push_expo_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(push_expo_exports);
|
|
36
36
|
var Notifications = __toESM(require("expo-notifications"), 1);
|
|
37
37
|
var import_react_native = require("react-native");
|
|
38
|
+
var ANDROID_CHANNEL_ID = "default";
|
|
39
|
+
async function ensureAndroidChannel(platform) {
|
|
40
|
+
if (platform !== "android") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await Notifications.setNotificationChannelAsync(ANDROID_CHANNEL_ID, {
|
|
44
|
+
name: ANDROID_CHANNEL_ID,
|
|
45
|
+
importance: Notifications.AndroidImportance.MAX
|
|
46
|
+
});
|
|
47
|
+
}
|
|
38
48
|
function resolvePlatform() {
|
|
39
49
|
if (import_react_native.Platform.OS === "ios" || import_react_native.Platform.OS === "android") {
|
|
40
50
|
return import_react_native.Platform.OS;
|
|
@@ -68,21 +78,34 @@ function createNoopAdapter() {
|
|
|
68
78
|
addNotificationOpenedListener: () => noopUnsubscribe
|
|
69
79
|
};
|
|
70
80
|
}
|
|
81
|
+
function configureForegroundNotifications() {
|
|
82
|
+
Notifications.setNotificationHandler({
|
|
83
|
+
handleNotification: async () => ({
|
|
84
|
+
shouldPlaySound: true,
|
|
85
|
+
shouldSetBadge: true,
|
|
86
|
+
shouldShowBanner: true,
|
|
87
|
+
shouldShowList: true
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
}
|
|
71
91
|
function createExpoAdapter(opts) {
|
|
72
92
|
const platform = resolvePlatform();
|
|
73
93
|
if (!platform) {
|
|
74
94
|
return createNoopAdapter();
|
|
75
95
|
}
|
|
96
|
+
configureForegroundNotifications();
|
|
76
97
|
return {
|
|
77
98
|
async getPermissionStatus() {
|
|
78
99
|
const { status } = await Notifications.getPermissionsAsync();
|
|
79
100
|
return mapPermissionStatus(status);
|
|
80
101
|
},
|
|
81
102
|
async requestPermission() {
|
|
103
|
+
await ensureAndroidChannel(platform);
|
|
82
104
|
const { status } = await Notifications.requestPermissionsAsync();
|
|
83
105
|
return status === "granted";
|
|
84
106
|
},
|
|
85
107
|
async getToken() {
|
|
108
|
+
await ensureAndroidChannel(platform);
|
|
86
109
|
const permission = await this.getPermissionStatus();
|
|
87
110
|
if (permission !== "granted") {
|
|
88
111
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/push-expo/index.ts"],"sourcesContent":["import * as Notifications from \"expo-notifications\";\nimport { Platform } from \"react-native\";\nimport type { PushAdapter, PushAdapterToken } from \"../push/types.js\";\n\nexport type { PushAdapter, PushAdapterToken } from \"../push/types.js\";\n\ntype PlatformOS = \"ios\" | \"android\";\n\nfunction resolvePlatform(): PlatformOS | null {\n if (Platform.OS === \"ios\" || Platform.OS === \"android\") {\n return Platform.OS;\n }\n return null;\n}\n\nfunction flattenPayload(\n notification: Notifications.Notification,\n): Record<string, unknown> {\n const data = notification.request.content.data;\n if (data && typeof data === \"object\" && !Array.isArray(data)) {\n return { ...(data as Record<string, unknown>) };\n }\n return {};\n}\n\nfunction mapPermissionStatus(\n status: string,\n): \"granted\" | \"denied\" | \"undetermined\" {\n if (status === \"granted\") {\n return \"granted\";\n }\n if (status === \"denied\") {\n return \"denied\";\n }\n return \"undetermined\";\n}\n\nconst noopUnsubscribe = (): void => {};\n\nfunction createNoopAdapter(): PushAdapter {\n return {\n getPermissionStatus: async () => \"denied\",\n requestPermission: async () => false,\n getToken: async () => null,\n addNotificationReceivedListener: () => noopUnsubscribe,\n addNotificationOpenedListener: () => noopUnsubscribe,\n };\n}\n\nexport function createExpoAdapter(opts: { projectId: string }): PushAdapter {\n const platform = resolvePlatform();\n if (!platform) {\n return createNoopAdapter();\n }\n\n return {\n async getPermissionStatus() {\n const { status } = await Notifications.getPermissionsAsync();\n return mapPermissionStatus(status);\n },\n\n async requestPermission() {\n const { status } = await Notifications.requestPermissionsAsync();\n return status === \"granted\";\n },\n\n async getToken(): Promise<PushAdapterToken | null> {\n const permission = await this.getPermissionStatus();\n if (permission !== \"granted\") {\n return null;\n }\n\n try {\n const result = await Notifications.getExpoPushTokenAsync({\n projectId: opts.projectId,\n });\n return {\n token: result.data,\n provider: \"expo\",\n platform,\n };\n } catch {\n return null;\n }\n },\n\n addNotificationReceivedListener(handler) {\n const subscription = Notifications.addNotificationReceivedListener(\n (notification) => {\n handler(flattenPayload(notification));\n },\n );\n return () => subscription.remove();\n },\n\n addNotificationOpenedListener(handler) {\n const subscription =\n Notifications.addNotificationResponseReceivedListener((response) => {\n handler(flattenPayload(response.notification));\n });\n return () => subscription.remove();\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA+B;AAC/B,0BAAyB;AAOzB,SAAS,kBAAqC;AAC5C,MAAI,6BAAS,OAAO,SAAS,6BAAS,OAAO,WAAW;AACtD,WAAO,6BAAS;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,eACP,cACyB;AACzB,QAAM,OAAO,aAAa,QAAQ,QAAQ;AAC1C,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;AAC5D,WAAO,EAAE,GAAI,KAAiC;AAAA,EAChD;AACA,SAAO,CAAC;AACV;AAEA,SAAS,oBACP,QACuC;AACvC,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,UAAU;AACvB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,IAAM,kBAAkB,MAAY;AAAC;AAErC,SAAS,oBAAiC;AACxC,SAAO;AAAA,IACL,qBAAqB,YAAY;AAAA,IACjC,mBAAmB,YAAY;AAAA,IAC/B,UAAU,YAAY;AAAA,IACtB,iCAAiC,MAAM;AAAA,IACvC,+BAA+B,MAAM;AAAA,EACvC;AACF;AAEO,SAAS,kBAAkB,MAA0C;AAC1E,QAAM,WAAW,gBAAgB;AACjC,MAAI,CAAC,UAAU;AACb,WAAO,kBAAkB;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL,MAAM,sBAAsB;AAC1B,YAAM,EAAE,OAAO,IAAI,MAAoB,kCAAoB;AAC3D,aAAO,oBAAoB,MAAM;AAAA,IACnC;AAAA,IAEA,MAAM,oBAAoB;AACxB,YAAM,EAAE,OAAO,IAAI,MAAoB,sCAAwB;AAC/D,aAAO,WAAW;AAAA,IACpB;AAAA,IAEA,MAAM,WAA6C;AACjD,YAAM,aAAa,MAAM,KAAK,oBAAoB;AAClD,UAAI,eAAe,WAAW;AAC5B,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,SAAS,MAAoB,oCAAsB;AAAA,UACvD,WAAW,KAAK;AAAA,QAClB,CAAC;AACD,eAAO;AAAA,UACL,OAAO,OAAO;AAAA,UACd,UAAU;AAAA,UACV;AAAA,QACF;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,gCAAgC,SAAS;AACvC,YAAM,eAA6B;AAAA,QACjC,CAAC,iBAAiB;AAChB,kBAAQ,eAAe,YAAY,CAAC;AAAA,QACtC;AAAA,MACF;AACA,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,IAEA,8BAA8B,SAAS;AACrC,YAAM,eACU,sDAAwC,CAAC,aAAa;AAClE,gBAAQ,eAAe,SAAS,YAAY,CAAC;AAAA,MAC/C,CAAC;AACH,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/push-expo/index.ts"],"sourcesContent":["import * as Notifications from \"expo-notifications\";\nimport { Platform } from \"react-native\";\nimport type { PushAdapter, PushAdapterToken } from \"../push/types.js\";\n\nexport type { PushAdapter, PushAdapterToken } from \"../push/types.js\";\n\ntype PlatformOS = \"ios\" | \"android\";\n\nconst ANDROID_CHANNEL_ID = \"default\";\n\nasync function ensureAndroidChannel(platform: PlatformOS): Promise<void> {\n if (platform !== \"android\") {\n return;\n }\n\n await Notifications.setNotificationChannelAsync(ANDROID_CHANNEL_ID, {\n name: ANDROID_CHANNEL_ID,\n importance: Notifications.AndroidImportance.MAX,\n });\n}\n\nfunction resolvePlatform(): PlatformOS | null {\n if (Platform.OS === \"ios\" || Platform.OS === \"android\") {\n return Platform.OS;\n }\n return null;\n}\n\nfunction flattenPayload(\n notification: Notifications.Notification,\n): Record<string, unknown> {\n const data = notification.request.content.data;\n if (data && typeof data === \"object\" && !Array.isArray(data)) {\n return { ...(data as Record<string, unknown>) };\n }\n return {};\n}\n\nfunction mapPermissionStatus(\n status: string,\n): \"granted\" | \"denied\" | \"undetermined\" {\n if (status === \"granted\") {\n return \"granted\";\n }\n if (status === \"denied\") {\n return \"denied\";\n }\n return \"undetermined\";\n}\n\nconst noopUnsubscribe = (): void => {};\n\nfunction createNoopAdapter(): PushAdapter {\n return {\n getPermissionStatus: async () => \"denied\",\n requestPermission: async () => false,\n getToken: async () => null,\n addNotificationReceivedListener: () => noopUnsubscribe,\n addNotificationOpenedListener: () => noopUnsubscribe,\n };\n}\n\nfunction configureForegroundNotifications(): void {\n Notifications.setNotificationHandler({\n handleNotification: async () => ({\n shouldPlaySound: true,\n shouldSetBadge: true,\n shouldShowBanner: true,\n shouldShowList: true,\n }),\n });\n}\n\nexport function createExpoAdapter(opts: { projectId: string }): PushAdapter {\n const platform = resolvePlatform();\n if (!platform) {\n return createNoopAdapter();\n }\n\n configureForegroundNotifications();\n\n return {\n async getPermissionStatus() {\n const { status } = await Notifications.getPermissionsAsync();\n return mapPermissionStatus(status);\n },\n\n async requestPermission() {\n await ensureAndroidChannel(platform);\n const { status } = await Notifications.requestPermissionsAsync();\n return status === \"granted\";\n },\n\n async getToken(): Promise<PushAdapterToken | null> {\n await ensureAndroidChannel(platform);\n const permission = await this.getPermissionStatus();\n if (permission !== \"granted\") {\n return null;\n }\n\n try {\n const result = await Notifications.getExpoPushTokenAsync({\n projectId: opts.projectId,\n });\n return {\n token: result.data,\n provider: \"expo\",\n platform,\n };\n } catch {\n return null;\n }\n },\n\n addNotificationReceivedListener(handler) {\n const subscription = Notifications.addNotificationReceivedListener(\n (notification) => {\n handler(flattenPayload(notification));\n },\n );\n return () => subscription.remove();\n },\n\n addNotificationOpenedListener(handler) {\n const subscription =\n Notifications.addNotificationResponseReceivedListener((response) => {\n handler(flattenPayload(response.notification));\n });\n return () => subscription.remove();\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA+B;AAC/B,0BAAyB;AAOzB,IAAM,qBAAqB;AAE3B,eAAe,qBAAqB,UAAqC;AACvE,MAAI,aAAa,WAAW;AAC1B;AAAA,EACF;AAEA,QAAoB,0CAA4B,oBAAoB;AAAA,IAClE,MAAM;AAAA,IACN,YAA0B,gCAAkB;AAAA,EAC9C,CAAC;AACH;AAEA,SAAS,kBAAqC;AAC5C,MAAI,6BAAS,OAAO,SAAS,6BAAS,OAAO,WAAW;AACtD,WAAO,6BAAS;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,eACP,cACyB;AACzB,QAAM,OAAO,aAAa,QAAQ,QAAQ;AAC1C,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;AAC5D,WAAO,EAAE,GAAI,KAAiC;AAAA,EAChD;AACA,SAAO,CAAC;AACV;AAEA,SAAS,oBACP,QACuC;AACvC,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,UAAU;AACvB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,IAAM,kBAAkB,MAAY;AAAC;AAErC,SAAS,oBAAiC;AACxC,SAAO;AAAA,IACL,qBAAqB,YAAY;AAAA,IACjC,mBAAmB,YAAY;AAAA,IAC/B,UAAU,YAAY;AAAA,IACtB,iCAAiC,MAAM;AAAA,IACvC,+BAA+B,MAAM;AAAA,EACvC;AACF;AAEA,SAAS,mCAAyC;AAChD,EAAc,qCAAuB;AAAA,IACnC,oBAAoB,aAAa;AAAA,MAC/B,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,IAClB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,kBAAkB,MAA0C;AAC1E,QAAM,WAAW,gBAAgB;AACjC,MAAI,CAAC,UAAU;AACb,WAAO,kBAAkB;AAAA,EAC3B;AAEA,mCAAiC;AAEjC,SAAO;AAAA,IACL,MAAM,sBAAsB;AAC1B,YAAM,EAAE,OAAO,IAAI,MAAoB,kCAAoB;AAC3D,aAAO,oBAAoB,MAAM;AAAA,IACnC;AAAA,IAEA,MAAM,oBAAoB;AACxB,YAAM,qBAAqB,QAAQ;AACnC,YAAM,EAAE,OAAO,IAAI,MAAoB,sCAAwB;AAC/D,aAAO,WAAW;AAAA,IACpB;AAAA,IAEA,MAAM,WAA6C;AACjD,YAAM,qBAAqB,QAAQ;AACnC,YAAM,aAAa,MAAM,KAAK,oBAAoB;AAClD,UAAI,eAAe,WAAW;AAC5B,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,SAAS,MAAoB,oCAAsB;AAAA,UACvD,WAAW,KAAK;AAAA,QAClB,CAAC;AACD,eAAO;AAAA,UACL,OAAO,OAAO;AAAA,UACd,UAAU;AAAA,UACV;AAAA,QACF;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,gCAAgC,SAAS;AACvC,YAAM,eAA6B;AAAA,QACjC,CAAC,iBAAiB;AAChB,kBAAQ,eAAe,YAAY,CAAC;AAAA,QACtC;AAAA,MACF;AACA,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,IAEA,8BAA8B,SAAS;AACrC,YAAM,eACU,sDAAwC,CAAC,aAAa;AAClE,gBAAQ,eAAe,SAAS,YAAY,CAAC;AAAA,MAC/C,CAAC;AACH,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,EACF;AACF;","names":[]}
|
package/dist/push-expo/index.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
// src/push-expo/index.ts
|
|
2
2
|
import * as Notifications from "expo-notifications";
|
|
3
3
|
import { Platform } from "react-native";
|
|
4
|
+
var ANDROID_CHANNEL_ID = "default";
|
|
5
|
+
async function ensureAndroidChannel(platform) {
|
|
6
|
+
if (platform !== "android") {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
await Notifications.setNotificationChannelAsync(ANDROID_CHANNEL_ID, {
|
|
10
|
+
name: ANDROID_CHANNEL_ID,
|
|
11
|
+
importance: Notifications.AndroidImportance.MAX
|
|
12
|
+
});
|
|
13
|
+
}
|
|
4
14
|
function resolvePlatform() {
|
|
5
15
|
if (Platform.OS === "ios" || Platform.OS === "android") {
|
|
6
16
|
return Platform.OS;
|
|
@@ -34,21 +44,34 @@ function createNoopAdapter() {
|
|
|
34
44
|
addNotificationOpenedListener: () => noopUnsubscribe
|
|
35
45
|
};
|
|
36
46
|
}
|
|
47
|
+
function configureForegroundNotifications() {
|
|
48
|
+
Notifications.setNotificationHandler({
|
|
49
|
+
handleNotification: async () => ({
|
|
50
|
+
shouldPlaySound: true,
|
|
51
|
+
shouldSetBadge: true,
|
|
52
|
+
shouldShowBanner: true,
|
|
53
|
+
shouldShowList: true
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
}
|
|
37
57
|
function createExpoAdapter(opts) {
|
|
38
58
|
const platform = resolvePlatform();
|
|
39
59
|
if (!platform) {
|
|
40
60
|
return createNoopAdapter();
|
|
41
61
|
}
|
|
62
|
+
configureForegroundNotifications();
|
|
42
63
|
return {
|
|
43
64
|
async getPermissionStatus() {
|
|
44
65
|
const { status } = await Notifications.getPermissionsAsync();
|
|
45
66
|
return mapPermissionStatus(status);
|
|
46
67
|
},
|
|
47
68
|
async requestPermission() {
|
|
69
|
+
await ensureAndroidChannel(platform);
|
|
48
70
|
const { status } = await Notifications.requestPermissionsAsync();
|
|
49
71
|
return status === "granted";
|
|
50
72
|
},
|
|
51
73
|
async getToken() {
|
|
74
|
+
await ensureAndroidChannel(platform);
|
|
52
75
|
const permission = await this.getPermissionStatus();
|
|
53
76
|
if (permission !== "granted") {
|
|
54
77
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/push-expo/index.ts"],"sourcesContent":["import * as Notifications from \"expo-notifications\";\nimport { Platform } from \"react-native\";\nimport type { PushAdapter, PushAdapterToken } from \"../push/types.js\";\n\nexport type { PushAdapter, PushAdapterToken } from \"../push/types.js\";\n\ntype PlatformOS = \"ios\" | \"android\";\n\nfunction resolvePlatform(): PlatformOS | null {\n if (Platform.OS === \"ios\" || Platform.OS === \"android\") {\n return Platform.OS;\n }\n return null;\n}\n\nfunction flattenPayload(\n notification: Notifications.Notification,\n): Record<string, unknown> {\n const data = notification.request.content.data;\n if (data && typeof data === \"object\" && !Array.isArray(data)) {\n return { ...(data as Record<string, unknown>) };\n }\n return {};\n}\n\nfunction mapPermissionStatus(\n status: string,\n): \"granted\" | \"denied\" | \"undetermined\" {\n if (status === \"granted\") {\n return \"granted\";\n }\n if (status === \"denied\") {\n return \"denied\";\n }\n return \"undetermined\";\n}\n\nconst noopUnsubscribe = (): void => {};\n\nfunction createNoopAdapter(): PushAdapter {\n return {\n getPermissionStatus: async () => \"denied\",\n requestPermission: async () => false,\n getToken: async () => null,\n addNotificationReceivedListener: () => noopUnsubscribe,\n addNotificationOpenedListener: () => noopUnsubscribe,\n };\n}\n\nexport function createExpoAdapter(opts: { projectId: string }): PushAdapter {\n const platform = resolvePlatform();\n if (!platform) {\n return createNoopAdapter();\n }\n\n return {\n async getPermissionStatus() {\n const { status } = await Notifications.getPermissionsAsync();\n return mapPermissionStatus(status);\n },\n\n async requestPermission() {\n const { status } = await Notifications.requestPermissionsAsync();\n return status === \"granted\";\n },\n\n async getToken(): Promise<PushAdapterToken | null> {\n const permission = await this.getPermissionStatus();\n if (permission !== \"granted\") {\n return null;\n }\n\n try {\n const result = await Notifications.getExpoPushTokenAsync({\n projectId: opts.projectId,\n });\n return {\n token: result.data,\n provider: \"expo\",\n platform,\n };\n } catch {\n return null;\n }\n },\n\n addNotificationReceivedListener(handler) {\n const subscription = Notifications.addNotificationReceivedListener(\n (notification) => {\n handler(flattenPayload(notification));\n },\n );\n return () => subscription.remove();\n },\n\n addNotificationOpenedListener(handler) {\n const subscription =\n Notifications.addNotificationResponseReceivedListener((response) => {\n handler(flattenPayload(response.notification));\n });\n return () => subscription.remove();\n },\n };\n}\n"],"mappings":";AAAA,YAAY,mBAAmB;AAC/B,SAAS,gBAAgB;AAOzB,SAAS,kBAAqC;AAC5C,MAAI,SAAS,OAAO,SAAS,SAAS,OAAO,WAAW;AACtD,WAAO,SAAS;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,eACP,cACyB;AACzB,QAAM,OAAO,aAAa,QAAQ,QAAQ;AAC1C,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;AAC5D,WAAO,EAAE,GAAI,KAAiC;AAAA,EAChD;AACA,SAAO,CAAC;AACV;AAEA,SAAS,oBACP,QACuC;AACvC,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,UAAU;AACvB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,IAAM,kBAAkB,MAAY;AAAC;AAErC,SAAS,oBAAiC;AACxC,SAAO;AAAA,IACL,qBAAqB,YAAY;AAAA,IACjC,mBAAmB,YAAY;AAAA,IAC/B,UAAU,YAAY;AAAA,IACtB,iCAAiC,MAAM;AAAA,IACvC,+BAA+B,MAAM;AAAA,EACvC;AACF;AAEO,SAAS,kBAAkB,MAA0C;AAC1E,QAAM,WAAW,gBAAgB;AACjC,MAAI,CAAC,UAAU;AACb,WAAO,kBAAkB;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL,MAAM,sBAAsB;AAC1B,YAAM,EAAE,OAAO,IAAI,MAAoB,kCAAoB;AAC3D,aAAO,oBAAoB,MAAM;AAAA,IACnC;AAAA,IAEA,MAAM,oBAAoB;AACxB,YAAM,EAAE,OAAO,IAAI,MAAoB,sCAAwB;AAC/D,aAAO,WAAW;AAAA,IACpB;AAAA,IAEA,MAAM,WAA6C;AACjD,YAAM,aAAa,MAAM,KAAK,oBAAoB;AAClD,UAAI,eAAe,WAAW;AAC5B,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,SAAS,MAAoB,oCAAsB;AAAA,UACvD,WAAW,KAAK;AAAA,QAClB,CAAC;AACD,eAAO;AAAA,UACL,OAAO,OAAO;AAAA,UACd,UAAU;AAAA,UACV;AAAA,QACF;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,gCAAgC,SAAS;AACvC,YAAM,eAA6B;AAAA,QACjC,CAAC,iBAAiB;AAChB,kBAAQ,eAAe,YAAY,CAAC;AAAA,QACtC;AAAA,MACF;AACA,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,IAEA,8BAA8B,SAAS;AACrC,YAAM,eACU,sDAAwC,CAAC,aAAa;AAClE,gBAAQ,eAAe,SAAS,YAAY,CAAC;AAAA,MAC/C,CAAC;AACH,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/push-expo/index.ts"],"sourcesContent":["import * as Notifications from \"expo-notifications\";\nimport { Platform } from \"react-native\";\nimport type { PushAdapter, PushAdapterToken } from \"../push/types.js\";\n\nexport type { PushAdapter, PushAdapterToken } from \"../push/types.js\";\n\ntype PlatformOS = \"ios\" | \"android\";\n\nconst ANDROID_CHANNEL_ID = \"default\";\n\nasync function ensureAndroidChannel(platform: PlatformOS): Promise<void> {\n if (platform !== \"android\") {\n return;\n }\n\n await Notifications.setNotificationChannelAsync(ANDROID_CHANNEL_ID, {\n name: ANDROID_CHANNEL_ID,\n importance: Notifications.AndroidImportance.MAX,\n });\n}\n\nfunction resolvePlatform(): PlatformOS | null {\n if (Platform.OS === \"ios\" || Platform.OS === \"android\") {\n return Platform.OS;\n }\n return null;\n}\n\nfunction flattenPayload(\n notification: Notifications.Notification,\n): Record<string, unknown> {\n const data = notification.request.content.data;\n if (data && typeof data === \"object\" && !Array.isArray(data)) {\n return { ...(data as Record<string, unknown>) };\n }\n return {};\n}\n\nfunction mapPermissionStatus(\n status: string,\n): \"granted\" | \"denied\" | \"undetermined\" {\n if (status === \"granted\") {\n return \"granted\";\n }\n if (status === \"denied\") {\n return \"denied\";\n }\n return \"undetermined\";\n}\n\nconst noopUnsubscribe = (): void => {};\n\nfunction createNoopAdapter(): PushAdapter {\n return {\n getPermissionStatus: async () => \"denied\",\n requestPermission: async () => false,\n getToken: async () => null,\n addNotificationReceivedListener: () => noopUnsubscribe,\n addNotificationOpenedListener: () => noopUnsubscribe,\n };\n}\n\nfunction configureForegroundNotifications(): void {\n Notifications.setNotificationHandler({\n handleNotification: async () => ({\n shouldPlaySound: true,\n shouldSetBadge: true,\n shouldShowBanner: true,\n shouldShowList: true,\n }),\n });\n}\n\nexport function createExpoAdapter(opts: { projectId: string }): PushAdapter {\n const platform = resolvePlatform();\n if (!platform) {\n return createNoopAdapter();\n }\n\n configureForegroundNotifications();\n\n return {\n async getPermissionStatus() {\n const { status } = await Notifications.getPermissionsAsync();\n return mapPermissionStatus(status);\n },\n\n async requestPermission() {\n await ensureAndroidChannel(platform);\n const { status } = await Notifications.requestPermissionsAsync();\n return status === \"granted\";\n },\n\n async getToken(): Promise<PushAdapterToken | null> {\n await ensureAndroidChannel(platform);\n const permission = await this.getPermissionStatus();\n if (permission !== \"granted\") {\n return null;\n }\n\n try {\n const result = await Notifications.getExpoPushTokenAsync({\n projectId: opts.projectId,\n });\n return {\n token: result.data,\n provider: \"expo\",\n platform,\n };\n } catch {\n return null;\n }\n },\n\n addNotificationReceivedListener(handler) {\n const subscription = Notifications.addNotificationReceivedListener(\n (notification) => {\n handler(flattenPayload(notification));\n },\n );\n return () => subscription.remove();\n },\n\n addNotificationOpenedListener(handler) {\n const subscription =\n Notifications.addNotificationResponseReceivedListener((response) => {\n handler(flattenPayload(response.notification));\n });\n return () => subscription.remove();\n },\n };\n}\n"],"mappings":";AAAA,YAAY,mBAAmB;AAC/B,SAAS,gBAAgB;AAOzB,IAAM,qBAAqB;AAE3B,eAAe,qBAAqB,UAAqC;AACvE,MAAI,aAAa,WAAW;AAC1B;AAAA,EACF;AAEA,QAAoB,0CAA4B,oBAAoB;AAAA,IAClE,MAAM;AAAA,IACN,YAA0B,gCAAkB;AAAA,EAC9C,CAAC;AACH;AAEA,SAAS,kBAAqC;AAC5C,MAAI,SAAS,OAAO,SAAS,SAAS,OAAO,WAAW;AACtD,WAAO,SAAS;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,eACP,cACyB;AACzB,QAAM,OAAO,aAAa,QAAQ,QAAQ;AAC1C,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;AAC5D,WAAO,EAAE,GAAI,KAAiC;AAAA,EAChD;AACA,SAAO,CAAC;AACV;AAEA,SAAS,oBACP,QACuC;AACvC,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,UAAU;AACvB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,IAAM,kBAAkB,MAAY;AAAC;AAErC,SAAS,oBAAiC;AACxC,SAAO;AAAA,IACL,qBAAqB,YAAY;AAAA,IACjC,mBAAmB,YAAY;AAAA,IAC/B,UAAU,YAAY;AAAA,IACtB,iCAAiC,MAAM;AAAA,IACvC,+BAA+B,MAAM;AAAA,EACvC;AACF;AAEA,SAAS,mCAAyC;AAChD,EAAc,qCAAuB;AAAA,IACnC,oBAAoB,aAAa;AAAA,MAC/B,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,IAClB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,kBAAkB,MAA0C;AAC1E,QAAM,WAAW,gBAAgB;AACjC,MAAI,CAAC,UAAU;AACb,WAAO,kBAAkB;AAAA,EAC3B;AAEA,mCAAiC;AAEjC,SAAO;AAAA,IACL,MAAM,sBAAsB;AAC1B,YAAM,EAAE,OAAO,IAAI,MAAoB,kCAAoB;AAC3D,aAAO,oBAAoB,MAAM;AAAA,IACnC;AAAA,IAEA,MAAM,oBAAoB;AACxB,YAAM,qBAAqB,QAAQ;AACnC,YAAM,EAAE,OAAO,IAAI,MAAoB,sCAAwB;AAC/D,aAAO,WAAW;AAAA,IACpB;AAAA,IAEA,MAAM,WAA6C;AACjD,YAAM,qBAAqB,QAAQ;AACnC,YAAM,aAAa,MAAM,KAAK,oBAAoB;AAClD,UAAI,eAAe,WAAW;AAC5B,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,SAAS,MAAoB,oCAAsB;AAAA,UACvD,WAAW,KAAK;AAAA,QAClB,CAAC;AACD,eAAO;AAAA,UACL,OAAO,OAAO;AAAA,UACd,UAAU;AAAA,UACV;AAAA,QACF;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,gCAAgC,SAAS;AACvC,YAAM,eAA6B;AAAA,QACjC,CAAC,iBAAiB;AAChB,kBAAQ,eAAe,YAAY,CAAC;AAAA,QACtC;AAAA,MACF;AACA,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,IAEA,8BAA8B,SAAS;AACrC,YAAM,eACU,sDAAwC,CAAC,aAAa;AAClE,gBAAQ,eAAe,SAAS,YAAY,CAAC;AAAA,MAC/C,CAAC;AACH,aAAO,MAAM,aAAa,OAAO;AAAA,IACnC;AAAA,EACF;AACF;","names":[]}
|