@glassly/engine 0.3.7-dev.954 → 0.3.7-dev.958

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.
@@ -1,9 +1,9 @@
1
1
  export const ENGINE_RELEASE_METADATA = Object.freeze({
2
2
  "schemaVersion": 1,
3
3
  "familyBaseVersion": "0.3.7",
4
- "releaseIdentity": "0.3.7-dev.954",
5
- "releaseSetId": "glassly-0.3.7-dev.954",
6
- "sourceCommit": "393f593dcfbf49e891275efe62f85f2ee92325b5",
4
+ "releaseIdentity": "0.3.7-dev.958",
5
+ "releaseSetId": "glassly-0.3.7-dev.958",
6
+ "sourceCommit": "cb9da4ac87e203755f06fb289b4fb8732946b11c",
7
7
  "otaManifestUrl": null,
8
8
  "otaManifestSha256": null
9
9
  });
@@ -1 +1 @@
1
- {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.3.7\",\n \"releaseIdentity\": \"0.3.7-dev.954\",\n \"releaseSetId\": \"glassly-0.3.7-dev.954\",\n \"sourceCommit\": \"393f593dcfbf49e891275efe62f85f2ee92325b5\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
1
+ {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.3.7\",\n \"releaseIdentity\": \"0.3.7-dev.958\",\n \"releaseSetId\": \"glassly-0.3.7-dev.958\",\n \"sourceCommit\": \"cb9da4ac87e203755f06fb289b4fb8732946b11c\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneCalendarService.d.ts","sourceRoot":"","sources":["../../src/services/PhoneCalendarService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,kBAAkB,EAAC,MAAM,kBAAkB,CAAA;AAE1E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAA;AA0BzD,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoBvG"}
1
+ {"version":3,"file":"PhoneCalendarService.d.ts","sourceRoot":"","sources":["../../src/services/PhoneCalendarService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,kBAAkB,EAAC,MAAM,kBAAkB,CAAA;AAE1E,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAA;AA0BzD,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA2BvG"}
@@ -33,9 +33,17 @@ export async function listPhoneCalendarEvents(request) {
33
33
  if (calendars.length === 0)
34
34
  return { events: [], truncated: false };
35
35
  const rawEvents = await Calendar.listEvents(calendars, startsAt, endsAt);
36
+ const seen = new Set();
36
37
  const events = rawEvents
37
38
  .map(normalizeCalendarEvent)
38
39
  .filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))
40
+ .filter((event) => {
41
+ const key = `${event.title}\n${event.startsAt}\n${event.endsAt}\n${event.allDay}`;
42
+ if (seen.has(key))
43
+ return false;
44
+ seen.add(key);
45
+ return true;
46
+ })
39
47
  .sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt));
40
48
  return {
41
49
  events: events.slice(0, limit),
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneCalendarService.js","sourceRoot":"","sources":["../../src/services/PhoneCalendarService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAA;AACrC,OAAO,EAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAC,MAAM,0BAA0B,CAAA;AAEpE,OAAO,EAAC,gBAAgB,EAA0B,MAAM,kBAAkB,CAAA;AAE1E,OAAO,EACL,2BAA2B,EAE3B,sBAAsB,EACtB,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAA;AAMzD;;;;;GAKG;AACH,KAAK,UAAU,yBAAyB,CAAC,MAAgC;IACvE,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;QAC9B,OAAO,6FAA6F,CAAA;IACtG,CAAC;IACD,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;QACrF,IAAI,SAAS,KAAK,OAAO,CAAC,OAAO,IAAI,SAAS,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;YACnE,OAAO,0HAA0H,CAAA;QACnI,CAAC;QACD,OAAO,gGAAgG,CAAA;IACzG,CAAC;IACD,OAAO,qGAAqG,CAAA;AAC9G,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAA4B;IACxE,MAAM,EAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAC,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAA;IACtE,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,sBAAsB,EAAE,CAAA;IAC1D,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,IAAI,kBAAkB,CAAC,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,yBAAyB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;IACtH,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IACzE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAC,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAC,CAAA;IAEjE,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;IACxE,MAAM,MAAM,GAAG,SAAS;SACrB,GAAG,CAAC,sBAAsB,CAAC;SAC3B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,2BAA2B,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;SACvE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;IAEjH,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;QAC9B,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,KAAK;KACjC,CAAA;AACH,CAAC","sourcesContent":["import * as Calendar from \"expo-calendar\"\nimport {Platform} from \"react-native\"\nimport {check, PERMISSIONS, RESULTS} from \"react-native-permissions\"\n\nimport {MiniappErrorCode, type CalendarListResult} from \"@glassly/miniapp\"\n\nimport {\n calendarEventOverlapsWindow,\n type CalendarListRequest,\n normalizeCalendarEvent,\n PhoneCalendarError,\n validateCalendarListRequest,\n} from \"./PhoneCalendarHelpers\"\n\nexport {PhoneCalendarError} from \"./PhoneCalendarHelpers\"\n\ntype CalendarPermissionStatus = Awaited<\n ReturnType<typeof Calendar.getCalendarPermissionsAsync>\n>[\"status\"]\n\n/**\n * expo-calendar reports anything below EventKit full access as not granted,\n * so distinguish the states the user can actually fix. \"Reopen the miniapp\"\n * is only true while the permission is undetermined — once iOS has resolved\n * it (denied or write-only/limited), only Settings can change it.\n */\nasync function calendarPermissionMessage(status: CalendarPermissionStatus): Promise<string> {\n if (status === \"undetermined\") {\n return \"Calendar permission has not been granted yet. Reopen the miniapp and allow calendar access.\"\n }\n if (Platform.OS === \"ios\") {\n const writeOnly = await check(PERMISSIONS.IOS.CALENDARS_WRITE_ONLY).catch(() => null)\n if (writeOnly === RESULTS.GRANTED || writeOnly === RESULTS.LIMITED) {\n return 'Calendar access is limited to write-only. Enable \"Full Access\" for Glassly in Settings › Privacy & Security › Calendars.'\n }\n return \"Calendar access is denied. Enable it for Glassly in Settings › Privacy & Security › Calendars.\"\n }\n return \"Calendar access is denied. Enable the Calendar permission for Glassly in your phone's app settings.\"\n}\n\nexport async function listPhoneCalendarEvents(request: CalendarListRequest): Promise<CalendarListResult> {\n const {startsAt, endsAt, limit} = validateCalendarListRequest(request)\n const permission = await Calendar.getCalendarPermissions()\n if (permission.status !== \"granted\") {\n throw new PhoneCalendarError(MiniappErrorCode.PERMISSION_DENIED, await calendarPermissionMessage(permission.status))\n }\n\n const calendars = await Calendar.getCalendars(Calendar.EntityTypes.EVENT)\n if (calendars.length === 0) return {events: [], truncated: false}\n\n const rawEvents = await Calendar.listEvents(calendars, startsAt, endsAt)\n const events = rawEvents\n .map(normalizeCalendarEvent)\n .filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))\n .sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt))\n\n return {\n events: events.slice(0, limit),\n truncated: events.length > limit,\n }\n}\n"]}
1
+ {"version":3,"file":"PhoneCalendarService.js","sourceRoot":"","sources":["../../src/services/PhoneCalendarService.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Calendar from \"expo-calendar\"\nimport {Platform} from \"react-native\"\nimport {check, PERMISSIONS, RESULTS} from \"react-native-permissions\"\n\nimport {MiniappErrorCode, type CalendarListResult} from \"@glassly/miniapp\"\n\nimport {\n calendarEventOverlapsWindow,\n type CalendarListRequest,\n normalizeCalendarEvent,\n PhoneCalendarError,\n validateCalendarListRequest,\n} from \"./PhoneCalendarHelpers\"\n\nexport {PhoneCalendarError} from \"./PhoneCalendarHelpers\"\n\ntype CalendarPermissionStatus = Awaited<\n ReturnType<typeof Calendar.getCalendarPermissionsAsync>\n>[\"status\"]\n\n/**\n * expo-calendar reports anything below EventKit full access as not granted,\n * so distinguish the states the user can actually fix. \"Reopen the miniapp\"\n * is only true while the permission is undetermined — once iOS has resolved\n * it (denied or write-only/limited), only Settings can change it.\n */\nasync function calendarPermissionMessage(status: CalendarPermissionStatus): Promise<string> {\n if (status === \"undetermined\") {\n return \"Calendar permission has not been granted yet. Reopen the miniapp and allow calendar access.\"\n }\n if (Platform.OS === \"ios\") {\n const writeOnly = await check(PERMISSIONS.IOS.CALENDARS_WRITE_ONLY).catch(() => null)\n if (writeOnly === RESULTS.GRANTED || writeOnly === RESULTS.LIMITED) {\n return 'Calendar access is limited to write-only. Enable \"Full Access\" for Glassly in Settings › Privacy & Security › Calendars.'\n }\n return \"Calendar access is denied. Enable it for Glassly in Settings › Privacy & Security › Calendars.\"\n }\n return \"Calendar access is denied. Enable the Calendar permission for Glassly in your phone's app settings.\"\n}\n\nexport async function listPhoneCalendarEvents(request: CalendarListRequest): Promise<CalendarListResult> {\n const {startsAt, endsAt, limit} = validateCalendarListRequest(request)\n const permission = await Calendar.getCalendarPermissions()\n if (permission.status !== \"granted\") {\n throw new PhoneCalendarError(MiniappErrorCode.PERMISSION_DENIED, await calendarPermissionMessage(permission.status))\n }\n\n const calendars = await Calendar.getCalendars(Calendar.EntityTypes.EVENT)\n if (calendars.length === 0) return {events: [], truncated: false}\n\n const rawEvents = await Calendar.listEvents(calendars, startsAt, endsAt)\n const seen = new Set<string>()\n const events = rawEvents\n .map(normalizeCalendarEvent)\n .filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))\n .filter((event) => {\n const key = `${event.title}\\n${event.startsAt}\\n${event.endsAt}\\n${event.allDay}`\n if (seen.has(key)) return false\n seen.add(key)\n return true\n })\n .sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt))\n\n return {\n events: events.slice(0, limit),\n truncated: events.length > limit,\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneNotificationsSync.d.ts","sourceRoot":"","sources":["../../src/services/PhoneNotificationsSync.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,gBAAgB,CAAA;AAC1D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,wBAAwB,CAAA;AAUrE,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,oCAAoC,CAAA;AAK3C,YAAY,EAAC,iBAAiB,EAAC,CAAA;AAE/B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAID,wBAAgB,uBAAuB,IAAI,mBAAmB,EAAE,CAG/D;AAED,uEAAuE;AACvE,wBAAgB,wBAAwB,IAAI,eAAe,EAAE,CAS5D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAClD,KAAK,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,CAAC,CACnE,CAMA;AAED,8EAA8E;AAC9E,wBAAgB,4BAA4B,IAAI,OAAO,CAGtD;AAED,gFAAgF;AAChF,wBAAgB,8BAA8B,IAAI,MAAM,EAAE,CAGzD;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAgB,gCAAgC,IAAI,yBAAyB,CAa5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,OAAO,GAAG,iBAAiB,CAU/E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAMtF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAapF;AAuCD,wBAAgB,2BAA2B,IAAI,IAAI,CAgClD;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD"}
1
+ {"version":3,"file":"PhoneNotificationsSync.d.ts","sourceRoot":"","sources":["../../src/services/PhoneNotificationsSync.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,gBAAgB,CAAA;AAC1D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,wBAAwB,CAAA;AAUrE,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,oCAAoC,CAAA;AAK3C,YAAY,EAAC,iBAAiB,EAAC,CAAA;AAE/B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAID,wBAAgB,uBAAuB,IAAI,mBAAmB,EAAE,CAG/D;AAED,uEAAuE;AACvE,wBAAgB,wBAAwB,IAAI,eAAe,EAAE,CAS5D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAClD,KAAK,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,CAAC,CACnE,CAMA;AAED,8EAA8E;AAC9E,wBAAgB,4BAA4B,IAAI,OAAO,CAGtD;AAOD,gFAAgF;AAChF,wBAAgB,8BAA8B,IAAI,MAAM,EAAE,CAGzD;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAgB,gCAAgC,IAAI,yBAAyB,CAa5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,OAAO,GAAG,iBAAiB,CAU/E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAMtF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAapF;AAuCD,wBAAgB,2BAA2B,IAAI,IAAI,CAgClD;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD"}
@@ -67,6 +67,10 @@ export function nativeNotificationsSupported() {
67
67
  const model = useGlassesStore.getState().deviceModel;
68
68
  return getModelCapabilities(model).hasNativeNotifications === true;
69
69
  }
70
+ function notificationRelaySupported() {
71
+ const model = useGlassesStore.getState().deviceModel;
72
+ return Platform.OS === "ios" && getModelCapabilities(model).hasNotificationRelay === true;
73
+ }
70
74
  /** Popup durations the firmware accepts, or an empty list when it takes any. */
71
75
  export function nativeNotificationPopupSeconds() {
72
76
  const model = useGlassesStore.getState().deviceModel;
@@ -114,12 +118,12 @@ export function currentPresentationOwner(hostActive) {
114
118
  export function captureAuthorization() {
115
119
  if (Platform.OS !== "ios")
116
120
  return "not_needed";
117
- if (!nativeNotificationsSupported())
121
+ if (!nativeNotificationsSupported() && !notificationRelaySupported())
118
122
  return "not_needed";
119
123
  const glasses = useGlassesStore.getState();
120
124
  if (!isGlassesConnected(glasses.connection))
121
125
  return "unknown";
122
- return glasses.ancsAuthorized ? "granted" : "denied";
126
+ return glasses.ancsAuthorized ? "granted" : notificationRelaySupported() ? "unknown" : "denied";
123
127
  }
124
128
  /**
125
129
  * Hand one captured notification to glasses whose firmware presents it from
@@ -148,7 +152,7 @@ function pushListenerConfig() {
148
152
  CrustModule.setNotificationConfig(listenerEnabled, Array.isArray(blocklist) ? blocklist : []).catch((err) => console.warn(`PhoneNotificationsSync: setNotificationConfig failed: ${err?.message ?? err}`));
149
153
  }
150
154
  function pushGlassesConfig() {
151
- if (!nativeNotificationsSupported())
155
+ if (!nativeNotificationsSupported() && !notificationRelaySupported())
152
156
  return;
153
157
  if (!isGlassesConnected(useGlassesStore.getState().connection))
154
158
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneNotificationsSync.js","sourceRoot":"","sources":["../../src/services/PhoneNotificationsSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Phone-notifications sync — engine-owned, started by `engine.start()`. Keeps\n * three things in step with settings and the connected glasses, for any host:\n *\n * - Android: the notification listener's kill switch + per-app blocklist\n * (`CrustModule.setNotificationConfig`). The listener runs in its own\n * guarded process and is desired by default; the debug setting stays a\n * hidden emergency switch. Notify's running state gates presentation, not\n * capture.\n * - Glasses whose firmware presents notifications (`hasNativeNotifications`):\n * one `GlassesNotificationConfig` pushed through the Bluetooth SDK whenever\n * a setting, the phone's activity, the seen-apps list or the connected\n * glasses change. Drivers re-apply their last config on reconnect; the push\n * repeats on connection so a fresh driver gets it too.\n * - The seen-apps list (`notification_seen_apps`) from `notification_app_seen`:\n * glasses that read the phone's stream report each source app, which is\n * the only app enumeration iOS offers.\n *\n * Presentation is one question with one answer (`presentationOwner`); hosts\n * ask it per event instead of reasoning about models or platforms.\n */\nimport {Platform} from \"react-native\"\nimport {shallow} from \"zustand/shallow\"\nimport CrustModule from \"@glassly/crust\"\nimport type {PhoneNotificationEvent} from \"@glassly/crust\"\nimport type {GlassesNotificationConfig} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {NotificationAppSeenEvent} from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore, SETTINGS} from \"../stores/settings\"\nimport {DeviceTypes} from \"../types/enums\"\nimport {getModelCapabilities} from \"../types/hardware\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {notificationAppCatalog} from \"./notificationAppCatalog\"\nimport {\n glassesNotificationConfig,\n presentationOwner,\n type NotificationApp,\n type PresentationOwner,\n} from \"./notifications/notificationPolicy\"\nimport {overlayViewerRunning} from \"./pages/overlayApps\"\nimport {pagesEligible} from \"./pages/pageEligibility\"\nimport {isPhoneActive, onPhoneActivityChange} from \"./PhoneActivity\"\n\nexport type {PresentationOwner}\n\nexport interface SeenNotificationApp {\n appId: string\n displayName: string\n lastSeen: number\n}\n\nlet unsubscribers: Array<() => void> = []\n\nexport function getSeenNotificationApps(): SeenNotificationApp[] {\n const seen = useSettingsStore.getState().getSetting(SETTINGS.notification_seen_apps.key)\n return Array.isArray(seen) ? (seen as SeenNotificationApp[]) : []\n}\n\n/** Catalog ∪ seen apps, seen display names winning. Sorted by name. */\nexport function getKnownNotificationApps(): NotificationApp[] {\n const merged = new Map<string, string>()\n for (const app of notificationAppCatalog()) merged.set(app.appId, app.displayName)\n for (const app of getSeenNotificationApps()) {\n if (app.appId) merged.set(app.appId, app.displayName || app.appId)\n }\n return [...merged.entries()]\n .map(([appId, displayName]) => ({appId, displayName}))\n .sort((a, b) => a.displayName.localeCompare(b.displayName))\n}\n\n/**\n * Apps to show in a notification filter UI (package/bundle id, name, icon).\n * Android: installed apps. iOS: seeded catalog ∪ glasses-reported apps\n * (icons unavailable — iOS offers no cross-app icon lookup).\n */\nexport function installedNotificationApps(): Promise<\n Array<{packageName: string; appName: string; icon: string | null}>\n> {\n if (Platform.OS === \"android\") return CrustModule.getInstalledApps()\n if (Platform.OS !== \"ios\") return Promise.resolve([])\n return Promise.resolve(\n getKnownNotificationApps().map((app) => ({packageName: app.appId, appName: app.displayName, icon: null})),\n )\n}\n\n/** The connected (or remembered) model presents notifications in firmware. */\nexport function nativeNotificationsSupported(): boolean {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return getModelCapabilities(model).hasNativeNotifications === true\n}\n\n/** Popup durations the firmware accepts, or an empty list when it takes any. */\nexport function nativeNotificationPopupSeconds(): number[] {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return getModelCapabilities(model).nativeNotificationPopupSeconds ?? []\n}\n\nexport function notificationsEnabled(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.notifications_enabled.key) !== false\n}\n\nexport function desiredGlassesNotificationConfig(): GlassesNotificationConfig {\n const s = useSettingsStore.getState()\n const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)\n return glassesNotificationConfig({\n enabled: notificationsEnabled(),\n popups: s.getSetting(SETTINGS.notifications_popups.key) !== false,\n popupSeconds: Number(s.getSetting(SETTINGS.notifications_popup_seconds.key)),\n holdDuringApps: s.getSetting(SETTINGS.notifications_hold_during_apps.key) !== false,\n ignoreWhileActive: s.getSetting(SETTINGS.notifications_ignore_while_active.key) === true,\n phoneActive: isPhoneActive(),\n blockedApps: Array.isArray(blocklist) ? (blocklist as string[]) : [],\n knownApps: getKnownNotificationApps(),\n })\n}\n\n/**\n * Who presents the next phone notification. `hostActive` is the host's own\n * presentation surface (Notify in the Glassly App); the engine has no view of\n * it.\n */\nexport function currentPresentationOwner(hostActive: boolean): PresentationOwner {\n return presentationOwner({\n enabled: notificationsEnabled(),\n pagesEligible: pagesEligible(),\n // The viewer is an overlay miniapp listening to phone notifications\n // (ADR-0006): with none running, nothing draws one on eligible glasses.\n viewerRunning: overlayViewerRunning(),\n nativeNotifications: nativeNotificationsSupported() && isGlassesConnected(useGlassesStore.getState().connection),\n hostActive,\n })\n}\n\n/**\n * Whether the glasses may read the phone's notifications. iOS glasses read\n * the stream themselves under the Bluetooth notification-sharing consent the\n * driver reports as `ancsAuthorized`; Android glasses get content from the\n * phone's listener, whose grant the permission flow owns.\n */\nexport function captureAuthorization(): \"granted\" | \"denied\" | \"unknown\" | \"not_needed\" {\n if (Platform.OS !== \"ios\") return \"not_needed\"\n if (!nativeNotificationsSupported()) return \"not_needed\"\n const glasses = useGlassesStore.getState()\n if (!isGlassesConnected(glasses.connection)) return \"unknown\"\n return glasses.ancsAuthorized ? \"granted\" : \"denied\"\n}\n\n/**\n * Hand one captured notification to glasses whose firmware presents it from\n * uploaded content (Android). Glasses that read the phone's stream reject it;\n * callers only reach here when the owner is `firmware`.\n */\nexport function uploadPhoneNotification(event: PhoneNotificationEvent): Promise<void> {\n if (Platform.OS !== \"android\") return Promise.resolve()\n return Promise.resolve(\n BluetoothSdk.uploadPhoneNotification({\n notificationId: String(event.notificationId ?? \"\"),\n packageName: String(event.packageName ?? \"\"),\n appName: String(event.app ?? \"\"),\n title: String(event.title ?? \"\"),\n subtitle: \"\",\n body: String(event.content ?? \"\"),\n timestampMs: Number(event.timestamp) || Date.now(),\n }),\n )\n}\n\nfunction pushListenerConfig(): void {\n if (Platform.OS !== \"android\") return\n const s = useSettingsStore.getState()\n const listenerEnabled = Boolean(s.getSetting(SETTINGS.android_notification_listener_enabled.key))\n const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)\n CrustModule.setNotificationConfig(listenerEnabled, Array.isArray(blocklist) ? blocklist : []).catch((err: unknown) =>\n console.warn(`PhoneNotificationsSync: setNotificationConfig failed: ${(err as Error)?.message ?? err}`),\n )\n}\n\nfunction pushGlassesConfig(): void {\n if (!nativeNotificationsSupported()) return\n if (!isGlassesConnected(useGlassesStore.getState().connection)) return\n void Promise.resolve(BluetoothSdk.setGlassesNotificationConfig(desiredGlassesNotificationConfig())).catch(\n (err: unknown) =>\n console.warn(`PhoneNotificationsSync: glasses notification config failed: ${(err as Error)?.message ?? err}`),\n )\n}\n\nfunction pushAll(): void {\n pushListenerConfig()\n pushGlassesConfig()\n}\n\n/** Record a glasses-reported source app. Writes only on new app / renamed app. */\nfunction recordSeenApp(event: NotificationAppSeenEvent): void {\n if (!event.appId) return\n const seen = getSeenNotificationApps()\n const existing = seen.find((app) => app.appId === event.appId)\n const displayName = event.displayName || existing?.displayName || event.appId\n if (existing && existing.displayName === displayName) return\n const next = seen\n .filter((app) => app.appId !== event.appId)\n .concat([{appId: event.appId, displayName, lastSeen: event.timestamp || Date.now()}])\n useSettingsStore.getState().setSetting(SETTINGS.notification_seen_apps.key, next)\n}\n\nexport function startPhoneNotificationsSync(): void {\n if (unsubscribers.length) return\n pushAll()\n const seenApps = BluetoothSdk.addListener(\"notification_app_seen\", recordSeenApp)\n unsubscribers = [\n useSettingsStore.subscribe(\n (state) =>\n [\n SETTINGS.android_notification_listener_enabled,\n SETTINGS.notifications_blocklist,\n SETTINGS.notification_seen_apps,\n SETTINGS.notifications_enabled,\n SETTINGS.notifications_popups,\n SETTINGS.notifications_popup_seconds,\n SETTINGS.notifications_hold_during_apps,\n SETTINGS.notifications_ignore_while_active,\n ].map((setting) => state.getSetting(setting.key)),\n pushAll,\n {equalityFn: shallow},\n ),\n // A (re)connected driver gets the current config; the SDK dedupes nothing\n // here, so only connection state and model are watched, not every field.\n useGlassesStore.subscribe(\n (state) => ({model: state.deviceModel, connected: isGlassesConnected(state.connection)}),\n pushGlassesConfig,\n {equalityFn: shallow},\n ),\n // Lock / foreground changes flip the effective popups flag for firmware\n // that pops notifications up by itself.\n onPhoneActivityChange(pushGlassesConfig),\n () => seenApps.remove(),\n ]\n}\n\nexport function stopPhoneNotificationsSync(): void {\n for (const unsubscribe of unsubscribers) unsubscribe()\n unsubscribers = []\n}\n"]}
1
+ {"version":3,"file":"PhoneNotificationsSync.js","sourceRoot":"","sources":["../../src/services/PhoneNotificationsSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Phone-notifications sync — engine-owned, started by `engine.start()`. Keeps\n * three things in step with settings and the connected glasses, for any host:\n *\n * - Android: the notification listener's kill switch + per-app blocklist\n * (`CrustModule.setNotificationConfig`). The listener runs in its own\n * guarded process and is desired by default; the debug setting stays a\n * hidden emergency switch. Notify's running state gates presentation, not\n * capture.\n * - Glasses whose firmware presents notifications (`hasNativeNotifications`):\n * one `GlassesNotificationConfig` pushed through the Bluetooth SDK whenever\n * a setting, the phone's activity, the seen-apps list or the connected\n * glasses change. Drivers re-apply their last config on reconnect; the push\n * repeats on connection so a fresh driver gets it too.\n * - The seen-apps list (`notification_seen_apps`) from `notification_app_seen`:\n * glasses that read the phone's stream report each source app, which is\n * the only app enumeration iOS offers.\n *\n * Presentation is one question with one answer (`presentationOwner`); hosts\n * ask it per event instead of reasoning about models or platforms.\n */\nimport {Platform} from \"react-native\"\nimport {shallow} from \"zustand/shallow\"\nimport CrustModule from \"@glassly/crust\"\nimport type {PhoneNotificationEvent} from \"@glassly/crust\"\nimport type {GlassesNotificationConfig} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {NotificationAppSeenEvent} from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore, SETTINGS} from \"../stores/settings\"\nimport {DeviceTypes} from \"../types/enums\"\nimport {getModelCapabilities} from \"../types/hardware\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {notificationAppCatalog} from \"./notificationAppCatalog\"\nimport {\n glassesNotificationConfig,\n presentationOwner,\n type NotificationApp,\n type PresentationOwner,\n} from \"./notifications/notificationPolicy\"\nimport {overlayViewerRunning} from \"./pages/overlayApps\"\nimport {pagesEligible} from \"./pages/pageEligibility\"\nimport {isPhoneActive, onPhoneActivityChange} from \"./PhoneActivity\"\n\nexport type {PresentationOwner}\n\nexport interface SeenNotificationApp {\n appId: string\n displayName: string\n lastSeen: number\n}\n\nlet unsubscribers: Array<() => void> = []\n\nexport function getSeenNotificationApps(): SeenNotificationApp[] {\n const seen = useSettingsStore.getState().getSetting(SETTINGS.notification_seen_apps.key)\n return Array.isArray(seen) ? (seen as SeenNotificationApp[]) : []\n}\n\n/** Catalog ∪ seen apps, seen display names winning. Sorted by name. */\nexport function getKnownNotificationApps(): NotificationApp[] {\n const merged = new Map<string, string>()\n for (const app of notificationAppCatalog()) merged.set(app.appId, app.displayName)\n for (const app of getSeenNotificationApps()) {\n if (app.appId) merged.set(app.appId, app.displayName || app.appId)\n }\n return [...merged.entries()]\n .map(([appId, displayName]) => ({appId, displayName}))\n .sort((a, b) => a.displayName.localeCompare(b.displayName))\n}\n\n/**\n * Apps to show in a notification filter UI (package/bundle id, name, icon).\n * Android: installed apps. iOS: seeded catalog ∪ glasses-reported apps\n * (icons unavailable — iOS offers no cross-app icon lookup).\n */\nexport function installedNotificationApps(): Promise<\n Array<{packageName: string; appName: string; icon: string | null}>\n> {\n if (Platform.OS === \"android\") return CrustModule.getInstalledApps()\n if (Platform.OS !== \"ios\") return Promise.resolve([])\n return Promise.resolve(\n getKnownNotificationApps().map((app) => ({packageName: app.appId, appName: app.displayName, icon: null})),\n )\n}\n\n/** The connected (or remembered) model presents notifications in firmware. */\nexport function nativeNotificationsSupported(): boolean {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return getModelCapabilities(model).hasNativeNotifications === true\n}\n\nfunction notificationRelaySupported(): boolean {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return Platform.OS === \"ios\" && getModelCapabilities(model).hasNotificationRelay === true\n}\n\n/** Popup durations the firmware accepts, or an empty list when it takes any. */\nexport function nativeNotificationPopupSeconds(): number[] {\n const model = useGlassesStore.getState().deviceModel as DeviceTypes\n return getModelCapabilities(model).nativeNotificationPopupSeconds ?? []\n}\n\nexport function notificationsEnabled(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.notifications_enabled.key) !== false\n}\n\nexport function desiredGlassesNotificationConfig(): GlassesNotificationConfig {\n const s = useSettingsStore.getState()\n const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)\n return glassesNotificationConfig({\n enabled: notificationsEnabled(),\n popups: s.getSetting(SETTINGS.notifications_popups.key) !== false,\n popupSeconds: Number(s.getSetting(SETTINGS.notifications_popup_seconds.key)),\n holdDuringApps: s.getSetting(SETTINGS.notifications_hold_during_apps.key) !== false,\n ignoreWhileActive: s.getSetting(SETTINGS.notifications_ignore_while_active.key) === true,\n phoneActive: isPhoneActive(),\n blockedApps: Array.isArray(blocklist) ? (blocklist as string[]) : [],\n knownApps: getKnownNotificationApps(),\n })\n}\n\n/**\n * Who presents the next phone notification. `hostActive` is the host's own\n * presentation surface (Notify in the Glassly App); the engine has no view of\n * it.\n */\nexport function currentPresentationOwner(hostActive: boolean): PresentationOwner {\n return presentationOwner({\n enabled: notificationsEnabled(),\n pagesEligible: pagesEligible(),\n // The viewer is an overlay miniapp listening to phone notifications\n // (ADR-0006): with none running, nothing draws one on eligible glasses.\n viewerRunning: overlayViewerRunning(),\n nativeNotifications: nativeNotificationsSupported() && isGlassesConnected(useGlassesStore.getState().connection),\n hostActive,\n })\n}\n\n/**\n * Whether the glasses may read the phone's notifications. iOS glasses read\n * the stream themselves under the Bluetooth notification-sharing consent the\n * driver reports as `ancsAuthorized`; Android glasses get content from the\n * phone's listener, whose grant the permission flow owns.\n */\nexport function captureAuthorization(): \"granted\" | \"denied\" | \"unknown\" | \"not_needed\" {\n if (Platform.OS !== \"ios\") return \"not_needed\"\n if (!nativeNotificationsSupported() && !notificationRelaySupported()) return \"not_needed\"\n const glasses = useGlassesStore.getState()\n if (!isGlassesConnected(glasses.connection)) return \"unknown\"\n return glasses.ancsAuthorized ? \"granted\" : notificationRelaySupported() ? \"unknown\" : \"denied\"\n}\n\n/**\n * Hand one captured notification to glasses whose firmware presents it from\n * uploaded content (Android). Glasses that read the phone's stream reject it;\n * callers only reach here when the owner is `firmware`.\n */\nexport function uploadPhoneNotification(event: PhoneNotificationEvent): Promise<void> {\n if (Platform.OS !== \"android\") return Promise.resolve()\n return Promise.resolve(\n BluetoothSdk.uploadPhoneNotification({\n notificationId: String(event.notificationId ?? \"\"),\n packageName: String(event.packageName ?? \"\"),\n appName: String(event.app ?? \"\"),\n title: String(event.title ?? \"\"),\n subtitle: \"\",\n body: String(event.content ?? \"\"),\n timestampMs: Number(event.timestamp) || Date.now(),\n }),\n )\n}\n\nfunction pushListenerConfig(): void {\n if (Platform.OS !== \"android\") return\n const s = useSettingsStore.getState()\n const listenerEnabled = Boolean(s.getSetting(SETTINGS.android_notification_listener_enabled.key))\n const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)\n CrustModule.setNotificationConfig(listenerEnabled, Array.isArray(blocklist) ? blocklist : []).catch((err: unknown) =>\n console.warn(`PhoneNotificationsSync: setNotificationConfig failed: ${(err as Error)?.message ?? err}`),\n )\n}\n\nfunction pushGlassesConfig(): void {\n if (!nativeNotificationsSupported() && !notificationRelaySupported()) return\n if (!isGlassesConnected(useGlassesStore.getState().connection)) return\n void Promise.resolve(BluetoothSdk.setGlassesNotificationConfig(desiredGlassesNotificationConfig())).catch(\n (err: unknown) =>\n console.warn(`PhoneNotificationsSync: glasses notification config failed: ${(err as Error)?.message ?? err}`),\n )\n}\n\nfunction pushAll(): void {\n pushListenerConfig()\n pushGlassesConfig()\n}\n\n/** Record a glasses-reported source app. Writes only on new app / renamed app. */\nfunction recordSeenApp(event: NotificationAppSeenEvent): void {\n if (!event.appId) return\n const seen = getSeenNotificationApps()\n const existing = seen.find((app) => app.appId === event.appId)\n const displayName = event.displayName || existing?.displayName || event.appId\n if (existing && existing.displayName === displayName) return\n const next = seen\n .filter((app) => app.appId !== event.appId)\n .concat([{appId: event.appId, displayName, lastSeen: event.timestamp || Date.now()}])\n useSettingsStore.getState().setSetting(SETTINGS.notification_seen_apps.key, next)\n}\n\nexport function startPhoneNotificationsSync(): void {\n if (unsubscribers.length) return\n pushAll()\n const seenApps = BluetoothSdk.addListener(\"notification_app_seen\", recordSeenApp)\n unsubscribers = [\n useSettingsStore.subscribe(\n (state) =>\n [\n SETTINGS.android_notification_listener_enabled,\n SETTINGS.notifications_blocklist,\n SETTINGS.notification_seen_apps,\n SETTINGS.notifications_enabled,\n SETTINGS.notifications_popups,\n SETTINGS.notifications_popup_seconds,\n SETTINGS.notifications_hold_during_apps,\n SETTINGS.notifications_ignore_while_active,\n ].map((setting) => state.getSetting(setting.key)),\n pushAll,\n {equalityFn: shallow},\n ),\n // A (re)connected driver gets the current config; the SDK dedupes nothing\n // here, so only connection state and model are watched, not every field.\n useGlassesStore.subscribe(\n (state) => ({model: state.deviceModel, connected: isGlassesConnected(state.connection)}),\n pushGlassesConfig,\n {equalityFn: shallow},\n ),\n // Lock / foreground changes flip the effective popups flag for firmware\n // that pops notifications up by itself.\n onPhoneActivityChange(pushGlassesConfig),\n () => seenApps.remove(),\n ]\n}\n\nexport function stopPhoneNotificationsSync(): void {\n for (const unsubscribe of unsubscribers) unsubscribe()\n unsubscribers = []\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"memomind-one.d.ts","sourceRoot":"","sources":["../../../src/types/capabilities/memomind-one.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAA;AAE7C,eAAO,MAAM,WAAW,EAAE,YAqCzB,CAAA"}
1
+ {"version":3,"file":"memomind-one.d.ts","sourceRoot":"","sources":["../../../src/types/capabilities/memomind-one.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAA;AAE7C,eAAO,MAAM,WAAW,EAAE,YAuCzB,CAAA"}
@@ -14,6 +14,7 @@ export const memoMindOne = {
14
14
  intensityLevels: 16,
15
15
  partialUpdate: true,
16
16
  adjustBrightness: true,
17
+ positionRange: { height: { min: 0, max: 8 }, depth: { min: 0, max: 8 } },
17
18
  },
18
19
  hasMicrophone: true,
19
20
  microphone: { count: 1, hasVAD: false },
@@ -35,5 +36,6 @@ export const memoMindOne = {
35
36
  hasNativeDashboard: false,
36
37
  hasNativeNavigation: false,
37
38
  hasNativeNotifications: false,
39
+ hasNotificationRelay: true,
38
40
  };
39
41
  //# sourceMappingURL=memomind-one.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"memomind-one.js","sourceRoot":"","sources":["../../../src/types/capabilities/memomind-one.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAiB;IACvC,SAAS,EAAE,cAAc;IACzB,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE;QACP,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,KAAK;QACd,gBAAgB,EAAE,IAAI;QACtB,UAAU,EAAE,EAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAC;QACrC,KAAK,EAAE,GAAG;QACV,MAAM,EAAE,GAAG;QACX,WAAW,EAAE,IAAI;QACjB,eAAe,EAAE,EAAE;QACnB,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,IAAI;KACvB;IACD,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,EAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAC;IACrC,UAAU,EAAE,KAAK;IACjB,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,KAAK;IACb,GAAG,EAAE,IAAI;IACT,SAAS,EAAE,IAAI;IACf,MAAM,EAAE;QACN,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,CAAC,EAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,KAAK,EAAC,CAAC;KACxG;IACD,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,EAAC,kBAAkB,EAAE,KAAK,EAAC;IAClC,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,KAAK;IACb,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,KAAK;IACzB,mBAAmB,EAAE,KAAK;IAC1B,sBAAsB,EAAE,KAAK;CAC9B,CAAA","sourcesContent":["import type {Capabilities} from \"../hardware\"\n\nexport const memoMindOne: Capabilities = {\n modelName: \"MemoMind One\",\n hasCamera: false,\n camera: null,\n hasDisplay: true,\n display: {\n count: 1,\n isColor: false,\n canDisplayBitmap: true,\n resolution: {width: 600, height: 350},\n width: 600,\n height: 350,\n canPosition: true,\n intensityLevels: 16,\n partialUpdate: true,\n adjustBrightness: true,\n },\n hasMicrophone: true,\n microphone: {count: 1, hasVAD: false},\n hasSpeaker: false,\n speaker: null,\n hasIMU: false,\n imu: null,\n hasButton: true,\n button: {\n count: 1,\n buttons: [{type: \"press\", events: [\"TAP\", \"DOUBLE_TAP\", \"PRESS_HOLD\", \"RELEASE\"], isCapacitive: false}],\n },\n hasLight: false,\n light: null,\n power: {hasExternalBattery: false},\n hasWifi: false,\n hasOta: false,\n hasCustomFirmware: true,\n hasNativeDashboard: false,\n hasNativeNavigation: false,\n hasNativeNotifications: false,\n}\n"]}
1
+ {"version":3,"file":"memomind-one.js","sourceRoot":"","sources":["../../../src/types/capabilities/memomind-one.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAiB;IACvC,SAAS,EAAE,cAAc;IACzB,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE;QACP,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,KAAK;QACd,gBAAgB,EAAE,IAAI;QACtB,UAAU,EAAE,EAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAC;QACrC,KAAK,EAAE,GAAG;QACV,MAAM,EAAE,GAAG;QACX,WAAW,EAAE,IAAI;QACjB,eAAe,EAAE,EAAE;QACnB,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,IAAI;QACtB,aAAa,EAAE,EAAC,MAAM,EAAE,EAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAC,EAAE,KAAK,EAAE,EAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAC,EAAC;KACnE;IACD,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,EAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAC;IACrC,UAAU,EAAE,KAAK;IACjB,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,KAAK;IACb,GAAG,EAAE,IAAI;IACT,SAAS,EAAE,IAAI;IACf,MAAM,EAAE;QACN,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,CAAC,EAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,KAAK,EAAC,CAAC;KACxG;IACD,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,EAAC,kBAAkB,EAAE,KAAK,EAAC;IAClC,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,KAAK;IACb,iBAAiB,EAAE,IAAI;IACvB,kBAAkB,EAAE,KAAK;IACzB,mBAAmB,EAAE,KAAK;IAC1B,sBAAsB,EAAE,KAAK;IAC7B,oBAAoB,EAAE,IAAI;CAC3B,CAAA","sourcesContent":["import type {Capabilities} from \"../hardware\"\n\nexport const memoMindOne: Capabilities = {\n modelName: \"MemoMind One\",\n hasCamera: false,\n camera: null,\n hasDisplay: true,\n display: {\n count: 1,\n isColor: false,\n canDisplayBitmap: true,\n resolution: {width: 600, height: 350},\n width: 600,\n height: 350,\n canPosition: true,\n intensityLevels: 16,\n partialUpdate: true,\n adjustBrightness: true,\n positionRange: {height: {min: 0, max: 8}, depth: {min: 0, max: 8}},\n },\n hasMicrophone: true,\n microphone: {count: 1, hasVAD: false},\n hasSpeaker: false,\n speaker: null,\n hasIMU: false,\n imu: null,\n hasButton: true,\n button: {\n count: 1,\n buttons: [{type: \"press\", events: [\"TAP\", \"DOUBLE_TAP\", \"PRESS_HOLD\", \"RELEASE\"], isCapacitive: false}],\n },\n hasLight: false,\n light: null,\n power: {hasExternalBattery: false},\n hasWifi: false,\n hasOta: false,\n hasCustomFirmware: true,\n hasNativeDashboard: false,\n hasNativeNavigation: false,\n hasNativeNotifications: false,\n hasNotificationRelay: true,\n}\n"]}
@@ -173,6 +173,7 @@ export interface Capabilities {
173
173
  hasNativeDashboard?: boolean;
174
174
  hasNativeNavigation?: boolean;
175
175
  hasNativeNotifications?: boolean;
176
+ hasNotificationRelay?: boolean;
176
177
  nativeNotificationPopupSeconds?: number[];
177
178
  }
178
179
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"hardware.d.ts","sourceRoot":"","sources":["../../src/types/hardware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hardware.d.ts","sourceRoot":"","sources":["../../src/types/hardware.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"hardware.js","sourceRoot":"","sources":["../../src/types/hardware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAC,eAAe,EAAC,MAAM,kCAAkC,CAAA;AAChE,OAAO,EAAC,eAAe,EAAC,MAAM,kCAAkC,CAAA;AAChE,OAAO,EAAC,aAAa,EAAC,MAAM,+BAA+B,CAAA;AAC3D,OAAO,EAAC,UAAU,EAAC,MAAM,4BAA4B,CAAA;AACrD,OAAO,EAAC,gBAAgB,EAAC,MAAM,kCAAkC,CAAA;AACjE,OAAO,EAAC,IAAI,EAAC,MAAM,qBAAqB,CAAA;AACxC,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAC,WAAW,EAAC,MAAM,6BAA6B,CAAA;AACvD,OAAO,EAAC,SAAS,EAAC,MAAM,2BAA2B,CAAA;AACnD,OAAO,EAAC,IAAI,EAAC,MAAM,qBAAqB,CAAA;AACxC,OAAO,EAAC,WAAW,EAAC,MAAM,SAAS,CAAA;AA2EnC,oEAAoE;AACpE,MAAM,CAAC,MAAM,kBAAkB,GAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;AAuIxH;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAiC;IACjE,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,eAAe;IAC5C,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,eAAe;IAC5C,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,aAAa;IACxC,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,UAAU;IAClC,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,gBAAgB;IAC9C,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,SAAS;IAChC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI;IACtB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ;IAC9B,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,WAAW;IACpC,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,mDAAmD;IACnF,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE;QAClB,GAAG,eAAe;QAClB,SAAS,EAAE,WAAW,CAAC,IAAI;QAC3B,OAAO,EAAE;YACP,GAAG,eAAe,CAAC,OAAO;YAC1B,gBAAgB,EAAE,KAAK;YACvB,+CAA+C;YAC/C,aAAa,EAAE,SAAS;SACzB;QACD,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE;YACV,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,KAAK;SACd;KACF;IACD,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI;CACvB,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,UAAuB,EAAgB,EAAE;IAC5E,MAAM,SAAS,GAAG,UAAoB,CAAA;IACtC,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,OAAO,qBAAqB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IAChD,CAAC;IACD,OAAO,qBAAqB,CAAC,SAAS,CAAC,CAAA;AACzC,CAAC,CAAA;AAED,iCAAiC;AACjC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,WAAW,GACZ,CAAA","sourcesContent":["/**\n * Hardware capability types\n */\n\nimport {evenRealitiesG1} from \"./capabilities/even-realities-g1\"\nimport {evenRealitiesG2} from \"./capabilities/even-realities-g2\"\nimport {mentraDisplay} from \"./capabilities/mentra-display\"\nimport {mentraLive} from \"./capabilities/mentra-live\"\nimport {simulatedGlasses} from \"./capabilities/simulated-glasses\"\nimport {nimo} from \"./capabilities/nimo\"\nimport {rayneoIO} from \"./capabilities/rayneo-io\"\nimport {memoMindOne} from \"./capabilities/memomind-one\"\nimport {vuzixZ100} from \"./capabilities/vuzix-z100\"\nimport {none} from \"./capabilities/none\"\nimport {DeviceTypes} from \"./enums\"\n\n// Canonical def lives in @glassly/miniapp; re-exported for engine consumers.\nexport type {HardwareRequirement} from \"@glassly/miniapp/hardware\"\n\n/**\n * Camera capabilities\n */\nexport interface CameraCapabilities {\n resolution?: {width: number; height: number}\n hasHDR?: boolean\n hasFocus?: boolean\n video: {\n canRecord: boolean\n canStream: boolean\n supportedStreamTypes?: string[]\n supportedResolutions?: {width: number; height: number}[]\n }\n}\n\nexport interface LevelRange {\n min: number\n max: number\n}\n\n/**\n * Display capabilities\n */\nexport interface DisplayCapabilities {\n count?: number\n isColor?: boolean\n color?: string // e.g., \"green\", \"full_color\", \"pallet\"\n canDisplayBitmap?: boolean\n resolution?: {width: number; height: number}\n fieldOfView?: {horizontal?: number; vertical?: number}\n maxTextLines?: number\n adjustBrightness?: boolean\n /** The `dashboard_height` / `dashboard_depth` steps the glasses accept, inclusive; absent ⇒ no position control. */\n positionRange?: {height: LevelRange; depth: LevelRange}\n\n // --- Scene display API (display.render()) — typed capabilities ---\n // Pure DATA the host scene pipeline acts on generically; a device without\n // canPosition renders scenes via the host degrade path instead.\n /** Public drawable canvas in px — the raw coordinate space for render() boxes. */\n width?: number\n height?: number\n /** True ⇒ the device renders positioned elements (containers / canvas components). */\n canPosition?: boolean\n /** Element budgets. Rects share the text pool on container-based devices. */\n maxTextElements?: number\n maxImageElements?: number\n /** Per-image dimension cap (box-level), when the firmware has one. */\n maxImagePx?: {width: number; height: number}\n shapes?: DisplayShape[]\n /** True ⇒ the device animates element transitions itself. */\n animation?: boolean\n /** Geometric and SVG rotations, including duration-based transitions. */\n rotation?: boolean\n /** Per-element stereo `depth` (a lens disparity) is honored; otherwise elements draw flat. */\n depth?: boolean\n intensityLevels?: number\n\n partialUpdate?: boolean\n /**\n * The views a miniapp may draw right now: \"main\" for every app, \"dashboard\"\n * (the overlay slot) only for the designated system dashboard app while the\n * glasses are eligible. Per app; filled in by the miniapp runtime.\n */\n views?: DisplayView[]\n}\n\nexport type DisplayView = \"main\" | \"dashboard\"\n\nexport type DisplayShape = \"rect\" | \"circle\" | \"arc\" | \"pie\" | \"line\" | \"triangle\" | \"quad\" | \"bezier\" | \"svg\"\n\n/** Every vector shape the Glassly CFW rasterizes on the glasses. */\nexport const ALL_DISPLAY_SHAPES: DisplayShape[] = [\"rect\", \"circle\", \"arc\", \"pie\", \"line\", \"triangle\", \"quad\", \"bezier\"]\n\n/**\n * Microphone capabilities\n */\nexport interface MicrophoneCapabilities {\n count?: number\n hasVAD?: boolean // Voice Activity Detection\n // The connected firmware can stop streaming audio below a volume floor\n // (G2 Glassly CFW revision 40+). Never set on a model profile: it is\n // reported at runtime and layered on by getEffectiveCapabilities.\n volumeGate?: boolean\n}\n\n/**\n * Speaker capabilities\n */\nexport interface SpeakerCapabilities {\n count?: number\n isPrivate?: boolean // e.g., bone conduction\n}\n\n/**\n * IMU (Inertial Measurement Unit) capabilities\n */\nexport interface IMUCapabilities {\n axisCount?: number\n hasAccelerometer?: boolean\n hasCompass?: boolean\n hasGyroscope?: boolean\n}\n\n/**\n * Button capabilities\n */\nexport interface ButtonCapabilities {\n count?: number\n buttons?: {\n type: \"press\" | \"swipe1d\" | \"swipe2d\"\n events: string[] // e.g., \"press\", \"double_press\", \"long_press\", \"swipe_up\", \"swipe_down\"\n isCapacitive?: boolean\n }[]\n}\n\n/**\n * Light capabilities\n */\nexport interface LightCapabilities {\n count?: number\n lights?: {\n id: string // Unique identifier for the LED (e.g., \"privacy\", \"user_feedback\")\n purpose: \"privacy\" | \"user_feedback\" | \"general\" // LED purpose/function\n isFullColor: boolean\n color?: string // e.g., \"white\", \"rgb\"\n position?: \"front_facing\" | \"user_facing\" | \"side\" | \"unknown\" // LED physical position\n }[]\n}\n\n/**\n * Power capabilities\n */\nexport interface PowerCapabilities {\n hasExternalBattery: boolean // e.g., a case or puck\n}\n\n/**\n * Device hardware capabilities\n * Complete information about what hardware a device has\n */\nexport interface Capabilities {\n modelName: string\n\n // Camera capabilities\n hasCamera: boolean\n camera: CameraCapabilities | null\n\n // Display capabilities\n hasDisplay: boolean\n display: DisplayCapabilities | null\n\n // Microphone capabilities\n hasMicrophone: boolean\n microphone: MicrophoneCapabilities | null\n\n // Speaker capabilities\n hasSpeaker: boolean\n speaker: SpeakerCapabilities | null\n\n // IMU capabilities\n hasIMU: boolean\n imu: IMUCapabilities | null\n\n // Button capabilities\n hasButton: boolean\n button: ButtonCapabilities | null\n\n // Light capabilities\n hasLight: boolean\n light: LightCapabilities | null\n\n // Power capabilities\n power: PowerCapabilities\n\n // WiFi capability\n hasWifi: boolean\n\n // OTA capability\n hasOta?: boolean\n\n // True when the app ships a flashable Glassly custom firmware for this\n // model, so pairing can offer to install it and Settings exposes the flasher\n hasCustomFirmware?: boolean\n\n // Native dashboard capability\n // True when the device renders its own dashboard in firmware (e.g. Even\n // Realities' built-in dashboard on G2). Glassly does not manage or expose\n // dashboard settings for these devices. Absent/false means Glassly owns the\n // dashboard.\n hasNativeDashboard?: boolean\n\n // True when the device renders its own turn-by-turn nav UI in firmware\n // (G2 nav mode: instruction card + map slots + magnetometer compass); nav\n // apps feed it structured fields and map bitmaps instead of scene elements\n hasNativeNavigation?: boolean\n\n // True when the firmware presents phone notifications itself (popup +\n // history) and takes a GlassesNotificationConfig. How it gets the content\n // (reading the phone's stream, or an upload from the phone) is the driver's\n // business; the engine only decides who presents.\n hasNativeNotifications?: boolean\n // Popup durations the firmware accepts, in seconds (0 = firmware default).\n // Drivers snap other values; the settings UI offers exactly these.\n nativeNotificationPopupSeconds?: number[]\n}\n\n/**\n * Hardware capability profiles for supported glasses models\n * Key: model_name string (e.g., \"Even Realities G1\", \"Mentra Live\")\n * Value: Capabilities object defining device features\n */\nexport const HARDWARE_CAPABILITIES: Record<string, Capabilities> = {\n [evenRealitiesG1.modelName]: evenRealitiesG1,\n [evenRealitiesG2.modelName]: evenRealitiesG2,\n [mentraDisplay.modelName]: mentraDisplay,\n [mentraLive.modelName]: mentraLive,\n [simulatedGlasses.modelName]: simulatedGlasses,\n [vuzixZ100.modelName]: vuzixZ100,\n [nimo.modelName]: nimo,\n [rayneoIO.modelName]: rayneoIO,\n [memoMindOne.modelName]: memoMindOne,\n [DeviceTypes.MACH1]: vuzixZ100, // Mach1 uses same Vuzix Ultralite hardware as Z100\n [DeviceTypes.AR99]: {\n ...evenRealitiesG1,\n modelName: DeviceTypes.AR99,\n display: {\n ...evenRealitiesG1.display,\n canDisplayBitmap: false,\n // The AR99 firmware takes no position command.\n positionRange: undefined,\n },\n hasMicrophone: true,\n hasOta: true,\n microphone: {\n count: 1,\n hasVAD: false,\n },\n },\n [none.modelName]: none,\n}\n\nexport const getModelCapabilities = (deviceType: DeviceTypes): Capabilities => {\n const modelName = deviceType as string\n if (!HARDWARE_CAPABILITIES[modelName]) {\n return HARDWARE_CAPABILITIES[DeviceTypes.NONE]\n }\n return HARDWARE_CAPABILITIES[modelName]\n}\n\n// export * from \"./capabilities\"\nexport {\n simulatedGlasses,\n evenRealitiesG1,\n evenRealitiesG2,\n mentraLive,\n vuzixZ100,\n mentraDisplay,\n nimo,\n rayneoIO,\n memoMindOne,\n}\n"]}
1
+ {"version":3,"file":"hardware.js","sourceRoot":"","sources":["../../src/types/hardware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAC,eAAe,EAAC,MAAM,kCAAkC,CAAA;AAChE,OAAO,EAAC,eAAe,EAAC,MAAM,kCAAkC,CAAA;AAChE,OAAO,EAAC,aAAa,EAAC,MAAM,+BAA+B,CAAA;AAC3D,OAAO,EAAC,UAAU,EAAC,MAAM,4BAA4B,CAAA;AACrD,OAAO,EAAC,gBAAgB,EAAC,MAAM,kCAAkC,CAAA;AACjE,OAAO,EAAC,IAAI,EAAC,MAAM,qBAAqB,CAAA;AACxC,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAC,WAAW,EAAC,MAAM,6BAA6B,CAAA;AACvD,OAAO,EAAC,SAAS,EAAC,MAAM,2BAA2B,CAAA;AACnD,OAAO,EAAC,IAAI,EAAC,MAAM,qBAAqB,CAAA;AACxC,OAAO,EAAC,WAAW,EAAC,MAAM,SAAS,CAAA;AA2EnC,oEAAoE;AACpE,MAAM,CAAC,MAAM,kBAAkB,GAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;AAwIxH;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAiC;IACjE,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,eAAe;IAC5C,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,eAAe;IAC5C,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,aAAa;IACxC,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,UAAU;IAClC,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,gBAAgB;IAC9C,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,SAAS;IAChC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI;IACtB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ;IAC9B,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,WAAW;IACpC,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,mDAAmD;IACnF,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE;QAClB,GAAG,eAAe;QAClB,SAAS,EAAE,WAAW,CAAC,IAAI;QAC3B,OAAO,EAAE;YACP,GAAG,eAAe,CAAC,OAAO;YAC1B,gBAAgB,EAAE,KAAK;YACvB,+CAA+C;YAC/C,aAAa,EAAE,SAAS;SACzB;QACD,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE;YACV,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,KAAK;SACd;KACF;IACD,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI;CACvB,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,UAAuB,EAAgB,EAAE;IAC5E,MAAM,SAAS,GAAG,UAAoB,CAAA;IACtC,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,OAAO,qBAAqB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IAChD,CAAC;IACD,OAAO,qBAAqB,CAAC,SAAS,CAAC,CAAA;AACzC,CAAC,CAAA;AAED,iCAAiC;AACjC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,WAAW,GACZ,CAAA","sourcesContent":["/**\n * Hardware capability types\n */\n\nimport {evenRealitiesG1} from \"./capabilities/even-realities-g1\"\nimport {evenRealitiesG2} from \"./capabilities/even-realities-g2\"\nimport {mentraDisplay} from \"./capabilities/mentra-display\"\nimport {mentraLive} from \"./capabilities/mentra-live\"\nimport {simulatedGlasses} from \"./capabilities/simulated-glasses\"\nimport {nimo} from \"./capabilities/nimo\"\nimport {rayneoIO} from \"./capabilities/rayneo-io\"\nimport {memoMindOne} from \"./capabilities/memomind-one\"\nimport {vuzixZ100} from \"./capabilities/vuzix-z100\"\nimport {none} from \"./capabilities/none\"\nimport {DeviceTypes} from \"./enums\"\n\n// Canonical def lives in @glassly/miniapp; re-exported for engine consumers.\nexport type {HardwareRequirement} from \"@glassly/miniapp/hardware\"\n\n/**\n * Camera capabilities\n */\nexport interface CameraCapabilities {\n resolution?: {width: number; height: number}\n hasHDR?: boolean\n hasFocus?: boolean\n video: {\n canRecord: boolean\n canStream: boolean\n supportedStreamTypes?: string[]\n supportedResolutions?: {width: number; height: number}[]\n }\n}\n\nexport interface LevelRange {\n min: number\n max: number\n}\n\n/**\n * Display capabilities\n */\nexport interface DisplayCapabilities {\n count?: number\n isColor?: boolean\n color?: string // e.g., \"green\", \"full_color\", \"pallet\"\n canDisplayBitmap?: boolean\n resolution?: {width: number; height: number}\n fieldOfView?: {horizontal?: number; vertical?: number}\n maxTextLines?: number\n adjustBrightness?: boolean\n /** The `dashboard_height` / `dashboard_depth` steps the glasses accept, inclusive; absent ⇒ no position control. */\n positionRange?: {height: LevelRange; depth: LevelRange}\n\n // --- Scene display API (display.render()) — typed capabilities ---\n // Pure DATA the host scene pipeline acts on generically; a device without\n // canPosition renders scenes via the host degrade path instead.\n /** Public drawable canvas in px — the raw coordinate space for render() boxes. */\n width?: number\n height?: number\n /** True ⇒ the device renders positioned elements (containers / canvas components). */\n canPosition?: boolean\n /** Element budgets. Rects share the text pool on container-based devices. */\n maxTextElements?: number\n maxImageElements?: number\n /** Per-image dimension cap (box-level), when the firmware has one. */\n maxImagePx?: {width: number; height: number}\n shapes?: DisplayShape[]\n /** True ⇒ the device animates element transitions itself. */\n animation?: boolean\n /** Geometric and SVG rotations, including duration-based transitions. */\n rotation?: boolean\n /** Per-element stereo `depth` (a lens disparity) is honored; otherwise elements draw flat. */\n depth?: boolean\n intensityLevels?: number\n\n partialUpdate?: boolean\n /**\n * The views a miniapp may draw right now: \"main\" for every app, \"dashboard\"\n * (the overlay slot) only for the designated system dashboard app while the\n * glasses are eligible. Per app; filled in by the miniapp runtime.\n */\n views?: DisplayView[]\n}\n\nexport type DisplayView = \"main\" | \"dashboard\"\n\nexport type DisplayShape = \"rect\" | \"circle\" | \"arc\" | \"pie\" | \"line\" | \"triangle\" | \"quad\" | \"bezier\" | \"svg\"\n\n/** Every vector shape the Glassly CFW rasterizes on the glasses. */\nexport const ALL_DISPLAY_SHAPES: DisplayShape[] = [\"rect\", \"circle\", \"arc\", \"pie\", \"line\", \"triangle\", \"quad\", \"bezier\"]\n\n/**\n * Microphone capabilities\n */\nexport interface MicrophoneCapabilities {\n count?: number\n hasVAD?: boolean // Voice Activity Detection\n // The connected firmware can stop streaming audio below a volume floor\n // (G2 Glassly CFW revision 40+). Never set on a model profile: it is\n // reported at runtime and layered on by getEffectiveCapabilities.\n volumeGate?: boolean\n}\n\n/**\n * Speaker capabilities\n */\nexport interface SpeakerCapabilities {\n count?: number\n isPrivate?: boolean // e.g., bone conduction\n}\n\n/**\n * IMU (Inertial Measurement Unit) capabilities\n */\nexport interface IMUCapabilities {\n axisCount?: number\n hasAccelerometer?: boolean\n hasCompass?: boolean\n hasGyroscope?: boolean\n}\n\n/**\n * Button capabilities\n */\nexport interface ButtonCapabilities {\n count?: number\n buttons?: {\n type: \"press\" | \"swipe1d\" | \"swipe2d\"\n events: string[] // e.g., \"press\", \"double_press\", \"long_press\", \"swipe_up\", \"swipe_down\"\n isCapacitive?: boolean\n }[]\n}\n\n/**\n * Light capabilities\n */\nexport interface LightCapabilities {\n count?: number\n lights?: {\n id: string // Unique identifier for the LED (e.g., \"privacy\", \"user_feedback\")\n purpose: \"privacy\" | \"user_feedback\" | \"general\" // LED purpose/function\n isFullColor: boolean\n color?: string // e.g., \"white\", \"rgb\"\n position?: \"front_facing\" | \"user_facing\" | \"side\" | \"unknown\" // LED physical position\n }[]\n}\n\n/**\n * Power capabilities\n */\nexport interface PowerCapabilities {\n hasExternalBattery: boolean // e.g., a case or puck\n}\n\n/**\n * Device hardware capabilities\n * Complete information about what hardware a device has\n */\nexport interface Capabilities {\n modelName: string\n\n // Camera capabilities\n hasCamera: boolean\n camera: CameraCapabilities | null\n\n // Display capabilities\n hasDisplay: boolean\n display: DisplayCapabilities | null\n\n // Microphone capabilities\n hasMicrophone: boolean\n microphone: MicrophoneCapabilities | null\n\n // Speaker capabilities\n hasSpeaker: boolean\n speaker: SpeakerCapabilities | null\n\n // IMU capabilities\n hasIMU: boolean\n imu: IMUCapabilities | null\n\n // Button capabilities\n hasButton: boolean\n button: ButtonCapabilities | null\n\n // Light capabilities\n hasLight: boolean\n light: LightCapabilities | null\n\n // Power capabilities\n power: PowerCapabilities\n\n // WiFi capability\n hasWifi: boolean\n\n // OTA capability\n hasOta?: boolean\n\n // True when the app ships a flashable Glassly custom firmware for this\n // model, so pairing can offer to install it and Settings exposes the flasher\n hasCustomFirmware?: boolean\n\n // Native dashboard capability\n // True when the device renders its own dashboard in firmware (e.g. Even\n // Realities' built-in dashboard on G2). Glassly does not manage or expose\n // dashboard settings for these devices. Absent/false means Glassly owns the\n // dashboard.\n hasNativeDashboard?: boolean\n\n // True when the device renders its own turn-by-turn nav UI in firmware\n // (G2 nav mode: instruction card + map slots + magnetometer compass); nav\n // apps feed it structured fields and map bitmaps instead of scene elements\n hasNativeNavigation?: boolean\n\n // True when the firmware presents phone notifications itself (popup +\n // history) and takes a GlassesNotificationConfig. How it gets the content\n // (reading the phone's stream, or an upload from the phone) is the driver's\n // business; the engine only decides who presents.\n hasNativeNotifications?: boolean\n hasNotificationRelay?: boolean\n // Popup durations the firmware accepts, in seconds (0 = firmware default).\n // Drivers snap other values; the settings UI offers exactly these.\n nativeNotificationPopupSeconds?: number[]\n}\n\n/**\n * Hardware capability profiles for supported glasses models\n * Key: model_name string (e.g., \"Even Realities G1\", \"Mentra Live\")\n * Value: Capabilities object defining device features\n */\nexport const HARDWARE_CAPABILITIES: Record<string, Capabilities> = {\n [evenRealitiesG1.modelName]: evenRealitiesG1,\n [evenRealitiesG2.modelName]: evenRealitiesG2,\n [mentraDisplay.modelName]: mentraDisplay,\n [mentraLive.modelName]: mentraLive,\n [simulatedGlasses.modelName]: simulatedGlasses,\n [vuzixZ100.modelName]: vuzixZ100,\n [nimo.modelName]: nimo,\n [rayneoIO.modelName]: rayneoIO,\n [memoMindOne.modelName]: memoMindOne,\n [DeviceTypes.MACH1]: vuzixZ100, // Mach1 uses same Vuzix Ultralite hardware as Z100\n [DeviceTypes.AR99]: {\n ...evenRealitiesG1,\n modelName: DeviceTypes.AR99,\n display: {\n ...evenRealitiesG1.display,\n canDisplayBitmap: false,\n // The AR99 firmware takes no position command.\n positionRange: undefined,\n },\n hasMicrophone: true,\n hasOta: true,\n microphone: {\n count: 1,\n hasVAD: false,\n },\n },\n [none.modelName]: none,\n}\n\nexport const getModelCapabilities = (deviceType: DeviceTypes): Capabilities => {\n const modelName = deviceType as string\n if (!HARDWARE_CAPABILITIES[modelName]) {\n return HARDWARE_CAPABILITIES[DeviceTypes.NONE]\n }\n return HARDWARE_CAPABILITIES[modelName]\n}\n\n// export * from \"./capabilities\"\nexport {\n simulatedGlasses,\n evenRealitiesG1,\n evenRealitiesG2,\n mentraLive,\n vuzixZ100,\n mentraDisplay,\n nimo,\n rayneoIO,\n memoMindOne,\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/engine",
3
- "version": "0.3.7-dev.954",
3
+ "version": "0.3.7-dev.958",
4
4
  "description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -97,17 +97,17 @@
97
97
  "registry": "https://registry.npmjs.org/"
98
98
  },
99
99
  "dependencies": {
100
- "@glassly/bluetooth-sdk": "0.3.7-dev.954",
101
- "@glassly/cloud-client": "0.3.7-dev.954",
102
- "@glassly/cloud-protocol": "0.3.7-dev.954",
103
- "@glassly/crust": "0.3.7-dev.954",
104
- "@glassly/miniapp": "0.3.7-dev.954",
100
+ "@glassly/bluetooth-sdk": "0.3.7-dev.958",
101
+ "@glassly/cloud-client": "0.3.7-dev.958",
102
+ "@glassly/cloud-protocol": "0.3.7-dev.958",
103
+ "@glassly/crust": "0.3.7-dev.958",
104
+ "@glassly/miniapp": "0.3.7-dev.958",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "react-native-marked": "8.3.2",
108
108
  "semver": "^7.7.4",
109
109
  "typesafe-ts": "^1.7.1",
110
- "@glassly/llm-providers": "0.3.7-dev.954",
110
+ "@glassly/llm-providers": "0.3.7-dev.958",
111
111
  "@huggingface/jinja": "^0.5.9",
112
112
  "jsonrepair": "^3.12.0"
113
113
  },
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "0.3.7",
15
- "releaseIdentity": "0.3.7-dev.954",
16
- "releaseSetId": "glassly-0.3.7-dev.954",
17
- "sourceCommit": "393f593dcfbf49e891275efe62f85f2ee92325b5",
15
+ "releaseIdentity": "0.3.7-dev.958",
16
+ "releaseSetId": "glassly-0.3.7-dev.958",
17
+ "sourceCommit": "cb9da4ac87e203755f06fb289b4fb8732946b11c",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
@@ -49,9 +49,16 @@ export async function listPhoneCalendarEvents(request: CalendarListRequest): Pro
49
49
  if (calendars.length === 0) return {events: [], truncated: false}
50
50
 
51
51
  const rawEvents = await Calendar.listEvents(calendars, startsAt, endsAt)
52
+ const seen = new Set<string>()
52
53
  const events = rawEvents
53
54
  .map(normalizeCalendarEvent)
54
55
  .filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))
56
+ .filter((event) => {
57
+ const key = `${event.title}\n${event.startsAt}\n${event.endsAt}\n${event.allDay}`
58
+ if (seen.has(key)) return false
59
+ seen.add(key)
60
+ return true
61
+ })
55
62
  .sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt))
56
63
 
57
64
  return {
@@ -91,6 +91,11 @@ export function nativeNotificationsSupported(): boolean {
91
91
  return getModelCapabilities(model).hasNativeNotifications === true
92
92
  }
93
93
 
94
+ function notificationRelaySupported(): boolean {
95
+ const model = useGlassesStore.getState().deviceModel as DeviceTypes
96
+ return Platform.OS === "ios" && getModelCapabilities(model).hasNotificationRelay === true
97
+ }
98
+
94
99
  /** Popup durations the firmware accepts, or an empty list when it takes any. */
95
100
  export function nativeNotificationPopupSeconds(): number[] {
96
101
  const model = useGlassesStore.getState().deviceModel as DeviceTypes
@@ -141,10 +146,10 @@ export function currentPresentationOwner(hostActive: boolean): PresentationOwner
141
146
  */
142
147
  export function captureAuthorization(): "granted" | "denied" | "unknown" | "not_needed" {
143
148
  if (Platform.OS !== "ios") return "not_needed"
144
- if (!nativeNotificationsSupported()) return "not_needed"
149
+ if (!nativeNotificationsSupported() && !notificationRelaySupported()) return "not_needed"
145
150
  const glasses = useGlassesStore.getState()
146
151
  if (!isGlassesConnected(glasses.connection)) return "unknown"
147
- return glasses.ancsAuthorized ? "granted" : "denied"
152
+ return glasses.ancsAuthorized ? "granted" : notificationRelaySupported() ? "unknown" : "denied"
148
153
  }
149
154
 
150
155
  /**
@@ -178,7 +183,7 @@ function pushListenerConfig(): void {
178
183
  }
179
184
 
180
185
  function pushGlassesConfig(): void {
181
- if (!nativeNotificationsSupported()) return
186
+ if (!nativeNotificationsSupported() && !notificationRelaySupported()) return
182
187
  if (!isGlassesConnected(useGlassesStore.getState().connection)) return
183
188
  void Promise.resolve(BluetoothSdk.setGlassesNotificationConfig(desiredGlassesNotificationConfig())).catch(
184
189
  (err: unknown) =>
@@ -16,6 +16,7 @@ export const memoMindOne: Capabilities = {
16
16
  intensityLevels: 16,
17
17
  partialUpdate: true,
18
18
  adjustBrightness: true,
19
+ positionRange: {height: {min: 0, max: 8}, depth: {min: 0, max: 8}},
19
20
  },
20
21
  hasMicrophone: true,
21
22
  microphone: {count: 1, hasVAD: false},
@@ -37,4 +38,5 @@ export const memoMindOne: Capabilities = {
37
38
  hasNativeDashboard: false,
38
39
  hasNativeNavigation: false,
39
40
  hasNativeNotifications: false,
41
+ hasNotificationRelay: true,
40
42
  }
@@ -218,6 +218,7 @@ export interface Capabilities {
218
218
  // (reading the phone's stream, or an upload from the phone) is the driver's
219
219
  // business; the engine only decides who presents.
220
220
  hasNativeNotifications?: boolean
221
+ hasNotificationRelay?: boolean
221
222
  // Popup durations the firmware accepts, in seconds (0 = firmware default).
222
223
  // Drivers snap other values; the settings UI offers exactly these.
223
224
  nativeNotificationPopupSeconds?: number[]