@ceralive/modem-control 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +317 -0
  2. package/dist/backend/at-lease.d.ts +57 -0
  3. package/dist/backend/at-lease.js +109 -0
  4. package/dist/backend/cell-info.d.ts +46 -0
  5. package/dist/backend/cell-info.js +124 -0
  6. package/dist/backend/constants.d.ts +24 -0
  7. package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
  8. package/dist/backend/device-classifier.d.ts +46 -0
  9. package/dist/backend/device-classifier.js +258 -0
  10. package/dist/backend/enrichment.d.ts +28 -0
  11. package/dist/backend/enrichment.js +59 -0
  12. package/dist/backend/features.d.ts +61 -0
  13. package/dist/backend/features.js +109 -0
  14. package/dist/backend/identity-ladder.d.ts +57 -0
  15. package/dist/backend/identity-ladder.js +158 -0
  16. package/dist/backend/identity-registry.d.ts +51 -0
  17. package/dist/backend/identity-registry.js +101 -0
  18. package/dist/backend/index.d.ts +30 -0
  19. package/dist/backend/index.js +35 -0
  20. package/dist/backend/lifecycle-interlock.d.ts +25 -0
  21. package/dist/backend/lifecycle-interlock.js +18 -0
  22. package/dist/backend/managed-objects.d.ts +39 -0
  23. package/dist/backend/managed-objects.js +82 -0
  24. package/dist/backend/mapping.d.ts +11 -0
  25. package/dist/backend/mapping.js +148 -0
  26. package/dist/backend/mm-backend.d.ts +44 -0
  27. package/dist/backend/mm-backend.js +154 -0
  28. package/dist/backend/mm-location.d.ts +21 -0
  29. package/dist/backend/mm-location.js +237 -0
  30. package/dist/backend/mm-mutations.d.ts +42 -0
  31. package/dist/backend/mm-mutations.js +259 -0
  32. package/dist/backend/modem-actor.d.ts +41 -0
  33. package/dist/backend/modem-actor.js +78 -0
  34. package/dist/backend/nm-auto-apn.d.ts +54 -0
  35. package/dist/backend/nm-auto-apn.js +124 -0
  36. package/dist/backend/nm-gsm-fields.d.ts +23 -0
  37. package/dist/backend/nm-gsm-fields.js +107 -0
  38. package/dist/backend/nmcli-nm-port.d.ts +28 -0
  39. package/dist/backend/nmcli-nm-port.js +173 -0
  40. package/dist/backend/nmcli-runner.d.ts +24 -0
  41. package/dist/backend/nmcli-runner.js +35 -0
  42. package/dist/backend/observer.d.ts +37 -0
  43. package/dist/backend/observer.js +219 -0
  44. package/dist/backend/power-contract.d.ts +49 -0
  45. package/dist/backend/power-contract.js +34 -0
  46. package/dist/backend/recovery-attribution.d.ts +32 -0
  47. package/dist/backend/recovery-attribution.js +57 -0
  48. package/dist/backend/recovery-budget.d.ts +45 -0
  49. package/dist/backend/recovery-budget.js +44 -0
  50. package/dist/backend/recovery-ladder.d.ts +94 -0
  51. package/dist/backend/recovery-ladder.js +116 -0
  52. package/dist/backend/router-ethernet.d.ts +19 -0
  53. package/dist/backend/router-ethernet.js +66 -0
  54. package/dist/backend/row-store.d.ts +13 -0
  55. package/dist/backend/row-store.js +82 -0
  56. package/dist/backend/signal-setup.d.ts +30 -0
  57. package/dist/backend/signal-setup.js +92 -0
  58. package/dist/backend/sim-unlock.d.ts +13 -0
  59. package/dist/backend/sim-unlock.js +153 -0
  60. package/dist/backend/transition-preconditions.d.ts +76 -0
  61. package/dist/backend/transition-preconditions.js +64 -0
  62. package/dist/backend/usage/accounting.d.ts +39 -0
  63. package/dist/backend/usage/accounting.js +73 -0
  64. package/dist/backend/usage/billing-cycle.d.ts +11 -0
  65. package/dist/backend/usage/billing-cycle.js +39 -0
  66. package/dist/backend/usage/boot-id.d.ts +6 -0
  67. package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
  68. package/dist/backend/usage/index.d.ts +8 -0
  69. package/dist/backend/usage/index.js +11 -0
  70. package/dist/backend/usage/policy-store.d.ts +50 -0
  71. package/dist/backend/usage/policy-store.js +161 -0
  72. package/dist/backend/usage/policy-write.d.ts +65 -0
  73. package/dist/backend/usage/policy-write.js +112 -0
  74. package/dist/backend/usage/proc-net-dev.d.ts +18 -0
  75. package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
  76. package/dist/backend/usage/sampler.d.ts +75 -0
  77. package/dist/backend/usage/sampler.js +211 -0
  78. package/dist/backend/usage/store.d.ts +38 -0
  79. package/dist/backend/usage/store.js +126 -0
  80. package/dist/backend/usb-device-snapshot.d.ts +31 -0
  81. package/dist/backend/usb-device-snapshot.js +1 -0
  82. package/dist/backend/usb-enumerator.d.ts +21 -0
  83. package/dist/backend/usb-enumerator.js +153 -0
  84. package/dist/backend/usb-mode-transition.d.ts +29 -0
  85. package/dist/backend/usb-mode-transition.js +196 -0
  86. package/dist/band/band-names.d.ts +42 -0
  87. package/dist/band/band-names.js +150 -0
  88. package/dist/band/certification.d.ts +84 -0
  89. package/dist/band/certification.js +127 -0
  90. package/dist/band/certified-bands.json +4 -0
  91. package/dist/band/index.d.ts +2 -0
  92. package/dist/band/index.js +8 -0
  93. package/dist/capability/detect.d.ts +52 -0
  94. package/dist/capability/detect.js +86 -0
  95. package/dist/capability/five-g-preference.d.ts +104 -0
  96. package/dist/capability/five-g-preference.js +171 -0
  97. package/dist/capability/index.d.ts +3 -0
  98. package/dist/capability/index.js +10 -0
  99. package/dist/capability/support-claim.d.ts +39 -0
  100. package/dist/capability/support-claim.js +81 -0
  101. package/dist/domain/brand.d.ts +10 -0
  102. package/dist/domain/brand.js +21 -0
  103. package/dist/domain/errors.d.ts +32 -0
  104. package/dist/domain/errors.js +48 -0
  105. package/dist/domain/generation.d.ts +8 -0
  106. package/dist/domain/generation.js +12 -0
  107. package/dist/domain/guards.d.ts +6 -0
  108. package/dist/domain/guards.js +127 -0
  109. package/dist/domain/identity.d.ts +109 -0
  110. package/dist/domain/identity.js +86 -0
  111. package/dist/domain/index.d.ts +14 -0
  112. package/dist/domain/index.js +18 -0
  113. package/dist/domain/mm-enums.d.ts +12 -0
  114. package/dist/domain/mm-enums.js +139 -0
  115. package/dist/domain/modem-presentation.d.ts +10 -0
  116. package/dist/domain/modem-presentation.js +39 -0
  117. package/dist/domain/observation.d.ts +39 -0
  118. package/dist/domain/observation.js +4 -0
  119. package/dist/domain/operation.d.ts +118 -0
  120. package/dist/domain/operation.js +85 -0
  121. package/dist/domain/physical-identity.d.ts +43 -0
  122. package/dist/domain/physical-identity.js +113 -0
  123. package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
  124. package/dist/domain/policy.js +38 -0
  125. package/dist/domain/shadow-divergence.d.ts +27 -0
  126. package/dist/domain/shadow-divergence.js +70 -0
  127. package/dist/domain/snapshot.d.ts +53 -0
  128. package/dist/domain/snapshot.js +75 -0
  129. package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
  130. package/dist/domain/state.js +38 -0
  131. package/dist/fcc/coverage.d.ts +63 -0
  132. package/dist/fcc/coverage.js +102 -0
  133. package/dist/fcc/index.d.ts +3 -0
  134. package/dist/fcc/index.js +12 -0
  135. package/dist/fcc/policy-store.d.ts +40 -0
  136. package/dist/fcc/policy-store.js +146 -0
  137. package/dist/fcc/policy-write.d.ts +32 -0
  138. package/dist/fcc/policy-write.js +37 -0
  139. package/dist/hardware/hilink-protocol.d.ts +39 -0
  140. package/dist/hardware/hilink-protocol.js +58 -0
  141. package/dist/hardware/index.d.ts +3 -0
  142. package/dist/hardware/index.js +15 -0
  143. package/dist/hardware/router-parsers.d.ts +89 -0
  144. package/dist/hardware/router-parsers.js +234 -0
  145. package/dist/index.d.ts +19 -0
  146. package/dist/index.js +26 -0
  147. package/dist/journal/codec.d.ts +44 -0
  148. package/dist/journal/codec.js +198 -0
  149. package/dist/journal/engine.d.ts +28 -0
  150. package/dist/journal/engine.js +68 -0
  151. package/dist/journal/entry.d.ts +74 -0
  152. package/dist/journal/entry.js +56 -0
  153. package/dist/journal/index.d.ts +6 -0
  154. package/dist/journal/index.js +6 -0
  155. package/dist/journal/legacy-ceraui.d.ts +73 -0
  156. package/dist/journal/legacy-ceraui.js +227 -0
  157. package/dist/journal/recovery.d.ts +58 -0
  158. package/dist/journal/recovery.js +117 -0
  159. package/dist/journal/store.d.ts +55 -0
  160. package/dist/journal/store.js +150 -0
  161. package/dist/location/fix-state.d.ts +53 -0
  162. package/dist/location/fix-state.js +75 -0
  163. package/dist/location/index.d.ts +2 -0
  164. package/dist/location/index.js +8 -0
  165. package/dist/location/nmea.d.ts +10 -0
  166. package/dist/location/nmea.js +89 -0
  167. package/dist/observations/envelope.d.ts +63 -0
  168. package/dist/observations/envelope.js +77 -0
  169. package/dist/observations/freshness.d.ts +28 -0
  170. package/dist/observations/freshness.js +76 -0
  171. package/dist/observations/index.d.ts +13 -0
  172. package/dist/observations/index.js +24 -0
  173. package/dist/observations/metric.d.ts +47 -0
  174. package/dist/observations/metric.js +73 -0
  175. package/dist/observations/model.d.ts +80 -0
  176. package/dist/observations/model.js +11 -0
  177. package/dist/observations/provenance.d.ts +94 -0
  178. package/dist/observations/provenance.js +67 -0
  179. package/dist/observations/raw.d.ts +42 -0
  180. package/dist/observations/raw.js +146 -0
  181. package/dist/observations/reading.d.ts +51 -0
  182. package/dist/observations/reading.js +65 -0
  183. package/dist/observations/sources/hilink.d.ts +10 -0
  184. package/dist/observations/sources/hilink.js +84 -0
  185. package/dist/observations/sources/modemmanager.d.ts +18 -0
  186. package/dist/observations/sources/modemmanager.js +239 -0
  187. package/dist/observations/sources/router-shared.d.ts +29 -0
  188. package/dist/observations/sources/router-shared.js +68 -0
  189. package/dist/observations/sources/ufi.d.ts +10 -0
  190. package/dist/observations/sources/ufi.js +111 -0
  191. package/dist/observations/sources/zte.d.ts +7 -0
  192. package/dist/observations/sources/zte.js +71 -0
  193. package/dist/observations/state-separation.d.ts +64 -0
  194. package/dist/observations/state-separation.js +52 -0
  195. package/dist/operations/contracts.d.ts +72 -0
  196. package/dist/operations/contracts.js +1 -0
  197. package/dist/operations/index.d.ts +1 -0
  198. package/dist/operations/index.js +1 -0
  199. package/dist/operations/operation-engine.d.ts +16 -0
  200. package/dist/operations/operation-engine.js +194 -0
  201. package/dist/ports/index.d.ts +12 -0
  202. package/{src/ports/index.ts → dist/ports/index.js} +12 -8
  203. package/dist/ports/location.d.ts +87 -0
  204. package/dist/ports/location.js +36 -0
  205. package/dist/ports/modem-manager.d.ts +89 -0
  206. package/dist/ports/modem-manager.js +9 -0
  207. package/dist/ports/mutation-admission.d.ts +27 -0
  208. package/dist/ports/mutation-admission.js +9 -0
  209. package/dist/ports/network-manager.d.ts +68 -0
  210. package/dist/ports/network-manager.js +13 -0
  211. package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
  212. package/dist/ports/observation.js +7 -0
  213. package/dist/ports/ops.d.ts +44 -0
  214. package/dist/ports/ops.js +16 -0
  215. package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
  216. package/dist/ports/receipts.js +9 -0
  217. package/dist/ports/reconcile.d.ts +33 -0
  218. package/dist/ports/reconcile.js +200 -0
  219. package/dist/ports/resource-ownership.d.ts +29 -0
  220. package/dist/ports/resource-ownership.js +1 -0
  221. package/dist/ports/router.d.ts +19 -0
  222. package/dist/ports/router.js +7 -0
  223. package/dist/ports/sms.d.ts +64 -0
  224. package/dist/ports/sms.js +24 -0
  225. package/dist/ports/uhubctl.d.ts +6 -0
  226. package/dist/ports/uhubctl.js +1 -0
  227. package/dist/providers/contracts.d.ts +124 -0
  228. package/dist/providers/contracts.js +10 -0
  229. package/dist/providers/huawei-hilink/index.d.ts +2 -0
  230. package/dist/providers/huawei-hilink/index.js +2 -0
  231. package/dist/providers/huawei-hilink/operations.d.ts +20 -0
  232. package/dist/providers/huawei-hilink/operations.js +56 -0
  233. package/dist/providers/huawei-hilink/provider.d.ts +52 -0
  234. package/dist/providers/huawei-hilink/provider.js +76 -0
  235. package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
  236. package/dist/providers/huawei-hilink/runtime.js +171 -0
  237. package/dist/providers/huawei-hilink/session.d.ts +28 -0
  238. package/dist/providers/huawei-hilink/session.js +120 -0
  239. package/dist/providers/huawei-hilink/transport.d.ts +19 -0
  240. package/dist/providers/huawei-hilink/transport.js +1 -0
  241. package/dist/providers/index.d.ts +8 -0
  242. package/dist/providers/index.js +8 -0
  243. package/dist/providers/matcher.d.ts +3 -0
  244. package/dist/providers/matcher.js +205 -0
  245. package/dist/providers/modem-manager/errors.d.ts +7 -0
  246. package/dist/providers/modem-manager/errors.js +37 -0
  247. package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
  248. package/dist/providers/modem-manager/generic-operations.js +207 -0
  249. package/dist/providers/modem-manager/index.d.ts +3 -0
  250. package/dist/providers/modem-manager/index.js +3 -0
  251. package/dist/providers/modem-manager/module-operations.d.ts +21 -0
  252. package/dist/providers/modem-manager/module-operations.js +118 -0
  253. package/dist/providers/modem-manager/provider.d.ts +39 -0
  254. package/dist/providers/modem-manager/provider.js +152 -0
  255. package/dist/providers/modem-manager/snapshot.d.ts +5 -0
  256. package/dist/providers/modem-manager/snapshot.js +204 -0
  257. package/dist/providers/modem-manager/types.d.ts +135 -0
  258. package/dist/providers/modem-manager/types.js +1 -0
  259. package/dist/providers/network-manager/adapter.d.ts +71 -0
  260. package/dist/providers/network-manager/adapter.js +348 -0
  261. package/dist/providers/network-manager/index.d.ts +2 -0
  262. package/dist/providers/network-manager/index.js +2 -0
  263. package/dist/providers/network-manager/types.d.ts +163 -0
  264. package/dist/providers/network-manager/types.js +77 -0
  265. package/dist/providers/registry.d.ts +13 -0
  266. package/dist/providers/registry.js +33 -0
  267. package/dist/providers/ufi-himi/index.d.ts +6 -0
  268. package/dist/providers/ufi-himi/index.js +6 -0
  269. package/dist/providers/ufi-himi/operations.d.ts +41 -0
  270. package/dist/providers/ufi-himi/operations.js +66 -0
  271. package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
  272. package/dist/providers/ufi-himi/prohibitions.js +88 -0
  273. package/dist/providers/ufi-himi/provider.d.ts +41 -0
  274. package/dist/providers/ufi-himi/provider.js +204 -0
  275. package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
  276. package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
  277. package/dist/providers/ufi-himi/session.d.ts +46 -0
  278. package/dist/providers/ufi-himi/session.js +92 -0
  279. package/dist/providers/ufi-himi/transport.d.ts +29 -0
  280. package/dist/providers/ufi-himi/transport.js +25 -0
  281. package/dist/providers/zte-goform/index.d.ts +2 -0
  282. package/dist/providers/zte-goform/index.js +2 -0
  283. package/dist/providers/zte-goform/provider.d.ts +51 -0
  284. package/dist/providers/zte-goform/provider.js +101 -0
  285. package/dist/providers/zte-goform/session.d.ts +17 -0
  286. package/dist/providers/zte-goform/session.js +133 -0
  287. package/dist/providers/zte-goform/transport.d.ts +16 -0
  288. package/dist/providers/zte-goform/transport.js +1 -0
  289. package/dist/radio/band-truth.d.ts +50 -0
  290. package/dist/radio/band-truth.js +92 -0
  291. package/dist/radio/index.d.ts +3 -0
  292. package/dist/radio/index.js +10 -0
  293. package/dist/radio/mode-combinations.d.ts +78 -0
  294. package/dist/radio/mode-combinations.js +198 -0
  295. package/dist/radio/mode-truth.d.ts +67 -0
  296. package/dist/radio/mode-truth.js +112 -0
  297. package/dist/redact.d.ts +15 -0
  298. package/dist/redact.js +189 -0
  299. package/dist/safety/composition-root.d.ts +28 -0
  300. package/dist/safety/composition-root.js +65 -0
  301. package/dist/safety/flock-resource-ownership.d.ts +11 -0
  302. package/dist/safety/flock-resource-ownership.js +151 -0
  303. package/dist/safety/index.d.ts +2 -0
  304. package/dist/safety/index.js +2 -0
  305. package/dist/sms/dbus-messaging.d.ts +17 -0
  306. package/dist/sms/dbus-messaging.js +185 -0
  307. package/dist/sms/inbox-store.d.ts +10 -0
  308. package/dist/sms/inbox-store.js +82 -0
  309. package/dist/sms/index.d.ts +4 -0
  310. package/dist/sms/index.js +10 -0
  311. package/dist/sms/mmcli-parse.d.ts +54 -0
  312. package/dist/sms/mmcli-parse.js +224 -0
  313. package/dist/sms/normalize.d.ts +42 -0
  314. package/dist/sms/normalize.js +95 -0
  315. package/dist/testing/domain-fakes.d.ts +58 -0
  316. package/dist/testing/domain-fakes.js +98 -0
  317. package/dist/testing/index.d.ts +2 -0
  318. package/dist/testing/index.js +17 -0
  319. package/dist/testing/provider-fakes.d.ts +45 -0
  320. package/dist/testing/provider-fakes.js +64 -0
  321. package/dist/transport/calls.d.ts +9 -0
  322. package/dist/transport/calls.js +88 -0
  323. package/dist/transport/codec.d.ts +3 -0
  324. package/dist/transport/codec.js +207 -0
  325. package/dist/transport/dbus-native.d.ts +57 -0
  326. package/dist/transport/dbus-native.js +17 -0
  327. package/dist/transport/errors.d.ts +21 -0
  328. package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
  329. package/dist/transport/index.d.ts +4 -0
  330. package/dist/transport/index.js +9 -0
  331. package/dist/transport/signals.d.ts +15 -0
  332. package/dist/transport/signals.js +123 -0
  333. package/dist/transport/signature.d.ts +7 -0
  334. package/dist/transport/signature.js +94 -0
  335. package/dist/transport/transport.d.ts +2 -0
  336. package/dist/transport/transport.js +202 -0
  337. package/dist/transport/types.d.ts +61 -0
  338. package/dist/transport/types.js +19 -0
  339. package/dist/usb-mode/catalog-schema.d.ts +139 -0
  340. package/dist/usb-mode/catalog-schema.js +97 -0
  341. package/dist/usb-mode/catalog.d.ts +21 -0
  342. package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
  343. package/dist/usb-mode/certified-catalog.json +67 -0
  344. package/dist/usb-mode/index.d.ts +5 -0
  345. package/dist/usb-mode/index.js +15 -0
  346. package/dist/usb-mode/ingestion.d.ts +111 -0
  347. package/dist/usb-mode/ingestion.js +187 -0
  348. package/dist/usb-mode/promotion-review.d.ts +21 -0
  349. package/dist/usb-mode/promotion-review.js +87 -0
  350. package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
  351. package/dist/usb-mode/usb-devices-parse.js +157 -0
  352. package/dist/ussd/calls.d.ts +32 -0
  353. package/dist/ussd/calls.js +96 -0
  354. package/dist/ussd/index.d.ts +5 -0
  355. package/dist/ussd/index.js +12 -0
  356. package/dist/ussd/mm-ussd.d.ts +37 -0
  357. package/dist/ussd/mm-ussd.js +205 -0
  358. package/dist/ussd/refusal.d.ts +53 -0
  359. package/dist/ussd/refusal.js +154 -0
  360. package/dist/ussd/registration.d.ts +20 -0
  361. package/dist/ussd/registration.js +101 -0
  362. package/dist/ussd/session.d.ts +84 -0
  363. package/dist/ussd/session.js +163 -0
  364. package/package.json +38 -4
  365. package/src/backend/at-lease.test.ts +0 -106
  366. package/src/backend/at-lease.ts +0 -158
  367. package/src/backend/cell-info.test.ts +0 -154
  368. package/src/backend/cell-info.ts +0 -160
  369. package/src/backend/device-classifier.test.ts +0 -168
  370. package/src/backend/device-classifier.ts +0 -248
  371. package/src/backend/enrichment.ts +0 -96
  372. package/src/backend/features.test.ts +0 -162
  373. package/src/backend/features.ts +0 -179
  374. package/src/backend/identity-ladder.test.ts +0 -117
  375. package/src/backend/identity-ladder.ts +0 -221
  376. package/src/backend/identity-registry.test.ts +0 -89
  377. package/src/backend/identity-registry.ts +0 -151
  378. package/src/backend/index.ts +0 -221
  379. package/src/backend/lifecycle-interlock.ts +0 -38
  380. package/src/backend/managed-objects.ts +0 -108
  381. package/src/backend/mapping.ts +0 -160
  382. package/src/backend/mm-backend.ts +0 -191
  383. package/src/backend/mm-mutations.ts +0 -228
  384. package/src/backend/modem-actor.test.ts +0 -95
  385. package/src/backend/modem-actor.ts +0 -112
  386. package/src/backend/nm-auto-apn.ts +0 -161
  387. package/src/backend/nm-gsm-fields.ts +0 -122
  388. package/src/backend/nmcli-nm-port.ts +0 -228
  389. package/src/backend/nmcli-runner.ts +0 -52
  390. package/src/backend/observer.ts +0 -297
  391. package/src/backend/power-contract.test.ts +0 -40
  392. package/src/backend/power-contract.ts +0 -83
  393. package/src/backend/recovery-attribution.test.ts +0 -102
  394. package/src/backend/recovery-attribution.ts +0 -86
  395. package/src/backend/recovery-budget.test.ts +0 -64
  396. package/src/backend/recovery-budget.ts +0 -84
  397. package/src/backend/recovery-ladder.test.ts +0 -257
  398. package/src/backend/recovery-ladder.ts +0 -249
  399. package/src/backend/router-ethernet.test.ts +0 -71
  400. package/src/backend/router-ethernet.ts +0 -90
  401. package/src/backend/row-store.ts +0 -105
  402. package/src/backend/signal-setup.ts +0 -112
  403. package/src/backend/sim-unlock.ts +0 -193
  404. package/src/backend/transition-preconditions.ts +0 -149
  405. package/src/backend/usage/accounting.test.ts +0 -147
  406. package/src/backend/usage/accounting.ts +0 -123
  407. package/src/backend/usage/billing-cycle.test.ts +0 -62
  408. package/src/backend/usage/billing-cycle.ts +0 -45
  409. package/src/backend/usage/index.ts +0 -37
  410. package/src/backend/usage/proc-net-dev.test.ts +0 -56
  411. package/src/backend/usage/sampler.test.ts +0 -219
  412. package/src/backend/usage/sampler.ts +0 -228
  413. package/src/backend/usage/store.test.ts +0 -148
  414. package/src/backend/usage/store.ts +0 -177
  415. package/src/backend/usb-enumerator.test.ts +0 -87
  416. package/src/backend/usb-enumerator.ts +0 -181
  417. package/src/backend/usb-mode-transition.test.ts +0 -323
  418. package/src/backend/usb-mode-transition.ts +0 -253
  419. package/src/domain/brand.ts +0 -29
  420. package/src/domain/errors.ts +0 -77
  421. package/src/domain/guards.test.ts +0 -218
  422. package/src/domain/guards.ts +0 -144
  423. package/src/domain/identity.test.ts +0 -83
  424. package/src/domain/identity.ts +0 -165
  425. package/src/domain/index.ts +0 -12
  426. package/src/domain/snapshot.test.ts +0 -266
  427. package/src/domain/snapshot.ts +0 -120
  428. package/src/index.test.ts +0 -6
  429. package/src/index.ts +0 -15
  430. package/src/ports/README.md +0 -61
  431. package/src/ports/forbidden-surface.test.ts +0 -241
  432. package/src/ports/modem-manager.ts +0 -72
  433. package/src/ports/network-manager.ts +0 -87
  434. package/src/ports/ops.ts +0 -60
  435. package/src/ports/ops.type-test.ts +0 -39
  436. package/src/ports/receipts.test.ts +0 -153
  437. package/src/ports/reconcile.test.ts +0 -152
  438. package/src/ports/reconcile.ts +0 -338
  439. package/src/ports/router.ts +0 -29
  440. package/src/redact.test.ts +0 -82
  441. package/src/redact.ts +0 -73
  442. package/src/transport/README.md +0 -65
  443. package/src/transport/calls.ts +0 -113
  444. package/src/transport/characterization.test.ts +0 -260
  445. package/src/transport/codec.test.ts +0 -118
  446. package/src/transport/codec.ts +0 -240
  447. package/src/transport/conformance-python.test.ts +0 -152
  448. package/src/transport/conformance-same-lib.test.ts +0 -115
  449. package/src/transport/dbus-native-lib.d.ts +0 -19
  450. package/src/transport/dbus-native.ts +0 -85
  451. package/src/transport/index.ts +0 -30
  452. package/src/transport/no-library-leak.test.ts +0 -61
  453. package/src/transport/reliability.test.ts +0 -173
  454. package/src/transport/signals.ts +0 -150
  455. package/src/transport/signature.ts +0 -110
  456. package/src/transport/test-support/fake-service.ts +0 -168
  457. package/src/transport/test-support/independent-producer.py +0 -110
  458. package/src/transport/test-support/private-bus.ts +0 -66
  459. package/src/transport/transport.ts +0 -250
  460. package/src/transport/types.ts +0 -118
  461. package/src/usb-mode/catalog-schema.test.ts +0 -181
  462. package/src/usb-mode/catalog-schema.ts +0 -113
  463. package/src/usb-mode/certified-catalog.json +0 -67
  464. package/src/usb-mode/index.ts +0 -27
package/dist/redact.js ADDED
@@ -0,0 +1,189 @@
1
+ // Redaction — strip sensitive identifiers from any value before it is logged,
2
+ // serialized into a receipt, or written to a bundle.
3
+ //
4
+ // The sensitive CLASSES (draft §Oracle #1, round-5 auth semantics): ICCID, IMSI,
5
+ // EID, SIM PIN, SIM PUK, and APN / connection passwords, PLUS the GNSS coordinate
6
+ // class below. Redaction is KEY-BASED and RECURSIVE: it walks nested objects and
7
+ // arrays and replaces the value under any sensitive key with a fixed marker, no
8
+ // matter how deep — e.g. a password at `policy.connection.auth.password`, or an
9
+ // `iccid` inside an array of SIM slots.
10
+ /** The marker substituted for every redacted value. */
11
+ export const REDACTED = '[redacted]';
12
+ // Leaf key names carrying a sensitive value, matched case-insensitively. The match
13
+ // is EXACT (or exact on the last dotted segment), so NM-style keys like
14
+ // `gsm.password` are caught while non-secret siblings like `gsm.password-flags`
15
+ // (a "0"/"4" flag, not a secret) are not.
16
+ const SENSITIVE_KEYS = new Set([
17
+ 'iccid',
18
+ 'imsi',
19
+ 'eid',
20
+ 'imei',
21
+ 'equipmentidentifier',
22
+ 'equipment-identifier',
23
+ 'equipment_identifier',
24
+ 'pin',
25
+ 'pin2',
26
+ 'newpin',
27
+ 'puk',
28
+ 'puk2',
29
+ 'password',
30
+ 'passwd',
31
+ 'subscriptionid',
32
+ ]);
33
+ // GNSS coordinate keys. A fix says where the operator physically is, so it is
34
+ // sensitive for a reason none of the keys above share, and it gets its own set so
35
+ // the privacy fence stays readable: the GPS module keeps a fix in memory for a
36
+ // live display and NEVER persists, uploads, or logs one.
37
+ //
38
+ // Scoped to a GNSS fix on purpose. `3gpp-lac-ci` (coarse cell location) is NOT
39
+ // here — it is the existing cell-info module's deliberate, separately-gated output,
40
+ // and silently blanking it would break a surface that already ships.
41
+ const LOCATION_KEYS = new Set([
42
+ 'latitude',
43
+ 'longitude',
44
+ 'altitude',
45
+ 'lat',
46
+ 'lon',
47
+ 'lng',
48
+ 'nmea',
49
+ 'nmeasentences',
50
+ 'coordinates',
51
+ ]);
52
+ /**
53
+ * SMS content is its own key class, and it may NOT be folded into
54
+ * `SENSITIVE_KEYS` above. That set matches a leaf name exactly (or the last
55
+ * dotted segment), so adding `text` / `number` / `sender` to it would redact
56
+ * every unrelated `text` and `number` in the package — a receipt's reason text,
57
+ * a slot number, a signal reading. These keys are matched WHOLE after case- AND
58
+ * separator-folding, so only a key that genuinely names message content or an
59
+ * originator is scrubbed.
60
+ *
61
+ * A message body routinely carries a one-time code (the bench SIM's inbox holds
62
+ * a literal "Tu pin es: …") and a sender number identifies the subscriber, so
63
+ * both are treated exactly like a PIN: never rendered anywhere.
64
+ *
65
+ * This mirrors CeraUI's `isSmsSensitiveKey` (`helpers/logger.ts`) key-for-key.
66
+ * It is a Rule-D MIRROR, never a shared import — the two halves are kept honest
67
+ * by their tests, not by a path.
68
+ */
69
+ const SMS_SENSITIVE_KEYS = new Set([
70
+ 'smstext',
71
+ 'smsbody',
72
+ 'smsfrom',
73
+ 'smssender',
74
+ 'smsnumber',
75
+ 'messagetext',
76
+ 'messagebody',
77
+ 'msisdn',
78
+ 'sender',
79
+ 'sms.content.text',
80
+ 'sms.content.number',
81
+ ]);
82
+ /** Whole-key SMS-content test, case- and separator-insensitive. */
83
+ export function isSmsSensitiveKey(key) {
84
+ return SMS_SENSITIVE_KEYS.has(key.toLowerCase().replace(/[_-]/g, ''));
85
+ }
86
+ /**
87
+ * USSD carrier text — its own class, for the same reason SMS is: `reply`,
88
+ * `command`, and `response` are far too common to redact by leaf name, so these
89
+ * are matched WHOLE after case- and separator-folding.
90
+ *
91
+ * BOTH DIRECTIONS are sensitive, not just the reply. A USSD dialogue is how a
92
+ * subscriber tops up a prepaid line, so the COMMAND routinely carries a voucher
93
+ * code (`*123*<16 digits>#`) and the reply carries a balance, a subscriber
94
+ * number, or a one-time code. `NetworkNotification` / `NetworkRequest` are
95
+ * ModemManager's own property names for network-initiated USSD text and are
96
+ * included so a raw property dump cannot leak what the call path masks.
97
+ *
98
+ * This is why the USSD module names its carrier-text fields `ussdCommand`,
99
+ * `ussdResponse`, and `ussdReply` rather than the shorter names that read better:
100
+ * redaction here is key-based, so the FIELD NAME is the guarantee.
101
+ */
102
+ const USSD_SENSITIVE_KEYS = new Set([
103
+ 'ussd',
104
+ 'ussdcommand',
105
+ 'ussdreply',
106
+ 'ussdresponse',
107
+ 'ussdtext',
108
+ 'networknotification',
109
+ 'networkrequest',
110
+ ]);
111
+ /** Whole-key USSD-content test, case- and separator-insensitive. */
112
+ export function isUssdSensitiveKey(key) {
113
+ return USSD_SENSITIVE_KEYS.has(key.toLowerCase().replace(/[_-]/g, ''));
114
+ }
115
+ /**
116
+ * The SIM's OWN number (MSISDN) — its own class, matched WHOLE after case-,
117
+ * separator- AND dot-folding so ModemManager's `Modem.OwnNumbers` and mmcli's
118
+ * `modem.generic.own-numbers` are both caught by one rule.
119
+ *
120
+ * It cannot join `SENSITIVE_KEYS`: that set matches a leaf name, and `number` /
121
+ * `numbers` are far too common — a slot number and a band count would both
122
+ * vanish. `msisdn` stays in the SMS set (its historical home) and is not
123
+ * duplicated here.
124
+ *
125
+ * It is DISPLAYED to the operator behind an explicit reveal. That is a
126
+ * rendering decision about a surface the subscriber already owns; it does not
127
+ * make the value loggable, so it is redacted exactly like a PIN.
128
+ */
129
+ const OWN_NUMBER_SENSITIVE_KEYS = new Set([
130
+ 'ownnumber',
131
+ 'ownnumbers',
132
+ 'phonenumber',
133
+ 'phonenumbers',
134
+ 'simnumber',
135
+ 'subscribernumber',
136
+ 'modemgenericownnumbers',
137
+ 'modemownnumbers',
138
+ ]);
139
+ /** Whole-key own-number test, case-, separator- and dot-insensitive. */
140
+ export function isOwnNumberSensitiveKey(key) {
141
+ return OWN_NUMBER_SENSITIVE_KEYS.has(key.toLowerCase().replace(/[_.-]/g, ''));
142
+ }
143
+ function isSensitiveKey(key) {
144
+ const lower = key.toLowerCase();
145
+ if (SENSITIVE_KEYS.has(lower) || LOCATION_KEYS.has(lower)) {
146
+ return true;
147
+ }
148
+ if (isSmsSensitiveKey(key) || isUssdSensitiveKey(key) || isOwnNumberSensitiveKey(key)) {
149
+ return true;
150
+ }
151
+ const dot = lower.lastIndexOf('.');
152
+ if (dot < 0) {
153
+ return false;
154
+ }
155
+ const leaf = lower.slice(dot + 1);
156
+ return SENSITIVE_KEYS.has(leaf) || LOCATION_KEYS.has(leaf);
157
+ }
158
+ function isPlainObject(value) {
159
+ if (typeof value !== 'object' || value === null) {
160
+ return false;
161
+ }
162
+ const proto = Object.getPrototypeOf(value);
163
+ return proto === Object.prototype || proto === null;
164
+ }
165
+ function redactValue(value, underSensitiveKey) {
166
+ if (underSensitiveKey) {
167
+ return REDACTED;
168
+ }
169
+ if (Array.isArray(value)) {
170
+ return value.map((item) => redactValue(item, false));
171
+ }
172
+ if (isPlainObject(value)) {
173
+ const out = {};
174
+ for (const [key, child] of Object.entries(value)) {
175
+ out[key] = redactValue(child, isSensitiveKey(key));
176
+ }
177
+ return out;
178
+ }
179
+ return value;
180
+ }
181
+ /**
182
+ * Return a deep copy of `value` with every sensitive field redacted. Plain objects
183
+ * and arrays are walked recursively; all other values (primitives, and opaque
184
+ * objects like `Date` / `Set` / `Map`) are returned unchanged. The input is never
185
+ * mutated.
186
+ */
187
+ export function redact(value) {
188
+ return redactValue(value, false);
189
+ }
@@ -0,0 +1,28 @@
1
+ import { ModemActor, type QuiesceHook } from '../backend/modem-actor.js';
2
+ import type { PhysicalModemId } from '../domain/index.js';
3
+ import type { MutationAdmissionPort, ResourceOwnershipPort, ResourceOwnershipRequest, ResourceOwnershipResult, UhubctlPort } from '../ports/index.js';
4
+ export declare class CompositionRootAlreadyExistsError extends Error {
5
+ readonly name = "CompositionRootAlreadyExistsError";
6
+ constructor();
7
+ }
8
+ export type ModemControlCompositionRootOptions = {
9
+ readonly admission: MutationAdmissionPort;
10
+ readonly ownership: ResourceOwnershipPort | undefined;
11
+ readonly quiesce?: QuiesceHook;
12
+ readonly uhubctl?: UhubctlPort;
13
+ };
14
+ export declare class ModemControlCompositionRoot {
15
+ #private;
16
+ readonly admission: MutationAdmissionPort;
17
+ readonly ownership: ResourceOwnershipPort;
18
+ readonly uhubctl: UhubctlPort | undefined;
19
+ constructor(options: ModemControlCompositionRootOptions);
20
+ actorFor(modemId: PhysicalModemId): ModemActor;
21
+ acquireOwnership(request: ResourceOwnershipRequest): Promise<ResourceOwnershipResult>;
22
+ dispose(): Promise<void>;
23
+ }
24
+ export declare class MissingResourceOwnershipPortError extends Error {
25
+ readonly name = "MissingResourceOwnershipPortError";
26
+ constructor();
27
+ }
28
+ export declare function createModemControlCompositionRoot(options: ModemControlCompositionRootOptions): ModemControlCompositionRoot;
@@ -0,0 +1,65 @@
1
+ import { ModemActor } from '../backend/modem-actor.js';
2
+ let compositionRootExists = false;
3
+ export class CompositionRootAlreadyExistsError extends Error {
4
+ name = 'CompositionRootAlreadyExistsError';
5
+ constructor() {
6
+ super('a modem-control composition root already exists in this process');
7
+ }
8
+ }
9
+ export class ModemControlCompositionRoot {
10
+ admission;
11
+ ownership;
12
+ uhubctl;
13
+ #quiesce;
14
+ #actors = new Map();
15
+ #ownershipLeases = [];
16
+ #disposed = false;
17
+ constructor(options) {
18
+ if (compositionRootExists)
19
+ throw new CompositionRootAlreadyExistsError();
20
+ if (options.ownership === undefined)
21
+ throw new MissingResourceOwnershipPortError();
22
+ compositionRootExists = true;
23
+ this.admission = options.admission;
24
+ this.ownership = options.ownership;
25
+ this.#quiesce = options.quiesce;
26
+ this.uhubctl = options.uhubctl;
27
+ }
28
+ actorFor(modemId) {
29
+ const existing = this.#actors.get(modemId);
30
+ if (existing !== undefined)
31
+ return existing;
32
+ const actor = new ModemActor(this.#quiesce);
33
+ this.#actors.set(modemId, actor);
34
+ return actor;
35
+ }
36
+ async acquireOwnership(request) {
37
+ const result = await this.ownership.acquire(request);
38
+ if (result.status === 'acquired')
39
+ this.#ownershipLeases.push(result.lease);
40
+ return result;
41
+ }
42
+ async dispose() {
43
+ if (this.#disposed)
44
+ return;
45
+ this.#disposed = true;
46
+ try {
47
+ for (const lease of this.#ownershipLeases.splice(0).reverse()) {
48
+ await lease.release();
49
+ }
50
+ }
51
+ finally {
52
+ this.#actors.clear();
53
+ compositionRootExists = false;
54
+ }
55
+ }
56
+ }
57
+ export class MissingResourceOwnershipPortError extends Error {
58
+ name = 'MissingResourceOwnershipPortError';
59
+ constructor() {
60
+ super('a ResourceOwnershipPort is required; no pass-through default exists');
61
+ }
62
+ }
63
+ export function createModemControlCompositionRoot(options) {
64
+ return new ModemControlCompositionRoot(options);
65
+ }
@@ -0,0 +1,11 @@
1
+ import type { ResourceOwnershipPort } from '../ports/index.js';
2
+ export type FlockResourceOwnershipOptions = {
3
+ readonly lockPath: string;
4
+ readonly flockBinary?: string;
5
+ };
6
+ export declare class FlockResourceOwnershipError extends Error {
7
+ readonly detail: string;
8
+ readonly name = "FlockResourceOwnershipError";
9
+ constructor(detail: string);
10
+ }
11
+ export declare function createFlockResourceOwnershipPort(options: FlockResourceOwnershipOptions): ResourceOwnershipPort;
@@ -0,0 +1,151 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readFile } from 'node:fs/promises';
3
+ const LOCK_HELPER = String.raw `
4
+ import { writeFileSync } from 'node:fs';
5
+ const lockPath = process.env.CERALIVE_MODEM_CONTROL_LOCK_PATH;
6
+ if (!lockPath) process.exit(64);
7
+ const holder = { pid: process.pid, startedAtEpochMs: Date.now() };
8
+ writeFileSync(lockPath, JSON.stringify(holder) + '\n', { mode: 0o600 });
9
+ process.stdout.write(JSON.stringify({ type: 'acquired', holder }) + '\n');
10
+ process.stdin.resume();
11
+ `;
12
+ export class FlockResourceOwnershipError extends Error {
13
+ detail;
14
+ name = 'FlockResourceOwnershipError';
15
+ constructor(detail) {
16
+ super(`flock ownership failed: ${detail}`);
17
+ this.detail = detail;
18
+ }
19
+ }
20
+ export function createFlockResourceOwnershipPort(options) {
21
+ return {
22
+ async acquire() {
23
+ const child = spawn(options.flockBinary ?? 'flock', [
24
+ '--exclusive',
25
+ '--nonblock',
26
+ '--no-fork',
27
+ options.lockPath,
28
+ process.execPath,
29
+ '-e',
30
+ LOCK_HELPER,
31
+ ], {
32
+ env: {
33
+ ...process.env,
34
+ CERALIVE_MODEM_CONTROL_LOCK_PATH: options.lockPath,
35
+ },
36
+ stdio: ['pipe', 'pipe', 'pipe'],
37
+ });
38
+ const closed = childClosed(child);
39
+ const started = await childStarted(child);
40
+ if (started.status === 'closed') {
41
+ if (started.code === 1) {
42
+ const holder = await readHolder(options.lockPath);
43
+ return {
44
+ status: 'refused',
45
+ reason: 'already-owned',
46
+ ...(holder !== undefined && pidIsAlive(holder.pid) ? { holder } : {}),
47
+ };
48
+ }
49
+ throw new FlockResourceOwnershipError(`helper exited ${started.code ?? 'by signal'}${started.stderr ? `: ${started.stderr}` : ''}`);
50
+ }
51
+ return {
52
+ status: 'acquired',
53
+ lease: createLease(child, closed, started.holder),
54
+ };
55
+ },
56
+ };
57
+ }
58
+ function createLease(child, closed, holder) {
59
+ let released = false;
60
+ return {
61
+ holder,
62
+ lost: closed.then(() => ({ reason: 'holder-exited' })),
63
+ async release() {
64
+ if (released)
65
+ return;
66
+ released = true;
67
+ if (child.exitCode === null && child.signalCode === null) {
68
+ child.stdin.end();
69
+ }
70
+ await closed;
71
+ },
72
+ };
73
+ }
74
+ function childClosed(child) {
75
+ return new Promise((resolve) => child.once('close', () => resolve()));
76
+ }
77
+ function childStarted(child) {
78
+ return new Promise((resolve) => {
79
+ let stdout = '';
80
+ let stderr = '';
81
+ let settled = false;
82
+ const finish = (result) => {
83
+ if (settled)
84
+ return;
85
+ settled = true;
86
+ resolve(result);
87
+ };
88
+ child.stderr.on('data', (chunk) => {
89
+ stderr += chunk.toString('utf8');
90
+ });
91
+ child.stdout.on('data', (chunk) => {
92
+ stdout += chunk.toString('utf8');
93
+ const newline = stdout.indexOf('\n');
94
+ if (newline < 0)
95
+ return;
96
+ const holder = parseAcquiredLine(stdout.slice(0, newline));
97
+ if (holder !== undefined)
98
+ finish({ status: 'acquired', holder });
99
+ });
100
+ child.once('close', (code) => finish({ status: 'closed', code, stderr: stderr.trim() }));
101
+ });
102
+ }
103
+ function parseAcquiredLine(line) {
104
+ try {
105
+ const parsed = JSON.parse(line);
106
+ if (typeof parsed !== 'object' || parsed === null || !('holder' in parsed))
107
+ return undefined;
108
+ return parseHolder(parsed.holder);
109
+ }
110
+ catch (error) {
111
+ if (error instanceof SyntaxError)
112
+ return undefined;
113
+ throw error;
114
+ }
115
+ }
116
+ async function readHolder(lockPath) {
117
+ try {
118
+ return parseHolder(JSON.parse(await readFile(lockPath, 'utf8')));
119
+ }
120
+ catch (error) {
121
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
122
+ return undefined;
123
+ if (error instanceof SyntaxError)
124
+ return undefined;
125
+ throw error;
126
+ }
127
+ }
128
+ function parseHolder(value) {
129
+ if (typeof value !== 'object' || value === null)
130
+ return undefined;
131
+ if (!('pid' in value) || !('startedAtEpochMs' in value))
132
+ return undefined;
133
+ if (typeof value.pid !== 'number' || !Number.isSafeInteger(value.pid) || value.pid <= 0) {
134
+ return undefined;
135
+ }
136
+ if (typeof value.startedAtEpochMs !== 'number' || !Number.isFinite(value.startedAtEpochMs)) {
137
+ return undefined;
138
+ }
139
+ return { pid: value.pid, startedAtEpochMs: value.startedAtEpochMs };
140
+ }
141
+ function pidIsAlive(pid) {
142
+ try {
143
+ process.kill(pid, 0);
144
+ return true;
145
+ }
146
+ catch (error) {
147
+ if (error instanceof Error && 'code' in error && error.code === 'ESRCH')
148
+ return false;
149
+ return true;
150
+ }
151
+ }
@@ -0,0 +1,2 @@
1
+ export * from './composition-root.js';
2
+ export * from './flock-resource-ownership.js';
@@ -0,0 +1,2 @@
1
+ export * from './composition-root.js';
2
+ export * from './flock-resource-ownership.js';
@@ -0,0 +1,17 @@
1
+ import { type DecodedProps } from '../backend/managed-objects.js';
2
+ import type { SmsMessage, SmsObservationPort } from '../ports/sms.js';
3
+ import type { DbusTransport } from '../transport/index.js';
4
+ /** `org.freedesktop.ModemManager1.Sms` — one message object. */
5
+ export declare const SMS_IFACE = "org.freedesktop.ModemManager1.Sms";
6
+ /** Build one message from an SMS object's decoded `Sms` properties. */
7
+ export declare function smsFromProps(path: string, props: DecodedProps | undefined): SmsMessage;
8
+ export interface DbusSmsPortOptions {
9
+ readonly transport: DbusTransport;
10
+ /** The modem's ModemManager object path. */
11
+ readonly modemPath: string;
12
+ readonly destination?: string;
13
+ readonly cap?: number;
14
+ /** Called when a fold-time error must be surfaced without failing a read. */
15
+ readonly onError?: (error: unknown) => void;
16
+ }
17
+ export declare function createDbusSmsPort(options: DbusSmsPortOptions): SmsObservationPort;
@@ -0,0 +1,185 @@
1
+ // The ModemManager Messaging adapter — LIST ONCE, then follow Added / Deleted.
2
+ //
3
+ // Three verbs, all reads: `Messaging.List` for the object paths,
4
+ // `Properties.GetAll` per message, and a subscription to `Added` / `Deleted`.
5
+ // There is no `Create`, no `Send`, no `Delete`, and none may be added — see
6
+ // `../ports/sms.ts` for why that is permanent rather than staged, and
7
+ // `readonly-gate.test.ts` for the lock.
8
+ //
9
+ // NEVER RE-LIST ON A TICK. MM tells us about every change, so a poll would cost
10
+ // one method call per stored message per tick and still could not report an
11
+ // arrival sooner than the tick it happened to land on. The ONLY re-list is on a
12
+ // source RECONNECT, where the events that occurred while the bus was down were
13
+ // never delivered and folding is therefore impossible; that re-list is published
14
+ // as `resynced` so the consumer replaces its rows instead of merging (see
15
+ // `inbox-store.ts`).
16
+ //
17
+ // A MESSAGE THAT VANISHES BETWEEN THE LIST AND ITS READ IS ORDINARY. The modem's
18
+ // own storage rotates, so a failed per-message read SKIPS that message rather
19
+ // than failing the whole inbox. A failed LIST is different — nothing was read at
20
+ // all — and answers a typed refusal.
21
+ import { MM_BUS_NAME, PROPERTIES_IFACE } from '../backend/constants.js';
22
+ import { numberProp, stringProp } from '../backend/managed-objects.js';
23
+ // The one definition of MM's Messaging interface name lives with the capability
24
+ // probe that also keys on it; duplicating it here is how the two drift apart.
25
+ import { MESSAGING_IFACE } from '../capability/detect.js';
26
+ import { classifySmsFailure } from './mmcli-parse.js';
27
+ import { normalizeSmsState, SMS_INBOX_CAP, selectReadablePaths, sortAndCapSms } from './normalize.js';
28
+ /** `org.freedesktop.ModemManager1.Sms` — one message object. */
29
+ export const SMS_IFACE = 'org.freedesktop.ModemManager1.Sms';
30
+ /**
31
+ * `MMSmsState` (ModemManager `mm-enums.h`) as the port's own vocabulary. The
32
+ * index IS the enum value; an out-of-range value folds to `unknown` through
33
+ * {@link normalizeSmsState}, so a future MM state can never surface as a raw
34
+ * number an operator cannot act on.
35
+ */
36
+ const SMS_STATE_BY_ORDINAL = [
37
+ 'unknown',
38
+ 'stored',
39
+ 'receiving',
40
+ 'received',
41
+ 'sending',
42
+ 'sent',
43
+ ];
44
+ function stateFromOrdinal(ordinal) {
45
+ if (ordinal === undefined) {
46
+ return 'unknown';
47
+ }
48
+ return normalizeSmsState(SMS_STATE_BY_ORDINAL[ordinal]);
49
+ }
50
+ /** Build one message from an SMS object's decoded `Sms` properties. */
51
+ export function smsFromProps(path, props) {
52
+ const from = stringProp(props, 'Number');
53
+ const timestamp = stringProp(props, 'Timestamp');
54
+ const index = path.slice(path.lastIndexOf('/') + 1);
55
+ return {
56
+ id: index,
57
+ ...(from !== undefined && from !== '' ? { from } : {}),
58
+ ...(timestamp !== undefined && timestamp !== '' ? { timestamp } : {}),
59
+ // A data-only (WAP/PDU) message carries no text at all; `''` says so.
60
+ text: stringProp(props, 'Text') ?? '',
61
+ state: stateFromOrdinal(numberProp(props, 'State')),
62
+ };
63
+ }
64
+ function describe(error) {
65
+ return error instanceof Error ? error.message : String(error);
66
+ }
67
+ export function createDbusSmsPort(options) {
68
+ const { transport, modemPath } = options;
69
+ const destination = options.destination ?? MM_BUS_NAME;
70
+ const cap = options.cap ?? SMS_INBOX_CAP;
71
+ const listeners = new Set();
72
+ const subscriptions = [];
73
+ let wiring;
74
+ let stopped = false;
75
+ const emit = (event) => {
76
+ for (const listener of listeners) {
77
+ try {
78
+ listener(event);
79
+ }
80
+ catch (error) {
81
+ options.onError?.(error);
82
+ }
83
+ }
84
+ };
85
+ const readMessage = async (path) => {
86
+ try {
87
+ const reply = await transport.callMethod({
88
+ destination,
89
+ path,
90
+ interface: PROPERTIES_IFACE,
91
+ member: 'GetAll',
92
+ signature: 's',
93
+ args: [SMS_IFACE],
94
+ });
95
+ return smsFromProps(path, reply.body[0]);
96
+ }
97
+ catch {
98
+ // Vanished between the list and this read — ordinary storage rotation.
99
+ // Nothing is logged: the error text is the only place a body could hide.
100
+ return undefined;
101
+ }
102
+ };
103
+ const listInbox = async () => {
104
+ let paths;
105
+ try {
106
+ const reply = await transport.callMethod({
107
+ destination,
108
+ path: modemPath,
109
+ interface: MESSAGING_IFACE,
110
+ member: 'List',
111
+ });
112
+ paths = Array.isArray(reply.body[0]) ? reply.body[0] : [];
113
+ }
114
+ catch (error) {
115
+ return { ok: false, reason: classifySmsFailure(describe(error)) };
116
+ }
117
+ const candidates = selectReadablePaths(paths.filter((value) => typeof value === 'string'), cap);
118
+ const messages = [];
119
+ for (const path of candidates) {
120
+ const message = await readMessage(path);
121
+ if (message !== undefined) {
122
+ messages.push(message);
123
+ }
124
+ }
125
+ return { ok: true, messages: sortAndCapSms(messages, cap) };
126
+ };
127
+ const resync = async () => {
128
+ const result = await listInbox();
129
+ if (result.ok) {
130
+ emit({ kind: 'resynced', messages: result.messages });
131
+ }
132
+ };
133
+ const onAdded = async (body) => {
134
+ const path = body[0];
135
+ if (typeof path !== 'string') {
136
+ return;
137
+ }
138
+ const message = await readMessage(path);
139
+ if (message !== undefined) {
140
+ emit({ kind: 'added', message });
141
+ }
142
+ };
143
+ const wire = async () => {
144
+ const added = await transport.subscribeSignal({ interface: MESSAGING_IFACE, member: 'Added', path: modemPath }, (event) => {
145
+ void onAdded(event.body).catch((error) => options.onError?.(error));
146
+ });
147
+ const deleted = await transport.subscribeSignal({ interface: MESSAGING_IFACE, member: 'Deleted', path: modemPath }, (event) => {
148
+ const path = event.body[0];
149
+ if (typeof path === 'string') {
150
+ emit({ kind: 'deleted', id: path.slice(path.lastIndexOf('/') + 1) });
151
+ }
152
+ });
153
+ subscriptions.push(added, deleted);
154
+ transport.on('reconnected', reconnected);
155
+ // A subscription established AFTER the caller's own `list()` can have
156
+ // missed an arrival in between, so the wiring ends with one resync.
157
+ await resync();
158
+ };
159
+ const reconnected = () => {
160
+ void resync().catch((error) => options.onError?.(error));
161
+ };
162
+ return {
163
+ list: listInbox,
164
+ observe(listener) {
165
+ listeners.add(listener);
166
+ if (wiring === undefined && !stopped) {
167
+ wiring = wire().catch((error) => {
168
+ options.onError?.(error);
169
+ });
170
+ }
171
+ return () => {
172
+ listeners.delete(listener);
173
+ };
174
+ },
175
+ async stop() {
176
+ stopped = true;
177
+ listeners.clear();
178
+ transport.off('reconnected', reconnected);
179
+ await wiring?.catch(() => undefined);
180
+ wiring = undefined;
181
+ const held = subscriptions.splice(0, subscriptions.length);
182
+ await Promise.all(held.map((subscription) => subscription.unsubscribe()));
183
+ },
184
+ };
185
+ }
@@ -0,0 +1,10 @@
1
+ import type { SmsInboxEvent, SmsMessage } from '../ports/sms.js';
2
+ export interface SmsInboxStore {
3
+ /** Apply one event. Answers whether the visible inbox actually changed. */
4
+ apply(event: SmsInboxEvent): boolean;
5
+ /** The current inbox, newest-first and capped. */
6
+ snapshot(): SmsMessage[];
7
+ /** Number of retained rows, before the cap is applied. */
8
+ size(): number;
9
+ }
10
+ export declare function createSmsInboxStore(cap?: number): SmsInboxStore;