@ateam-ai-mobile/expo 0.1.0

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/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@ateam-ai-mobile/expo",
3
+ "version": "0.1.0",
4
+ "description": "Expo-backed providers for the ADAS SDK \u2014 haptics, bundle disk cache, attachment cache, device bridge.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "typecheck": "tsc --noEmit",
9
+ "build": "tsup"
10
+ },
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"
15
+ },
16
+ "peerDependencies": {
17
+ "@react-native-async-storage/async-storage": "*",
18
+ "expo-battery": "*",
19
+ "expo-calendar": "*",
20
+ "expo-contacts": "*",
21
+ "expo-device": "*",
22
+ "expo-file-system": "*",
23
+ "expo-haptics": "*",
24
+ "expo-linking": "*",
25
+ "expo-location": "*",
26
+ "expo-network": "*",
27
+ "expo-notifications": "*",
28
+ "expo-sms": "*",
29
+ "expo-web-browser": "*",
30
+ "react": ">=18",
31
+ "react-native": "*",
32
+ "expo-clipboard": "*"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "expo-battery": {
36
+ "optional": true
37
+ },
38
+ "expo-calendar": {
39
+ "optional": true
40
+ },
41
+ "expo-contacts": {
42
+ "optional": true
43
+ },
44
+ "expo-device": {
45
+ "optional": true
46
+ },
47
+ "expo-location": {
48
+ "optional": true
49
+ },
50
+ "expo-network": {
51
+ "optional": true
52
+ },
53
+ "expo-notifications": {
54
+ "optional": true
55
+ },
56
+ "expo-sms": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "exports": {
61
+ ".": {
62
+ "react-native": "./src/index.ts",
63
+ "types": "./dist/index.d.ts",
64
+ "import": "./dist/index.mjs",
65
+ "require": "./dist/index.js"
66
+ },
67
+ "./bridge": {
68
+ "react-native": "./src/createBridge.ts",
69
+ "types": "./dist/createBridge.d.ts",
70
+ "import": "./dist/createBridge.mjs",
71
+ "require": "./dist/createBridge.js"
72
+ }
73
+ },
74
+ "module": "dist/index.mjs",
75
+ "source": "src/index.ts",
76
+ "react-native": "src/index.ts",
77
+ "files": [
78
+ "dist",
79
+ "src"
80
+ ],
81
+ "publishConfig": {
82
+ "access": "public"
83
+ }
84
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Expo implementation of @ateam-ai-mobile/core's AttachmentCache: attachment bytes on
3
+ * disk, index in AsyncStorage. Mirrors the reference app's chat attachment
4
+ * cache (last-100 retention).
5
+ */
6
+ import * as FileSystem from 'expo-file-system/legacy';
7
+ import AsyncStorage from '@react-native-async-storage/async-storage';
8
+ import type { AttachmentCache, AttachmentPreview, OutgoingAttachment } from '@ateam-ai-mobile/core';
9
+
10
+ const INDEX_KEY = 'adas-chat-attachment-index-v1';
11
+ const DIR = `${FileSystem.documentDirectory}chat-attachments/`;
12
+ const MAX_JOBS = 100;
13
+
14
+ interface CachedAttachment { mimeType: string; name: string; filePath: string }
15
+
16
+ export class ExpoAttachmentCache implements AttachmentCache {
17
+ private index: Record<string, CachedAttachment[]> = {};
18
+ private loaded: Promise<void>;
19
+
20
+ constructor() {
21
+ this.loaded = (async () => {
22
+ try {
23
+ const info = await FileSystem.getInfoAsync(DIR);
24
+ if (!info.exists) await FileSystem.makeDirectoryAsync(DIR, { intermediates: true });
25
+ const raw = await AsyncStorage.getItem(INDEX_KEY);
26
+ if (raw) this.index = JSON.parse(raw);
27
+ } catch {}
28
+ })();
29
+ }
30
+
31
+ async ready(): Promise<void> {
32
+ await this.loaded;
33
+ }
34
+
35
+ async save(jobId: string, attachments: OutgoingAttachment[]): Promise<void> {
36
+ await this.loaded;
37
+ const entries: CachedAttachment[] = [];
38
+ for (let i = 0; i < attachments.length; i++) {
39
+ const att = attachments[i];
40
+ const ext = att.mimeType.startsWith('image/') ? att.mimeType.split('/')[1] || 'jpg' : 'bin';
41
+ const filePath = `${DIR}${jobId}_${i}.${ext}`;
42
+ try {
43
+ await FileSystem.writeAsStringAsync(filePath, att.data, { encoding: FileSystem.EncodingType.Base64 });
44
+ entries.push({ mimeType: att.mimeType, name: att.name, filePath });
45
+ } catch (err) {
46
+ console.warn('[ExpoAttachmentCache] write failed:', err);
47
+ }
48
+ }
49
+ if (entries.length === 0) return;
50
+
51
+ this.index[jobId] = entries;
52
+ // Keep only the most recent MAX_JOBS jobs; delete evicted files.
53
+ const keys = Object.keys(this.index);
54
+ if (keys.length > MAX_JOBS) {
55
+ for (const k of keys.slice(0, keys.length - MAX_JOBS)) {
56
+ const old = this.index[k];
57
+ if (old) for (const o of old) FileSystem.deleteAsync(o.filePath, { idempotent: true }).catch(() => {});
58
+ delete this.index[k];
59
+ }
60
+ }
61
+ try {
62
+ await AsyncStorage.setItem(INDEX_KEY, JSON.stringify(this.index));
63
+ } catch {}
64
+ }
65
+
66
+ get(jobId: string): AttachmentPreview[] | undefined {
67
+ const entries = this.index[jobId];
68
+ if (!entries || entries.length === 0) return undefined;
69
+ return entries.map(e => ({
70
+ mimeType: e.mimeType,
71
+ name: e.name,
72
+ previewUri: e.filePath,
73
+ }));
74
+ }
75
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Disk-backed BundleStorage: bundle files in the Expo cache directory,
3
+ * index in AsyncStorage. Mirrors the reference app's cache semantics
4
+ * (index version key, files under plugin-bundles/).
5
+ */
6
+ import * as FileSystem from 'expo-file-system/legacy';
7
+ import AsyncStorage from '@react-native-async-storage/async-storage';
8
+ import type { BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/plugin-runtime';
9
+
10
+ const CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;
11
+ const CACHE_INDEX_KEY = 'adas-plugin-bundles-index-v1';
12
+
13
+ export class ExpoBundleStorage implements BundleStorage {
14
+ private dirReady = false;
15
+
16
+ private async ensureDir(): Promise<void> {
17
+ if (this.dirReady) return;
18
+ try {
19
+ const info = await FileSystem.getInfoAsync(CACHE_DIR);
20
+ if (!info.exists) {
21
+ await FileSystem.makeDirectoryAsync(CACHE_DIR, { intermediates: true });
22
+ }
23
+ } catch (err) {
24
+ console.warn('[ExpoBundleStorage] cache dir error:', err);
25
+ }
26
+ this.dirReady = true;
27
+ }
28
+
29
+ async loadIndex(): Promise<Record<string, BundleCacheEntry>> {
30
+ try {
31
+ const raw = await AsyncStorage.getItem(CACHE_INDEX_KEY);
32
+ return raw ? JSON.parse(raw) : {};
33
+ } catch {
34
+ return {};
35
+ }
36
+ }
37
+
38
+ async saveIndex(index: Record<string, BundleCacheEntry>): Promise<void> {
39
+ try {
40
+ await AsyncStorage.setItem(CACHE_INDEX_KEY, JSON.stringify(index));
41
+ } catch (err) {
42
+ console.warn('[ExpoBundleStorage] index save error:', err);
43
+ }
44
+ }
45
+
46
+ async readBundle(localPath: string): Promise<string | null> {
47
+ try {
48
+ const info = await FileSystem.getInfoAsync(localPath);
49
+ if (!info.exists) return null;
50
+ return await FileSystem.readAsStringAsync(localPath);
51
+ } catch {
52
+ return null; // file gone — caller re-downloads
53
+ }
54
+ }
55
+
56
+ async writeBundle(key: string, content: string): Promise<string> {
57
+ await this.ensureDir();
58
+ const localPath = `${CACHE_DIR}${key}`;
59
+ await FileSystem.writeAsStringAsync(localPath, content);
60
+ return localPath;
61
+ }
62
+
63
+ async deleteBundle(localPath: string): Promise<void> {
64
+ await FileSystem.deleteAsync(localPath, { idempotent: true }).catch(() => {});
65
+ }
66
+
67
+ async clearAll(): Promise<void> {
68
+ await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {});
69
+ await AsyncStorage.removeItem(CACHE_INDEX_KEY).catch(() => {});
70
+ this.dirReady = false;
71
+ }
72
+ }