@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,1075 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/push/silentSync.ts
|
|
31
|
+
var silentSync_exports = {};
|
|
32
|
+
__export(silentSync_exports, {
|
|
33
|
+
registerSilentPushSync: () => registerSilentPushSync
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(silentSync_exports);
|
|
36
|
+
|
|
37
|
+
// src/device-bridge/background.ts
|
|
38
|
+
var TaskManager = __toESM(require("expo-task-manager"));
|
|
39
|
+
var BackgroundFetch = __toESM(require("expo-background-fetch"));
|
|
40
|
+
|
|
41
|
+
// src/device-bridge/relay-client.ts
|
|
42
|
+
var RelayClient = class {
|
|
43
|
+
constructor(config2) {
|
|
44
|
+
this.offlineQueue = [];
|
|
45
|
+
this.baseUrl = config2.relayUrl.replace(/\/$/, "");
|
|
46
|
+
this.deviceId = config2.deviceId;
|
|
47
|
+
this.tenant = config2.tenant;
|
|
48
|
+
this.actorId = config2.actorId ?? "default";
|
|
49
|
+
this.apiKey = config2.apiKey ?? "";
|
|
50
|
+
this.timeout = config2.timeout ?? 1e4;
|
|
51
|
+
}
|
|
52
|
+
// ─── Data Sync ───
|
|
53
|
+
/**
|
|
54
|
+
* Push a full device data bundle to the relay.
|
|
55
|
+
* If offline, buffers the bundle and retries on next sync.
|
|
56
|
+
*/
|
|
57
|
+
async pushData(bundle) {
|
|
58
|
+
try {
|
|
59
|
+
const response = await this.fetch(
|
|
60
|
+
`/api/device/${this.deviceId}/sync`,
|
|
61
|
+
{
|
|
62
|
+
method: "POST",
|
|
63
|
+
body: JSON.stringify(bundle)
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
if (response.ok) {
|
|
67
|
+
await this.flushOfflineQueue();
|
|
68
|
+
return { ok: true };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: `Relay returned ${response.status}: ${await response.text()}`
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
this.offlineQueue.push(bundle);
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
error: `Offline \u2014 buffered (${this.offlineQueue.length} pending)`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Push a single domain update (partial sync).
|
|
84
|
+
*/
|
|
85
|
+
async pushDomain(domain, data) {
|
|
86
|
+
try {
|
|
87
|
+
const response = await this.fetch(
|
|
88
|
+
`/api/device/${this.deviceId}/sync/${domain}`,
|
|
89
|
+
{
|
|
90
|
+
method: "POST",
|
|
91
|
+
body: JSON.stringify({ domain, data, timestamp: (/* @__PURE__ */ new Date()).toISOString(), actorId: this.actorId })
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
if (response.ok) return { ok: true };
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
error: `Relay returned ${response.status}`
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return { ok: false, error: "Network error \u2014 domain sync failed" };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// ─── Action Queue ───
|
|
104
|
+
/**
|
|
105
|
+
* Poll for pending actions the phone needs to execute.
|
|
106
|
+
*/
|
|
107
|
+
async pollActions() {
|
|
108
|
+
try {
|
|
109
|
+
const response = await this.fetch(
|
|
110
|
+
`/api/device/${this.deviceId}/actions`,
|
|
111
|
+
{ method: "GET" }
|
|
112
|
+
);
|
|
113
|
+
if (response.ok) {
|
|
114
|
+
const data = await response.json();
|
|
115
|
+
return { ok: true, data: data.actions ?? [] };
|
|
116
|
+
}
|
|
117
|
+
return { ok: false, error: `Relay returned ${response.status}` };
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return { ok: false, error: "Network error \u2014 action poll failed" };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Report action execution result and remove from queue.
|
|
124
|
+
*/
|
|
125
|
+
async completeAction(result) {
|
|
126
|
+
try {
|
|
127
|
+
const response = await this.fetch(
|
|
128
|
+
`/api/device/${this.deviceId}/actions/${result.id}`,
|
|
129
|
+
{
|
|
130
|
+
method: "DELETE",
|
|
131
|
+
body: JSON.stringify(result)
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
if (response.ok) return { ok: true };
|
|
135
|
+
return { ok: false, error: `Relay returned ${response.status}` };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return { ok: false, error: "Network error \u2014 action complete failed" };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ─── Health ───
|
|
141
|
+
/**
|
|
142
|
+
* Ping the relay to check connectivity.
|
|
143
|
+
*/
|
|
144
|
+
async ping() {
|
|
145
|
+
try {
|
|
146
|
+
const response = await this.fetch(
|
|
147
|
+
`/api/health`,
|
|
148
|
+
{ method: "GET" },
|
|
149
|
+
3e3
|
|
150
|
+
// short timeout for health check
|
|
151
|
+
);
|
|
152
|
+
return response.ok;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get count of buffered offline bundles.
|
|
159
|
+
*/
|
|
160
|
+
getOfflineQueueSize() {
|
|
161
|
+
return this.offlineQueue.length;
|
|
162
|
+
}
|
|
163
|
+
// ─── Internal ───
|
|
164
|
+
async fetch(path, init, timeout) {
|
|
165
|
+
const controller = new AbortController();
|
|
166
|
+
const timer = setTimeout(
|
|
167
|
+
() => controller.abort(),
|
|
168
|
+
timeout ?? this.timeout
|
|
169
|
+
);
|
|
170
|
+
try {
|
|
171
|
+
return await fetch(`${this.baseUrl}${path}`, {
|
|
172
|
+
...init,
|
|
173
|
+
signal: controller.signal,
|
|
174
|
+
headers: {
|
|
175
|
+
"Content-Type": "application/json",
|
|
176
|
+
"X-ADAS-TENANT": this.tenant,
|
|
177
|
+
"X-ADAS-ACTOR": this.actorId,
|
|
178
|
+
"X-Device-Id": this.deviceId,
|
|
179
|
+
// The relay (post-2026-04-25) gates every /api/device/* call on
|
|
180
|
+
// a shared secret. Send it as `Authorization: Bearer` so the
|
|
181
|
+
// server's relayAuth middleware accepts the request. Empty/missing
|
|
182
|
+
// secret = 401 from the relay (correct fail-closed behavior).
|
|
183
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
|
|
184
|
+
...init.headers || {}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
} finally {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async flushOfflineQueue() {
|
|
192
|
+
if (this.offlineQueue.length === 0) return;
|
|
193
|
+
const queue = [...this.offlineQueue];
|
|
194
|
+
this.offlineQueue = [];
|
|
195
|
+
for (const bundle of queue) {
|
|
196
|
+
try {
|
|
197
|
+
const response = await this.fetch(
|
|
198
|
+
`/api/device/${this.deviceId}/sync`,
|
|
199
|
+
{
|
|
200
|
+
method: "POST",
|
|
201
|
+
body: JSON.stringify(bundle)
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
this.offlineQueue.push(bundle);
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
this.offlineQueue.push(bundle);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// src/device-bridge/collectors/calendar.ts
|
|
216
|
+
var ExpoCalendar = __toESM(require("expo-calendar"));
|
|
217
|
+
async function requestPermission() {
|
|
218
|
+
const { status } = await ExpoCalendar.requestCalendarPermissionsAsync();
|
|
219
|
+
return status === "granted";
|
|
220
|
+
}
|
|
221
|
+
async function hasPermission() {
|
|
222
|
+
const { status } = await ExpoCalendar.getCalendarPermissionsAsync();
|
|
223
|
+
return status === "granted";
|
|
224
|
+
}
|
|
225
|
+
async function collectToday() {
|
|
226
|
+
const now = /* @__PURE__ */ new Date();
|
|
227
|
+
const startOfDay2 = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
228
|
+
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
|
|
229
|
+
const calendars = await ExpoCalendar.getCalendarsAsync(ExpoCalendar.EntityTypes.EVENT);
|
|
230
|
+
const userCalendarIds = getUserCalendarIds(calendars);
|
|
231
|
+
const events = await ExpoCalendar.getEventsAsync(userCalendarIds, startOfDay2, endOfDay);
|
|
232
|
+
const filtered = filterHolidays(events);
|
|
233
|
+
const mapped = filtered.map((e) => ({
|
|
234
|
+
id: e.id,
|
|
235
|
+
title: e.title || "Untitled",
|
|
236
|
+
date: startOfDay2.toISOString().slice(0, 10),
|
|
237
|
+
start: formatTime(new Date(e.startDate)),
|
|
238
|
+
end: formatTime(new Date(e.endDate)),
|
|
239
|
+
location: e.location || "",
|
|
240
|
+
attendees: (e.attendees || []).map((a) => a.name || a.email || "Unknown"),
|
|
241
|
+
notes: e.notes || ""
|
|
242
|
+
}));
|
|
243
|
+
mapped.sort((a, b) => a.start.localeCompare(b.start));
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
date: startOfDay2.toISOString().slice(0, 10),
|
|
247
|
+
day_of_week: now.toLocaleDateString("en-US", { weekday: "long" }),
|
|
248
|
+
event_count: mapped.length,
|
|
249
|
+
events: mapped
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async function collectUpcoming(daysAhead = 3) {
|
|
253
|
+
const now = /* @__PURE__ */ new Date();
|
|
254
|
+
const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
|
|
255
|
+
const endDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + daysAhead + 1);
|
|
256
|
+
const calendars = await ExpoCalendar.getCalendarsAsync(ExpoCalendar.EntityTypes.EVENT);
|
|
257
|
+
const userCalendarIds = getUserCalendarIds(calendars);
|
|
258
|
+
const events = await ExpoCalendar.getEventsAsync(userCalendarIds, tomorrow, endDate);
|
|
259
|
+
const filtered = filterHolidays(events);
|
|
260
|
+
const mapped = filtered.map((e) => {
|
|
261
|
+
const startDt = new Date(e.startDate);
|
|
262
|
+
return {
|
|
263
|
+
id: e.id,
|
|
264
|
+
title: e.title || "Untitled",
|
|
265
|
+
date: startDt.toISOString().slice(0, 10),
|
|
266
|
+
start: formatTime(startDt),
|
|
267
|
+
end: formatTime(new Date(e.endDate)),
|
|
268
|
+
location: e.location || "",
|
|
269
|
+
attendees: (e.attendees || []).map((a) => a.name || a.email || "Unknown"),
|
|
270
|
+
notes: e.notes || ""
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
mapped.sort(
|
|
274
|
+
(a, b) => a.date === b.date ? a.start.localeCompare(b.start) : a.date.localeCompare(b.date)
|
|
275
|
+
);
|
|
276
|
+
return {
|
|
277
|
+
ok: true,
|
|
278
|
+
from: tomorrow.toISOString().slice(0, 10),
|
|
279
|
+
days_ahead: daysAhead,
|
|
280
|
+
event_count: mapped.length,
|
|
281
|
+
events: mapped
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function getUserCalendarIds(calendars) {
|
|
285
|
+
const SKIP_SOURCES = ["birthdays", "other"];
|
|
286
|
+
const SKIP_TYPES = ["birthdays", "other"];
|
|
287
|
+
return calendars.filter((c) => {
|
|
288
|
+
const sourceType = (c.source?.type || "").toLowerCase();
|
|
289
|
+
const calType = (c.type || "").toLowerCase();
|
|
290
|
+
if (SKIP_SOURCES.includes(sourceType)) return false;
|
|
291
|
+
if (SKIP_TYPES.includes(calType)) return false;
|
|
292
|
+
return true;
|
|
293
|
+
}).map((c) => c.id);
|
|
294
|
+
}
|
|
295
|
+
function filterHolidays(events) {
|
|
296
|
+
const HOLIDAY_PATTERNS = [
|
|
297
|
+
/holiday/i,
|
|
298
|
+
/observance/i,
|
|
299
|
+
/eve\b.*\btentative\b/i,
|
|
300
|
+
/\btentative\b/i
|
|
301
|
+
];
|
|
302
|
+
return events.filter((e) => {
|
|
303
|
+
const isAllDay = e.allDay === true;
|
|
304
|
+
const notes = (e.notes || "").toLowerCase();
|
|
305
|
+
if (isAllDay && HOLIDAY_PATTERNS.some((p) => p.test(notes))) return false;
|
|
306
|
+
if (isAllDay && HOLIDAY_PATTERNS.some((p) => p.test(e.title || ""))) return false;
|
|
307
|
+
return true;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function formatTime(date) {
|
|
311
|
+
return date.toLocaleTimeString("en-US", {
|
|
312
|
+
hour: "2-digit",
|
|
313
|
+
minute: "2-digit",
|
|
314
|
+
hour12: false
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/device-bridge/collectors/contacts.ts
|
|
319
|
+
var ExpoContacts = __toESM(require("expo-contacts"));
|
|
320
|
+
var import_async_storage = __toESM(require("@react-native-async-storage/async-storage"));
|
|
321
|
+
var HASH_STORE_KEY = "device-bridge.contacts.hashes.v1";
|
|
322
|
+
async function requestPermission2() {
|
|
323
|
+
const { status } = await ExpoContacts.requestPermissionsAsync();
|
|
324
|
+
return status === "granted";
|
|
325
|
+
}
|
|
326
|
+
async function hasPermission2() {
|
|
327
|
+
const { status } = await ExpoContacts.getPermissionsAsync();
|
|
328
|
+
return status === "granted";
|
|
329
|
+
}
|
|
330
|
+
var lastContactsStats = null;
|
|
331
|
+
async function collectAll() {
|
|
332
|
+
const result = await ExpoContacts.getContactsAsync({
|
|
333
|
+
fields: [
|
|
334
|
+
ExpoContacts.Fields.Name,
|
|
335
|
+
ExpoContacts.Fields.PhoneNumbers,
|
|
336
|
+
ExpoContacts.Fields.Emails,
|
|
337
|
+
ExpoContacts.Fields.Birthday
|
|
338
|
+
],
|
|
339
|
+
sort: ExpoContacts.SortTypes.LastName
|
|
340
|
+
});
|
|
341
|
+
const raw = Array.isArray(result?.data) ? result.data : [];
|
|
342
|
+
const contacts = [];
|
|
343
|
+
const currentHashes = {};
|
|
344
|
+
for (let i = 0; i < raw.length; i++) {
|
|
345
|
+
const c = raw[i];
|
|
346
|
+
const id = c.id || `c${i}`;
|
|
347
|
+
const mapped = {
|
|
348
|
+
id,
|
|
349
|
+
name: c.name || `${c.firstName || ""} ${c.lastName || ""}`.trim() || "Unknown",
|
|
350
|
+
relationship: "",
|
|
351
|
+
// dropped — was the iOS Unauthorized-Keys trigger
|
|
352
|
+
phone: c.phoneNumbers?.[0]?.number || null,
|
|
353
|
+
email: c.emails?.[0]?.email || null,
|
|
354
|
+
birthday: formatBirthday(c.birthday),
|
|
355
|
+
notes: ""
|
|
356
|
+
// dropped — Notes is restricted on iOS 16+
|
|
357
|
+
};
|
|
358
|
+
contacts.push(mapped);
|
|
359
|
+
currentHashes[id] = hashContact(mapped);
|
|
360
|
+
}
|
|
361
|
+
let stored = {};
|
|
362
|
+
try {
|
|
363
|
+
const blob = await import_async_storage.default.getItem(HASH_STORE_KEY);
|
|
364
|
+
if (blob) stored = JSON.parse(blob);
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
const fullSync = Object.keys(stored).length === 0;
|
|
368
|
+
let added = 0;
|
|
369
|
+
let updated = 0;
|
|
370
|
+
let unchanged = 0;
|
|
371
|
+
for (const id of Object.keys(currentHashes)) {
|
|
372
|
+
const prev = stored[id];
|
|
373
|
+
if (prev === void 0) added++;
|
|
374
|
+
else if (prev !== currentHashes[id]) updated++;
|
|
375
|
+
else unchanged++;
|
|
376
|
+
}
|
|
377
|
+
let removed = 0;
|
|
378
|
+
for (const id of Object.keys(stored)) {
|
|
379
|
+
if (currentHashes[id] === void 0) removed++;
|
|
380
|
+
}
|
|
381
|
+
lastContactsStats = {
|
|
382
|
+
total: contacts.length,
|
|
383
|
+
added,
|
|
384
|
+
updated,
|
|
385
|
+
removed,
|
|
386
|
+
unchanged,
|
|
387
|
+
full_sync: fullSync
|
|
388
|
+
};
|
|
389
|
+
if (!fullSync && added === 0 && updated === 0 && removed === 0) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
await import_async_storage.default.setItem(HASH_STORE_KEY, JSON.stringify(currentHashes));
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
return contacts;
|
|
397
|
+
}
|
|
398
|
+
function fnv1a(str) {
|
|
399
|
+
let h = 2166136261;
|
|
400
|
+
for (let i = 0; i < str.length; i++) {
|
|
401
|
+
h ^= str.charCodeAt(i);
|
|
402
|
+
h = Math.imul(h, 16777619);
|
|
403
|
+
}
|
|
404
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
405
|
+
}
|
|
406
|
+
function hashContact(c) {
|
|
407
|
+
const parts = [
|
|
408
|
+
c.id,
|
|
409
|
+
(c.name || "").trim(),
|
|
410
|
+
c.phone || "\0",
|
|
411
|
+
(c.email || "\0").toLowerCase(),
|
|
412
|
+
c.birthday || "\0"
|
|
413
|
+
];
|
|
414
|
+
return fnv1a(parts.join(""));
|
|
415
|
+
}
|
|
416
|
+
function formatBirthday(birthday) {
|
|
417
|
+
if (!birthday) return null;
|
|
418
|
+
const { year, month, day } = birthday;
|
|
419
|
+
if (month === void 0 || day === void 0) return null;
|
|
420
|
+
const m = String(month + 1).padStart(2, "0");
|
|
421
|
+
const d = String(day).padStart(2, "0");
|
|
422
|
+
if (!year) return `--${m}-${d}`;
|
|
423
|
+
return `${year}-${m}-${d}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/device-bridge/collectors/location.ts
|
|
427
|
+
var ExpoLocation2 = __toESM(require("expo-location"));
|
|
428
|
+
|
|
429
|
+
// src/maps/index.ts
|
|
430
|
+
var import_react_native = require("react-native");
|
|
431
|
+
var import_expo_modules_core = require("expo-modules-core");
|
|
432
|
+
var ExpoLocation = __toESM(require("expo-location"));
|
|
433
|
+
|
|
434
|
+
// src/maps/normalize.ts
|
|
435
|
+
var text = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
|
|
436
|
+
function toMapsAddress(raw) {
|
|
437
|
+
const r = raw ?? {};
|
|
438
|
+
return {
|
|
439
|
+
name: text(r.name),
|
|
440
|
+
street: text(r.street),
|
|
441
|
+
streetNumber: text(r.streetNumber),
|
|
442
|
+
district: text(r.district),
|
|
443
|
+
city: text(r.city),
|
|
444
|
+
region: text(r.region),
|
|
445
|
+
postalCode: text(r.postalCode),
|
|
446
|
+
country: text(r.country),
|
|
447
|
+
isoCountryCode: text(r.isoCountryCode)
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/maps/index.ts
|
|
452
|
+
var native = (0, import_expo_modules_core.requireOptionalNativeModule)("AdaMaps");
|
|
453
|
+
async function reverseGeocode(at) {
|
|
454
|
+
const [first] = await ExpoLocation.reverseGeocodeAsync({ latitude: at.latitude, longitude: at.longitude });
|
|
455
|
+
return first ? toMapsAddress(first) : null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/device-bridge/collectors/location.ts
|
|
459
|
+
async function requestPermission3() {
|
|
460
|
+
const { status: fg } = await ExpoLocation2.requestForegroundPermissionsAsync();
|
|
461
|
+
if (fg !== "granted") return false;
|
|
462
|
+
try {
|
|
463
|
+
await ExpoLocation2.requestBackgroundPermissionsAsync();
|
|
464
|
+
} catch {
|
|
465
|
+
}
|
|
466
|
+
return fg === "granted";
|
|
467
|
+
}
|
|
468
|
+
async function hasPermission3() {
|
|
469
|
+
const { status } = await ExpoLocation2.getForegroundPermissionsAsync();
|
|
470
|
+
return status === "granted";
|
|
471
|
+
}
|
|
472
|
+
async function collectCurrent() {
|
|
473
|
+
const location = await ExpoLocation2.getCurrentPositionAsync({
|
|
474
|
+
accuracy: ExpoLocation2.Accuracy.Balanced
|
|
475
|
+
});
|
|
476
|
+
let place = "Current Location";
|
|
477
|
+
let address = `${location.coords.latitude.toFixed(4)}, ${location.coords.longitude.toFixed(4)}`;
|
|
478
|
+
let locationType = "unknown";
|
|
479
|
+
try {
|
|
480
|
+
const geocoded = await reverseGeocode({
|
|
481
|
+
latitude: location.coords.latitude,
|
|
482
|
+
longitude: location.coords.longitude
|
|
483
|
+
});
|
|
484
|
+
if (geocoded) {
|
|
485
|
+
place = geocoded.name || geocoded.street || place;
|
|
486
|
+
address = [
|
|
487
|
+
geocoded.streetNumber,
|
|
488
|
+
geocoded.street,
|
|
489
|
+
geocoded.city,
|
|
490
|
+
geocoded.region
|
|
491
|
+
].filter(Boolean).join(", ") || address;
|
|
492
|
+
locationType = classifyLocation(geocoded);
|
|
493
|
+
}
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
ok: true,
|
|
498
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
499
|
+
location: {
|
|
500
|
+
place,
|
|
501
|
+
address,
|
|
502
|
+
lat: location.coords.latitude,
|
|
503
|
+
lng: location.coords.longitude,
|
|
504
|
+
type: locationType
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
var WATCH_TIER = {
|
|
509
|
+
navigation: ExpoLocation2.Accuracy.BestForNavigation,
|
|
510
|
+
high: ExpoLocation2.Accuracy.High,
|
|
511
|
+
balanced: ExpoLocation2.Accuracy.Balanced,
|
|
512
|
+
low: ExpoLocation2.Accuracy.Low
|
|
513
|
+
};
|
|
514
|
+
function classifyLocation(geocoded) {
|
|
515
|
+
const name = (geocoded.name || "").toLowerCase();
|
|
516
|
+
const street = (geocoded.street || "").toLowerCase();
|
|
517
|
+
if (name.includes("home") || name.includes("residence")) return "home";
|
|
518
|
+
if (name.includes("office") || name.includes("workplace") || name.includes("tech park")) return "work";
|
|
519
|
+
if (name.includes("school") || name.includes("elementary") || name.includes("university")) return "school";
|
|
520
|
+
if (name.includes("gym") || name.includes("fitness")) return "fitness";
|
|
521
|
+
if (name.includes("hospital") || name.includes("clinic") || name.includes("dental")) return "medical";
|
|
522
|
+
return "other";
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/device-bridge/collectors/battery.ts
|
|
526
|
+
var ExpoBattery = __toESM(require("expo-battery"));
|
|
527
|
+
async function collectBattery() {
|
|
528
|
+
const level = await ExpoBattery.getBatteryLevelAsync();
|
|
529
|
+
const state = await ExpoBattery.getBatteryStateAsync();
|
|
530
|
+
const lowPower = await ExpoBattery.isLowPowerModeEnabledAsync();
|
|
531
|
+
return {
|
|
532
|
+
ok: true,
|
|
533
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
534
|
+
battery: {
|
|
535
|
+
level: Math.round(level * 100),
|
|
536
|
+
// expo-battery returns 0-1
|
|
537
|
+
state: mapBatteryState(state),
|
|
538
|
+
low_power_mode: lowPower
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function subscribeToBattery(callback) {
|
|
543
|
+
let lastLevel = null;
|
|
544
|
+
const levelSub = ExpoBattery.addBatteryLevelListener(({ batteryLevel }) => {
|
|
545
|
+
const pct = Math.round(batteryLevel * 100);
|
|
546
|
+
if (lastLevel === null || Math.abs(pct - lastLevel) >= 5) {
|
|
547
|
+
lastLevel = pct;
|
|
548
|
+
callback();
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
const stateSub = ExpoBattery.addBatteryStateListener(() => {
|
|
552
|
+
callback();
|
|
553
|
+
});
|
|
554
|
+
return () => {
|
|
555
|
+
levelSub?.remove?.();
|
|
556
|
+
stateSub?.remove?.();
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function mapBatteryState(state) {
|
|
560
|
+
switch (state) {
|
|
561
|
+
case ExpoBattery.BatteryState.CHARGING:
|
|
562
|
+
return "charging";
|
|
563
|
+
case ExpoBattery.BatteryState.FULL:
|
|
564
|
+
return "full";
|
|
565
|
+
case ExpoBattery.BatteryState.UNPLUGGED:
|
|
566
|
+
default:
|
|
567
|
+
return "unplugged";
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/device-bridge/collectors/connectivity.ts
|
|
572
|
+
var ExpoNetwork = __toESM(require("expo-network"));
|
|
573
|
+
async function collectConnectivity() {
|
|
574
|
+
const networkState = await ExpoNetwork.getNetworkStateAsync();
|
|
575
|
+
let type = "none";
|
|
576
|
+
let wifiName;
|
|
577
|
+
let cellularType;
|
|
578
|
+
if (networkState.type === ExpoNetwork.NetworkStateType.WIFI) {
|
|
579
|
+
type = "wifi";
|
|
580
|
+
try {
|
|
581
|
+
wifiName = await getWifiName();
|
|
582
|
+
} catch {
|
|
583
|
+
wifiName = void 0;
|
|
584
|
+
}
|
|
585
|
+
} else if (networkState.type === ExpoNetwork.NetworkStateType.CELLULAR) {
|
|
586
|
+
type = "cellular";
|
|
587
|
+
cellularType = getCellularGeneration();
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
ok: true,
|
|
591
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
592
|
+
connectivity: {
|
|
593
|
+
type,
|
|
594
|
+
is_connected: networkState.isConnected ?? false,
|
|
595
|
+
...wifiName && { wifi_name: wifiName },
|
|
596
|
+
...cellularType && { cellular_type: cellularType }
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
async function getWifiName() {
|
|
601
|
+
return void 0;
|
|
602
|
+
}
|
|
603
|
+
function getCellularGeneration() {
|
|
604
|
+
return "LTE";
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/device-bridge/collectors/notifications.ts
|
|
608
|
+
var ExpoNotifications = __toESM(require("expo-notifications"));
|
|
609
|
+
async function requestPermission4() {
|
|
610
|
+
const { status } = await ExpoNotifications.requestPermissionsAsync();
|
|
611
|
+
return status === "granted";
|
|
612
|
+
}
|
|
613
|
+
async function hasPermission4() {
|
|
614
|
+
const { status } = await ExpoNotifications.getPermissionsAsync();
|
|
615
|
+
return status === "granted";
|
|
616
|
+
}
|
|
617
|
+
async function collectNotifications() {
|
|
618
|
+
const presented = await ExpoNotifications.getPresentedNotificationsAsync();
|
|
619
|
+
const notifications = presented.map((n, i) => ({
|
|
620
|
+
id: n.request.identifier || `n${i}`,
|
|
621
|
+
app: n.request.content.data?.app || "A-Team",
|
|
622
|
+
title: n.request.content.title || "",
|
|
623
|
+
body: n.request.content.body || "",
|
|
624
|
+
timestamp: n.date ? new Date(n.date).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
625
|
+
sender: n.request.content.data?.sender || null,
|
|
626
|
+
priority: mapPriority(n.request.content.data?.priority),
|
|
627
|
+
read: false
|
|
628
|
+
// Presented notifications are unread by definition
|
|
629
|
+
}));
|
|
630
|
+
const unread = notifications.filter((n) => !n.read).length;
|
|
631
|
+
return {
|
|
632
|
+
ok: true,
|
|
633
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
634
|
+
total: notifications.length,
|
|
635
|
+
unread,
|
|
636
|
+
notifications
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
function mapPriority(p) {
|
|
640
|
+
if (p === "high" || p === "max") return "high";
|
|
641
|
+
if (p === "low" || p === "min") return "low";
|
|
642
|
+
return "medium";
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/device-bridge/collectors/health.ts
|
|
646
|
+
var import_react_native2 = require("react-native");
|
|
647
|
+
var _HK = null;
|
|
648
|
+
var _hkProbed = false;
|
|
649
|
+
function getHK() {
|
|
650
|
+
if (import_react_native2.Platform.OS !== "ios") return null;
|
|
651
|
+
if (_hkProbed) return _HK;
|
|
652
|
+
_hkProbed = true;
|
|
653
|
+
try {
|
|
654
|
+
const mod = require("@kingstinct/react-native-healthkit");
|
|
655
|
+
if (mod && typeof mod.requestAuthorization === "function") {
|
|
656
|
+
_HK = mod;
|
|
657
|
+
} else {
|
|
658
|
+
console.warn("[health] @kingstinct/react-native-healthkit native bridge unavailable \u2014 HealthKit disabled (Expo Go?)");
|
|
659
|
+
}
|
|
660
|
+
} catch (e) {
|
|
661
|
+
console.warn("[health] @kingstinct/react-native-healthkit require failed:", e);
|
|
662
|
+
}
|
|
663
|
+
return _HK;
|
|
664
|
+
}
|
|
665
|
+
var lastHealthInitError = null;
|
|
666
|
+
var READ_TYPES = [
|
|
667
|
+
"HKQuantityTypeIdentifierStepCount",
|
|
668
|
+
"HKQuantityTypeIdentifierFlightsClimbed",
|
|
669
|
+
"HKQuantityTypeIdentifierActiveEnergyBurned",
|
|
670
|
+
"HKQuantityTypeIdentifierDistanceWalkingRunning",
|
|
671
|
+
// Added for mood signals — all read-only, covered by the same auth sheet.
|
|
672
|
+
"HKQuantityTypeIdentifierRestingHeartRate",
|
|
673
|
+
"HKQuantityTypeIdentifierAppleExerciseTime",
|
|
674
|
+
"HKCategoryTypeIdentifierSleepAnalysis"
|
|
675
|
+
];
|
|
676
|
+
var _initPromise = null;
|
|
677
|
+
async function ensureInit() {
|
|
678
|
+
const HK = getHK();
|
|
679
|
+
if (!HK) {
|
|
680
|
+
lastHealthInitError = "@kingstinct/react-native-healthkit native module not loaded";
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
if (_initPromise) return _initPromise;
|
|
684
|
+
_initPromise = (async () => {
|
|
685
|
+
try {
|
|
686
|
+
await HK.requestAuthorization({ toRead: READ_TYPES, toWrite: [] });
|
|
687
|
+
lastHealthInitError = null;
|
|
688
|
+
return true;
|
|
689
|
+
} catch (e) {
|
|
690
|
+
lastHealthInitError = `requestAuthorization threw: ${e?.message || String(e)}`;
|
|
691
|
+
console.warn("[health]", lastHealthInitError);
|
|
692
|
+
_initPromise = null;
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
})();
|
|
696
|
+
return _initPromise;
|
|
697
|
+
}
|
|
698
|
+
async function hasPermission5() {
|
|
699
|
+
return ensureInit();
|
|
700
|
+
}
|
|
701
|
+
async function requestPermission5() {
|
|
702
|
+
return ensureInit();
|
|
703
|
+
}
|
|
704
|
+
function startOfDay(d) {
|
|
705
|
+
const out = new Date(d);
|
|
706
|
+
out.setHours(0, 0, 0, 0);
|
|
707
|
+
return out;
|
|
708
|
+
}
|
|
709
|
+
function dateKey(d) {
|
|
710
|
+
const y = d.getFullYear();
|
|
711
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
712
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
713
|
+
return `${y}-${m}-${day}`;
|
|
714
|
+
}
|
|
715
|
+
async function readDailySum(identifier, unit, start, end) {
|
|
716
|
+
const HK = getHK();
|
|
717
|
+
if (!HK?.queryStatisticsForQuantity) return 0;
|
|
718
|
+
try {
|
|
719
|
+
const result = await HK.queryStatisticsForQuantity(
|
|
720
|
+
identifier,
|
|
721
|
+
["cumulativeSum"],
|
|
722
|
+
{
|
|
723
|
+
unit,
|
|
724
|
+
filter: { date: { startDate: start, endDate: end } }
|
|
725
|
+
}
|
|
726
|
+
);
|
|
727
|
+
const q = result?.sumQuantity?.quantity;
|
|
728
|
+
return typeof q === "number" ? q : 0;
|
|
729
|
+
} catch {
|
|
730
|
+
return 0;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async function readDay(date) {
|
|
734
|
+
const start = startOfDay(date);
|
|
735
|
+
const end = new Date(start);
|
|
736
|
+
end.setDate(end.getDate() + 1);
|
|
737
|
+
const [steps, floors, energy, distance] = await Promise.all([
|
|
738
|
+
readDailySum("HKQuantityTypeIdentifierStepCount", "count", start, end),
|
|
739
|
+
readDailySum("HKQuantityTypeIdentifierFlightsClimbed", "count", start, end),
|
|
740
|
+
readDailySum("HKQuantityTypeIdentifierActiveEnergyBurned", "kcal", start, end),
|
|
741
|
+
// Ask for kilometers directly so we don't have to convert from meters.
|
|
742
|
+
readDailySum("HKQuantityTypeIdentifierDistanceWalkingRunning", "km", start, end)
|
|
743
|
+
]);
|
|
744
|
+
return {
|
|
745
|
+
date: dateKey(start),
|
|
746
|
+
available: true,
|
|
747
|
+
steps: Math.round(steps),
|
|
748
|
+
floors: Math.round(floors),
|
|
749
|
+
active_energy_kcal: Math.round(energy),
|
|
750
|
+
distance_km: Math.round(distance * 100) / 100
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function unavailableDay(date) {
|
|
754
|
+
return {
|
|
755
|
+
date: dateKey(date),
|
|
756
|
+
available: false,
|
|
757
|
+
steps: null,
|
|
758
|
+
floors: null,
|
|
759
|
+
active_energy_kcal: null,
|
|
760
|
+
distance_km: null
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
async function collectToday2() {
|
|
764
|
+
const ok = await ensureInit();
|
|
765
|
+
if (!ok) {
|
|
766
|
+
return unavailableDay(/* @__PURE__ */ new Date());
|
|
767
|
+
}
|
|
768
|
+
return readDay(/* @__PURE__ */ new Date());
|
|
769
|
+
}
|
|
770
|
+
async function collectHistory(days) {
|
|
771
|
+
const n = Math.max(1, Math.min(30, Math.floor(days)));
|
|
772
|
+
const ok = await ensureInit();
|
|
773
|
+
if (!ok) return [];
|
|
774
|
+
const today = startOfDay(/* @__PURE__ */ new Date());
|
|
775
|
+
const out = [];
|
|
776
|
+
for (let i = 0; i < n; i++) {
|
|
777
|
+
const d = new Date(today);
|
|
778
|
+
d.setDate(d.getDate() - i);
|
|
779
|
+
out.push(await readDay(d));
|
|
780
|
+
}
|
|
781
|
+
return out;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/device-bridge/collectors/appEngagement.ts
|
|
785
|
+
var import_react_native3 = require("react-native");
|
|
786
|
+
var import_async_storage2 = __toESM(require("@react-native-async-storage/async-storage"));
|
|
787
|
+
|
|
788
|
+
// src/device-bridge/collectors/camera.ts
|
|
789
|
+
var import_expo_camera = require("expo-camera");
|
|
790
|
+
|
|
791
|
+
// src/device-bridge/capabilities.ts
|
|
792
|
+
function calendarCapability() {
|
|
793
|
+
return {
|
|
794
|
+
id: "calendar",
|
|
795
|
+
name: "Calendar",
|
|
796
|
+
description: "Syncs your calendar events so your assistant knows your schedule.",
|
|
797
|
+
reason: "Needed for scheduling, meeting prep, commute alerts, and daily briefings.",
|
|
798
|
+
icon: "calendar",
|
|
799
|
+
requiresPermission: true,
|
|
800
|
+
permissionState: "not_requested",
|
|
801
|
+
requestPermission,
|
|
802
|
+
checkPermission: hasPermission,
|
|
803
|
+
collect: async () => {
|
|
804
|
+
const [today, upcoming] = await Promise.all([
|
|
805
|
+
collectToday(),
|
|
806
|
+
collectUpcoming(7)
|
|
807
|
+
]);
|
|
808
|
+
return { today, upcoming };
|
|
809
|
+
},
|
|
810
|
+
defaultInterval: 6e4
|
|
811
|
+
// 1 min
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function contactsCapability() {
|
|
815
|
+
return {
|
|
816
|
+
id: "contacts",
|
|
817
|
+
name: "Contacts",
|
|
818
|
+
description: "Syncs your contacts so your assistant can find people and relationships.",
|
|
819
|
+
reason: "Needed for messaging, birthday reminders, and relationship insights.",
|
|
820
|
+
icon: "people",
|
|
821
|
+
requiresPermission: true,
|
|
822
|
+
permissionState: "not_requested",
|
|
823
|
+
requestPermission: requestPermission2,
|
|
824
|
+
checkPermission: hasPermission2,
|
|
825
|
+
collect: async () => {
|
|
826
|
+
const list = await collectAll();
|
|
827
|
+
if (list === null) return null;
|
|
828
|
+
return { contacts: list };
|
|
829
|
+
},
|
|
830
|
+
defaultInterval: 3e5
|
|
831
|
+
// 5 min
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function locationCapability() {
|
|
835
|
+
return {
|
|
836
|
+
id: "location",
|
|
837
|
+
name: "Location",
|
|
838
|
+
description: "Syncs your location for weather, commute, and context-aware assistance.",
|
|
839
|
+
reason: "Needed for commute estimates, weather at your location, and situation detection.",
|
|
840
|
+
icon: "location",
|
|
841
|
+
requiresPermission: true,
|
|
842
|
+
permissionState: "not_requested",
|
|
843
|
+
requestPermission: requestPermission3,
|
|
844
|
+
checkPermission: hasPermission3,
|
|
845
|
+
collect: collectCurrent,
|
|
846
|
+
defaultInterval: 3e4
|
|
847
|
+
// 30 sec
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function batteryCapability() {
|
|
851
|
+
return {
|
|
852
|
+
id: "battery",
|
|
853
|
+
name: "Battery",
|
|
854
|
+
description: "Syncs battery level and charging status.",
|
|
855
|
+
reason: "Helps your assistant adjust behavior when battery is low.",
|
|
856
|
+
icon: "battery",
|
|
857
|
+
requiresPermission: false,
|
|
858
|
+
permissionState: "not_needed",
|
|
859
|
+
requestPermission: async () => true,
|
|
860
|
+
checkPermission: async () => true,
|
|
861
|
+
collect: collectBattery,
|
|
862
|
+
defaultInterval: 6e4,
|
|
863
|
+
// 1 min
|
|
864
|
+
subscribe: subscribeToBattery
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function connectivityCapability() {
|
|
868
|
+
return {
|
|
869
|
+
id: "connectivity",
|
|
870
|
+
name: "Connectivity",
|
|
871
|
+
description: "Syncs WiFi/cellular status.",
|
|
872
|
+
reason: "Helps your assistant know if you have internet access.",
|
|
873
|
+
icon: "wifi",
|
|
874
|
+
requiresPermission: false,
|
|
875
|
+
permissionState: "not_needed",
|
|
876
|
+
requestPermission: async () => true,
|
|
877
|
+
checkPermission: async () => true,
|
|
878
|
+
collect: collectConnectivity,
|
|
879
|
+
defaultInterval: 3e4
|
|
880
|
+
// 30 sec
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function healthCapability() {
|
|
884
|
+
return {
|
|
885
|
+
id: "health",
|
|
886
|
+
name: "Health",
|
|
887
|
+
description: "Reads daily steps, floors, calories, and distance from Apple Health.",
|
|
888
|
+
reason: "Powers the personal health coach and pairs activity data with nutrition tracking.",
|
|
889
|
+
icon: "heart",
|
|
890
|
+
requiresPermission: true,
|
|
891
|
+
permissionState: "not_requested",
|
|
892
|
+
requestPermission: requestPermission5,
|
|
893
|
+
checkPermission: hasPermission5,
|
|
894
|
+
collect: async () => {
|
|
895
|
+
const days = await collectHistory(30);
|
|
896
|
+
const today = days[0] || await collectToday2();
|
|
897
|
+
return { today, days };
|
|
898
|
+
},
|
|
899
|
+
defaultInterval: 3e5
|
|
900
|
+
// 5 min
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function notificationsCapability() {
|
|
904
|
+
return {
|
|
905
|
+
id: "notifications",
|
|
906
|
+
name: "Notifications",
|
|
907
|
+
description: "Syncs your notifications for triage and smart grouping.",
|
|
908
|
+
reason: "Needed for notification triage, priority filtering, and Do Not Disturb.",
|
|
909
|
+
icon: "notifications",
|
|
910
|
+
requiresPermission: true,
|
|
911
|
+
permissionState: "not_requested",
|
|
912
|
+
requestPermission: requestPermission4,
|
|
913
|
+
checkPermission: hasPermission4,
|
|
914
|
+
collect: collectNotifications,
|
|
915
|
+
defaultInterval: 15e3
|
|
916
|
+
// 15 sec
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/device-bridge/background.ts
|
|
921
|
+
var CAPABILITY_FACTORIES = {
|
|
922
|
+
calendar: calendarCapability,
|
|
923
|
+
contacts: contactsCapability,
|
|
924
|
+
location: locationCapability,
|
|
925
|
+
battery: batteryCapability,
|
|
926
|
+
connectivity: connectivityCapability,
|
|
927
|
+
notifications: notificationsCapability,
|
|
928
|
+
health: healthCapability
|
|
929
|
+
};
|
|
930
|
+
async function syncDomains(config2, domains) {
|
|
931
|
+
const relay = new RelayClient({
|
|
932
|
+
relayUrl: config2.relayUrl,
|
|
933
|
+
deviceId: config2.deviceId,
|
|
934
|
+
tenant: config2.tenant,
|
|
935
|
+
actorId: config2.actorId,
|
|
936
|
+
apiKey: config2.apiKey,
|
|
937
|
+
timeout: 15e3
|
|
938
|
+
// longer than foreground — background tasks are budget-limited but not latency-sensitive
|
|
939
|
+
});
|
|
940
|
+
const results = await Promise.allSettled(
|
|
941
|
+
domains.map((d) => collectAndPush(relay, d))
|
|
942
|
+
);
|
|
943
|
+
return results.some((r) => r.status === "fulfilled" && r.value);
|
|
944
|
+
}
|
|
945
|
+
async function collectAndPush(relay, domain) {
|
|
946
|
+
const factory = CAPABILITY_FACTORIES[domain];
|
|
947
|
+
if (!factory) {
|
|
948
|
+
console.warn(`[DeviceBridge] Unknown sync domain: ${domain}`);
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
const cap = factory();
|
|
952
|
+
if (cap.requiresPermission) {
|
|
953
|
+
const granted = await cap.checkPermission();
|
|
954
|
+
if (!granted) return false;
|
|
955
|
+
}
|
|
956
|
+
const data = await cap.collect();
|
|
957
|
+
const result = await relay.pushDomain(domain, data);
|
|
958
|
+
return result.ok;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/device-bridge/deviceId.ts
|
|
962
|
+
var DEVICE_ID_KEY = "ateam.deviceId";
|
|
963
|
+
function readDeviceId(storage) {
|
|
964
|
+
return storage.getItem(DEVICE_ID_KEY);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/capabilities/config.ts
|
|
968
|
+
var config = null;
|
|
969
|
+
function getDeviceSyncConfig() {
|
|
970
|
+
return config;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/capabilities/choices.ts
|
|
974
|
+
var import_async_storage3 = __toESM(require("@react-native-async-storage/async-storage"));
|
|
975
|
+
var STORAGE_KEY = "ateam.capabilities";
|
|
976
|
+
var SETUP_KEY = "ateam.capabilities.setup";
|
|
977
|
+
var DEFAULT_ENABLED = {
|
|
978
|
+
calendar: true,
|
|
979
|
+
contacts: true,
|
|
980
|
+
location: true,
|
|
981
|
+
battery: true,
|
|
982
|
+
connectivity: true,
|
|
983
|
+
health: true,
|
|
984
|
+
mood: true,
|
|
985
|
+
// Opt-IN: the camera is more invasive than the sync collectors and should not
|
|
986
|
+
// prompt every user for camera access on first launch. It lights up only when
|
|
987
|
+
// the user turns it on (which then requests the OS permission), so a solution
|
|
988
|
+
// that wants a camera preview asks the user to enable it.
|
|
989
|
+
camera: false
|
|
990
|
+
};
|
|
991
|
+
var HIDDEN_FROM_UI = /* @__PURE__ */ new Set(["notifications"]);
|
|
992
|
+
async function loadChoices() {
|
|
993
|
+
const setupDone = await import_async_storage3.default.getItem(SETUP_KEY);
|
|
994
|
+
const saved = await import_async_storage3.default.getItem(STORAGE_KEY);
|
|
995
|
+
return { setupDone: setupDone === "true", prefs: saved ? JSON.parse(saved) : {} };
|
|
996
|
+
}
|
|
997
|
+
function isEnabled(id, prefs) {
|
|
998
|
+
return prefs[id] ?? DEFAULT_ENABLED[id] ?? true;
|
|
999
|
+
}
|
|
1000
|
+
async function sharedDomains(domains) {
|
|
1001
|
+
let choices;
|
|
1002
|
+
try {
|
|
1003
|
+
choices = await loadChoices();
|
|
1004
|
+
} catch {
|
|
1005
|
+
return [];
|
|
1006
|
+
}
|
|
1007
|
+
if (!choices.setupDone) return [];
|
|
1008
|
+
return domains.filter((d) => !HIDDEN_FROM_UI.has(d) && isEnabled(d, choices.prefs));
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/push/silentSync.ts
|
|
1012
|
+
var import_core = require("@ateam-ai-mobile/core");
|
|
1013
|
+
|
|
1014
|
+
// src/push/messaging.ts
|
|
1015
|
+
var probed = false;
|
|
1016
|
+
var messaging = null;
|
|
1017
|
+
function getMessaging() {
|
|
1018
|
+
if (probed) return messaging;
|
|
1019
|
+
probed = true;
|
|
1020
|
+
try {
|
|
1021
|
+
const candidate = require("@react-native-firebase/messaging").default;
|
|
1022
|
+
candidate();
|
|
1023
|
+
messaging = candidate;
|
|
1024
|
+
} catch {
|
|
1025
|
+
console.warn("[adas/push] @react-native-firebase/messaging native module unavailable \u2014 push disabled (Expo Go?)");
|
|
1026
|
+
}
|
|
1027
|
+
return messaging;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/push/silentSync.ts
|
|
1031
|
+
function registerSilentPushSync(opts) {
|
|
1032
|
+
const messaging2 = getMessaging();
|
|
1033
|
+
if (!messaging2) return;
|
|
1034
|
+
messaging2().setBackgroundMessageHandler((msg) => handleSilentPush(msg, opts));
|
|
1035
|
+
}
|
|
1036
|
+
async function handleSilentPush(msg, opts) {
|
|
1037
|
+
const data = msg?.data || {};
|
|
1038
|
+
if (data.action !== "sync") return;
|
|
1039
|
+
let requested = data.domains;
|
|
1040
|
+
try {
|
|
1041
|
+
if (typeof requested === "string") requested = JSON.parse(requested);
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
console.warn("[adas/push] silent sync: bad domains:", err);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (!Array.isArray(requested)) return;
|
|
1047
|
+
const domains = await sharedDomains(requested.filter((d) => typeof d === "string"));
|
|
1048
|
+
if (domains.length === 0) return;
|
|
1049
|
+
const deviceSync = getDeviceSyncConfig();
|
|
1050
|
+
if (!deviceSync) {
|
|
1051
|
+
console.warn("[adas/push] silent sync skipped: device sync is not configured");
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const deviceId = await readDeviceId(deviceSync.secureStorage);
|
|
1055
|
+
if (!deviceId) return;
|
|
1056
|
+
const jwt = await opts.loadToken();
|
|
1057
|
+
if (!jwt) return;
|
|
1058
|
+
const tenant = (0, import_core.tenantFromToken)(jwt);
|
|
1059
|
+
const actorId = (0, import_core.userFromJwt)(jwt)?.actorId;
|
|
1060
|
+
if (!tenant || !actorId) return;
|
|
1061
|
+
console.log(`[adas/push] silent sync: ${domains.join(", ")}`);
|
|
1062
|
+
try {
|
|
1063
|
+
await syncDomains(
|
|
1064
|
+
{ relayUrl: deviceSync.relayUrl(), deviceId, tenant, actorId, apiKey: deviceSync.relayKey },
|
|
1065
|
+
domains
|
|
1066
|
+
);
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
console.warn("[adas/push] silent sync failed:", err);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1072
|
+
0 && (module.exports = {
|
|
1073
|
+
registerSilentPushSync
|
|
1074
|
+
});
|
|
1075
|
+
//# sourceMappingURL=silentSync.js.map
|