@ateam-ai-mobile/expo 0.1.19 → 0.1.21
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/aiLive/provider.d.mts +20 -0
- package/dist/aiLive/provider.d.ts +20 -0
- package/dist/aiLive/provider.js +400 -0
- package/dist/aiLive/provider.js.map +1 -0
- package/dist/aiLive/provider.mjs +15 -0
- package/dist/aiLive/provider.mjs.map +1 -0
- package/dist/bootstrap.js +350 -3
- package/dist/bootstrap.js.map +1 -1
- package/dist/bootstrap.mjs +17 -12
- package/dist/bootstrap.mjs.map +1 -1
- package/dist/capabilities/index.d.mts +40 -1
- package/dist/capabilities/index.d.ts +40 -1
- package/dist/capabilities/index.js +50 -14
- package/dist/capabilities/index.js.map +1 -1
- package/dist/capabilities/index.mjs +49 -32
- package/dist/capabilities/index.mjs.map +1 -1
- package/dist/{chunk-42FP5DTO.mjs → chunk-2ZQEQJQ6.mjs} +1 -1
- package/dist/chunk-2ZQEQJQ6.mjs.map +1 -0
- package/dist/chunk-AE55BKYV.mjs +24 -0
- package/dist/chunk-AE55BKYV.mjs.map +1 -0
- package/dist/chunk-BAZFMTCC.mjs +815 -0
- package/dist/chunk-BAZFMTCC.mjs.map +1 -0
- package/dist/{chunk-Z5AI2SXX.mjs → chunk-C62NQA4F.mjs} +203 -1004
- package/dist/chunk-C62NQA4F.mjs.map +1 -0
- package/dist/{chunk-DE6Y4VRI.mjs → chunk-GA7DS3PA.mjs} +1 -1
- package/dist/{chunk-DE6Y4VRI.mjs.map → chunk-GA7DS3PA.mjs.map} +1 -1
- package/dist/chunk-GW7YGXOW.mjs +47 -0
- package/dist/chunk-GW7YGXOW.mjs.map +1 -0
- package/dist/chunk-JUBLG3PN.mjs +351 -0
- package/dist/chunk-JUBLG3PN.mjs.map +1 -0
- package/dist/{chunk-Z3P7RUCC.mjs → chunk-OEX4OKYQ.mjs} +13 -13
- package/dist/{chunk-6JX222YX.mjs → chunk-WQK6PUBN.mjs} +9 -2
- package/dist/chunk-WQK6PUBN.mjs.map +1 -0
- package/dist/device/createDeviceProvider.js.map +1 -1
- package/dist/device/createDeviceProvider.mjs +7 -6
- package/dist/device-bridge/index.mjs +8 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/push/index.d.mts +24 -0
- package/dist/push/index.d.ts +24 -0
- package/dist/push/index.js +224 -0
- package/dist/push/index.js.map +1 -0
- package/dist/push/index.mjs +174 -0
- package/dist/push/index.mjs.map +1 -0
- package/dist/push/silentSync.d.mts +8 -0
- package/dist/push/silentSync.d.ts +8 -0
- package/dist/push/silentSync.js +1075 -0
- package/dist/push/silentSync.js.map +1 -0
- package/dist/push/silentSync.mjs +67 -0
- package/dist/push/silentSync.mjs.map +1 -0
- package/dist/vision/index.d.mts +7 -1
- package/dist/vision/index.d.ts +7 -1
- package/dist/vision/index.js +8 -0
- package/dist/vision/index.js.map +1 -1
- package/dist/vision/index.mjs +3 -1
- package/ios/AdaVisionModule.swift +44 -1
- package/package.json +28 -6
- package/src/aiLive/provider.ts +242 -0
- package/src/aiLive/streamProtocol.ts +90 -0
- package/src/bootstrap.ts +7 -1
- package/src/capabilities/choices.ts +65 -0
- package/src/capabilities/config.ts +5 -4
- package/src/capabilities/index.ts +2 -0
- package/src/capabilities/picturesToAi.ts +40 -0
- package/src/capabilities/store.ts +38 -37
- package/src/index.ts +3 -0
- package/src/push/index.ts +18 -0
- package/src/push/messaging.ts +49 -0
- package/src/push/registration.ts +31 -0
- package/src/push/silentSync.ts +83 -0
- package/src/push/usePushNotifications.ts +224 -0
- package/src/vision/index.ts +14 -0
- package/dist/chunk-42FP5DTO.mjs.map +0 -1
- package/dist/chunk-6JX222YX.mjs.map +0 -1
- package/dist/chunk-Z5AI2SXX.mjs.map +0 -1
- /package/dist/{chunk-Z3P7RUCC.mjs.map → chunk-OEX4OKYQ.mjs.map} +0 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RelayClient,
|
|
3
|
+
registerBackgroundSync,
|
|
4
|
+
unregisterBackgroundSync
|
|
5
|
+
} from "./chunk-C62NQA4F.mjs";
|
|
6
|
+
import {
|
|
7
|
+
collectHistory,
|
|
8
|
+
collectToday
|
|
9
|
+
} from "./chunk-XQSY6MHP.mjs";
|
|
10
|
+
import {
|
|
11
|
+
__export
|
|
12
|
+
} from "./chunk-WMJKH4XE.mjs";
|
|
13
|
+
|
|
14
|
+
// src/device-bridge/actions/messaging.ts
|
|
15
|
+
import * as SMS from "expo-sms";
|
|
16
|
+
async function execute(actionId, params) {
|
|
17
|
+
const phone = params.phone;
|
|
18
|
+
const message = params.message;
|
|
19
|
+
if (!phone || !message) {
|
|
20
|
+
return {
|
|
21
|
+
id: actionId,
|
|
22
|
+
ok: false,
|
|
23
|
+
error: "Missing phone or message parameter",
|
|
24
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const available = await SMS.isAvailableAsync();
|
|
29
|
+
if (!available) {
|
|
30
|
+
return {
|
|
31
|
+
id: actionId,
|
|
32
|
+
ok: false,
|
|
33
|
+
error: "SMS not available on this device",
|
|
34
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const { result } = await SMS.sendSMSAsync([phone], message);
|
|
38
|
+
return {
|
|
39
|
+
id: actionId,
|
|
40
|
+
ok: result === "sent" || result === "unknown",
|
|
41
|
+
// 'unknown' on Android means it was sent
|
|
42
|
+
result: {
|
|
43
|
+
to: params.contact_name || phone,
|
|
44
|
+
phone,
|
|
45
|
+
message,
|
|
46
|
+
status: result,
|
|
47
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
48
|
+
},
|
|
49
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
id: actionId,
|
|
54
|
+
ok: false,
|
|
55
|
+
error: `SMS send failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
56
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/device-bridge/actions/calendar-actions.ts
|
|
62
|
+
import * as ExpoCalendar from "expo-calendar";
|
|
63
|
+
async function getDefaultCalendarId() {
|
|
64
|
+
const calendars = await ExpoCalendar.getCalendarsAsync(ExpoCalendar.EntityTypes.EVENT);
|
|
65
|
+
const defaultCal = calendars.find((c) => c.isPrimary) || calendars[0];
|
|
66
|
+
return defaultCal?.id || null;
|
|
67
|
+
}
|
|
68
|
+
async function executeCreate(actionId, params) {
|
|
69
|
+
const title = params.title;
|
|
70
|
+
const date = params.date;
|
|
71
|
+
const startTime = params.start_time;
|
|
72
|
+
const endTime = params.end_time;
|
|
73
|
+
if (!title || !date || !startTime) {
|
|
74
|
+
return {
|
|
75
|
+
id: actionId,
|
|
76
|
+
ok: false,
|
|
77
|
+
error: "Missing title, date, or start_time",
|
|
78
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const calendarId = await getDefaultCalendarId();
|
|
83
|
+
if (!calendarId) {
|
|
84
|
+
return {
|
|
85
|
+
id: actionId,
|
|
86
|
+
ok: false,
|
|
87
|
+
error: "No calendar found on device",
|
|
88
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const [year, month, day] = date.split("-").map(Number);
|
|
92
|
+
const [startH, startM] = startTime.split(":").map(Number);
|
|
93
|
+
const startDate = new Date(year, month - 1, day, startH, startM);
|
|
94
|
+
let endDate;
|
|
95
|
+
if (endTime) {
|
|
96
|
+
const [endH, endM] = endTime.split(":").map(Number);
|
|
97
|
+
endDate = new Date(year, month - 1, day, endH, endM);
|
|
98
|
+
} else {
|
|
99
|
+
endDate = new Date(startDate.getTime() + 60 * 60 * 1e3);
|
|
100
|
+
}
|
|
101
|
+
const eventId = await ExpoCalendar.createEventAsync(calendarId, {
|
|
102
|
+
title,
|
|
103
|
+
startDate,
|
|
104
|
+
endDate,
|
|
105
|
+
location: params.location || void 0,
|
|
106
|
+
notes: params.notes || void 0,
|
|
107
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
id: actionId,
|
|
111
|
+
ok: true,
|
|
112
|
+
result: {
|
|
113
|
+
event_id: eventId,
|
|
114
|
+
title,
|
|
115
|
+
date,
|
|
116
|
+
start: startTime,
|
|
117
|
+
end: endTime || formatTime(endDate),
|
|
118
|
+
message: `Event "${title}" created successfully`
|
|
119
|
+
},
|
|
120
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
id: actionId,
|
|
125
|
+
ok: false,
|
|
126
|
+
error: `Calendar create failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
127
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function executeReschedule(actionId, params) {
|
|
132
|
+
const eventId = params.event_id;
|
|
133
|
+
if (!eventId) {
|
|
134
|
+
return {
|
|
135
|
+
id: actionId,
|
|
136
|
+
ok: false,
|
|
137
|
+
error: "Missing event_id",
|
|
138
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const updates = {};
|
|
143
|
+
if (params.new_date && params.new_start_time) {
|
|
144
|
+
const [year, month, day] = params.new_date.split("-").map(Number);
|
|
145
|
+
const [h, m] = params.new_start_time.split(":").map(Number);
|
|
146
|
+
updates.startDate = new Date(year, month - 1, day, h, m);
|
|
147
|
+
if (params.new_end_time) {
|
|
148
|
+
const [eh, em] = params.new_end_time.split(":").map(Number);
|
|
149
|
+
updates.endDate = new Date(year, month - 1, day, eh, em);
|
|
150
|
+
} else {
|
|
151
|
+
updates.endDate = new Date(updates.startDate.getTime() + 60 * 60 * 1e3);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
await ExpoCalendar.updateEventAsync(eventId, updates);
|
|
155
|
+
return {
|
|
156
|
+
id: actionId,
|
|
157
|
+
ok: true,
|
|
158
|
+
result: { event_id: eventId, message: "Event rescheduled successfully" },
|
|
159
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return {
|
|
163
|
+
id: actionId,
|
|
164
|
+
ok: false,
|
|
165
|
+
error: `Calendar reschedule failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
166
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function executeCancel(actionId, params) {
|
|
171
|
+
const eventId = params.event_id;
|
|
172
|
+
if (!eventId) {
|
|
173
|
+
return {
|
|
174
|
+
id: actionId,
|
|
175
|
+
ok: false,
|
|
176
|
+
error: "Missing event_id",
|
|
177
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
await ExpoCalendar.deleteEventAsync(eventId);
|
|
182
|
+
return {
|
|
183
|
+
id: actionId,
|
|
184
|
+
ok: true,
|
|
185
|
+
result: { event_id: eventId, message: "Event cancelled successfully" },
|
|
186
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
187
|
+
};
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return {
|
|
190
|
+
id: actionId,
|
|
191
|
+
ok: false,
|
|
192
|
+
error: `Calendar cancel failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
193
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function formatTime(date) {
|
|
198
|
+
return date.toLocaleTimeString("en-US", {
|
|
199
|
+
hour: "2-digit",
|
|
200
|
+
minute: "2-digit",
|
|
201
|
+
hour12: false
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/device-bridge/actions/dnd.ts
|
|
206
|
+
var currentDndState = {
|
|
207
|
+
enabled: false,
|
|
208
|
+
schedule: { start: "22:00", end: "07:00" },
|
|
209
|
+
allowlist: []
|
|
210
|
+
};
|
|
211
|
+
async function execute2(actionId, params) {
|
|
212
|
+
try {
|
|
213
|
+
if (params.enabled !== void 0) {
|
|
214
|
+
currentDndState.enabled = params.enabled;
|
|
215
|
+
}
|
|
216
|
+
if (params.schedule_start) {
|
|
217
|
+
currentDndState.schedule.start = params.schedule_start;
|
|
218
|
+
}
|
|
219
|
+
if (params.schedule_end) {
|
|
220
|
+
currentDndState.schedule.end = params.schedule_end;
|
|
221
|
+
}
|
|
222
|
+
if (params.allowlist) {
|
|
223
|
+
currentDndState.allowlist = params.allowlist;
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
id: actionId,
|
|
227
|
+
ok: true,
|
|
228
|
+
result: {
|
|
229
|
+
message: `DND ${currentDndState.enabled ? "enabled" : "disabled"}`,
|
|
230
|
+
dnd: currentDndState
|
|
231
|
+
},
|
|
232
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
233
|
+
};
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return {
|
|
236
|
+
id: actionId,
|
|
237
|
+
ok: false,
|
|
238
|
+
error: `DND set failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
239
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/device-bridge/actions/navigation.ts
|
|
245
|
+
import * as Linking from "expo-linking";
|
|
246
|
+
import { Platform } from "react-native";
|
|
247
|
+
async function execute3(actionId, params) {
|
|
248
|
+
const destination = params.destination;
|
|
249
|
+
const mode = params.mode || "driving";
|
|
250
|
+
if (!destination) {
|
|
251
|
+
return {
|
|
252
|
+
id: actionId,
|
|
253
|
+
ok: false,
|
|
254
|
+
error: "Missing destination parameter",
|
|
255
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const encodedDest = encodeURIComponent(destination);
|
|
260
|
+
let url;
|
|
261
|
+
if (Platform.OS === "ios") {
|
|
262
|
+
const modeMap = {
|
|
263
|
+
driving: "d",
|
|
264
|
+
transit: "r",
|
|
265
|
+
walking: "w"
|
|
266
|
+
};
|
|
267
|
+
url = `maps://maps.apple.com/?daddr=${encodedDest}&dirflg=${modeMap[mode] || "d"}`;
|
|
268
|
+
} else {
|
|
269
|
+
const modeMap = {
|
|
270
|
+
driving: "driving",
|
|
271
|
+
transit: "transit",
|
|
272
|
+
walking: "walking"
|
|
273
|
+
};
|
|
274
|
+
url = `google.navigation:q=${encodedDest}&mode=${modeMap[mode] || "driving"}`;
|
|
275
|
+
}
|
|
276
|
+
const canOpen = await Linking.canOpenURL(url);
|
|
277
|
+
if (!canOpen) {
|
|
278
|
+
const webModeMap = {
|
|
279
|
+
driving: "driving",
|
|
280
|
+
transit: "transit",
|
|
281
|
+
walking: "walking"
|
|
282
|
+
};
|
|
283
|
+
url = `https://www.google.com/maps/dir/?api=1&destination=${encodedDest}&travelmode=${webModeMap[mode] || "driving"}`;
|
|
284
|
+
}
|
|
285
|
+
await Linking.openURL(url);
|
|
286
|
+
return {
|
|
287
|
+
id: actionId,
|
|
288
|
+
ok: true,
|
|
289
|
+
result: {
|
|
290
|
+
message: `Navigation started to ${destination} via ${mode}`,
|
|
291
|
+
destination,
|
|
292
|
+
mode,
|
|
293
|
+
status: "navigating"
|
|
294
|
+
},
|
|
295
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
296
|
+
};
|
|
297
|
+
} catch (error) {
|
|
298
|
+
return {
|
|
299
|
+
id: actionId,
|
|
300
|
+
ok: false,
|
|
301
|
+
error: `Navigation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
302
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/device-bridge/actions/health-actions.ts
|
|
308
|
+
async function executeToday(actionId) {
|
|
309
|
+
try {
|
|
310
|
+
const today = await collectToday();
|
|
311
|
+
return {
|
|
312
|
+
id: actionId,
|
|
313
|
+
ok: true,
|
|
314
|
+
result: { today },
|
|
315
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
316
|
+
};
|
|
317
|
+
} catch (e) {
|
|
318
|
+
return {
|
|
319
|
+
id: actionId,
|
|
320
|
+
ok: false,
|
|
321
|
+
error: String(e?.message || e),
|
|
322
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function executeHistory(actionId, params) {
|
|
327
|
+
try {
|
|
328
|
+
const days = Number(params?.days) || 30;
|
|
329
|
+
const list = await collectHistory(days);
|
|
330
|
+
return {
|
|
331
|
+
id: actionId,
|
|
332
|
+
ok: true,
|
|
333
|
+
result: { days: list },
|
|
334
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
335
|
+
};
|
|
336
|
+
} catch (e) {
|
|
337
|
+
return {
|
|
338
|
+
id: actionId,
|
|
339
|
+
ok: false,
|
|
340
|
+
error: String(e?.message || e),
|
|
341
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/device-bridge/actions/queue-poller.ts
|
|
347
|
+
var QueuePoller = class {
|
|
348
|
+
constructor(config) {
|
|
349
|
+
this.timer = null;
|
|
350
|
+
this.running = false;
|
|
351
|
+
this.relayClient = config.relayClient;
|
|
352
|
+
this.pollInterval = config.pollInterval ?? 5e3;
|
|
353
|
+
this.onActionExecuted = config.onActionExecuted;
|
|
354
|
+
this.onError = config.onError;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Start polling for actions.
|
|
358
|
+
*/
|
|
359
|
+
start() {
|
|
360
|
+
if (this.running) return;
|
|
361
|
+
this.running = true;
|
|
362
|
+
this.poll();
|
|
363
|
+
this.timer = setInterval(() => this.poll(), this.pollInterval);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Stop polling.
|
|
367
|
+
*/
|
|
368
|
+
stop() {
|
|
369
|
+
this.running = false;
|
|
370
|
+
if (this.timer) {
|
|
371
|
+
clearInterval(this.timer);
|
|
372
|
+
this.timer = null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Check if poller is running.
|
|
377
|
+
*/
|
|
378
|
+
isRunning() {
|
|
379
|
+
return this.running;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Execute a single poll cycle.
|
|
383
|
+
*/
|
|
384
|
+
async poll() {
|
|
385
|
+
if (!this.running) return;
|
|
386
|
+
try {
|
|
387
|
+
const response = await this.relayClient.pollActions();
|
|
388
|
+
if (!response.ok || !response.data) return;
|
|
389
|
+
for (const action of response.data) {
|
|
390
|
+
if (action.status && action.status !== "pending") continue;
|
|
391
|
+
const result = await this.executeAction(action);
|
|
392
|
+
await this.relayClient.completeAction(result);
|
|
393
|
+
this.onActionExecuted?.(result);
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
this.onError?.(
|
|
397
|
+
error instanceof Error ? error : new Error(String(error))
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Route an action to the appropriate executor.
|
|
403
|
+
*/
|
|
404
|
+
async executeAction(action) {
|
|
405
|
+
const { id, type, params } = action;
|
|
406
|
+
try {
|
|
407
|
+
switch (type) {
|
|
408
|
+
case "messaging.send":
|
|
409
|
+
return await execute(id, params);
|
|
410
|
+
case "calendar.create":
|
|
411
|
+
return await executeCreate(id, params);
|
|
412
|
+
case "calendar.reschedule":
|
|
413
|
+
return await executeReschedule(id, params);
|
|
414
|
+
case "calendar.cancel":
|
|
415
|
+
return await executeCancel(id, params);
|
|
416
|
+
case "dnd.set":
|
|
417
|
+
return await execute2(id, params);
|
|
418
|
+
case "navigation.start":
|
|
419
|
+
return await execute3(id, params);
|
|
420
|
+
case "health.today":
|
|
421
|
+
return await executeToday(id);
|
|
422
|
+
case "health.history":
|
|
423
|
+
return await executeHistory(id, params);
|
|
424
|
+
default:
|
|
425
|
+
return {
|
|
426
|
+
id,
|
|
427
|
+
ok: false,
|
|
428
|
+
error: `Unknown action type: ${type}`,
|
|
429
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
return {
|
|
434
|
+
id,
|
|
435
|
+
ok: false,
|
|
436
|
+
error: `Action execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
437
|
+
executed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/device-bridge/biometrics.ts
|
|
444
|
+
var biometrics_exports = {};
|
|
445
|
+
__export(biometrics_exports, {
|
|
446
|
+
authenticate: () => authenticate,
|
|
447
|
+
capabilities: () => capabilities
|
|
448
|
+
});
|
|
449
|
+
import * as LocalAuthentication from "expo-local-authentication";
|
|
450
|
+
async function capabilities() {
|
|
451
|
+
const [hasHardware, isEnrolled, types] = await Promise.all([
|
|
452
|
+
LocalAuthentication.hasHardwareAsync(),
|
|
453
|
+
LocalAuthentication.isEnrolledAsync(),
|
|
454
|
+
LocalAuthentication.supportedAuthenticationTypesAsync()
|
|
455
|
+
]);
|
|
456
|
+
const supportedTypes = types.map((t) => {
|
|
457
|
+
if (t === LocalAuthentication.AuthenticationType.FINGERPRINT) return "fingerprint";
|
|
458
|
+
if (t === LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION) return "face";
|
|
459
|
+
if (t === LocalAuthentication.AuthenticationType.IRIS) return "iris";
|
|
460
|
+
return "none";
|
|
461
|
+
}).filter((t) => t !== "none");
|
|
462
|
+
return { hasHardware, isEnrolled, supportedTypes };
|
|
463
|
+
}
|
|
464
|
+
async function authenticate(opts) {
|
|
465
|
+
try {
|
|
466
|
+
const res = await LocalAuthentication.authenticateAsync({
|
|
467
|
+
promptMessage: opts.reason,
|
|
468
|
+
cancelLabel: opts.cancelLabel,
|
|
469
|
+
disableDeviceFallback: opts.fallbackToPasscode === false
|
|
470
|
+
});
|
|
471
|
+
if (res.success) return { success: true };
|
|
472
|
+
return { success: false, error: res.error || "unknown" };
|
|
473
|
+
} catch (e) {
|
|
474
|
+
return { success: false, error: String(e?.message || e) };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/device-bridge/sharing.ts
|
|
479
|
+
var sharing_exports = {};
|
|
480
|
+
__export(sharing_exports, {
|
|
481
|
+
isAvailable: () => isAvailable,
|
|
482
|
+
shareFile: () => shareFile
|
|
483
|
+
});
|
|
484
|
+
import * as Sharing from "expo-sharing";
|
|
485
|
+
async function shareFile(fileUri, opts = {}) {
|
|
486
|
+
try {
|
|
487
|
+
const available = await Sharing.isAvailableAsync();
|
|
488
|
+
if (!available) return { ok: false, error: "sharing_unavailable" };
|
|
489
|
+
await Sharing.shareAsync(fileUri, opts);
|
|
490
|
+
return { ok: true };
|
|
491
|
+
} catch (e) {
|
|
492
|
+
return { ok: false, error: String(e?.message || e) };
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async function isAvailable() {
|
|
496
|
+
return Sharing.isAvailableAsync();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/device-bridge/scanner.ts
|
|
500
|
+
var scanner_exports = {};
|
|
501
|
+
__export(scanner_exports, {
|
|
502
|
+
CameraView: () => CameraView,
|
|
503
|
+
normalizeResult: () => normalizeResult,
|
|
504
|
+
requestPermissions: () => requestPermissions,
|
|
505
|
+
useCameraPermissions: () => useCameraPermissions
|
|
506
|
+
});
|
|
507
|
+
import { CameraView, useCameraPermissions } from "expo-camera";
|
|
508
|
+
async function requestPermissions() {
|
|
509
|
+
const { Camera } = await import("expo-camera");
|
|
510
|
+
const { status } = await Camera.requestCameraPermissionsAsync();
|
|
511
|
+
return { granted: status === "granted", status };
|
|
512
|
+
}
|
|
513
|
+
function normalizeResult(r) {
|
|
514
|
+
return { type: r.type, data: r.data };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/device-bridge/index.ts
|
|
518
|
+
var DeviceBridge = class {
|
|
519
|
+
constructor(config) {
|
|
520
|
+
// Capability registry
|
|
521
|
+
this.caps = /* @__PURE__ */ new Map();
|
|
522
|
+
this.syncTimers = /* @__PURE__ */ new Map();
|
|
523
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
524
|
+
this.domainStatus = /* @__PURE__ */ new Map();
|
|
525
|
+
this.running = false;
|
|
526
|
+
this.config = config;
|
|
527
|
+
this.relay = new RelayClient({
|
|
528
|
+
relayUrl: config.relayUrl,
|
|
529
|
+
deviceId: config.deviceId,
|
|
530
|
+
tenant: config.tenant,
|
|
531
|
+
actorId: config.actorId,
|
|
532
|
+
apiKey: config.apiKey
|
|
533
|
+
});
|
|
534
|
+
this.queuePoller = new QueuePoller({
|
|
535
|
+
relayClient: this.relay,
|
|
536
|
+
pollInterval: 5e3,
|
|
537
|
+
onActionExecuted: () => this.notifyStatusChange(),
|
|
538
|
+
onError: (error) => this.config.onError?.({
|
|
539
|
+
domain: "actions",
|
|
540
|
+
message: error.message,
|
|
541
|
+
code: "ACTION_ERROR",
|
|
542
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
543
|
+
})
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
// ─── Capability Registration ───
|
|
547
|
+
/**
|
|
548
|
+
* Register a capability. Does NOT request permission — just makes it known.
|
|
549
|
+
* Can be called before or after startSync().
|
|
550
|
+
*/
|
|
551
|
+
register(cap) {
|
|
552
|
+
this.caps.set(cap.id, cap);
|
|
553
|
+
this.domainStatus.set(cap.id, {
|
|
554
|
+
domain: cap.id,
|
|
555
|
+
lastSync: null,
|
|
556
|
+
state: "idle"
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Register multiple capabilities at once.
|
|
561
|
+
*/
|
|
562
|
+
registerAll(caps) {
|
|
563
|
+
for (const cap of caps) {
|
|
564
|
+
this.register(cap);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get a registered capability by ID.
|
|
569
|
+
* Use this to read name/description/reason for your permission UI.
|
|
570
|
+
*/
|
|
571
|
+
getCapability(id) {
|
|
572
|
+
return this.caps.get(id);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Get all registered capabilities with their current status.
|
|
576
|
+
*/
|
|
577
|
+
listCapabilities() {
|
|
578
|
+
return Array.from(this.caps.values()).map((cap) => ({
|
|
579
|
+
id: cap.id,
|
|
580
|
+
name: cap.name,
|
|
581
|
+
permission: cap.permissionState,
|
|
582
|
+
sync: this.domainStatus.get(cap.id) || {
|
|
583
|
+
domain: cap.id,
|
|
584
|
+
lastSync: null,
|
|
585
|
+
state: "idle"
|
|
586
|
+
}
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
// ─── Permissions (host app controls the flow) ───
|
|
590
|
+
/**
|
|
591
|
+
* Request permission for ONE capability.
|
|
592
|
+
* Call after showing your own explanation screen.
|
|
593
|
+
* If bridge is already running and granted, syncing starts automatically.
|
|
594
|
+
*/
|
|
595
|
+
async requestPermission(capId) {
|
|
596
|
+
const cap = this.caps.get(capId);
|
|
597
|
+
if (!cap) throw new Error(`Capability "${capId}" not registered`);
|
|
598
|
+
if (!cap.requiresPermission) {
|
|
599
|
+
cap.permissionState = "not_needed";
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
const granted = await cap.requestPermission();
|
|
603
|
+
cap.permissionState = granted ? "granted" : "denied";
|
|
604
|
+
if (granted && this.running) {
|
|
605
|
+
this.startCapabilitySync(cap);
|
|
606
|
+
}
|
|
607
|
+
this.notifyStatusChange();
|
|
608
|
+
return granted;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Check permission WITHOUT prompting.
|
|
612
|
+
*/
|
|
613
|
+
async checkPermission(capId) {
|
|
614
|
+
const cap = this.caps.get(capId);
|
|
615
|
+
if (!cap) throw new Error(`Capability "${capId}" not registered`);
|
|
616
|
+
if (!cap.requiresPermission) return "not_needed";
|
|
617
|
+
const granted = await cap.checkPermission();
|
|
618
|
+
cap.permissionState = granted ? "granted" : cap.permissionState;
|
|
619
|
+
return cap.permissionState;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Request ALL permissions sequentially (one at a time, not bombarded).
|
|
623
|
+
*/
|
|
624
|
+
async requestAllPermissions() {
|
|
625
|
+
const results = /* @__PURE__ */ new Map();
|
|
626
|
+
for (const [id, cap] of this.caps) {
|
|
627
|
+
if (!cap.requiresPermission) {
|
|
628
|
+
cap.permissionState = "not_needed";
|
|
629
|
+
results.set(id, true);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
const granted = await cap.requestPermission();
|
|
633
|
+
cap.permissionState = granted ? "granted" : "denied";
|
|
634
|
+
results.set(id, granted);
|
|
635
|
+
}
|
|
636
|
+
this.notifyStatusChange();
|
|
637
|
+
return results;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Get capabilities that still need permission.
|
|
641
|
+
* Use this to drive a "next permission" onboarding flow.
|
|
642
|
+
*/
|
|
643
|
+
getPendingPermissions() {
|
|
644
|
+
return Array.from(this.caps.values()).filter(
|
|
645
|
+
(cap) => cap.requiresPermission && cap.permissionState === "not_requested"
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
// ─── Sync Control ───
|
|
649
|
+
/**
|
|
650
|
+
* Start syncing all permitted capabilities + action queue poller.
|
|
651
|
+
*/
|
|
652
|
+
async startSync() {
|
|
653
|
+
if (this.running) return;
|
|
654
|
+
this.running = true;
|
|
655
|
+
for (const [, cap] of this.caps) {
|
|
656
|
+
const permitted = !cap.requiresPermission || cap.permissionState === "granted" || cap.permissionState === "not_needed";
|
|
657
|
+
if (permitted) {
|
|
658
|
+
this.startCapabilitySync(cap);
|
|
659
|
+
} else {
|
|
660
|
+
this.updateDomainStatus(cap.id, "error", "Permission not granted");
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
this.queuePoller.start();
|
|
664
|
+
if (this.config.tenant && this.config.apiKey) {
|
|
665
|
+
registerBackgroundSync({
|
|
666
|
+
relayUrl: this.config.relayUrl,
|
|
667
|
+
deviceId: this.config.deviceId,
|
|
668
|
+
tenant: this.config.tenant,
|
|
669
|
+
actorId: this.config.actorId,
|
|
670
|
+
apiKey: this.config.apiKey
|
|
671
|
+
}).catch((e) => console.warn("[adas/bridge] registerBackgroundSync failed:", e?.message || e));
|
|
672
|
+
}
|
|
673
|
+
this.notifyStatusChange();
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Stop all syncing.
|
|
677
|
+
*/
|
|
678
|
+
stopSync() {
|
|
679
|
+
this.running = false;
|
|
680
|
+
for (const [id, timer] of this.syncTimers) {
|
|
681
|
+
clearInterval(timer);
|
|
682
|
+
this.updateDomainStatus(id, "stopped");
|
|
683
|
+
}
|
|
684
|
+
this.syncTimers.clear();
|
|
685
|
+
for (const unsub of this.subscriptions.values()) {
|
|
686
|
+
try {
|
|
687
|
+
unsub();
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
this.subscriptions.clear();
|
|
692
|
+
this.queuePoller.stop();
|
|
693
|
+
unregisterBackgroundSync().catch((e) => console.warn("[adas/bridge] unregisterBackgroundSync failed:", e?.message || e));
|
|
694
|
+
this.notifyStatusChange();
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Force immediate sync of all permitted capabilities.
|
|
698
|
+
*/
|
|
699
|
+
async syncNow() {
|
|
700
|
+
const promises = Array.from(this.caps.values()).filter((c) => c.permissionState === "granted" || c.permissionState === "not_needed").map((c) => this.syncCapability(c));
|
|
701
|
+
await Promise.allSettled(promises);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Force immediate sync of a single capability by id. Used by the host to
|
|
705
|
+
* refresh fast-moving sources (battery) on app-foreground so the relay
|
|
706
|
+
* gets a fresh reading the moment the user opens the app.
|
|
707
|
+
*/
|
|
708
|
+
async syncCapabilityNow(capId) {
|
|
709
|
+
const cap = this.caps.get(capId);
|
|
710
|
+
if (!cap) return;
|
|
711
|
+
const permitted = !cap.requiresPermission || cap.permissionState === "granted" || cap.permissionState === "not_needed";
|
|
712
|
+
if (!permitted) return;
|
|
713
|
+
await this.syncCapability(cap);
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Get current bridge status.
|
|
717
|
+
*/
|
|
718
|
+
getStatus() {
|
|
719
|
+
return {
|
|
720
|
+
running: this.running,
|
|
721
|
+
capabilities: this.listCapabilities(),
|
|
722
|
+
lastFullSync: this.getLastFullSync(),
|
|
723
|
+
pendingActions: this.relay.getOfflineQueueSize()
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Check if the relay is reachable.
|
|
728
|
+
*/
|
|
729
|
+
async checkConnectivity() {
|
|
730
|
+
return this.relay.ping();
|
|
731
|
+
}
|
|
732
|
+
// ─── Internal ───
|
|
733
|
+
startCapabilitySync(cap) {
|
|
734
|
+
if (this.syncTimers.has(cap.id)) return;
|
|
735
|
+
const interval = this.config.syncIntervals?.[cap.id] ?? cap.defaultInterval;
|
|
736
|
+
this.syncCapability(cap);
|
|
737
|
+
const timer = setInterval(() => this.syncCapability(cap), interval);
|
|
738
|
+
this.syncTimers.set(cap.id, timer);
|
|
739
|
+
if (cap.subscribe && !this.subscriptions.has(cap.id)) {
|
|
740
|
+
try {
|
|
741
|
+
const unsub = cap.subscribe(() => {
|
|
742
|
+
if (this.running) this.syncCapability(cap);
|
|
743
|
+
});
|
|
744
|
+
this.subscriptions.set(cap.id, unsub);
|
|
745
|
+
} catch (err) {
|
|
746
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
747
|
+
this.config.onError?.({
|
|
748
|
+
domain: cap.id,
|
|
749
|
+
message: `subscribe failed: ${message}`,
|
|
750
|
+
code: "SUBSCRIBE_ERROR",
|
|
751
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async syncCapability(cap) {
|
|
757
|
+
if (!this.running) return;
|
|
758
|
+
this.updateDomainStatus(cap.id, "syncing");
|
|
759
|
+
try {
|
|
760
|
+
const data = await cap.collect();
|
|
761
|
+
if (data === null) {
|
|
762
|
+
this.updateDomainStatus(cap.id, "idle");
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const result = await this.relay.pushDomain(cap.id, data);
|
|
766
|
+
if (result.ok) {
|
|
767
|
+
this.updateDomainStatus(cap.id, "idle");
|
|
768
|
+
} else {
|
|
769
|
+
this.updateDomainStatus(cap.id, "error", result.error);
|
|
770
|
+
this.config.onError?.({
|
|
771
|
+
domain: cap.id,
|
|
772
|
+
message: result.error || "Push failed",
|
|
773
|
+
code: "RELAY_ERROR",
|
|
774
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
} catch (error) {
|
|
778
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
779
|
+
this.updateDomainStatus(cap.id, "error", message);
|
|
780
|
+
this.config.onError?.({
|
|
781
|
+
domain: cap.id,
|
|
782
|
+
message,
|
|
783
|
+
code: "COLLECTOR_ERROR",
|
|
784
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
updateDomainStatus(id, state, error) {
|
|
789
|
+
const current = this.domainStatus.get(id);
|
|
790
|
+
this.domainStatus.set(id, {
|
|
791
|
+
domain: id,
|
|
792
|
+
lastSync: state === "idle" ? (/* @__PURE__ */ new Date()).toISOString() : current?.lastSync ?? null,
|
|
793
|
+
state,
|
|
794
|
+
error,
|
|
795
|
+
itemCount: current?.itemCount
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
getLastFullSync() {
|
|
799
|
+
const syncs = Array.from(this.domainStatus.values()).map((d) => d.lastSync).filter((s) => s !== null);
|
|
800
|
+
if (syncs.length === 0) return null;
|
|
801
|
+
return syncs.sort()[0];
|
|
802
|
+
}
|
|
803
|
+
notifyStatusChange() {
|
|
804
|
+
this.config.onStatusChange?.(this.getStatus());
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
export {
|
|
809
|
+
QueuePoller,
|
|
810
|
+
biometrics_exports,
|
|
811
|
+
sharing_exports,
|
|
812
|
+
scanner_exports,
|
|
813
|
+
DeviceBridge
|
|
814
|
+
};
|
|
815
|
+
//# sourceMappingURL=chunk-BAZFMTCC.mjs.map
|