@ateam-ai-mobile/expo 0.1.0 → 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/dist/index.d.mts CHANGED
@@ -4,7 +4,13 @@ import { HapticsAPI, BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/pl
4
4
  declare class ExpoAttachmentCache implements AttachmentCache {
5
5
  private index;
6
6
  private loaded;
7
- constructor();
7
+ private indexKey;
8
+ /** Pass `indexKey` when migrating from an app that already persisted an
9
+ * index under a different AsyncStorage key — otherwise existing users
10
+ * lose every cached preview on upgrade. */
11
+ constructor(opts?: {
12
+ indexKey?: string;
13
+ });
8
14
  ready(): Promise<void>;
9
15
  save(jobId: string, attachments: OutgoingAttachment[]): Promise<void>;
10
16
  get(jobId: string): AttachmentPreview[] | undefined;
@@ -14,6 +20,12 @@ declare const expoHaptics: HapticsAPI;
14
20
 
15
21
  declare class ExpoBundleStorage implements BundleStorage {
16
22
  private dirReady;
23
+ private indexKey;
24
+ /** Pass `indexKey` when migrating from an app that already persisted a
25
+ * bundle index under a different key — keeps the existing cache warm. */
26
+ constructor(opts?: {
27
+ indexKey?: string;
28
+ });
17
29
  private ensureDir;
18
30
  loadIndex(): Promise<Record<string, BundleCacheEntry>>;
19
31
  saveIndex(index: Record<string, BundleCacheEntry>): Promise<void>;
@@ -35,6 +47,10 @@ declare function setupExpoProviders(opts?: {
35
47
  /** Extra modules remote bundles may require(), e.g. react-native-webview.
36
48
  * Static imports must live in the HOST (it owns the native build). */
37
49
  bundleModules?: Record<string, any>;
50
+ /** AsyncStorage key overrides — pass your pre-SDK keys when migrating an
51
+ * existing app so users keep their attachment previews / bundle cache. */
52
+ attachmentIndexKey?: string;
53
+ bundleIndexKey?: string;
38
54
  }): {
39
55
  attachmentCache: ExpoAttachmentCache;
40
56
  };
package/dist/index.d.ts CHANGED
@@ -4,7 +4,13 @@ import { HapticsAPI, BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/pl
4
4
  declare class ExpoAttachmentCache implements AttachmentCache {
5
5
  private index;
6
6
  private loaded;
7
- constructor();
7
+ private indexKey;
8
+ /** Pass `indexKey` when migrating from an app that already persisted an
9
+ * index under a different AsyncStorage key — otherwise existing users
10
+ * lose every cached preview on upgrade. */
11
+ constructor(opts?: {
12
+ indexKey?: string;
13
+ });
8
14
  ready(): Promise<void>;
9
15
  save(jobId: string, attachments: OutgoingAttachment[]): Promise<void>;
10
16
  get(jobId: string): AttachmentPreview[] | undefined;
@@ -14,6 +20,12 @@ declare const expoHaptics: HapticsAPI;
14
20
 
15
21
  declare class ExpoBundleStorage implements BundleStorage {
16
22
  private dirReady;
23
+ private indexKey;
24
+ /** Pass `indexKey` when migrating from an app that already persisted a
25
+ * bundle index under a different key — keeps the existing cache warm. */
26
+ constructor(opts?: {
27
+ indexKey?: string;
28
+ });
17
29
  private ensureDir;
18
30
  loadIndex(): Promise<Record<string, BundleCacheEntry>>;
19
31
  saveIndex(index: Record<string, BundleCacheEntry>): Promise<void>;
@@ -35,6 +47,10 @@ declare function setupExpoProviders(opts?: {
35
47
  /** Extra modules remote bundles may require(), e.g. react-native-webview.
36
48
  * Static imports must live in the HOST (it owns the native build). */
37
49
  bundleModules?: Record<string, any>;
50
+ /** AsyncStorage key overrides — pass your pre-SDK keys when migrating an
51
+ * existing app so users keep their attachment previews / bundle cache. */
52
+ attachmentIndexKey?: string;
53
+ bundleIndexKey?: string;
38
54
  }): {
39
55
  attachmentCache: ExpoAttachmentCache;
40
56
  };
package/dist/index.js CHANGED
@@ -39,6 +39,8 @@ module.exports = __toCommonJS(index_exports);
39
39
  var ExpoWebBrowser = __toESM(require("expo-web-browser"));
40
40
  var ExpoLinking = __toESM(require("expo-linking"));
41
41
  var ExpoClipboard = __toESM(require("expo-clipboard"));
42
+ var import_async_storage3 = __toESM(require("@react-native-async-storage/async-storage"));
43
+ var import_core = require("@ateam-ai-mobile/core");
42
44
  var import_plugin_runtime = require("@ateam-ai-mobile/plugin-runtime");
43
45
  var import_ui = require("@ateam-ai-mobile/ui");
44
46
 
@@ -73,10 +75,13 @@ var expoHaptics = {
73
75
  var FileSystem = __toESM(require("expo-file-system/legacy"));
74
76
  var import_async_storage = __toESM(require("@react-native-async-storage/async-storage"));
75
77
  var CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;
76
- var CACHE_INDEX_KEY = "adas-plugin-bundles-index-v1";
78
+ var DEFAULT_CACHE_INDEX_KEY = "adas-plugin-bundles-index-v1";
77
79
  var ExpoBundleStorage = class {
78
- constructor() {
80
+ /** Pass `indexKey` when migrating from an app that already persisted a
81
+ * bundle index under a different key — keeps the existing cache warm. */
82
+ constructor(opts) {
79
83
  this.dirReady = false;
84
+ this.indexKey = opts?.indexKey || DEFAULT_CACHE_INDEX_KEY;
80
85
  }
81
86
  async ensureDir() {
82
87
  if (this.dirReady) return;
@@ -92,7 +97,7 @@ var ExpoBundleStorage = class {
92
97
  }
93
98
  async loadIndex() {
94
99
  try {
95
- const raw = await import_async_storage.default.getItem(CACHE_INDEX_KEY);
100
+ const raw = await import_async_storage.default.getItem(this.indexKey);
96
101
  return raw ? JSON.parse(raw) : {};
97
102
  } catch {
98
103
  return {};
@@ -100,7 +105,7 @@ var ExpoBundleStorage = class {
100
105
  }
101
106
  async saveIndex(index) {
102
107
  try {
103
- await import_async_storage.default.setItem(CACHE_INDEX_KEY, JSON.stringify(index));
108
+ await import_async_storage.default.setItem(this.indexKey, JSON.stringify(index));
104
109
  } catch (err) {
105
110
  console.warn("[ExpoBundleStorage] index save error:", err);
106
111
  }
@@ -127,7 +132,7 @@ var ExpoBundleStorage = class {
127
132
  async clearAll() {
128
133
  await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {
129
134
  });
130
- await import_async_storage.default.removeItem(CACHE_INDEX_KEY).catch(() => {
135
+ await import_async_storage.default.removeItem(this.indexKey).catch(() => {
131
136
  });
132
137
  this.dirReady = false;
133
138
  }
@@ -136,17 +141,22 @@ var ExpoBundleStorage = class {
136
141
  // src/attachmentCache.ts
137
142
  var FileSystem2 = __toESM(require("expo-file-system/legacy"));
138
143
  var import_async_storage2 = __toESM(require("@react-native-async-storage/async-storage"));
139
- var INDEX_KEY = "adas-chat-attachment-index-v1";
144
+ var DEFAULT_INDEX_KEY = "adas-chat-attachment-index-v1";
140
145
  var DIR = `${FileSystem2.documentDirectory}chat-attachments/`;
141
146
  var MAX_JOBS = 100;
142
147
  var ExpoAttachmentCache = class {
143
- constructor() {
148
+ /** Pass `indexKey` when migrating from an app that already persisted an
149
+ * index under a different AsyncStorage key — otherwise existing users
150
+ * lose every cached preview on upgrade. */
151
+ constructor(opts) {
144
152
  this.index = {};
153
+ const indexKey = opts?.indexKey || DEFAULT_INDEX_KEY;
154
+ this.indexKey = indexKey;
145
155
  this.loaded = (async () => {
146
156
  try {
147
157
  const info = await FileSystem2.getInfoAsync(DIR);
148
158
  if (!info.exists) await FileSystem2.makeDirectoryAsync(DIR, { intermediates: true });
149
- const raw = await import_async_storage2.default.getItem(INDEX_KEY);
159
+ const raw = await import_async_storage2.default.getItem(indexKey);
150
160
  if (raw) this.index = JSON.parse(raw);
151
161
  } catch {
152
162
  }
@@ -181,7 +191,7 @@ var ExpoAttachmentCache = class {
181
191
  }
182
192
  }
183
193
  try {
184
- await import_async_storage2.default.setItem(INDEX_KEY, JSON.stringify(this.index));
194
+ await import_async_storage2.default.setItem(this.indexKey, JSON.stringify(this.index));
185
195
  } catch {
186
196
  }
187
197
  }
@@ -199,7 +209,7 @@ var ExpoAttachmentCache = class {
199
209
  // src/index.ts
200
210
  function setupExpoProviders(opts) {
201
211
  (0, import_plugin_runtime.setHapticsProvider)(expoHaptics);
202
- (0, import_plugin_runtime.setBundleStorage)(new ExpoBundleStorage());
212
+ (0, import_plugin_runtime.setBundleStorage)(new ExpoBundleStorage({ indexKey: opts?.bundleIndexKey }));
203
213
  (0, import_ui.setClipboardProvider)({
204
214
  setString: (text) => ExpoClipboard.setStringAsync(text).then(() => {
205
215
  })
@@ -209,7 +219,11 @@ function setupExpoProviders(opts) {
209
219
  "expo-linking": ExpoLinking,
210
220
  ...opts?.bundleModules || {}
211
221
  });
212
- return { attachmentCache: new ExpoAttachmentCache() };
222
+ (0, import_core.setAcsCacheStorage)({
223
+ getItem: (k) => import_async_storage3.default.getItem(k),
224
+ setItem: (k, v) => import_async_storage3.default.setItem(k, v)
225
+ });
226
+ return { attachmentCache: new ExpoAttachmentCache({ indexKey: opts?.attachmentIndexKey }) };
213
227
  }
214
228
  // Annotate the CommonJS export names for ESM import in node:
215
229
  0 && (module.exports = {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/haptics.ts","../src/bundleStorage.ts","../src/attachmentCache.ts"],"sourcesContent":["/**\n * @ateam-ai-mobile/expo — Expo-backed providers for the ADAS SDK.\n *\n * Bare-RN hosts skip this package entirely (core + plugin-runtime have safe\n * defaults); Expo hosts call setupExpoProviders() once at startup to light\n * up haptics, persistent bundle caching, attachment previews, and the\n * expo modules remote bundles may require.\n */\nimport * as ExpoWebBrowser from 'expo-web-browser';\nimport * as ExpoLinking from 'expo-linking';\nimport * as ExpoClipboard from 'expo-clipboard';\nimport {\n setHapticsProvider,\n setBundleStorage,\n registerBundleModules,\n} from '@ateam-ai-mobile/plugin-runtime';\nimport { setClipboardProvider } from '@ateam-ai-mobile/ui';\n\nimport { expoHaptics } from './haptics';\nimport { ExpoBundleStorage } from './bundleStorage';\nimport { ExpoAttachmentCache } from './attachmentCache';\n\nexport { expoHaptics } from './haptics';\nexport { ExpoBundleStorage } from './bundleStorage';\nexport { ExpoAttachmentCache } from './attachmentCache';\n// NOTE: createBridge (device capabilities bridge) is intentionally NOT\n// exported here — it statically imports the optional expo modules\n// (calendar/contacts/location/…), which would break Metro builds in hosts\n// that don't install them. Install those modules and import it from the\n// subpath: `import { createBridge } from '@ateam-ai-mobile/expo/bridge'`.\n\n/**\n * Wire all Expo providers in one call. Runs before initAdas() is fine —\n * these registries are independent of the core config.\n *\n * Returns the attachment cache so the host can pass it to initAdas():\n * const { attachmentCache } = setupExpoProviders();\n * initAdas({ ..., attachmentCache });\n */\nexport function setupExpoProviders(opts?: {\n /** Extra modules remote bundles may require(), e.g. react-native-webview.\n * Static imports must live in the HOST (it owns the native build). */\n bundleModules?: Record<string, any>;\n}): { attachmentCache: ExpoAttachmentCache } {\n setHapticsProvider(expoHaptics);\n setBundleStorage(new ExpoBundleStorage());\n setClipboardProvider({\n setString: (text: string) => ExpoClipboard.setStringAsync(text).then(() => {}),\n });\n registerBundleModules({\n 'expo-web-browser': ExpoWebBrowser,\n 'expo-linking': ExpoLinking,\n ...(opts?.bundleModules || {}),\n });\n return { attachmentCache: new ExpoAttachmentCache() };\n}\n","/**\n * expo-haptics implementation of the plugin HapticsAPI.\n */\nimport * as Haptics from 'expo-haptics';\nimport type { HapticsAPI } from '@ateam-ai-mobile/plugin-runtime';\n\nexport const expoHaptics: HapticsAPI = {\n impact(style) {\n const map = {\n light: Haptics.ImpactFeedbackStyle.Light,\n medium: Haptics.ImpactFeedbackStyle.Medium,\n heavy: Haptics.ImpactFeedbackStyle.Heavy,\n } as const;\n Haptics.impactAsync(map[style]).catch(() => {});\n },\n notification(type) {\n const map = {\n success: Haptics.NotificationFeedbackType.Success,\n warning: Haptics.NotificationFeedbackType.Warning,\n error: Haptics.NotificationFeedbackType.Error,\n } as const;\n Haptics.notificationAsync(map[type]).catch(() => {});\n },\n selection() {\n Haptics.selectionAsync().catch(() => {});\n },\n};\n","/**\n * Disk-backed BundleStorage: bundle files in the Expo cache directory,\n * index in AsyncStorage. Mirrors the reference app's cache semantics\n * (index version key, files under plugin-bundles/).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/plugin-runtime';\n\nconst CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;\nconst CACHE_INDEX_KEY = 'adas-plugin-bundles-index-v1';\n\nexport class ExpoBundleStorage implements BundleStorage {\n private dirReady = false;\n\n private async ensureDir(): Promise<void> {\n if (this.dirReady) return;\n try {\n const info = await FileSystem.getInfoAsync(CACHE_DIR);\n if (!info.exists) {\n await FileSystem.makeDirectoryAsync(CACHE_DIR, { intermediates: true });\n }\n } catch (err) {\n console.warn('[ExpoBundleStorage] cache dir error:', err);\n }\n this.dirReady = true;\n }\n\n async loadIndex(): Promise<Record<string, BundleCacheEntry>> {\n try {\n const raw = await AsyncStorage.getItem(CACHE_INDEX_KEY);\n return raw ? JSON.parse(raw) : {};\n } catch {\n return {};\n }\n }\n\n async saveIndex(index: Record<string, BundleCacheEntry>): Promise<void> {\n try {\n await AsyncStorage.setItem(CACHE_INDEX_KEY, JSON.stringify(index));\n } catch (err) {\n console.warn('[ExpoBundleStorage] index save error:', err);\n }\n }\n\n async readBundle(localPath: string): Promise<string | null> {\n try {\n const info = await FileSystem.getInfoAsync(localPath);\n if (!info.exists) return null;\n return await FileSystem.readAsStringAsync(localPath);\n } catch {\n return null; // file gone — caller re-downloads\n }\n }\n\n async writeBundle(key: string, content: string): Promise<string> {\n await this.ensureDir();\n const localPath = `${CACHE_DIR}${key}`;\n await FileSystem.writeAsStringAsync(localPath, content);\n return localPath;\n }\n\n async deleteBundle(localPath: string): Promise<void> {\n await FileSystem.deleteAsync(localPath, { idempotent: true }).catch(() => {});\n }\n\n async clearAll(): Promise<void> {\n await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {});\n await AsyncStorage.removeItem(CACHE_INDEX_KEY).catch(() => {});\n this.dirReady = false;\n }\n}\n","/**\n * Expo implementation of @ateam-ai-mobile/core's AttachmentCache: attachment bytes on\n * disk, index in AsyncStorage. Mirrors the reference app's chat attachment\n * cache (last-100 retention).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { AttachmentCache, AttachmentPreview, OutgoingAttachment } from '@ateam-ai-mobile/core';\n\nconst INDEX_KEY = 'adas-chat-attachment-index-v1';\nconst DIR = `${FileSystem.documentDirectory}chat-attachments/`;\nconst MAX_JOBS = 100;\n\ninterface CachedAttachment { mimeType: string; name: string; filePath: string }\n\nexport class ExpoAttachmentCache implements AttachmentCache {\n private index: Record<string, CachedAttachment[]> = {};\n private loaded: Promise<void>;\n\n constructor() {\n this.loaded = (async () => {\n try {\n const info = await FileSystem.getInfoAsync(DIR);\n if (!info.exists) await FileSystem.makeDirectoryAsync(DIR, { intermediates: true });\n const raw = await AsyncStorage.getItem(INDEX_KEY);\n if (raw) this.index = JSON.parse(raw);\n } catch {}\n })();\n }\n\n async ready(): Promise<void> {\n await this.loaded;\n }\n\n async save(jobId: string, attachments: OutgoingAttachment[]): Promise<void> {\n await this.loaded;\n const entries: CachedAttachment[] = [];\n for (let i = 0; i < attachments.length; i++) {\n const att = attachments[i];\n const ext = att.mimeType.startsWith('image/') ? att.mimeType.split('/')[1] || 'jpg' : 'bin';\n const filePath = `${DIR}${jobId}_${i}.${ext}`;\n try {\n await FileSystem.writeAsStringAsync(filePath, att.data, { encoding: FileSystem.EncodingType.Base64 });\n entries.push({ mimeType: att.mimeType, name: att.name, filePath });\n } catch (err) {\n console.warn('[ExpoAttachmentCache] write failed:', err);\n }\n }\n if (entries.length === 0) return;\n\n this.index[jobId] = entries;\n // Keep only the most recent MAX_JOBS jobs; delete evicted files.\n const keys = Object.keys(this.index);\n if (keys.length > MAX_JOBS) {\n for (const k of keys.slice(0, keys.length - MAX_JOBS)) {\n const old = this.index[k];\n if (old) for (const o of old) FileSystem.deleteAsync(o.filePath, { idempotent: true }).catch(() => {});\n delete this.index[k];\n }\n }\n try {\n await AsyncStorage.setItem(INDEX_KEY, JSON.stringify(this.index));\n } catch {}\n }\n\n get(jobId: string): AttachmentPreview[] | undefined {\n const entries = this.index[jobId];\n if (!entries || entries.length === 0) return undefined;\n return entries.map(e => ({\n mimeType: e.mimeType,\n name: e.name,\n previewUri: e.filePath,\n }));\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,qBAAgC;AAChC,kBAA6B;AAC7B,oBAA+B;AAC/B,4BAIO;AACP,gBAAqC;;;ACbrC,cAAyB;AAGlB,IAAM,cAA0B;AAAA,EACrC,OAAO,OAAO;AACZ,UAAM,MAAM;AAAA,MACV,OAAe,4BAAoB;AAAA,MACnC,QAAgB,4BAAoB;AAAA,MACpC,OAAe,4BAAoB;AAAA,IACrC;AACA,IAAQ,oBAAY,IAAI,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAChD;AAAA,EACA,aAAa,MAAM;AACjB,UAAM,MAAM;AAAA,MACV,SAAiB,iCAAyB;AAAA,MAC1C,SAAiB,iCAAyB;AAAA,MAC1C,OAAe,iCAAyB;AAAA,IAC1C;AACA,IAAQ,0BAAkB,IAAI,IAAI,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACrD;AAAA,EACA,YAAY;AACV,IAAQ,uBAAe,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzC;AACF;;;ACrBA,iBAA4B;AAC5B,2BAAyB;AAGzB,IAAM,YAAY,GAAc,yBAAc;AAC9C,IAAM,kBAAkB;AAEjB,IAAM,oBAAN,MAAiD;AAAA,EAAjD;AACL,SAAQ,WAAW;AAAA;AAAA,EAEnB,MAAc,YAA2B;AACvC,QAAI,KAAK,SAAU;AACnB,QAAI;AACF,YAAM,OAAO,MAAiB,wBAAa,SAAS;AACpD,UAAI,CAAC,KAAK,QAAQ;AAChB,cAAiB,8BAAmB,WAAW,EAAE,eAAe,KAAK,CAAC;AAAA,MACxE;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,wCAAwC,GAAG;AAAA,IAC1D;AACA,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,YAAuD;AAC3D,QAAI;AACF,YAAM,MAAM,MAAM,qBAAAA,QAAa,QAAQ,eAAe;AACtD,aAAO,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAAA,IAClC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,OAAwD;AACtE,QAAI;AACF,YAAM,qBAAAA,QAAa,QAAQ,iBAAiB,KAAK,UAAU,KAAK,CAAC;AAAA,IACnE,SAAS,KAAK;AACZ,cAAQ,KAAK,yCAAyC,GAAG;AAAA,IAC3D;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,WAA2C;AAC1D,QAAI;AACF,YAAM,OAAO,MAAiB,wBAAa,SAAS;AACpD,UAAI,CAAC,KAAK,OAAQ,QAAO;AACzB,aAAO,MAAiB,6BAAkB,SAAS;AAAA,IACrD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,KAAa,SAAkC;AAC/D,UAAM,KAAK,UAAU;AACrB,UAAM,YAAY,GAAG,SAAS,GAAG,GAAG;AACpC,UAAiB,8BAAmB,WAAW,OAAO;AACtD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,WAAkC;AACnD,UAAiB,uBAAY,WAAW,EAAE,YAAY,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC9E;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAiB,uBAAY,WAAW,EAAE,YAAY,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC5E,UAAM,qBAAAA,QAAa,WAAW,eAAe,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC7D,SAAK,WAAW;AAAA,EAClB;AACF;;;AClEA,IAAAC,cAA4B;AAC5B,IAAAC,wBAAyB;AAGzB,IAAM,YAAY;AAClB,IAAM,MAAM,GAAc,6BAAiB;AAC3C,IAAM,WAAW;AAIV,IAAM,sBAAN,MAAqD;AAAA,EAI1D,cAAc;AAHd,SAAQ,QAA4C,CAAC;AAInD,SAAK,UAAU,YAAY;AACzB,UAAI;AACF,cAAM,OAAO,MAAiB,yBAAa,GAAG;AAC9C,YAAI,CAAC,KAAK,OAAQ,OAAiB,+BAAmB,KAAK,EAAE,eAAe,KAAK,CAAC;AAClF,cAAM,MAAM,MAAM,sBAAAC,QAAa,QAAQ,SAAS;AAChD,YAAI,IAAK,MAAK,QAAQ,KAAK,MAAM,GAAG;AAAA,MACtC,QAAQ;AAAA,MAAC;AAAA,IACX,GAAG;AAAA,EACL;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK;AAAA,EACb;AAAA,EAEA,MAAM,KAAK,OAAe,aAAkD;AAC1E,UAAM,KAAK;AACX,UAAM,UAA8B,CAAC;AACrC,aAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,YAAM,MAAM,YAAY,CAAC;AACzB,YAAM,MAAM,IAAI,SAAS,WAAW,QAAQ,IAAI,IAAI,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK,QAAQ;AACtF,YAAM,WAAW,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC,IAAI,GAAG;AAC3C,UAAI;AACF,cAAiB,+BAAmB,UAAU,IAAI,MAAM,EAAE,UAAqB,yBAAa,OAAO,CAAC;AACpG,gBAAQ,KAAK,EAAE,UAAU,IAAI,UAAU,MAAM,IAAI,MAAM,SAAS,CAAC;AAAA,MACnE,SAAS,KAAK;AACZ,gBAAQ,KAAK,uCAAuC,GAAG;AAAA,MACzD;AAAA,IACF;AACA,QAAI,QAAQ,WAAW,EAAG;AAE1B,SAAK,MAAM,KAAK,IAAI;AAEpB,UAAM,OAAO,OAAO,KAAK,KAAK,KAAK;AACnC,QAAI,KAAK,SAAS,UAAU;AAC1B,iBAAW,KAAK,KAAK,MAAM,GAAG,KAAK,SAAS,QAAQ,GAAG;AACrD,cAAM,MAAM,KAAK,MAAM,CAAC;AACxB,YAAI,IAAK,YAAW,KAAK,IAAK,CAAW,wBAAY,EAAE,UAAU,EAAE,YAAY,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACrG,eAAO,KAAK,MAAM,CAAC;AAAA,MACrB;AAAA,IACF;AACA,QAAI;AACF,YAAM,sBAAAA,QAAa,QAAQ,WAAW,KAAK,UAAU,KAAK,KAAK,CAAC;AAAA,IAClE,QAAQ;AAAA,IAAC;AAAA,EACX;AAAA,EAEA,IAAI,OAAgD;AAClD,UAAM,UAAU,KAAK,MAAM,KAAK;AAChC,QAAI,CAAC,WAAW,QAAQ,WAAW,EAAG,QAAO;AAC7C,WAAO,QAAQ,IAAI,QAAM;AAAA,MACvB,UAAU,EAAE;AAAA,MACZ,MAAM,EAAE;AAAA,MACR,YAAY,EAAE;AAAA,IAChB,EAAE;AAAA,EACJ;AACF;;;AHnCO,SAAS,mBAAmB,MAIU;AAC3C,gDAAmB,WAAW;AAC9B,8CAAiB,IAAI,kBAAkB,CAAC;AACxC,sCAAqB;AAAA,IACnB,WAAW,CAAC,SAA+B,6BAAe,IAAI,EAAE,KAAK,MAAM;AAAA,IAAC,CAAC;AAAA,EAC/E,CAAC;AACD,mDAAsB;AAAA,IACpB,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB,GAAI,MAAM,iBAAiB,CAAC;AAAA,EAC9B,CAAC;AACD,SAAO,EAAE,iBAAiB,IAAI,oBAAoB,EAAE;AACtD;","names":["AsyncStorage","FileSystem","import_async_storage","AsyncStorage"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/haptics.ts","../src/bundleStorage.ts","../src/attachmentCache.ts"],"sourcesContent":["/**\n * @ateam-ai-mobile/expo — Expo-backed providers for the ADAS SDK.\n *\n * Bare-RN hosts skip this package entirely (core + plugin-runtime have safe\n * defaults); Expo hosts call setupExpoProviders() once at startup to light\n * up haptics, persistent bundle caching, attachment previews, and the\n * expo modules remote bundles may require.\n */\nimport * as ExpoWebBrowser from 'expo-web-browser';\nimport * as ExpoLinking from 'expo-linking';\nimport * as ExpoClipboard from 'expo-clipboard';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { setAcsCacheStorage } from '@ateam-ai-mobile/core';\nimport {\n setHapticsProvider,\n setBundleStorage,\n registerBundleModules,\n} from '@ateam-ai-mobile/plugin-runtime';\nimport { setClipboardProvider } from '@ateam-ai-mobile/ui';\n\nimport { expoHaptics } from './haptics';\nimport { ExpoBundleStorage } from './bundleStorage';\nimport { ExpoAttachmentCache } from './attachmentCache';\n\nexport { expoHaptics } from './haptics';\nexport { ExpoBundleStorage } from './bundleStorage';\nexport { ExpoAttachmentCache } from './attachmentCache';\n// NOTE: createBridge (device capabilities bridge) is intentionally NOT\n// exported here — it statically imports the optional expo modules\n// (calendar/contacts/location/…), which would break Metro builds in hosts\n// that don't install them. Install those modules and import it from the\n// subpath: `import { createBridge } from '@ateam-ai-mobile/expo/bridge'`.\n\n/**\n * Wire all Expo providers in one call. Runs before initAdas() is fine —\n * these registries are independent of the core config.\n *\n * Returns the attachment cache so the host can pass it to initAdas():\n * const { attachmentCache } = setupExpoProviders();\n * initAdas({ ..., attachmentCache });\n */\nexport function setupExpoProviders(opts?: {\n /** Extra modules remote bundles may require(), e.g. react-native-webview.\n * Static imports must live in the HOST (it owns the native build). */\n bundleModules?: Record<string, any>;\n /** AsyncStorage key overrides — pass your pre-SDK keys when migrating an\n * existing app so users keep their attachment previews / bundle cache. */\n attachmentIndexKey?: string;\n bundleIndexKey?: string;\n}): { attachmentCache: ExpoAttachmentCache } {\n setHapticsProvider(expoHaptics);\n setBundleStorage(new ExpoBundleStorage({ indexKey: opts?.bundleIndexKey }));\n setClipboardProvider({\n setString: (text: string) => ExpoClipboard.setStringAsync(text).then(() => {}),\n });\n registerBundleModules({\n 'expo-web-browser': ExpoWebBrowser,\n 'expo-linking': ExpoLinking,\n ...(opts?.bundleModules || {}),\n });\n // Persist the ACS home-actions feed across cold starts.\n setAcsCacheStorage({\n getItem: (k: string) => AsyncStorage.getItem(k),\n setItem: (k: string, v: string) => AsyncStorage.setItem(k, v),\n });\n return { attachmentCache: new ExpoAttachmentCache({ indexKey: opts?.attachmentIndexKey }) };\n}\n","/**\n * expo-haptics implementation of the plugin HapticsAPI.\n */\nimport * as Haptics from 'expo-haptics';\nimport type { HapticsAPI } from '@ateam-ai-mobile/plugin-runtime';\n\nexport const expoHaptics: HapticsAPI = {\n impact(style) {\n const map = {\n light: Haptics.ImpactFeedbackStyle.Light,\n medium: Haptics.ImpactFeedbackStyle.Medium,\n heavy: Haptics.ImpactFeedbackStyle.Heavy,\n } as const;\n Haptics.impactAsync(map[style]).catch(() => {});\n },\n notification(type) {\n const map = {\n success: Haptics.NotificationFeedbackType.Success,\n warning: Haptics.NotificationFeedbackType.Warning,\n error: Haptics.NotificationFeedbackType.Error,\n } as const;\n Haptics.notificationAsync(map[type]).catch(() => {});\n },\n selection() {\n Haptics.selectionAsync().catch(() => {});\n },\n};\n","/**\n * Disk-backed BundleStorage: bundle files in the Expo cache directory,\n * index in AsyncStorage. Mirrors the reference app's cache semantics\n * (index version key, files under plugin-bundles/).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/plugin-runtime';\n\nconst CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;\nconst DEFAULT_CACHE_INDEX_KEY = 'adas-plugin-bundles-index-v1';\n\nexport class ExpoBundleStorage implements BundleStorage {\n private dirReady = false;\n private indexKey: string;\n\n /** Pass `indexKey` when migrating from an app that already persisted a\n * bundle index under a different key — keeps the existing cache warm. */\n constructor(opts?: { indexKey?: string }) {\n this.indexKey = opts?.indexKey || DEFAULT_CACHE_INDEX_KEY;\n }\n\n private async ensureDir(): Promise<void> {\n if (this.dirReady) return;\n try {\n const info = await FileSystem.getInfoAsync(CACHE_DIR);\n if (!info.exists) {\n await FileSystem.makeDirectoryAsync(CACHE_DIR, { intermediates: true });\n }\n } catch (err) {\n console.warn('[ExpoBundleStorage] cache dir error:', err);\n }\n this.dirReady = true;\n }\n\n async loadIndex(): Promise<Record<string, BundleCacheEntry>> {\n try {\n const raw = await AsyncStorage.getItem(this.indexKey);\n return raw ? JSON.parse(raw) : {};\n } catch {\n return {};\n }\n }\n\n async saveIndex(index: Record<string, BundleCacheEntry>): Promise<void> {\n try {\n await AsyncStorage.setItem(this.indexKey, JSON.stringify(index));\n } catch (err) {\n console.warn('[ExpoBundleStorage] index save error:', err);\n }\n }\n\n async readBundle(localPath: string): Promise<string | null> {\n try {\n const info = await FileSystem.getInfoAsync(localPath);\n if (!info.exists) return null;\n return await FileSystem.readAsStringAsync(localPath);\n } catch {\n return null; // file gone — caller re-downloads\n }\n }\n\n async writeBundle(key: string, content: string): Promise<string> {\n await this.ensureDir();\n const localPath = `${CACHE_DIR}${key}`;\n await FileSystem.writeAsStringAsync(localPath, content);\n return localPath;\n }\n\n async deleteBundle(localPath: string): Promise<void> {\n await FileSystem.deleteAsync(localPath, { idempotent: true }).catch(() => {});\n }\n\n async clearAll(): Promise<void> {\n await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {});\n await AsyncStorage.removeItem(this.indexKey).catch(() => {});\n this.dirReady = false;\n }\n}\n","/**\n * Expo implementation of @ateam-ai-mobile/core's AttachmentCache: attachment bytes on\n * disk, index in AsyncStorage. Mirrors the reference app's chat attachment\n * cache (last-100 retention).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { AttachmentCache, AttachmentPreview, OutgoingAttachment } from '@ateam-ai-mobile/core';\n\nconst DEFAULT_INDEX_KEY = 'adas-chat-attachment-index-v1';\nconst DIR = `${FileSystem.documentDirectory}chat-attachments/`;\nconst MAX_JOBS = 100;\n\ninterface CachedAttachment { mimeType: string; name: string; filePath: string }\n\nexport class ExpoAttachmentCache implements AttachmentCache {\n private index: Record<string, CachedAttachment[]> = {};\n private loaded: Promise<void>;\n private indexKey: string;\n\n /** Pass `indexKey` when migrating from an app that already persisted an\n * index under a different AsyncStorage key — otherwise existing users\n * lose every cached preview on upgrade. */\n constructor(opts?: { indexKey?: string }) {\n const indexKey = opts?.indexKey || DEFAULT_INDEX_KEY;\n this.indexKey = indexKey;\n this.loaded = (async () => {\n try {\n const info = await FileSystem.getInfoAsync(DIR);\n if (!info.exists) await FileSystem.makeDirectoryAsync(DIR, { intermediates: true });\n const raw = await AsyncStorage.getItem(indexKey);\n if (raw) this.index = JSON.parse(raw);\n } catch {}\n })();\n }\n\n async ready(): Promise<void> {\n await this.loaded;\n }\n\n async save(jobId: string, attachments: OutgoingAttachment[]): Promise<void> {\n await this.loaded;\n const entries: CachedAttachment[] = [];\n for (let i = 0; i < attachments.length; i++) {\n const att = attachments[i];\n const ext = att.mimeType.startsWith('image/') ? att.mimeType.split('/')[1] || 'jpg' : 'bin';\n const filePath = `${DIR}${jobId}_${i}.${ext}`;\n try {\n await FileSystem.writeAsStringAsync(filePath, att.data, { encoding: FileSystem.EncodingType.Base64 });\n entries.push({ mimeType: att.mimeType, name: att.name, filePath });\n } catch (err) {\n console.warn('[ExpoAttachmentCache] write failed:', err);\n }\n }\n if (entries.length === 0) return;\n\n this.index[jobId] = entries;\n // Keep only the most recent MAX_JOBS jobs; delete evicted files.\n const keys = Object.keys(this.index);\n if (keys.length > MAX_JOBS) {\n for (const k of keys.slice(0, keys.length - MAX_JOBS)) {\n const old = this.index[k];\n if (old) for (const o of old) FileSystem.deleteAsync(o.filePath, { idempotent: true }).catch(() => {});\n delete this.index[k];\n }\n }\n try {\n await AsyncStorage.setItem(this.indexKey, JSON.stringify(this.index));\n } catch {}\n }\n\n get(jobId: string): AttachmentPreview[] | undefined {\n const entries = this.index[jobId];\n if (!entries || entries.length === 0) return undefined;\n return entries.map(e => ({\n mimeType: e.mimeType,\n name: e.name,\n previewUri: e.filePath,\n }));\n }\n}\n"],"mappings":"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","names":["import_async_storage","AsyncStorage","FileSystem","import_async_storage","AsyncStorage","AsyncStorage"]}
package/dist/index.mjs CHANGED
@@ -2,6 +2,8 @@
2
2
  import * as ExpoWebBrowser from "expo-web-browser";
3
3
  import * as ExpoLinking from "expo-linking";
4
4
  import * as ExpoClipboard from "expo-clipboard";
5
+ import AsyncStorage3 from "@react-native-async-storage/async-storage";
6
+ import { setAcsCacheStorage } from "@ateam-ai-mobile/core";
5
7
  import {
6
8
  setHapticsProvider,
7
9
  setBundleStorage,
@@ -40,10 +42,13 @@ var expoHaptics = {
40
42
  import * as FileSystem from "expo-file-system/legacy";
41
43
  import AsyncStorage from "@react-native-async-storage/async-storage";
42
44
  var CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;
43
- var CACHE_INDEX_KEY = "adas-plugin-bundles-index-v1";
45
+ var DEFAULT_CACHE_INDEX_KEY = "adas-plugin-bundles-index-v1";
44
46
  var ExpoBundleStorage = class {
45
- constructor() {
47
+ /** Pass `indexKey` when migrating from an app that already persisted a
48
+ * bundle index under a different key — keeps the existing cache warm. */
49
+ constructor(opts) {
46
50
  this.dirReady = false;
51
+ this.indexKey = opts?.indexKey || DEFAULT_CACHE_INDEX_KEY;
47
52
  }
48
53
  async ensureDir() {
49
54
  if (this.dirReady) return;
@@ -59,7 +64,7 @@ var ExpoBundleStorage = class {
59
64
  }
60
65
  async loadIndex() {
61
66
  try {
62
- const raw = await AsyncStorage.getItem(CACHE_INDEX_KEY);
67
+ const raw = await AsyncStorage.getItem(this.indexKey);
63
68
  return raw ? JSON.parse(raw) : {};
64
69
  } catch {
65
70
  return {};
@@ -67,7 +72,7 @@ var ExpoBundleStorage = class {
67
72
  }
68
73
  async saveIndex(index) {
69
74
  try {
70
- await AsyncStorage.setItem(CACHE_INDEX_KEY, JSON.stringify(index));
75
+ await AsyncStorage.setItem(this.indexKey, JSON.stringify(index));
71
76
  } catch (err) {
72
77
  console.warn("[ExpoBundleStorage] index save error:", err);
73
78
  }
@@ -94,7 +99,7 @@ var ExpoBundleStorage = class {
94
99
  async clearAll() {
95
100
  await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {
96
101
  });
97
- await AsyncStorage.removeItem(CACHE_INDEX_KEY).catch(() => {
102
+ await AsyncStorage.removeItem(this.indexKey).catch(() => {
98
103
  });
99
104
  this.dirReady = false;
100
105
  }
@@ -103,17 +108,22 @@ var ExpoBundleStorage = class {
103
108
  // src/attachmentCache.ts
104
109
  import * as FileSystem2 from "expo-file-system/legacy";
105
110
  import AsyncStorage2 from "@react-native-async-storage/async-storage";
106
- var INDEX_KEY = "adas-chat-attachment-index-v1";
111
+ var DEFAULT_INDEX_KEY = "adas-chat-attachment-index-v1";
107
112
  var DIR = `${FileSystem2.documentDirectory}chat-attachments/`;
108
113
  var MAX_JOBS = 100;
109
114
  var ExpoAttachmentCache = class {
110
- constructor() {
115
+ /** Pass `indexKey` when migrating from an app that already persisted an
116
+ * index under a different AsyncStorage key — otherwise existing users
117
+ * lose every cached preview on upgrade. */
118
+ constructor(opts) {
111
119
  this.index = {};
120
+ const indexKey = opts?.indexKey || DEFAULT_INDEX_KEY;
121
+ this.indexKey = indexKey;
112
122
  this.loaded = (async () => {
113
123
  try {
114
124
  const info = await FileSystem2.getInfoAsync(DIR);
115
125
  if (!info.exists) await FileSystem2.makeDirectoryAsync(DIR, { intermediates: true });
116
- const raw = await AsyncStorage2.getItem(INDEX_KEY);
126
+ const raw = await AsyncStorage2.getItem(indexKey);
117
127
  if (raw) this.index = JSON.parse(raw);
118
128
  } catch {
119
129
  }
@@ -148,7 +158,7 @@ var ExpoAttachmentCache = class {
148
158
  }
149
159
  }
150
160
  try {
151
- await AsyncStorage2.setItem(INDEX_KEY, JSON.stringify(this.index));
161
+ await AsyncStorage2.setItem(this.indexKey, JSON.stringify(this.index));
152
162
  } catch {
153
163
  }
154
164
  }
@@ -166,7 +176,7 @@ var ExpoAttachmentCache = class {
166
176
  // src/index.ts
167
177
  function setupExpoProviders(opts) {
168
178
  setHapticsProvider(expoHaptics);
169
- setBundleStorage(new ExpoBundleStorage());
179
+ setBundleStorage(new ExpoBundleStorage({ indexKey: opts?.bundleIndexKey }));
170
180
  setClipboardProvider({
171
181
  setString: (text) => ExpoClipboard.setStringAsync(text).then(() => {
172
182
  })
@@ -176,7 +186,11 @@ function setupExpoProviders(opts) {
176
186
  "expo-linking": ExpoLinking,
177
187
  ...opts?.bundleModules || {}
178
188
  });
179
- return { attachmentCache: new ExpoAttachmentCache() };
189
+ setAcsCacheStorage({
190
+ getItem: (k) => AsyncStorage3.getItem(k),
191
+ setItem: (k, v) => AsyncStorage3.setItem(k, v)
192
+ });
193
+ return { attachmentCache: new ExpoAttachmentCache({ indexKey: opts?.attachmentIndexKey }) };
180
194
  }
181
195
  export {
182
196
  ExpoAttachmentCache,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/haptics.ts","../src/bundleStorage.ts","../src/attachmentCache.ts"],"sourcesContent":["/**\n * @ateam-ai-mobile/expo — Expo-backed providers for the ADAS SDK.\n *\n * Bare-RN hosts skip this package entirely (core + plugin-runtime have safe\n * defaults); Expo hosts call setupExpoProviders() once at startup to light\n * up haptics, persistent bundle caching, attachment previews, and the\n * expo modules remote bundles may require.\n */\nimport * as ExpoWebBrowser from 'expo-web-browser';\nimport * as ExpoLinking from 'expo-linking';\nimport * as ExpoClipboard from 'expo-clipboard';\nimport {\n setHapticsProvider,\n setBundleStorage,\n registerBundleModules,\n} from '@ateam-ai-mobile/plugin-runtime';\nimport { setClipboardProvider } from '@ateam-ai-mobile/ui';\n\nimport { expoHaptics } from './haptics';\nimport { ExpoBundleStorage } from './bundleStorage';\nimport { ExpoAttachmentCache } from './attachmentCache';\n\nexport { expoHaptics } from './haptics';\nexport { ExpoBundleStorage } from './bundleStorage';\nexport { ExpoAttachmentCache } from './attachmentCache';\n// NOTE: createBridge (device capabilities bridge) is intentionally NOT\n// exported here — it statically imports the optional expo modules\n// (calendar/contacts/location/…), which would break Metro builds in hosts\n// that don't install them. Install those modules and import it from the\n// subpath: `import { createBridge } from '@ateam-ai-mobile/expo/bridge'`.\n\n/**\n * Wire all Expo providers in one call. Runs before initAdas() is fine —\n * these registries are independent of the core config.\n *\n * Returns the attachment cache so the host can pass it to initAdas():\n * const { attachmentCache } = setupExpoProviders();\n * initAdas({ ..., attachmentCache });\n */\nexport function setupExpoProviders(opts?: {\n /** Extra modules remote bundles may require(), e.g. react-native-webview.\n * Static imports must live in the HOST (it owns the native build). */\n bundleModules?: Record<string, any>;\n}): { attachmentCache: ExpoAttachmentCache } {\n setHapticsProvider(expoHaptics);\n setBundleStorage(new ExpoBundleStorage());\n setClipboardProvider({\n setString: (text: string) => ExpoClipboard.setStringAsync(text).then(() => {}),\n });\n registerBundleModules({\n 'expo-web-browser': ExpoWebBrowser,\n 'expo-linking': ExpoLinking,\n ...(opts?.bundleModules || {}),\n });\n return { attachmentCache: new ExpoAttachmentCache() };\n}\n","/**\n * expo-haptics implementation of the plugin HapticsAPI.\n */\nimport * as Haptics from 'expo-haptics';\nimport type { HapticsAPI } from '@ateam-ai-mobile/plugin-runtime';\n\nexport const expoHaptics: HapticsAPI = {\n impact(style) {\n const map = {\n light: Haptics.ImpactFeedbackStyle.Light,\n medium: Haptics.ImpactFeedbackStyle.Medium,\n heavy: Haptics.ImpactFeedbackStyle.Heavy,\n } as const;\n Haptics.impactAsync(map[style]).catch(() => {});\n },\n notification(type) {\n const map = {\n success: Haptics.NotificationFeedbackType.Success,\n warning: Haptics.NotificationFeedbackType.Warning,\n error: Haptics.NotificationFeedbackType.Error,\n } as const;\n Haptics.notificationAsync(map[type]).catch(() => {});\n },\n selection() {\n Haptics.selectionAsync().catch(() => {});\n },\n};\n","/**\n * Disk-backed BundleStorage: bundle files in the Expo cache directory,\n * index in AsyncStorage. Mirrors the reference app's cache semantics\n * (index version key, files under plugin-bundles/).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/plugin-runtime';\n\nconst CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;\nconst CACHE_INDEX_KEY = 'adas-plugin-bundles-index-v1';\n\nexport class ExpoBundleStorage implements BundleStorage {\n private dirReady = false;\n\n private async ensureDir(): Promise<void> {\n if (this.dirReady) return;\n try {\n const info = await FileSystem.getInfoAsync(CACHE_DIR);\n if (!info.exists) {\n await FileSystem.makeDirectoryAsync(CACHE_DIR, { intermediates: true });\n }\n } catch (err) {\n console.warn('[ExpoBundleStorage] cache dir error:', err);\n }\n this.dirReady = true;\n }\n\n async loadIndex(): Promise<Record<string, BundleCacheEntry>> {\n try {\n const raw = await AsyncStorage.getItem(CACHE_INDEX_KEY);\n return raw ? JSON.parse(raw) : {};\n } catch {\n return {};\n }\n }\n\n async saveIndex(index: Record<string, BundleCacheEntry>): Promise<void> {\n try {\n await AsyncStorage.setItem(CACHE_INDEX_KEY, JSON.stringify(index));\n } catch (err) {\n console.warn('[ExpoBundleStorage] index save error:', err);\n }\n }\n\n async readBundle(localPath: string): Promise<string | null> {\n try {\n const info = await FileSystem.getInfoAsync(localPath);\n if (!info.exists) return null;\n return await FileSystem.readAsStringAsync(localPath);\n } catch {\n return null; // file gone — caller re-downloads\n }\n }\n\n async writeBundle(key: string, content: string): Promise<string> {\n await this.ensureDir();\n const localPath = `${CACHE_DIR}${key}`;\n await FileSystem.writeAsStringAsync(localPath, content);\n return localPath;\n }\n\n async deleteBundle(localPath: string): Promise<void> {\n await FileSystem.deleteAsync(localPath, { idempotent: true }).catch(() => {});\n }\n\n async clearAll(): Promise<void> {\n await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {});\n await AsyncStorage.removeItem(CACHE_INDEX_KEY).catch(() => {});\n this.dirReady = false;\n }\n}\n","/**\n * Expo implementation of @ateam-ai-mobile/core's AttachmentCache: attachment bytes on\n * disk, index in AsyncStorage. Mirrors the reference app's chat attachment\n * cache (last-100 retention).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { AttachmentCache, AttachmentPreview, OutgoingAttachment } from '@ateam-ai-mobile/core';\n\nconst INDEX_KEY = 'adas-chat-attachment-index-v1';\nconst DIR = `${FileSystem.documentDirectory}chat-attachments/`;\nconst MAX_JOBS = 100;\n\ninterface CachedAttachment { mimeType: string; name: string; filePath: string }\n\nexport class ExpoAttachmentCache implements AttachmentCache {\n private index: Record<string, CachedAttachment[]> = {};\n private loaded: Promise<void>;\n\n constructor() {\n this.loaded = (async () => {\n try {\n const info = await FileSystem.getInfoAsync(DIR);\n if (!info.exists) await FileSystem.makeDirectoryAsync(DIR, { intermediates: true });\n const raw = await AsyncStorage.getItem(INDEX_KEY);\n if (raw) this.index = JSON.parse(raw);\n } catch {}\n })();\n }\n\n async ready(): Promise<void> {\n await this.loaded;\n }\n\n async save(jobId: string, attachments: OutgoingAttachment[]): Promise<void> {\n await this.loaded;\n const entries: CachedAttachment[] = [];\n for (let i = 0; i < attachments.length; i++) {\n const att = attachments[i];\n const ext = att.mimeType.startsWith('image/') ? att.mimeType.split('/')[1] || 'jpg' : 'bin';\n const filePath = `${DIR}${jobId}_${i}.${ext}`;\n try {\n await FileSystem.writeAsStringAsync(filePath, att.data, { encoding: FileSystem.EncodingType.Base64 });\n entries.push({ mimeType: att.mimeType, name: att.name, filePath });\n } catch (err) {\n console.warn('[ExpoAttachmentCache] write failed:', err);\n }\n }\n if (entries.length === 0) return;\n\n this.index[jobId] = entries;\n // Keep only the most recent MAX_JOBS jobs; delete evicted files.\n const keys = Object.keys(this.index);\n if (keys.length > MAX_JOBS) {\n for (const k of keys.slice(0, keys.length - MAX_JOBS)) {\n const old = this.index[k];\n if (old) for (const o of old) FileSystem.deleteAsync(o.filePath, { idempotent: true }).catch(() => {});\n delete this.index[k];\n }\n }\n try {\n await AsyncStorage.setItem(INDEX_KEY, JSON.stringify(this.index));\n } catch {}\n }\n\n get(jobId: string): AttachmentPreview[] | undefined {\n const entries = this.index[jobId];\n if (!entries || entries.length === 0) return undefined;\n return entries.map(e => ({\n mimeType: e.mimeType,\n name: e.name,\n previewUri: e.filePath,\n }));\n }\n}\n"],"mappings":"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","names":["FileSystem","AsyncStorage"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/haptics.ts","../src/bundleStorage.ts","../src/attachmentCache.ts"],"sourcesContent":["/**\n * @ateam-ai-mobile/expo — Expo-backed providers for the ADAS SDK.\n *\n * Bare-RN hosts skip this package entirely (core + plugin-runtime have safe\n * defaults); Expo hosts call setupExpoProviders() once at startup to light\n * up haptics, persistent bundle caching, attachment previews, and the\n * expo modules remote bundles may require.\n */\nimport * as ExpoWebBrowser from 'expo-web-browser';\nimport * as ExpoLinking from 'expo-linking';\nimport * as ExpoClipboard from 'expo-clipboard';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { setAcsCacheStorage } from '@ateam-ai-mobile/core';\nimport {\n setHapticsProvider,\n setBundleStorage,\n registerBundleModules,\n} from '@ateam-ai-mobile/plugin-runtime';\nimport { setClipboardProvider } from '@ateam-ai-mobile/ui';\n\nimport { expoHaptics } from './haptics';\nimport { ExpoBundleStorage } from './bundleStorage';\nimport { ExpoAttachmentCache } from './attachmentCache';\n\nexport { expoHaptics } from './haptics';\nexport { ExpoBundleStorage } from './bundleStorage';\nexport { ExpoAttachmentCache } from './attachmentCache';\n// NOTE: createBridge (device capabilities bridge) is intentionally NOT\n// exported here — it statically imports the optional expo modules\n// (calendar/contacts/location/…), which would break Metro builds in hosts\n// that don't install them. Install those modules and import it from the\n// subpath: `import { createBridge } from '@ateam-ai-mobile/expo/bridge'`.\n\n/**\n * Wire all Expo providers in one call. Runs before initAdas() is fine —\n * these registries are independent of the core config.\n *\n * Returns the attachment cache so the host can pass it to initAdas():\n * const { attachmentCache } = setupExpoProviders();\n * initAdas({ ..., attachmentCache });\n */\nexport function setupExpoProviders(opts?: {\n /** Extra modules remote bundles may require(), e.g. react-native-webview.\n * Static imports must live in the HOST (it owns the native build). */\n bundleModules?: Record<string, any>;\n /** AsyncStorage key overrides — pass your pre-SDK keys when migrating an\n * existing app so users keep their attachment previews / bundle cache. */\n attachmentIndexKey?: string;\n bundleIndexKey?: string;\n}): { attachmentCache: ExpoAttachmentCache } {\n setHapticsProvider(expoHaptics);\n setBundleStorage(new ExpoBundleStorage({ indexKey: opts?.bundleIndexKey }));\n setClipboardProvider({\n setString: (text: string) => ExpoClipboard.setStringAsync(text).then(() => {}),\n });\n registerBundleModules({\n 'expo-web-browser': ExpoWebBrowser,\n 'expo-linking': ExpoLinking,\n ...(opts?.bundleModules || {}),\n });\n // Persist the ACS home-actions feed across cold starts.\n setAcsCacheStorage({\n getItem: (k: string) => AsyncStorage.getItem(k),\n setItem: (k: string, v: string) => AsyncStorage.setItem(k, v),\n });\n return { attachmentCache: new ExpoAttachmentCache({ indexKey: opts?.attachmentIndexKey }) };\n}\n","/**\n * expo-haptics implementation of the plugin HapticsAPI.\n */\nimport * as Haptics from 'expo-haptics';\nimport type { HapticsAPI } from '@ateam-ai-mobile/plugin-runtime';\n\nexport const expoHaptics: HapticsAPI = {\n impact(style) {\n const map = {\n light: Haptics.ImpactFeedbackStyle.Light,\n medium: Haptics.ImpactFeedbackStyle.Medium,\n heavy: Haptics.ImpactFeedbackStyle.Heavy,\n } as const;\n Haptics.impactAsync(map[style]).catch(() => {});\n },\n notification(type) {\n const map = {\n success: Haptics.NotificationFeedbackType.Success,\n warning: Haptics.NotificationFeedbackType.Warning,\n error: Haptics.NotificationFeedbackType.Error,\n } as const;\n Haptics.notificationAsync(map[type]).catch(() => {});\n },\n selection() {\n Haptics.selectionAsync().catch(() => {});\n },\n};\n","/**\n * Disk-backed BundleStorage: bundle files in the Expo cache directory,\n * index in AsyncStorage. Mirrors the reference app's cache semantics\n * (index version key, files under plugin-bundles/).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/plugin-runtime';\n\nconst CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;\nconst DEFAULT_CACHE_INDEX_KEY = 'adas-plugin-bundles-index-v1';\n\nexport class ExpoBundleStorage implements BundleStorage {\n private dirReady = false;\n private indexKey: string;\n\n /** Pass `indexKey` when migrating from an app that already persisted a\n * bundle index under a different key — keeps the existing cache warm. */\n constructor(opts?: { indexKey?: string }) {\n this.indexKey = opts?.indexKey || DEFAULT_CACHE_INDEX_KEY;\n }\n\n private async ensureDir(): Promise<void> {\n if (this.dirReady) return;\n try {\n const info = await FileSystem.getInfoAsync(CACHE_DIR);\n if (!info.exists) {\n await FileSystem.makeDirectoryAsync(CACHE_DIR, { intermediates: true });\n }\n } catch (err) {\n console.warn('[ExpoBundleStorage] cache dir error:', err);\n }\n this.dirReady = true;\n }\n\n async loadIndex(): Promise<Record<string, BundleCacheEntry>> {\n try {\n const raw = await AsyncStorage.getItem(this.indexKey);\n return raw ? JSON.parse(raw) : {};\n } catch {\n return {};\n }\n }\n\n async saveIndex(index: Record<string, BundleCacheEntry>): Promise<void> {\n try {\n await AsyncStorage.setItem(this.indexKey, JSON.stringify(index));\n } catch (err) {\n console.warn('[ExpoBundleStorage] index save error:', err);\n }\n }\n\n async readBundle(localPath: string): Promise<string | null> {\n try {\n const info = await FileSystem.getInfoAsync(localPath);\n if (!info.exists) return null;\n return await FileSystem.readAsStringAsync(localPath);\n } catch {\n return null; // file gone — caller re-downloads\n }\n }\n\n async writeBundle(key: string, content: string): Promise<string> {\n await this.ensureDir();\n const localPath = `${CACHE_DIR}${key}`;\n await FileSystem.writeAsStringAsync(localPath, content);\n return localPath;\n }\n\n async deleteBundle(localPath: string): Promise<void> {\n await FileSystem.deleteAsync(localPath, { idempotent: true }).catch(() => {});\n }\n\n async clearAll(): Promise<void> {\n await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {});\n await AsyncStorage.removeItem(this.indexKey).catch(() => {});\n this.dirReady = false;\n }\n}\n","/**\n * Expo implementation of @ateam-ai-mobile/core's AttachmentCache: attachment bytes on\n * disk, index in AsyncStorage. Mirrors the reference app's chat attachment\n * cache (last-100 retention).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { AttachmentCache, AttachmentPreview, OutgoingAttachment } from '@ateam-ai-mobile/core';\n\nconst DEFAULT_INDEX_KEY = 'adas-chat-attachment-index-v1';\nconst DIR = `${FileSystem.documentDirectory}chat-attachments/`;\nconst MAX_JOBS = 100;\n\ninterface CachedAttachment { mimeType: string; name: string; filePath: string }\n\nexport class ExpoAttachmentCache implements AttachmentCache {\n private index: Record<string, CachedAttachment[]> = {};\n private loaded: Promise<void>;\n private indexKey: string;\n\n /** Pass `indexKey` when migrating from an app that already persisted an\n * index under a different AsyncStorage key — otherwise existing users\n * lose every cached preview on upgrade. */\n constructor(opts?: { indexKey?: string }) {\n const indexKey = opts?.indexKey || DEFAULT_INDEX_KEY;\n this.indexKey = indexKey;\n this.loaded = (async () => {\n try {\n const info = await FileSystem.getInfoAsync(DIR);\n if (!info.exists) await FileSystem.makeDirectoryAsync(DIR, { intermediates: true });\n const raw = await AsyncStorage.getItem(indexKey);\n if (raw) this.index = JSON.parse(raw);\n } catch {}\n })();\n }\n\n async ready(): Promise<void> {\n await this.loaded;\n }\n\n async save(jobId: string, attachments: OutgoingAttachment[]): Promise<void> {\n await this.loaded;\n const entries: CachedAttachment[] = [];\n for (let i = 0; i < attachments.length; i++) {\n const att = attachments[i];\n const ext = att.mimeType.startsWith('image/') ? att.mimeType.split('/')[1] || 'jpg' : 'bin';\n const filePath = `${DIR}${jobId}_${i}.${ext}`;\n try {\n await FileSystem.writeAsStringAsync(filePath, att.data, { encoding: FileSystem.EncodingType.Base64 });\n entries.push({ mimeType: att.mimeType, name: att.name, filePath });\n } catch (err) {\n console.warn('[ExpoAttachmentCache] write failed:', err);\n }\n }\n if (entries.length === 0) return;\n\n this.index[jobId] = entries;\n // Keep only the most recent MAX_JOBS jobs; delete evicted files.\n const keys = Object.keys(this.index);\n if (keys.length > MAX_JOBS) {\n for (const k of keys.slice(0, keys.length - MAX_JOBS)) {\n const old = this.index[k];\n if (old) for (const o of old) FileSystem.deleteAsync(o.filePath, { idempotent: true }).catch(() => {});\n delete this.index[k];\n }\n }\n try {\n await AsyncStorage.setItem(this.indexKey, JSON.stringify(this.index));\n } catch {}\n }\n\n get(jobId: string): AttachmentPreview[] | undefined {\n const entries = this.index[jobId];\n if (!entries || entries.length === 0) return undefined;\n return entries.map(e => ({\n mimeType: e.mimeType,\n name: e.name,\n previewUri: e.filePath,\n }));\n }\n}\n"],"mappings":";AAQA,YAAY,oBAAoB;AAChC,YAAY,iBAAiB;AAC7B,YAAY,mBAAmB;AAC/B,OAAOA,mBAAkB;AACzB,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,4BAA4B;;;ACfrC,YAAY,aAAa;AAGlB,IAAM,cAA0B;AAAA,EACrC,OAAO,OAAO;AACZ,UAAM,MAAM;AAAA,MACV,OAAe,4BAAoB;AAAA,MACnC,QAAgB,4BAAoB;AAAA,MACpC,OAAe,4BAAoB;AAAA,IACrC;AACA,IAAQ,oBAAY,IAAI,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAChD;AAAA,EACA,aAAa,MAAM;AACjB,UAAM,MAAM;AAAA,MACV,SAAiB,iCAAyB;AAAA,MAC1C,SAAiB,iCAAyB;AAAA,MAC1C,OAAe,iCAAyB;AAAA,IAC1C;AACA,IAAQ,0BAAkB,IAAI,IAAI,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACrD;AAAA,EACA,YAAY;AACV,IAAQ,uBAAe,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzC;AACF;;;ACrBA,YAAY,gBAAgB;AAC5B,OAAO,kBAAkB;AAGzB,IAAM,YAAY,GAAc,yBAAc;AAC9C,IAAM,0BAA0B;AAEzB,IAAM,oBAAN,MAAiD;AAAA;AAAA;AAAA,EAMtD,YAAY,MAA8B;AAL1C,SAAQ,WAAW;AAMjB,SAAK,WAAW,MAAM,YAAY;AAAA,EACpC;AAAA,EAEA,MAAc,YAA2B;AACvC,QAAI,KAAK,SAAU;AACnB,QAAI;AACF,YAAM,OAAO,MAAiB,wBAAa,SAAS;AACpD,UAAI,CAAC,KAAK,QAAQ;AAChB,cAAiB,8BAAmB,WAAW,EAAE,eAAe,KAAK,CAAC;AAAA,MACxE;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,wCAAwC,GAAG;AAAA,IAC1D;AACA,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,YAAuD;AAC3D,QAAI;AACF,YAAM,MAAM,MAAM,aAAa,QAAQ,KAAK,QAAQ;AACpD,aAAO,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAAA,IAClC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,OAAwD;AACtE,QAAI;AACF,YAAM,aAAa,QAAQ,KAAK,UAAU,KAAK,UAAU,KAAK,CAAC;AAAA,IACjE,SAAS,KAAK;AACZ,cAAQ,KAAK,yCAAyC,GAAG;AAAA,IAC3D;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,WAA2C;AAC1D,QAAI;AACF,YAAM,OAAO,MAAiB,wBAAa,SAAS;AACpD,UAAI,CAAC,KAAK,OAAQ,QAAO;AACzB,aAAO,MAAiB,6BAAkB,SAAS;AAAA,IACrD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,KAAa,SAAkC;AAC/D,UAAM,KAAK,UAAU;AACrB,UAAM,YAAY,GAAG,SAAS,GAAG,GAAG;AACpC,UAAiB,8BAAmB,WAAW,OAAO;AACtD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,WAAkC;AACnD,UAAiB,uBAAY,WAAW,EAAE,YAAY,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC9E;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAiB,uBAAY,WAAW,EAAE,YAAY,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC5E,UAAM,aAAa,WAAW,KAAK,QAAQ,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC3D,SAAK,WAAW;AAAA,EAClB;AACF;;;ACzEA,YAAYC,iBAAgB;AAC5B,OAAOC,mBAAkB;AAGzB,IAAM,oBAAoB;AAC1B,IAAM,MAAM,GAAc,6BAAiB;AAC3C,IAAM,WAAW;AAIV,IAAM,sBAAN,MAAqD;AAAA;AAAA;AAAA;AAAA,EAQ1D,YAAY,MAA8B;AAP1C,SAAQ,QAA4C,CAAC;AAQnD,UAAM,WAAW,MAAM,YAAY;AACnC,SAAK,WAAW;AAChB,SAAK,UAAU,YAAY;AACzB,UAAI;AACF,cAAM,OAAO,MAAiB,yBAAa,GAAG;AAC9C,YAAI,CAAC,KAAK,OAAQ,OAAiB,+BAAmB,KAAK,EAAE,eAAe,KAAK,CAAC;AAClF,cAAM,MAAM,MAAMA,cAAa,QAAQ,QAAQ;AAC/C,YAAI,IAAK,MAAK,QAAQ,KAAK,MAAM,GAAG;AAAA,MACtC,QAAQ;AAAA,MAAC;AAAA,IACX,GAAG;AAAA,EACL;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK;AAAA,EACb;AAAA,EAEA,MAAM,KAAK,OAAe,aAAkD;AAC1E,UAAM,KAAK;AACX,UAAM,UAA8B,CAAC;AACrC,aAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,YAAM,MAAM,YAAY,CAAC;AACzB,YAAM,MAAM,IAAI,SAAS,WAAW,QAAQ,IAAI,IAAI,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK,QAAQ;AACtF,YAAM,WAAW,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC,IAAI,GAAG;AAC3C,UAAI;AACF,cAAiB,+BAAmB,UAAU,IAAI,MAAM,EAAE,UAAqB,yBAAa,OAAO,CAAC;AACpG,gBAAQ,KAAK,EAAE,UAAU,IAAI,UAAU,MAAM,IAAI,MAAM,SAAS,CAAC;AAAA,MACnE,SAAS,KAAK;AACZ,gBAAQ,KAAK,uCAAuC,GAAG;AAAA,MACzD;AAAA,IACF;AACA,QAAI,QAAQ,WAAW,EAAG;AAE1B,SAAK,MAAM,KAAK,IAAI;AAEpB,UAAM,OAAO,OAAO,KAAK,KAAK,KAAK;AACnC,QAAI,KAAK,SAAS,UAAU;AAC1B,iBAAW,KAAK,KAAK,MAAM,GAAG,KAAK,SAAS,QAAQ,GAAG;AACrD,cAAM,MAAM,KAAK,MAAM,CAAC;AACxB,YAAI,IAAK,YAAW,KAAK,IAAK,CAAW,wBAAY,EAAE,UAAU,EAAE,YAAY,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACrG,eAAO,KAAK,MAAM,CAAC;AAAA,MACrB;AAAA,IACF;AACA,QAAI;AACF,YAAMA,cAAa,QAAQ,KAAK,UAAU,KAAK,UAAU,KAAK,KAAK,CAAC;AAAA,IACtE,QAAQ;AAAA,IAAC;AAAA,EACX;AAAA,EAEA,IAAI,OAAgD;AAClD,UAAM,UAAU,KAAK,MAAM,KAAK;AAChC,QAAI,CAAC,WAAW,QAAQ,WAAW,EAAG,QAAO;AAC7C,WAAO,QAAQ,IAAI,QAAM;AAAA,MACvB,UAAU,EAAE;AAAA,MACZ,MAAM,EAAE;AAAA,MACR,YAAY,EAAE;AAAA,IAChB,EAAE;AAAA,EACJ;AACF;;;AHvCO,SAAS,mBAAmB,MAQU;AAC3C,qBAAmB,WAAW;AAC9B,mBAAiB,IAAI,kBAAkB,EAAE,UAAU,MAAM,eAAe,CAAC,CAAC;AAC1E,uBAAqB;AAAA,IACnB,WAAW,CAAC,SAA+B,6BAAe,IAAI,EAAE,KAAK,MAAM;AAAA,IAAC,CAAC;AAAA,EAC/E,CAAC;AACD,wBAAsB;AAAA,IACpB,oBAAoB;AAAA,IACpB,gBAAgB;AAAA,IAChB,GAAI,MAAM,iBAAiB,CAAC;AAAA,EAC9B,CAAC;AAED,qBAAmB;AAAA,IACjB,SAAS,CAAC,MAAcC,cAAa,QAAQ,CAAC;AAAA,IAC9C,SAAS,CAAC,GAAW,MAAcA,cAAa,QAAQ,GAAG,CAAC;AAAA,EAC9D,CAAC;AACD,SAAO,EAAE,iBAAiB,IAAI,oBAAoB,EAAE,UAAU,MAAM,mBAAmB,CAAC,EAAE;AAC5F;","names":["AsyncStorage","FileSystem","AsyncStorage","AsyncStorage"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai-mobile/expo",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Expo-backed providers for the ADAS SDK \u2014 haptics, bundle disk cache, attachment cache, device bridge.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,9 +9,9 @@
9
9
  "build": "tsup"
10
10
  },
11
11
  "dependencies": {
12
- "@ateam-ai-mobile/core": "0.1.0",
13
- "@ateam-ai-mobile/plugin-runtime": "0.1.0",
14
- "@ateam-ai-mobile/ui": "0.1.0"
12
+ "@ateam-ai-mobile/core": "0.1.1",
13
+ "@ateam-ai-mobile/plugin-runtime": "0.1.1",
14
+ "@ateam-ai-mobile/ui": "0.1.1"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "@react-native-async-storage/async-storage": "*",
@@ -7,7 +7,7 @@ import * as FileSystem from 'expo-file-system/legacy';
7
7
  import AsyncStorage from '@react-native-async-storage/async-storage';
8
8
  import type { AttachmentCache, AttachmentPreview, OutgoingAttachment } from '@ateam-ai-mobile/core';
9
9
 
10
- const INDEX_KEY = 'adas-chat-attachment-index-v1';
10
+ const DEFAULT_INDEX_KEY = 'adas-chat-attachment-index-v1';
11
11
  const DIR = `${FileSystem.documentDirectory}chat-attachments/`;
12
12
  const MAX_JOBS = 100;
13
13
 
@@ -16,13 +16,19 @@ interface CachedAttachment { mimeType: string; name: string; filePath: string }
16
16
  export class ExpoAttachmentCache implements AttachmentCache {
17
17
  private index: Record<string, CachedAttachment[]> = {};
18
18
  private loaded: Promise<void>;
19
+ private indexKey: string;
19
20
 
20
- constructor() {
21
+ /** Pass `indexKey` when migrating from an app that already persisted an
22
+ * index under a different AsyncStorage key — otherwise existing users
23
+ * lose every cached preview on upgrade. */
24
+ constructor(opts?: { indexKey?: string }) {
25
+ const indexKey = opts?.indexKey || DEFAULT_INDEX_KEY;
26
+ this.indexKey = indexKey;
21
27
  this.loaded = (async () => {
22
28
  try {
23
29
  const info = await FileSystem.getInfoAsync(DIR);
24
30
  if (!info.exists) await FileSystem.makeDirectoryAsync(DIR, { intermediates: true });
25
- const raw = await AsyncStorage.getItem(INDEX_KEY);
31
+ const raw = await AsyncStorage.getItem(indexKey);
26
32
  if (raw) this.index = JSON.parse(raw);
27
33
  } catch {}
28
34
  })();
@@ -59,7 +65,7 @@ export class ExpoAttachmentCache implements AttachmentCache {
59
65
  }
60
66
  }
61
67
  try {
62
- await AsyncStorage.setItem(INDEX_KEY, JSON.stringify(this.index));
68
+ await AsyncStorage.setItem(this.indexKey, JSON.stringify(this.index));
63
69
  } catch {}
64
70
  }
65
71
 
@@ -8,10 +8,17 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
8
8
  import type { BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/plugin-runtime';
9
9
 
10
10
  const CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;
11
- const CACHE_INDEX_KEY = 'adas-plugin-bundles-index-v1';
11
+ const DEFAULT_CACHE_INDEX_KEY = 'adas-plugin-bundles-index-v1';
12
12
 
13
13
  export class ExpoBundleStorage implements BundleStorage {
14
14
  private dirReady = false;
15
+ private indexKey: string;
16
+
17
+ /** Pass `indexKey` when migrating from an app that already persisted a
18
+ * bundle index under a different key — keeps the existing cache warm. */
19
+ constructor(opts?: { indexKey?: string }) {
20
+ this.indexKey = opts?.indexKey || DEFAULT_CACHE_INDEX_KEY;
21
+ }
15
22
 
16
23
  private async ensureDir(): Promise<void> {
17
24
  if (this.dirReady) return;
@@ -28,7 +35,7 @@ export class ExpoBundleStorage implements BundleStorage {
28
35
 
29
36
  async loadIndex(): Promise<Record<string, BundleCacheEntry>> {
30
37
  try {
31
- const raw = await AsyncStorage.getItem(CACHE_INDEX_KEY);
38
+ const raw = await AsyncStorage.getItem(this.indexKey);
32
39
  return raw ? JSON.parse(raw) : {};
33
40
  } catch {
34
41
  return {};
@@ -37,7 +44,7 @@ export class ExpoBundleStorage implements BundleStorage {
37
44
 
38
45
  async saveIndex(index: Record<string, BundleCacheEntry>): Promise<void> {
39
46
  try {
40
- await AsyncStorage.setItem(CACHE_INDEX_KEY, JSON.stringify(index));
47
+ await AsyncStorage.setItem(this.indexKey, JSON.stringify(index));
41
48
  } catch (err) {
42
49
  console.warn('[ExpoBundleStorage] index save error:', err);
43
50
  }
@@ -66,7 +73,7 @@ export class ExpoBundleStorage implements BundleStorage {
66
73
 
67
74
  async clearAll(): Promise<void> {
68
75
  await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {});
69
- await AsyncStorage.removeItem(CACHE_INDEX_KEY).catch(() => {});
76
+ await AsyncStorage.removeItem(this.indexKey).catch(() => {});
70
77
  this.dirReady = false;
71
78
  }
72
79
  }
package/src/index.ts CHANGED
@@ -9,6 +9,8 @@
9
9
  import * as ExpoWebBrowser from 'expo-web-browser';
10
10
  import * as ExpoLinking from 'expo-linking';
11
11
  import * as ExpoClipboard from 'expo-clipboard';
12
+ import AsyncStorage from '@react-native-async-storage/async-storage';
13
+ import { setAcsCacheStorage } from '@ateam-ai-mobile/core';
12
14
  import {
13
15
  setHapticsProvider,
14
16
  setBundleStorage,
@@ -41,9 +43,13 @@ export function setupExpoProviders(opts?: {
41
43
  /** Extra modules remote bundles may require(), e.g. react-native-webview.
42
44
  * Static imports must live in the HOST (it owns the native build). */
43
45
  bundleModules?: Record<string, any>;
46
+ /** AsyncStorage key overrides — pass your pre-SDK keys when migrating an
47
+ * existing app so users keep their attachment previews / bundle cache. */
48
+ attachmentIndexKey?: string;
49
+ bundleIndexKey?: string;
44
50
  }): { attachmentCache: ExpoAttachmentCache } {
45
51
  setHapticsProvider(expoHaptics);
46
- setBundleStorage(new ExpoBundleStorage());
52
+ setBundleStorage(new ExpoBundleStorage({ indexKey: opts?.bundleIndexKey }));
47
53
  setClipboardProvider({
48
54
  setString: (text: string) => ExpoClipboard.setStringAsync(text).then(() => {}),
49
55
  });
@@ -52,5 +58,10 @@ export function setupExpoProviders(opts?: {
52
58
  'expo-linking': ExpoLinking,
53
59
  ...(opts?.bundleModules || {}),
54
60
  });
55
- return { attachmentCache: new ExpoAttachmentCache() };
61
+ // Persist the ACS home-actions feed across cold starts.
62
+ setAcsCacheStorage({
63
+ getItem: (k: string) => AsyncStorage.getItem(k),
64
+ setItem: (k: string, v: string) => AsyncStorage.setItem(k, v),
65
+ });
66
+ return { attachmentCache: new ExpoAttachmentCache({ indexKey: opts?.attachmentIndexKey }) };
56
67
  }