@safercity/sdk-react-native 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @safercity/sdk-react-native
2
2
 
3
- React Native adapter for SaferCity SDK with expo-fetch streaming support.
3
+ React Native adapter for SaferCity SDK with expo-fetch streaming support and secure token storage.
4
4
 
5
5
  ## Installation
6
6
 
@@ -19,10 +19,9 @@ bun add @safercity/sdk-react-native @safercity/sdk-react @tanstack/react-query
19
19
  ## Quick Start
20
20
 
21
21
  ```tsx
22
- import { createReactNativeClient, isStreamingSupported } from '@safercity/sdk-react-native';
22
+ import { createReactNativeClient } from '@safercity/sdk-react-native';
23
23
  import { SaferCityProvider } from '@safercity/sdk-react';
24
24
 
25
- // Create client with React Native optimizations
26
25
  const client = createReactNativeClient({
27
26
  baseUrl: 'https://api.safercity.com',
28
27
  token: userToken,
@@ -38,6 +37,76 @@ function App() {
38
37
  }
39
38
  ```
40
39
 
40
+ ## Authentication Modes
41
+
42
+ On mobile, **direct mode** is the most common pattern. Your app authenticates users with an external auth provider and passes the token to SaferCity:
43
+
44
+ ```tsx
45
+ import { SaferCityProvider } from '@safercity/sdk-react';
46
+
47
+ function App() {
48
+ return (
49
+ <SaferCityProvider
50
+ mode="direct"
51
+ baseUrl="https://api.safercity.com"
52
+ tenantId="tenant-123"
53
+ getAccessToken={() => getTokenFromAuthProvider()}
54
+ >
55
+ <Navigation />
56
+ </SaferCityProvider>
57
+ );
58
+ }
59
+ ```
60
+
61
+ **Proxy mode** also works if your mobile app communicates through a backend-for-frontend (BFF). Cookie mode is not typical for mobile apps.
62
+
63
+ ## Secure Token Storage
64
+
65
+ For server-side OAuth flows on mobile (e.g., background token management), the SDK provides secure storage that auto-detects the best available option:
66
+
67
+ ```typescript
68
+ import { createSecureTokenStorage, getStorageType } from '@safercity/sdk-react-native';
69
+ import { TokenManager } from '@safercity/sdk';
70
+
71
+ // Auto-detects: expo-secure-store > AsyncStorage > in-memory
72
+ const storage = createSecureTokenStorage();
73
+
74
+ const tokenManager = new TokenManager({
75
+ credentials: {
76
+ clientId: 'your-client-id',
77
+ clientSecret: 'your-client-secret',
78
+ },
79
+ storage,
80
+ });
81
+
82
+ // Get token (auto-refreshes if expired)
83
+ const token = await tokenManager.getToken();
84
+
85
+ // Check what storage is being used
86
+ const type = getStorageType(); // 'secure-store' | 'async-storage' | 'memory'
87
+ ```
88
+
89
+ ### Storage Priority
90
+
91
+ | Storage | Package | Security | Persistence |
92
+ |---------|---------|----------|-------------|
93
+ | `SecureStoreTokenStorage` | `expo-secure-store` | Encrypted (device keychain/keystore) | Yes |
94
+ | `AsyncStorageTokenStorage` | `@react-native-async-storage/async-storage` | Unencrypted | Yes |
95
+ | `InMemoryTokenStorage` | Built-in | N/A | No |
96
+
97
+ You can also use a specific storage class directly:
98
+
99
+ ```typescript
100
+ import {
101
+ SecureStoreTokenStorage,
102
+ AsyncStorageTokenStorage,
103
+ InMemoryTokenStorage,
104
+ } from '@safercity/sdk-react-native';
105
+
106
+ const storage = new SecureStoreTokenStorage();
107
+ await storage.waitForInit(); // wait for initial load from device storage
108
+ ```
109
+
41
110
  ## Streaming Support
42
111
 
43
112
  This package automatically uses `expo-fetch` for streaming when available (Expo SDK 52+).
@@ -45,7 +114,6 @@ This package automatically uses `expo-fetch` for streaming when available (Expo
45
114
  ```tsx
46
115
  import { isStreamingSupported } from '@safercity/sdk-react-native';
47
116
 
48
- // Check if streaming is supported
49
117
  if (isStreamingSupported()) {
50
118
  console.log('SSE streaming is available');
51
119
  } else {
@@ -57,6 +125,7 @@ if (isStreamingSupported()) {
57
125
 
58
126
  ```tsx
59
127
  import { usePanicStream } from '@safercity/sdk-react';
128
+ import { isStreamingSupported } from '@safercity/sdk-react-native';
60
129
 
61
130
  function PanicTracker({ panicId }: { panicId: string }) {
62
131
  const { data, isConnected, error } = usePanicStream(panicId, {
@@ -66,7 +135,6 @@ function PanicTracker({ panicId }: { panicId: string }) {
66
135
  });
67
136
 
68
137
  if (!isStreamingSupported()) {
69
- // Fallback to polling
70
138
  return <PollingPanicTracker panicId={panicId} />;
71
139
  }
72
140
 
@@ -81,13 +149,11 @@ function PanicTracker({ panicId }: { panicId: string }) {
81
149
 
82
150
  ## Without Expo
83
151
 
84
- For bare React Native projects without Expo, streaming will fall back to standard fetch.
85
- SSE may have limited support depending on your React Native version.
152
+ For bare React Native projects without Expo, streaming will fall back to standard fetch. SSE may have limited support depending on your React Native version.
86
153
 
87
154
  ```tsx
88
155
  import { createReactNativeClient } from '@safercity/sdk-react-native';
89
156
 
90
- // You can provide a custom fetch implementation
91
157
  const client = createReactNativeClient({
92
158
  baseUrl: 'https://api.safercity.com',
93
159
  token: userToken,
@@ -141,7 +207,19 @@ Returns `true` if expo-fetch is available and streaming is supported.
141
207
 
142
208
  ### getReactNativeFetch(customFetch?)
143
209
 
144
- Returns the best available fetch implementation for the current environment.
210
+ Returns the best available fetch implementation for the current environment. Priority: expo-fetch > custom > global fetch.
211
+
212
+ ### createReactNativeStreamAdapter(customFetch?)
213
+
214
+ Creates a `FetchStreamAdapter` using the best available fetch for the environment.
215
+
216
+ ### createSecureTokenStorage()
217
+
218
+ Auto-detects and returns the best available token storage. Priority: expo-secure-store > AsyncStorage > in-memory.
219
+
220
+ ### getStorageType()
221
+
222
+ Returns `'secure-store'` | `'async-storage'` | `'memory'` indicating what storage is available.
145
223
 
146
224
  ## Troubleshooting
147
225
 
package/dist/index.cjs CHANGED
@@ -50,9 +50,185 @@ function isStreamingSupported() {
50
50
  return supportsStreaming();
51
51
  }
52
52
 
53
+ // src/secure-storage.ts
54
+ var STORAGE_KEY = "@safercity:tokens";
55
+ function hasSecureStore() {
56
+ try {
57
+ __require("expo-secure-store");
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ function hasAsyncStorage() {
64
+ try {
65
+ __require("@react-native-async-storage/async-storage");
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+ var SecureStoreTokenStorage = class {
72
+ tokens = null;
73
+ initialized = false;
74
+ initPromise = null;
75
+ constructor() {
76
+ this.initPromise = this.loadFromStore();
77
+ }
78
+ async loadFromStore() {
79
+ if (this.initialized) return;
80
+ try {
81
+ const SecureStore = __require("expo-secure-store");
82
+ const value = await SecureStore.getItemAsync(STORAGE_KEY);
83
+ if (value) {
84
+ this.tokens = JSON.parse(value);
85
+ }
86
+ } catch (error) {
87
+ console.warn("[SaferCity] Failed to load tokens from SecureStore:", error);
88
+ }
89
+ this.initialized = true;
90
+ }
91
+ get() {
92
+ return this.tokens;
93
+ }
94
+ set(tokens) {
95
+ this.tokens = tokens;
96
+ this.saveToStore(tokens).catch((error) => {
97
+ console.warn("[SaferCity] Failed to save tokens to SecureStore:", error);
98
+ });
99
+ }
100
+ clear() {
101
+ this.tokens = null;
102
+ this.clearFromStore().catch((error) => {
103
+ console.warn("[SaferCity] Failed to clear tokens from SecureStore:", error);
104
+ });
105
+ }
106
+ async saveToStore(tokens) {
107
+ try {
108
+ const SecureStore = __require("expo-secure-store");
109
+ await SecureStore.setItemAsync(STORAGE_KEY, JSON.stringify(tokens));
110
+ } catch (error) {
111
+ console.warn("[SaferCity] Failed to save tokens to SecureStore:", error);
112
+ }
113
+ }
114
+ async clearFromStore() {
115
+ try {
116
+ const SecureStore = __require("expo-secure-store");
117
+ await SecureStore.deleteItemAsync(STORAGE_KEY);
118
+ } catch (error) {
119
+ console.warn("[SaferCity] Failed to clear tokens from SecureStore:", error);
120
+ }
121
+ }
122
+ /**
123
+ * Wait for initial load to complete
124
+ */
125
+ async waitForInit() {
126
+ if (this.initPromise) {
127
+ await this.initPromise;
128
+ }
129
+ }
130
+ };
131
+ var AsyncStorageTokenStorage = class {
132
+ tokens = null;
133
+ initialized = false;
134
+ initPromise = null;
135
+ constructor() {
136
+ this.initPromise = this.loadFromStorage();
137
+ }
138
+ async loadFromStorage() {
139
+ if (this.initialized) return;
140
+ try {
141
+ const AsyncStorage = __require("@react-native-async-storage/async-storage").default;
142
+ const value = await AsyncStorage.getItem(STORAGE_KEY);
143
+ if (value) {
144
+ this.tokens = JSON.parse(value);
145
+ }
146
+ } catch (error) {
147
+ console.warn("[SaferCity] Failed to load tokens from AsyncStorage:", error);
148
+ }
149
+ this.initialized = true;
150
+ }
151
+ get() {
152
+ return this.tokens;
153
+ }
154
+ set(tokens) {
155
+ this.tokens = tokens;
156
+ this.saveToStorage(tokens).catch((error) => {
157
+ console.warn("[SaferCity] Failed to save tokens to AsyncStorage:", error);
158
+ });
159
+ }
160
+ clear() {
161
+ this.tokens = null;
162
+ this.clearFromStorage().catch((error) => {
163
+ console.warn("[SaferCity] Failed to clear tokens from AsyncStorage:", error);
164
+ });
165
+ }
166
+ async saveToStorage(tokens) {
167
+ try {
168
+ const AsyncStorage = __require("@react-native-async-storage/async-storage").default;
169
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(tokens));
170
+ } catch (error) {
171
+ console.warn("[SaferCity] Failed to save tokens to AsyncStorage:", error);
172
+ }
173
+ }
174
+ async clearFromStorage() {
175
+ try {
176
+ const AsyncStorage = __require("@react-native-async-storage/async-storage").default;
177
+ await AsyncStorage.removeItem(STORAGE_KEY);
178
+ } catch (error) {
179
+ console.warn("[SaferCity] Failed to clear tokens from AsyncStorage:", error);
180
+ }
181
+ }
182
+ /**
183
+ * Wait for initial load to complete
184
+ */
185
+ async waitForInit() {
186
+ if (this.initPromise) {
187
+ await this.initPromise;
188
+ }
189
+ }
190
+ };
191
+ var InMemoryTokenStorage = class {
192
+ tokens = null;
193
+ get() {
194
+ return this.tokens;
195
+ }
196
+ set(tokens) {
197
+ this.tokens = tokens;
198
+ }
199
+ clear() {
200
+ this.tokens = null;
201
+ }
202
+ };
203
+ function createSecureTokenStorage() {
204
+ if (hasSecureStore()) {
205
+ return new SecureStoreTokenStorage();
206
+ }
207
+ if (hasAsyncStorage()) {
208
+ console.warn(
209
+ "[SaferCity] expo-secure-store not available. Using AsyncStorage which is less secure. Consider installing expo-secure-store for better security."
210
+ );
211
+ return new AsyncStorageTokenStorage();
212
+ }
213
+ console.warn(
214
+ "[SaferCity] No persistent storage available. Using in-memory storage. Tokens will be lost when the app closes."
215
+ );
216
+ return new InMemoryTokenStorage();
217
+ }
218
+ function getStorageType() {
219
+ if (hasSecureStore()) return "secure-store";
220
+ if (hasAsyncStorage()) return "async-storage";
221
+ return "memory";
222
+ }
223
+
224
+ exports.AsyncStorageTokenStorage = AsyncStorageTokenStorage;
225
+ exports.InMemoryTokenStorage = InMemoryTokenStorage;
226
+ exports.SecureStoreTokenStorage = SecureStoreTokenStorage;
53
227
  exports.createReactNativeClient = createReactNativeClient;
54
228
  exports.createReactNativeStreamAdapter = createReactNativeStreamAdapter;
229
+ exports.createSecureTokenStorage = createSecureTokenStorage;
55
230
  exports.getReactNativeFetch = getReactNativeFetch;
231
+ exports.getStorageType = getStorageType;
56
232
  exports.isStreamingSupported = isStreamingSupported;
57
233
  exports.supportsStreaming = supportsStreaming;
58
234
  Object.keys(sdk).forEach(function (k) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fetch-adapter.ts","../src/client.ts"],"names":["FetchStreamAdapter","createSaferCityClient"],"mappings":";;;;;;;;;;;AAYA,SAAS,YAAA,GAAoC;AAC3C,EAAA,IAAI;AAGF,IAAA,MAAM,SAAA,GAAY,UAAQ,YAAY,CAAA;AACtC,IAAA,OAAO,WAAW,KAAA,IAAS,IAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAUO,SAAS,oBAAoB,WAAA,EAA0C;AAC5E,EAAA,MAAM,YAAY,YAAA,EAAa;AAE/B,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,OAAO,SAAA;AAAA,EACT;AAEA,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,OAAO,WAAA;AAAA,EACT;AAIA,EAAA,OAAO,UAAA,CAAW,KAAA;AACpB;AAKO,SAAS,iBAAA,GAA6B;AAC3C,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,OAAO,SAAA,KAAc,IAAA;AACvB;AAMO,SAAS,+BAA+B,WAAA,EAAgD;AAC7F,EAAA,MAAM,OAAA,GAAU,oBAAoB,WAAW,CAAA;AAC/C,EAAA,OAAO,IAAIA,2BAAmB,OAAO,CAAA;AACvC;;;ACrBO,SAAS,wBAAwB,OAAA,EAAoD;AAC1F,EAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,OAAA,CAAQ,WAAW,CAAA;AACvD,EAAA,MAAM,aAAA,GAAgB,8BAAA,CAA+B,OAAA,CAAQ,WAAW,CAAA;AAExE,EAAA,OAAOC,yBAAA,CAAsB;AAAA,IAC3B,GAAG,OAAA;AAAA,IACH,KAAA,EAAO,OAAA;AAAA,IACP;AAAA,GACD,CAAA;AACH;AAgBO,SAAS,oBAAA,GAAgC;AAC9C,EAAA,OAAO,iBAAA,EAAkB;AAC3B","file":"index.cjs","sourcesContent":["/**\n * React Native fetch adapter with expo-fetch support for streaming\n * \n * Expo SDK 52+ includes expo-fetch which supports ReadableStream\n * for SSE and streaming responses.\n */\n\nimport { FetchStreamAdapter } from '@safercity/sdk-core';\n\n/**\n * Detect if expo-fetch is available (Expo SDK 52+)\n */\nfunction getExpoFetch(): typeof fetch | null {\n try {\n // Dynamic import to avoid bundling issues\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const expoFetch = require('expo/fetch');\n return expoFetch?.fetch ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the best available fetch implementation for React Native\n * \n * Priority:\n * 1. expo-fetch (if available) - supports streaming\n * 2. Custom fetch provided by user\n * 3. Global fetch (limited streaming support)\n */\nexport function getReactNativeFetch(customFetch?: typeof fetch): typeof fetch {\n const expoFetch = getExpoFetch();\n \n if (expoFetch) {\n return expoFetch;\n }\n \n if (customFetch) {\n return customFetch;\n }\n \n // Fall back to global fetch\n // Note: Streaming may not work properly without expo-fetch\n return globalThis.fetch;\n}\n\n/**\n * Check if the current environment supports streaming\n */\nexport function supportsStreaming(): boolean {\n const expoFetch = getExpoFetch();\n return expoFetch !== null;\n}\n\n/**\n * Create a stream adapter for React Native\n * Uses expo-fetch if available, otherwise falls back to standard fetch\n */\nexport function createReactNativeStreamAdapter(customFetch?: typeof fetch): FetchStreamAdapter {\n const fetchFn = getReactNativeFetch(customFetch);\n return new FetchStreamAdapter(fetchFn);\n}\n","/**\n * React Native SaferCity client\n * \n * Pre-configured client with React Native optimizations:\n * - Uses expo-fetch for streaming support (if available)\n * - Falls back to standard fetch for non-Expo apps\n */\n\nimport { createSaferCityClient, type SaferCityClientOptions, type SaferCityClient } from '@safercity/sdk';\nimport { createReactNativeStreamAdapter, getReactNativeFetch, supportsStreaming } from './fetch-adapter';\n\nexport interface ReactNativeClientOptions extends Omit<SaferCityClientOptions, 'fetch' | 'streamAdapter'> {\n /**\n * Custom fetch implementation (optional)\n * If not provided, will use expo-fetch (if available) or global fetch\n */\n customFetch?: typeof fetch;\n}\n\n/**\n * Create a SaferCity client optimized for React Native\n * \n * @example\n * ```tsx\n * import { createReactNativeClient } from '@safercity/sdk-react-native';\n * \n * const client = createReactNativeClient({\n * baseUrl: 'https://api.safercity.com',\n * token: userToken,\n * tenantId: 'tenant-123',\n * });\n * \n * // Use just like the regular client\n * const { data } = await client.health.check();\n * \n * // Streaming works with expo-fetch\n * for await (const event of client.panics.streamUpdates('panic-123')) {\n * console.log('Update:', event.data);\n * }\n * ```\n */\nexport function createReactNativeClient(options: ReactNativeClientOptions): SaferCityClient {\n const fetchFn = getReactNativeFetch(options.customFetch);\n const streamAdapter = createReactNativeStreamAdapter(options.customFetch);\n \n return createSaferCityClient({\n ...options,\n fetch: fetchFn,\n streamAdapter,\n });\n}\n\n/**\n * Check if streaming is supported in the current React Native environment\n * \n * @example\n * ```tsx\n * import { isStreamingSupported } from '@safercity/sdk-react-native';\n * \n * if (isStreamingSupported()) {\n * // Use streaming features\n * } else {\n * // Fall back to polling\n * }\n * ```\n */\nexport function isStreamingSupported(): boolean {\n return supportsStreaming();\n}\n"]}
1
+ {"version":3,"sources":["../src/fetch-adapter.ts","../src/client.ts","../src/secure-storage.ts"],"names":["FetchStreamAdapter","createSaferCityClient"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * React Native fetch adapter with expo-fetch support for streaming\n * \n * Expo SDK 52+ includes expo-fetch which supports ReadableStream\n * for SSE and streaming responses.\n */\n\nimport { FetchStreamAdapter } from '@safercity/sdk-core';\n\n/**\n * Detect if expo-fetch is available (Expo SDK 52+)\n */\nfunction getExpoFetch(): typeof fetch | null {\n try {\n // Dynamic import to avoid bundling issues\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const expoFetch = require('expo/fetch');\n return expoFetch?.fetch ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the best available fetch implementation for React Native\n * \n * Priority:\n * 1. expo-fetch (if available) - supports streaming\n * 2. Custom fetch provided by user\n * 3. Global fetch (limited streaming support)\n */\nexport function getReactNativeFetch(customFetch?: typeof fetch): typeof fetch {\n const expoFetch = getExpoFetch();\n \n if (expoFetch) {\n return expoFetch;\n }\n \n if (customFetch) {\n return customFetch;\n }\n \n // Fall back to global fetch\n // Note: Streaming may not work properly without expo-fetch\n return globalThis.fetch;\n}\n\n/**\n * Check if the current environment supports streaming\n */\nexport function supportsStreaming(): boolean {\n const expoFetch = getExpoFetch();\n return expoFetch !== null;\n}\n\n/**\n * Create a stream adapter for React Native\n * Uses expo-fetch if available, otherwise falls back to standard fetch\n */\nexport function createReactNativeStreamAdapter(customFetch?: typeof fetch): FetchStreamAdapter {\n const fetchFn = getReactNativeFetch(customFetch);\n return new FetchStreamAdapter(fetchFn);\n}\n","/**\n * React Native SaferCity client\n * \n * Pre-configured client with React Native optimizations:\n * - Uses expo-fetch for streaming support (if available)\n * - Falls back to standard fetch for non-Expo apps\n */\n\nimport { createSaferCityClient, type SaferCityClientOptions, type SaferCityClient } from '@safercity/sdk';\nimport { createReactNativeStreamAdapter, getReactNativeFetch, supportsStreaming } from './fetch-adapter';\n\nexport interface ReactNativeClientOptions extends Omit<SaferCityClientOptions, 'fetch' | 'streamAdapter'> {\n /**\n * Custom fetch implementation (optional)\n * If not provided, will use expo-fetch (if available) or global fetch\n */\n customFetch?: typeof fetch;\n}\n\n/**\n * Create a SaferCity client optimized for React Native\n * \n * @example\n * ```tsx\n * import { createReactNativeClient } from '@safercity/sdk-react-native';\n * \n * const client = createReactNativeClient({\n * baseUrl: 'https://api.safercity.com',\n * token: userToken,\n * tenantId: 'tenant-123',\n * });\n * \n * // Use just like the regular client\n * const { data } = await client.health.check();\n * \n * // Streaming works with expo-fetch\n * for await (const event of client.panics.streamUpdates('panic-123')) {\n * console.log('Update:', event.data);\n * }\n * ```\n */\nexport function createReactNativeClient(options: ReactNativeClientOptions): SaferCityClient {\n const fetchFn = getReactNativeFetch(options.customFetch);\n const streamAdapter = createReactNativeStreamAdapter(options.customFetch);\n \n return createSaferCityClient({\n ...options,\n fetch: fetchFn,\n streamAdapter,\n });\n}\n\n/**\n * Check if streaming is supported in the current React Native environment\n * \n * @example\n * ```tsx\n * import { isStreamingSupported } from '@safercity/sdk-react-native';\n * \n * if (isStreamingSupported()) {\n * // Use streaming features\n * } else {\n * // Fall back to polling\n * }\n * ```\n */\nexport function isStreamingSupported(): boolean {\n return supportsStreaming();\n}\n","/**\n * Secure Token Storage for React Native\n * \n * Provides secure storage for OAuth tokens using:\n * - expo-secure-store (preferred, if available)\n * - AsyncStorage fallback (less secure, but widely available)\n */\n\nimport type { TokenStorage, AuthTokens } from '@safercity/sdk-core';\n\n// Storage key prefix\nconst STORAGE_KEY = '@safercity:tokens';\n\n/**\n * Check if expo-secure-store is available\n */\nfunction hasSecureStore(): boolean {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('expo-secure-store');\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if @react-native-async-storage/async-storage is available\n */\nfunction hasAsyncStorage(): boolean {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('@react-native-async-storage/async-storage');\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Secure token storage using expo-secure-store\n * \n * This is the most secure option for React Native apps.\n * Data is encrypted using the device's secure enclave (iOS) or keystore (Android).\n */\nexport class SecureStoreTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n private initialized = false;\n private initPromise: Promise<void> | null = null;\n\n constructor() {\n // Start loading tokens immediately\n this.initPromise = this.loadFromStore();\n }\n\n private async loadFromStore(): Promise<void> {\n if (this.initialized) return;\n \n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const SecureStore = require('expo-secure-store');\n const value = await SecureStore.getItemAsync(STORAGE_KEY);\n \n if (value) {\n this.tokens = JSON.parse(value);\n }\n } catch (error) {\n console.warn('[SaferCity] Failed to load tokens from SecureStore:', error);\n }\n \n this.initialized = true;\n }\n\n get(): AuthTokens | null {\n // Return cached value synchronously\n return this.tokens;\n }\n\n set(tokens: AuthTokens): void {\n this.tokens = tokens;\n \n // Persist asynchronously\n this.saveToStore(tokens).catch((error) => {\n console.warn('[SaferCity] Failed to save tokens to SecureStore:', error);\n });\n }\n\n clear(): void {\n this.tokens = null;\n \n // Clear from storage asynchronously\n this.clearFromStore().catch((error) => {\n console.warn('[SaferCity] Failed to clear tokens from SecureStore:', error);\n });\n }\n\n private async saveToStore(tokens: AuthTokens): Promise<void> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const SecureStore = require('expo-secure-store');\n await SecureStore.setItemAsync(STORAGE_KEY, JSON.stringify(tokens));\n } catch (error) {\n console.warn('[SaferCity] Failed to save tokens to SecureStore:', error);\n }\n }\n\n private async clearFromStore(): Promise<void> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const SecureStore = require('expo-secure-store');\n await SecureStore.deleteItemAsync(STORAGE_KEY);\n } catch (error) {\n console.warn('[SaferCity] Failed to clear tokens from SecureStore:', error);\n }\n }\n\n /**\n * Wait for initial load to complete\n */\n async waitForInit(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n }\n }\n}\n\n/**\n * Token storage using AsyncStorage (fallback)\n * \n * Less secure than SecureStore but works without Expo.\n * Data is stored unencrypted on the device.\n */\nexport class AsyncStorageTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n private initialized = false;\n private initPromise: Promise<void> | null = null;\n\n constructor() {\n // Start loading tokens immediately\n this.initPromise = this.loadFromStorage();\n }\n\n private async loadFromStorage(): Promise<void> {\n if (this.initialized) return;\n \n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const AsyncStorage = require('@react-native-async-storage/async-storage').default;\n const value = await AsyncStorage.getItem(STORAGE_KEY);\n \n if (value) {\n this.tokens = JSON.parse(value);\n }\n } catch (error) {\n console.warn('[SaferCity] Failed to load tokens from AsyncStorage:', error);\n }\n \n this.initialized = true;\n }\n\n get(): AuthTokens | null {\n return this.tokens;\n }\n\n set(tokens: AuthTokens): void {\n this.tokens = tokens;\n \n this.saveToStorage(tokens).catch((error) => {\n console.warn('[SaferCity] Failed to save tokens to AsyncStorage:', error);\n });\n }\n\n clear(): void {\n this.tokens = null;\n \n this.clearFromStorage().catch((error) => {\n console.warn('[SaferCity] Failed to clear tokens from AsyncStorage:', error);\n });\n }\n\n private async saveToStorage(tokens: AuthTokens): Promise<void> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const AsyncStorage = require('@react-native-async-storage/async-storage').default;\n await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(tokens));\n } catch (error) {\n console.warn('[SaferCity] Failed to save tokens to AsyncStorage:', error);\n }\n }\n\n private async clearFromStorage(): Promise<void> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const AsyncStorage = require('@react-native-async-storage/async-storage').default;\n await AsyncStorage.removeItem(STORAGE_KEY);\n } catch (error) {\n console.warn('[SaferCity] Failed to clear tokens from AsyncStorage:', error);\n }\n }\n\n /**\n * Wait for initial load to complete\n */\n async waitForInit(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n }\n }\n}\n\n/**\n * In-memory token storage (no persistence)\n * \n * Use this when you don't want to persist tokens.\n * Tokens will be lost when the app is closed.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n get(): AuthTokens | null {\n return this.tokens;\n }\n\n set(tokens: AuthTokens): void {\n this.tokens = tokens;\n }\n\n clear(): void {\n this.tokens = null;\n }\n}\n\n/**\n * Create the best available token storage for the current environment\n * \n * Priority:\n * 1. expo-secure-store (most secure)\n * 2. @react-native-async-storage/async-storage (less secure)\n * 3. In-memory (no persistence)\n * \n * @example\n * ```tsx\n * import { createSecureTokenStorage } from '@safercity/sdk-react-native';\n * import { TokenManager } from '@safercity/sdk';\n * \n * const tokenManager = new TokenManager({\n * credentials: {\n * clientId: 'my-client',\n * clientSecret: 'my-secret',\n * },\n * storage: createSecureTokenStorage(),\n * });\n * ```\n */\nexport function createSecureTokenStorage(): TokenStorage {\n if (hasSecureStore()) {\n return new SecureStoreTokenStorage();\n }\n \n if (hasAsyncStorage()) {\n console.warn(\n '[SaferCity] expo-secure-store not available. ' +\n 'Using AsyncStorage which is less secure. ' +\n 'Consider installing expo-secure-store for better security.'\n );\n return new AsyncStorageTokenStorage();\n }\n \n console.warn(\n '[SaferCity] No persistent storage available. ' +\n 'Using in-memory storage. Tokens will be lost when the app closes.'\n );\n return new InMemoryTokenStorage();\n}\n\n/**\n * Check what type of storage is available\n */\nexport function getStorageType(): 'secure-store' | 'async-storage' | 'memory' {\n if (hasSecureStore()) return 'secure-store';\n if (hasAsyncStorage()) return 'async-storage';\n return 'memory';\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { SaferCityClientOptions, SaferCityClient } from '@safercity/sdk';
2
2
  export * from '@safercity/sdk';
3
- import { FetchStreamAdapter } from '@safercity/sdk-core';
3
+ import { FetchStreamAdapter, TokenStorage, AuthTokens } from '@safercity/sdk-core';
4
4
 
5
5
  /**
6
6
  * React Native SaferCity client
@@ -82,4 +82,96 @@ declare function supportsStreaming(): boolean;
82
82
  */
83
83
  declare function createReactNativeStreamAdapter(customFetch?: typeof fetch): FetchStreamAdapter;
84
84
 
85
- export { type ReactNativeClientOptions, createReactNativeClient, createReactNativeStreamAdapter, getReactNativeFetch, isStreamingSupported, supportsStreaming };
85
+ /**
86
+ * Secure Token Storage for React Native
87
+ *
88
+ * Provides secure storage for OAuth tokens using:
89
+ * - expo-secure-store (preferred, if available)
90
+ * - AsyncStorage fallback (less secure, but widely available)
91
+ */
92
+
93
+ /**
94
+ * Secure token storage using expo-secure-store
95
+ *
96
+ * This is the most secure option for React Native apps.
97
+ * Data is encrypted using the device's secure enclave (iOS) or keystore (Android).
98
+ */
99
+ declare class SecureStoreTokenStorage implements TokenStorage {
100
+ private tokens;
101
+ private initialized;
102
+ private initPromise;
103
+ constructor();
104
+ private loadFromStore;
105
+ get(): AuthTokens | null;
106
+ set(tokens: AuthTokens): void;
107
+ clear(): void;
108
+ private saveToStore;
109
+ private clearFromStore;
110
+ /**
111
+ * Wait for initial load to complete
112
+ */
113
+ waitForInit(): Promise<void>;
114
+ }
115
+ /**
116
+ * Token storage using AsyncStorage (fallback)
117
+ *
118
+ * Less secure than SecureStore but works without Expo.
119
+ * Data is stored unencrypted on the device.
120
+ */
121
+ declare class AsyncStorageTokenStorage implements TokenStorage {
122
+ private tokens;
123
+ private initialized;
124
+ private initPromise;
125
+ constructor();
126
+ private loadFromStorage;
127
+ get(): AuthTokens | null;
128
+ set(tokens: AuthTokens): void;
129
+ clear(): void;
130
+ private saveToStorage;
131
+ private clearFromStorage;
132
+ /**
133
+ * Wait for initial load to complete
134
+ */
135
+ waitForInit(): Promise<void>;
136
+ }
137
+ /**
138
+ * In-memory token storage (no persistence)
139
+ *
140
+ * Use this when you don't want to persist tokens.
141
+ * Tokens will be lost when the app is closed.
142
+ */
143
+ declare class InMemoryTokenStorage implements TokenStorage {
144
+ private tokens;
145
+ get(): AuthTokens | null;
146
+ set(tokens: AuthTokens): void;
147
+ clear(): void;
148
+ }
149
+ /**
150
+ * Create the best available token storage for the current environment
151
+ *
152
+ * Priority:
153
+ * 1. expo-secure-store (most secure)
154
+ * 2. @react-native-async-storage/async-storage (less secure)
155
+ * 3. In-memory (no persistence)
156
+ *
157
+ * @example
158
+ * ```tsx
159
+ * import { createSecureTokenStorage } from '@safercity/sdk-react-native';
160
+ * import { TokenManager } from '@safercity/sdk';
161
+ *
162
+ * const tokenManager = new TokenManager({
163
+ * credentials: {
164
+ * clientId: 'my-client',
165
+ * clientSecret: 'my-secret',
166
+ * },
167
+ * storage: createSecureTokenStorage(),
168
+ * });
169
+ * ```
170
+ */
171
+ declare function createSecureTokenStorage(): TokenStorage;
172
+ /**
173
+ * Check what type of storage is available
174
+ */
175
+ declare function getStorageType(): 'secure-store' | 'async-storage' | 'memory';
176
+
177
+ export { AsyncStorageTokenStorage, InMemoryTokenStorage, type ReactNativeClientOptions, SecureStoreTokenStorage, createReactNativeClient, createReactNativeStreamAdapter, createSecureTokenStorage, getReactNativeFetch, getStorageType, isStreamingSupported, supportsStreaming };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { SaferCityClientOptions, SaferCityClient } from '@safercity/sdk';
2
2
  export * from '@safercity/sdk';
3
- import { FetchStreamAdapter } from '@safercity/sdk-core';
3
+ import { FetchStreamAdapter, TokenStorage, AuthTokens } from '@safercity/sdk-core';
4
4
 
5
5
  /**
6
6
  * React Native SaferCity client
@@ -82,4 +82,96 @@ declare function supportsStreaming(): boolean;
82
82
  */
83
83
  declare function createReactNativeStreamAdapter(customFetch?: typeof fetch): FetchStreamAdapter;
84
84
 
85
- export { type ReactNativeClientOptions, createReactNativeClient, createReactNativeStreamAdapter, getReactNativeFetch, isStreamingSupported, supportsStreaming };
85
+ /**
86
+ * Secure Token Storage for React Native
87
+ *
88
+ * Provides secure storage for OAuth tokens using:
89
+ * - expo-secure-store (preferred, if available)
90
+ * - AsyncStorage fallback (less secure, but widely available)
91
+ */
92
+
93
+ /**
94
+ * Secure token storage using expo-secure-store
95
+ *
96
+ * This is the most secure option for React Native apps.
97
+ * Data is encrypted using the device's secure enclave (iOS) or keystore (Android).
98
+ */
99
+ declare class SecureStoreTokenStorage implements TokenStorage {
100
+ private tokens;
101
+ private initialized;
102
+ private initPromise;
103
+ constructor();
104
+ private loadFromStore;
105
+ get(): AuthTokens | null;
106
+ set(tokens: AuthTokens): void;
107
+ clear(): void;
108
+ private saveToStore;
109
+ private clearFromStore;
110
+ /**
111
+ * Wait for initial load to complete
112
+ */
113
+ waitForInit(): Promise<void>;
114
+ }
115
+ /**
116
+ * Token storage using AsyncStorage (fallback)
117
+ *
118
+ * Less secure than SecureStore but works without Expo.
119
+ * Data is stored unencrypted on the device.
120
+ */
121
+ declare class AsyncStorageTokenStorage implements TokenStorage {
122
+ private tokens;
123
+ private initialized;
124
+ private initPromise;
125
+ constructor();
126
+ private loadFromStorage;
127
+ get(): AuthTokens | null;
128
+ set(tokens: AuthTokens): void;
129
+ clear(): void;
130
+ private saveToStorage;
131
+ private clearFromStorage;
132
+ /**
133
+ * Wait for initial load to complete
134
+ */
135
+ waitForInit(): Promise<void>;
136
+ }
137
+ /**
138
+ * In-memory token storage (no persistence)
139
+ *
140
+ * Use this when you don't want to persist tokens.
141
+ * Tokens will be lost when the app is closed.
142
+ */
143
+ declare class InMemoryTokenStorage implements TokenStorage {
144
+ private tokens;
145
+ get(): AuthTokens | null;
146
+ set(tokens: AuthTokens): void;
147
+ clear(): void;
148
+ }
149
+ /**
150
+ * Create the best available token storage for the current environment
151
+ *
152
+ * Priority:
153
+ * 1. expo-secure-store (most secure)
154
+ * 2. @react-native-async-storage/async-storage (less secure)
155
+ * 3. In-memory (no persistence)
156
+ *
157
+ * @example
158
+ * ```tsx
159
+ * import { createSecureTokenStorage } from '@safercity/sdk-react-native';
160
+ * import { TokenManager } from '@safercity/sdk';
161
+ *
162
+ * const tokenManager = new TokenManager({
163
+ * credentials: {
164
+ * clientId: 'my-client',
165
+ * clientSecret: 'my-secret',
166
+ * },
167
+ * storage: createSecureTokenStorage(),
168
+ * });
169
+ * ```
170
+ */
171
+ declare function createSecureTokenStorage(): TokenStorage;
172
+ /**
173
+ * Check what type of storage is available
174
+ */
175
+ declare function getStorageType(): 'secure-store' | 'async-storage' | 'memory';
176
+
177
+ export { AsyncStorageTokenStorage, InMemoryTokenStorage, type ReactNativeClientOptions, SecureStoreTokenStorage, createReactNativeClient, createReactNativeStreamAdapter, createSecureTokenStorage, getReactNativeFetch, getStorageType, isStreamingSupported, supportsStreaming };
package/dist/index.js CHANGED
@@ -49,6 +49,177 @@ function isStreamingSupported() {
49
49
  return supportsStreaming();
50
50
  }
51
51
 
52
- export { createReactNativeClient, createReactNativeStreamAdapter, getReactNativeFetch, isStreamingSupported, supportsStreaming };
52
+ // src/secure-storage.ts
53
+ var STORAGE_KEY = "@safercity:tokens";
54
+ function hasSecureStore() {
55
+ try {
56
+ __require("expo-secure-store");
57
+ return true;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ function hasAsyncStorage() {
63
+ try {
64
+ __require("@react-native-async-storage/async-storage");
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+ var SecureStoreTokenStorage = class {
71
+ tokens = null;
72
+ initialized = false;
73
+ initPromise = null;
74
+ constructor() {
75
+ this.initPromise = this.loadFromStore();
76
+ }
77
+ async loadFromStore() {
78
+ if (this.initialized) return;
79
+ try {
80
+ const SecureStore = __require("expo-secure-store");
81
+ const value = await SecureStore.getItemAsync(STORAGE_KEY);
82
+ if (value) {
83
+ this.tokens = JSON.parse(value);
84
+ }
85
+ } catch (error) {
86
+ console.warn("[SaferCity] Failed to load tokens from SecureStore:", error);
87
+ }
88
+ this.initialized = true;
89
+ }
90
+ get() {
91
+ return this.tokens;
92
+ }
93
+ set(tokens) {
94
+ this.tokens = tokens;
95
+ this.saveToStore(tokens).catch((error) => {
96
+ console.warn("[SaferCity] Failed to save tokens to SecureStore:", error);
97
+ });
98
+ }
99
+ clear() {
100
+ this.tokens = null;
101
+ this.clearFromStore().catch((error) => {
102
+ console.warn("[SaferCity] Failed to clear tokens from SecureStore:", error);
103
+ });
104
+ }
105
+ async saveToStore(tokens) {
106
+ try {
107
+ const SecureStore = __require("expo-secure-store");
108
+ await SecureStore.setItemAsync(STORAGE_KEY, JSON.stringify(tokens));
109
+ } catch (error) {
110
+ console.warn("[SaferCity] Failed to save tokens to SecureStore:", error);
111
+ }
112
+ }
113
+ async clearFromStore() {
114
+ try {
115
+ const SecureStore = __require("expo-secure-store");
116
+ await SecureStore.deleteItemAsync(STORAGE_KEY);
117
+ } catch (error) {
118
+ console.warn("[SaferCity] Failed to clear tokens from SecureStore:", error);
119
+ }
120
+ }
121
+ /**
122
+ * Wait for initial load to complete
123
+ */
124
+ async waitForInit() {
125
+ if (this.initPromise) {
126
+ await this.initPromise;
127
+ }
128
+ }
129
+ };
130
+ var AsyncStorageTokenStorage = class {
131
+ tokens = null;
132
+ initialized = false;
133
+ initPromise = null;
134
+ constructor() {
135
+ this.initPromise = this.loadFromStorage();
136
+ }
137
+ async loadFromStorage() {
138
+ if (this.initialized) return;
139
+ try {
140
+ const AsyncStorage = __require("@react-native-async-storage/async-storage").default;
141
+ const value = await AsyncStorage.getItem(STORAGE_KEY);
142
+ if (value) {
143
+ this.tokens = JSON.parse(value);
144
+ }
145
+ } catch (error) {
146
+ console.warn("[SaferCity] Failed to load tokens from AsyncStorage:", error);
147
+ }
148
+ this.initialized = true;
149
+ }
150
+ get() {
151
+ return this.tokens;
152
+ }
153
+ set(tokens) {
154
+ this.tokens = tokens;
155
+ this.saveToStorage(tokens).catch((error) => {
156
+ console.warn("[SaferCity] Failed to save tokens to AsyncStorage:", error);
157
+ });
158
+ }
159
+ clear() {
160
+ this.tokens = null;
161
+ this.clearFromStorage().catch((error) => {
162
+ console.warn("[SaferCity] Failed to clear tokens from AsyncStorage:", error);
163
+ });
164
+ }
165
+ async saveToStorage(tokens) {
166
+ try {
167
+ const AsyncStorage = __require("@react-native-async-storage/async-storage").default;
168
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(tokens));
169
+ } catch (error) {
170
+ console.warn("[SaferCity] Failed to save tokens to AsyncStorage:", error);
171
+ }
172
+ }
173
+ async clearFromStorage() {
174
+ try {
175
+ const AsyncStorage = __require("@react-native-async-storage/async-storage").default;
176
+ await AsyncStorage.removeItem(STORAGE_KEY);
177
+ } catch (error) {
178
+ console.warn("[SaferCity] Failed to clear tokens from AsyncStorage:", error);
179
+ }
180
+ }
181
+ /**
182
+ * Wait for initial load to complete
183
+ */
184
+ async waitForInit() {
185
+ if (this.initPromise) {
186
+ await this.initPromise;
187
+ }
188
+ }
189
+ };
190
+ var InMemoryTokenStorage = class {
191
+ tokens = null;
192
+ get() {
193
+ return this.tokens;
194
+ }
195
+ set(tokens) {
196
+ this.tokens = tokens;
197
+ }
198
+ clear() {
199
+ this.tokens = null;
200
+ }
201
+ };
202
+ function createSecureTokenStorage() {
203
+ if (hasSecureStore()) {
204
+ return new SecureStoreTokenStorage();
205
+ }
206
+ if (hasAsyncStorage()) {
207
+ console.warn(
208
+ "[SaferCity] expo-secure-store not available. Using AsyncStorage which is less secure. Consider installing expo-secure-store for better security."
209
+ );
210
+ return new AsyncStorageTokenStorage();
211
+ }
212
+ console.warn(
213
+ "[SaferCity] No persistent storage available. Using in-memory storage. Tokens will be lost when the app closes."
214
+ );
215
+ return new InMemoryTokenStorage();
216
+ }
217
+ function getStorageType() {
218
+ if (hasSecureStore()) return "secure-store";
219
+ if (hasAsyncStorage()) return "async-storage";
220
+ return "memory";
221
+ }
222
+
223
+ export { AsyncStorageTokenStorage, InMemoryTokenStorage, SecureStoreTokenStorage, createReactNativeClient, createReactNativeStreamAdapter, createSecureTokenStorage, getReactNativeFetch, getStorageType, isStreamingSupported, supportsStreaming };
53
224
  //# sourceMappingURL=index.js.map
54
225
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fetch-adapter.ts","../src/client.ts"],"names":[],"mappings":";;;;;;;;;;AAYA,SAAS,YAAA,GAAoC;AAC3C,EAAA,IAAI;AAGF,IAAA,MAAM,SAAA,GAAY,UAAQ,YAAY,CAAA;AACtC,IAAA,OAAO,WAAW,KAAA,IAAS,IAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAUO,SAAS,oBAAoB,WAAA,EAA0C;AAC5E,EAAA,MAAM,YAAY,YAAA,EAAa;AAE/B,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,OAAO,SAAA;AAAA,EACT;AAEA,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,OAAO,WAAA;AAAA,EACT;AAIA,EAAA,OAAO,UAAA,CAAW,KAAA;AACpB;AAKO,SAAS,iBAAA,GAA6B;AAC3C,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,OAAO,SAAA,KAAc,IAAA;AACvB;AAMO,SAAS,+BAA+B,WAAA,EAAgD;AAC7F,EAAA,MAAM,OAAA,GAAU,oBAAoB,WAAW,CAAA;AAC/C,EAAA,OAAO,IAAI,mBAAmB,OAAO,CAAA;AACvC;;;ACrBO,SAAS,wBAAwB,OAAA,EAAoD;AAC1F,EAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,OAAA,CAAQ,WAAW,CAAA;AACvD,EAAA,MAAM,aAAA,GAAgB,8BAAA,CAA+B,OAAA,CAAQ,WAAW,CAAA;AAExE,EAAA,OAAO,qBAAA,CAAsB;AAAA,IAC3B,GAAG,OAAA;AAAA,IACH,KAAA,EAAO,OAAA;AAAA,IACP;AAAA,GACD,CAAA;AACH;AAgBO,SAAS,oBAAA,GAAgC;AAC9C,EAAA,OAAO,iBAAA,EAAkB;AAC3B","file":"index.js","sourcesContent":["/**\n * React Native fetch adapter with expo-fetch support for streaming\n * \n * Expo SDK 52+ includes expo-fetch which supports ReadableStream\n * for SSE and streaming responses.\n */\n\nimport { FetchStreamAdapter } from '@safercity/sdk-core';\n\n/**\n * Detect if expo-fetch is available (Expo SDK 52+)\n */\nfunction getExpoFetch(): typeof fetch | null {\n try {\n // Dynamic import to avoid bundling issues\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const expoFetch = require('expo/fetch');\n return expoFetch?.fetch ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the best available fetch implementation for React Native\n * \n * Priority:\n * 1. expo-fetch (if available) - supports streaming\n * 2. Custom fetch provided by user\n * 3. Global fetch (limited streaming support)\n */\nexport function getReactNativeFetch(customFetch?: typeof fetch): typeof fetch {\n const expoFetch = getExpoFetch();\n \n if (expoFetch) {\n return expoFetch;\n }\n \n if (customFetch) {\n return customFetch;\n }\n \n // Fall back to global fetch\n // Note: Streaming may not work properly without expo-fetch\n return globalThis.fetch;\n}\n\n/**\n * Check if the current environment supports streaming\n */\nexport function supportsStreaming(): boolean {\n const expoFetch = getExpoFetch();\n return expoFetch !== null;\n}\n\n/**\n * Create a stream adapter for React Native\n * Uses expo-fetch if available, otherwise falls back to standard fetch\n */\nexport function createReactNativeStreamAdapter(customFetch?: typeof fetch): FetchStreamAdapter {\n const fetchFn = getReactNativeFetch(customFetch);\n return new FetchStreamAdapter(fetchFn);\n}\n","/**\n * React Native SaferCity client\n * \n * Pre-configured client with React Native optimizations:\n * - Uses expo-fetch for streaming support (if available)\n * - Falls back to standard fetch for non-Expo apps\n */\n\nimport { createSaferCityClient, type SaferCityClientOptions, type SaferCityClient } from '@safercity/sdk';\nimport { createReactNativeStreamAdapter, getReactNativeFetch, supportsStreaming } from './fetch-adapter';\n\nexport interface ReactNativeClientOptions extends Omit<SaferCityClientOptions, 'fetch' | 'streamAdapter'> {\n /**\n * Custom fetch implementation (optional)\n * If not provided, will use expo-fetch (if available) or global fetch\n */\n customFetch?: typeof fetch;\n}\n\n/**\n * Create a SaferCity client optimized for React Native\n * \n * @example\n * ```tsx\n * import { createReactNativeClient } from '@safercity/sdk-react-native';\n * \n * const client = createReactNativeClient({\n * baseUrl: 'https://api.safercity.com',\n * token: userToken,\n * tenantId: 'tenant-123',\n * });\n * \n * // Use just like the regular client\n * const { data } = await client.health.check();\n * \n * // Streaming works with expo-fetch\n * for await (const event of client.panics.streamUpdates('panic-123')) {\n * console.log('Update:', event.data);\n * }\n * ```\n */\nexport function createReactNativeClient(options: ReactNativeClientOptions): SaferCityClient {\n const fetchFn = getReactNativeFetch(options.customFetch);\n const streamAdapter = createReactNativeStreamAdapter(options.customFetch);\n \n return createSaferCityClient({\n ...options,\n fetch: fetchFn,\n streamAdapter,\n });\n}\n\n/**\n * Check if streaming is supported in the current React Native environment\n * \n * @example\n * ```tsx\n * import { isStreamingSupported } from '@safercity/sdk-react-native';\n * \n * if (isStreamingSupported()) {\n * // Use streaming features\n * } else {\n * // Fall back to polling\n * }\n * ```\n */\nexport function isStreamingSupported(): boolean {\n return supportsStreaming();\n}\n"]}
1
+ {"version":3,"sources":["../src/fetch-adapter.ts","../src/client.ts","../src/secure-storage.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * React Native fetch adapter with expo-fetch support for streaming\n * \n * Expo SDK 52+ includes expo-fetch which supports ReadableStream\n * for SSE and streaming responses.\n */\n\nimport { FetchStreamAdapter } from '@safercity/sdk-core';\n\n/**\n * Detect if expo-fetch is available (Expo SDK 52+)\n */\nfunction getExpoFetch(): typeof fetch | null {\n try {\n // Dynamic import to avoid bundling issues\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const expoFetch = require('expo/fetch');\n return expoFetch?.fetch ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the best available fetch implementation for React Native\n * \n * Priority:\n * 1. expo-fetch (if available) - supports streaming\n * 2. Custom fetch provided by user\n * 3. Global fetch (limited streaming support)\n */\nexport function getReactNativeFetch(customFetch?: typeof fetch): typeof fetch {\n const expoFetch = getExpoFetch();\n \n if (expoFetch) {\n return expoFetch;\n }\n \n if (customFetch) {\n return customFetch;\n }\n \n // Fall back to global fetch\n // Note: Streaming may not work properly without expo-fetch\n return globalThis.fetch;\n}\n\n/**\n * Check if the current environment supports streaming\n */\nexport function supportsStreaming(): boolean {\n const expoFetch = getExpoFetch();\n return expoFetch !== null;\n}\n\n/**\n * Create a stream adapter for React Native\n * Uses expo-fetch if available, otherwise falls back to standard fetch\n */\nexport function createReactNativeStreamAdapter(customFetch?: typeof fetch): FetchStreamAdapter {\n const fetchFn = getReactNativeFetch(customFetch);\n return new FetchStreamAdapter(fetchFn);\n}\n","/**\n * React Native SaferCity client\n * \n * Pre-configured client with React Native optimizations:\n * - Uses expo-fetch for streaming support (if available)\n * - Falls back to standard fetch for non-Expo apps\n */\n\nimport { createSaferCityClient, type SaferCityClientOptions, type SaferCityClient } from '@safercity/sdk';\nimport { createReactNativeStreamAdapter, getReactNativeFetch, supportsStreaming } from './fetch-adapter';\n\nexport interface ReactNativeClientOptions extends Omit<SaferCityClientOptions, 'fetch' | 'streamAdapter'> {\n /**\n * Custom fetch implementation (optional)\n * If not provided, will use expo-fetch (if available) or global fetch\n */\n customFetch?: typeof fetch;\n}\n\n/**\n * Create a SaferCity client optimized for React Native\n * \n * @example\n * ```tsx\n * import { createReactNativeClient } from '@safercity/sdk-react-native';\n * \n * const client = createReactNativeClient({\n * baseUrl: 'https://api.safercity.com',\n * token: userToken,\n * tenantId: 'tenant-123',\n * });\n * \n * // Use just like the regular client\n * const { data } = await client.health.check();\n * \n * // Streaming works with expo-fetch\n * for await (const event of client.panics.streamUpdates('panic-123')) {\n * console.log('Update:', event.data);\n * }\n * ```\n */\nexport function createReactNativeClient(options: ReactNativeClientOptions): SaferCityClient {\n const fetchFn = getReactNativeFetch(options.customFetch);\n const streamAdapter = createReactNativeStreamAdapter(options.customFetch);\n \n return createSaferCityClient({\n ...options,\n fetch: fetchFn,\n streamAdapter,\n });\n}\n\n/**\n * Check if streaming is supported in the current React Native environment\n * \n * @example\n * ```tsx\n * import { isStreamingSupported } from '@safercity/sdk-react-native';\n * \n * if (isStreamingSupported()) {\n * // Use streaming features\n * } else {\n * // Fall back to polling\n * }\n * ```\n */\nexport function isStreamingSupported(): boolean {\n return supportsStreaming();\n}\n","/**\n * Secure Token Storage for React Native\n * \n * Provides secure storage for OAuth tokens using:\n * - expo-secure-store (preferred, if available)\n * - AsyncStorage fallback (less secure, but widely available)\n */\n\nimport type { TokenStorage, AuthTokens } from '@safercity/sdk-core';\n\n// Storage key prefix\nconst STORAGE_KEY = '@safercity:tokens';\n\n/**\n * Check if expo-secure-store is available\n */\nfunction hasSecureStore(): boolean {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('expo-secure-store');\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if @react-native-async-storage/async-storage is available\n */\nfunction hasAsyncStorage(): boolean {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('@react-native-async-storage/async-storage');\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Secure token storage using expo-secure-store\n * \n * This is the most secure option for React Native apps.\n * Data is encrypted using the device's secure enclave (iOS) or keystore (Android).\n */\nexport class SecureStoreTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n private initialized = false;\n private initPromise: Promise<void> | null = null;\n\n constructor() {\n // Start loading tokens immediately\n this.initPromise = this.loadFromStore();\n }\n\n private async loadFromStore(): Promise<void> {\n if (this.initialized) return;\n \n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const SecureStore = require('expo-secure-store');\n const value = await SecureStore.getItemAsync(STORAGE_KEY);\n \n if (value) {\n this.tokens = JSON.parse(value);\n }\n } catch (error) {\n console.warn('[SaferCity] Failed to load tokens from SecureStore:', error);\n }\n \n this.initialized = true;\n }\n\n get(): AuthTokens | null {\n // Return cached value synchronously\n return this.tokens;\n }\n\n set(tokens: AuthTokens): void {\n this.tokens = tokens;\n \n // Persist asynchronously\n this.saveToStore(tokens).catch((error) => {\n console.warn('[SaferCity] Failed to save tokens to SecureStore:', error);\n });\n }\n\n clear(): void {\n this.tokens = null;\n \n // Clear from storage asynchronously\n this.clearFromStore().catch((error) => {\n console.warn('[SaferCity] Failed to clear tokens from SecureStore:', error);\n });\n }\n\n private async saveToStore(tokens: AuthTokens): Promise<void> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const SecureStore = require('expo-secure-store');\n await SecureStore.setItemAsync(STORAGE_KEY, JSON.stringify(tokens));\n } catch (error) {\n console.warn('[SaferCity] Failed to save tokens to SecureStore:', error);\n }\n }\n\n private async clearFromStore(): Promise<void> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const SecureStore = require('expo-secure-store');\n await SecureStore.deleteItemAsync(STORAGE_KEY);\n } catch (error) {\n console.warn('[SaferCity] Failed to clear tokens from SecureStore:', error);\n }\n }\n\n /**\n * Wait for initial load to complete\n */\n async waitForInit(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n }\n }\n}\n\n/**\n * Token storage using AsyncStorage (fallback)\n * \n * Less secure than SecureStore but works without Expo.\n * Data is stored unencrypted on the device.\n */\nexport class AsyncStorageTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n private initialized = false;\n private initPromise: Promise<void> | null = null;\n\n constructor() {\n // Start loading tokens immediately\n this.initPromise = this.loadFromStorage();\n }\n\n private async loadFromStorage(): Promise<void> {\n if (this.initialized) return;\n \n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const AsyncStorage = require('@react-native-async-storage/async-storage').default;\n const value = await AsyncStorage.getItem(STORAGE_KEY);\n \n if (value) {\n this.tokens = JSON.parse(value);\n }\n } catch (error) {\n console.warn('[SaferCity] Failed to load tokens from AsyncStorage:', error);\n }\n \n this.initialized = true;\n }\n\n get(): AuthTokens | null {\n return this.tokens;\n }\n\n set(tokens: AuthTokens): void {\n this.tokens = tokens;\n \n this.saveToStorage(tokens).catch((error) => {\n console.warn('[SaferCity] Failed to save tokens to AsyncStorage:', error);\n });\n }\n\n clear(): void {\n this.tokens = null;\n \n this.clearFromStorage().catch((error) => {\n console.warn('[SaferCity] Failed to clear tokens from AsyncStorage:', error);\n });\n }\n\n private async saveToStorage(tokens: AuthTokens): Promise<void> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const AsyncStorage = require('@react-native-async-storage/async-storage').default;\n await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(tokens));\n } catch (error) {\n console.warn('[SaferCity] Failed to save tokens to AsyncStorage:', error);\n }\n }\n\n private async clearFromStorage(): Promise<void> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const AsyncStorage = require('@react-native-async-storage/async-storage').default;\n await AsyncStorage.removeItem(STORAGE_KEY);\n } catch (error) {\n console.warn('[SaferCity] Failed to clear tokens from AsyncStorage:', error);\n }\n }\n\n /**\n * Wait for initial load to complete\n */\n async waitForInit(): Promise<void> {\n if (this.initPromise) {\n await this.initPromise;\n }\n }\n}\n\n/**\n * In-memory token storage (no persistence)\n * \n * Use this when you don't want to persist tokens.\n * Tokens will be lost when the app is closed.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n get(): AuthTokens | null {\n return this.tokens;\n }\n\n set(tokens: AuthTokens): void {\n this.tokens = tokens;\n }\n\n clear(): void {\n this.tokens = null;\n }\n}\n\n/**\n * Create the best available token storage for the current environment\n * \n * Priority:\n * 1. expo-secure-store (most secure)\n * 2. @react-native-async-storage/async-storage (less secure)\n * 3. In-memory (no persistence)\n * \n * @example\n * ```tsx\n * import { createSecureTokenStorage } from '@safercity/sdk-react-native';\n * import { TokenManager } from '@safercity/sdk';\n * \n * const tokenManager = new TokenManager({\n * credentials: {\n * clientId: 'my-client',\n * clientSecret: 'my-secret',\n * },\n * storage: createSecureTokenStorage(),\n * });\n * ```\n */\nexport function createSecureTokenStorage(): TokenStorage {\n if (hasSecureStore()) {\n return new SecureStoreTokenStorage();\n }\n \n if (hasAsyncStorage()) {\n console.warn(\n '[SaferCity] expo-secure-store not available. ' +\n 'Using AsyncStorage which is less secure. ' +\n 'Consider installing expo-secure-store for better security.'\n );\n return new AsyncStorageTokenStorage();\n }\n \n console.warn(\n '[SaferCity] No persistent storage available. ' +\n 'Using in-memory storage. Tokens will be lost when the app closes.'\n );\n return new InMemoryTokenStorage();\n}\n\n/**\n * Check what type of storage is available\n */\nexport function getStorageType(): 'secure-store' | 'async-storage' | 'memory' {\n if (hasSecureStore()) return 'secure-store';\n if (hasAsyncStorage()) return 'async-storage';\n return 'memory';\n}\n"]}
package/package.json CHANGED
@@ -1,16 +1,11 @@
1
1
  {
2
2
  "name": "@safercity/sdk-react-native",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "description": "React Native adapter for SaferCity SDK with expo-fetch streaming support",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "SaferCity"
8
8
  },
9
- "repository": {
10
- "type": "git",
11
- "url": "https://github.com/safercity/safercity-v2.git",
12
- "directory": "packages/sdk/react-native"
13
- },
14
9
  "keywords": ["safercity", "sdk", "react-native", "expo", "streaming"],
15
10
  "sideEffects": false,
16
11
  "type": "module",