@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.
- package/README.md +317 -0
- package/dist/backend/at-lease.d.ts +57 -0
- package/dist/backend/at-lease.js +109 -0
- package/dist/backend/cell-info.d.ts +46 -0
- package/dist/backend/cell-info.js +124 -0
- package/dist/backend/constants.d.ts +24 -0
- package/{src/backend/constants.ts → dist/backend/constants.js} +4 -9
- package/dist/backend/device-classifier.d.ts +46 -0
- package/dist/backend/device-classifier.js +258 -0
- package/dist/backend/enrichment.d.ts +28 -0
- package/dist/backend/enrichment.js +59 -0
- package/dist/backend/features.d.ts +61 -0
- package/dist/backend/features.js +109 -0
- package/dist/backend/identity-ladder.d.ts +57 -0
- package/dist/backend/identity-ladder.js +158 -0
- package/dist/backend/identity-registry.d.ts +51 -0
- package/dist/backend/identity-registry.js +101 -0
- package/dist/backend/index.d.ts +30 -0
- package/dist/backend/index.js +35 -0
- package/dist/backend/lifecycle-interlock.d.ts +25 -0
- package/dist/backend/lifecycle-interlock.js +18 -0
- package/dist/backend/managed-objects.d.ts +39 -0
- package/dist/backend/managed-objects.js +82 -0
- package/dist/backend/mapping.d.ts +11 -0
- package/dist/backend/mapping.js +148 -0
- package/dist/backend/mm-backend.d.ts +44 -0
- package/dist/backend/mm-backend.js +154 -0
- package/dist/backend/mm-location.d.ts +21 -0
- package/dist/backend/mm-location.js +237 -0
- package/dist/backend/mm-mutations.d.ts +42 -0
- package/dist/backend/mm-mutations.js +259 -0
- package/dist/backend/modem-actor.d.ts +41 -0
- package/dist/backend/modem-actor.js +78 -0
- package/dist/backend/nm-auto-apn.d.ts +54 -0
- package/dist/backend/nm-auto-apn.js +124 -0
- package/dist/backend/nm-gsm-fields.d.ts +23 -0
- package/dist/backend/nm-gsm-fields.js +107 -0
- package/dist/backend/nmcli-nm-port.d.ts +28 -0
- package/dist/backend/nmcli-nm-port.js +173 -0
- package/dist/backend/nmcli-runner.d.ts +24 -0
- package/dist/backend/nmcli-runner.js +35 -0
- package/dist/backend/observer.d.ts +37 -0
- package/dist/backend/observer.js +219 -0
- package/dist/backend/power-contract.d.ts +49 -0
- package/dist/backend/power-contract.js +34 -0
- package/dist/backend/recovery-attribution.d.ts +32 -0
- package/dist/backend/recovery-attribution.js +57 -0
- package/dist/backend/recovery-budget.d.ts +45 -0
- package/dist/backend/recovery-budget.js +44 -0
- package/dist/backend/recovery-ladder.d.ts +94 -0
- package/dist/backend/recovery-ladder.js +116 -0
- package/dist/backend/router-ethernet.d.ts +19 -0
- package/dist/backend/router-ethernet.js +66 -0
- package/dist/backend/row-store.d.ts +13 -0
- package/dist/backend/row-store.js +82 -0
- package/dist/backend/signal-setup.d.ts +30 -0
- package/dist/backend/signal-setup.js +92 -0
- package/dist/backend/sim-unlock.d.ts +13 -0
- package/dist/backend/sim-unlock.js +153 -0
- package/dist/backend/transition-preconditions.d.ts +76 -0
- package/dist/backend/transition-preconditions.js +64 -0
- package/dist/backend/usage/accounting.d.ts +39 -0
- package/dist/backend/usage/accounting.js +73 -0
- package/dist/backend/usage/billing-cycle.d.ts +11 -0
- package/dist/backend/usage/billing-cycle.js +39 -0
- package/dist/backend/usage/boot-id.d.ts +6 -0
- package/{src/backend/usage/boot-id.ts → dist/backend/usage/boot-id.js} +7 -7
- package/dist/backend/usage/index.d.ts +8 -0
- package/dist/backend/usage/index.js +11 -0
- package/dist/backend/usage/policy-store.d.ts +50 -0
- package/dist/backend/usage/policy-store.js +161 -0
- package/dist/backend/usage/policy-write.d.ts +65 -0
- package/dist/backend/usage/policy-write.js +112 -0
- package/dist/backend/usage/proc-net-dev.d.ts +18 -0
- package/{src/backend/usage/proc-net-dev.ts → dist/backend/usage/proc-net-dev.js} +39 -47
- package/dist/backend/usage/sampler.d.ts +75 -0
- package/dist/backend/usage/sampler.js +211 -0
- package/dist/backend/usage/store.d.ts +38 -0
- package/dist/backend/usage/store.js +126 -0
- package/dist/backend/usb-device-snapshot.d.ts +31 -0
- package/dist/backend/usb-device-snapshot.js +1 -0
- package/dist/backend/usb-enumerator.d.ts +21 -0
- package/dist/backend/usb-enumerator.js +153 -0
- package/dist/backend/usb-mode-transition.d.ts +29 -0
- package/dist/backend/usb-mode-transition.js +196 -0
- package/dist/band/band-names.d.ts +42 -0
- package/dist/band/band-names.js +150 -0
- package/dist/band/certification.d.ts +84 -0
- package/dist/band/certification.js +127 -0
- package/dist/band/certified-bands.json +4 -0
- package/dist/band/index.d.ts +2 -0
- package/dist/band/index.js +8 -0
- package/dist/capability/detect.d.ts +52 -0
- package/dist/capability/detect.js +86 -0
- package/dist/capability/five-g-preference.d.ts +104 -0
- package/dist/capability/five-g-preference.js +171 -0
- package/dist/capability/index.d.ts +3 -0
- package/dist/capability/index.js +10 -0
- package/dist/capability/support-claim.d.ts +39 -0
- package/dist/capability/support-claim.js +81 -0
- package/dist/domain/brand.d.ts +10 -0
- package/dist/domain/brand.js +21 -0
- package/dist/domain/errors.d.ts +32 -0
- package/dist/domain/errors.js +48 -0
- package/dist/domain/generation.d.ts +8 -0
- package/dist/domain/generation.js +12 -0
- package/dist/domain/guards.d.ts +6 -0
- package/dist/domain/guards.js +127 -0
- package/dist/domain/identity.d.ts +109 -0
- package/dist/domain/identity.js +86 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.js +18 -0
- package/dist/domain/mm-enums.d.ts +12 -0
- package/dist/domain/mm-enums.js +139 -0
- package/dist/domain/modem-presentation.d.ts +10 -0
- package/dist/domain/modem-presentation.js +39 -0
- package/dist/domain/observation.d.ts +39 -0
- package/dist/domain/observation.js +4 -0
- package/dist/domain/operation.d.ts +118 -0
- package/dist/domain/operation.js +85 -0
- package/dist/domain/physical-identity.d.ts +43 -0
- package/dist/domain/physical-identity.js +113 -0
- package/{src/domain/policy.ts → dist/domain/policy.d.ts} +34 -69
- package/dist/domain/policy.js +38 -0
- package/dist/domain/shadow-divergence.d.ts +27 -0
- package/dist/domain/shadow-divergence.js +70 -0
- package/dist/domain/snapshot.d.ts +53 -0
- package/dist/domain/snapshot.js +75 -0
- package/{src/domain/state.ts → dist/domain/state.d.ts} +23 -122
- package/dist/domain/state.js +38 -0
- package/dist/fcc/coverage.d.ts +63 -0
- package/dist/fcc/coverage.js +102 -0
- package/dist/fcc/index.d.ts +3 -0
- package/dist/fcc/index.js +12 -0
- package/dist/fcc/policy-store.d.ts +40 -0
- package/dist/fcc/policy-store.js +146 -0
- package/dist/fcc/policy-write.d.ts +32 -0
- package/dist/fcc/policy-write.js +37 -0
- package/dist/hardware/hilink-protocol.d.ts +39 -0
- package/dist/hardware/hilink-protocol.js +58 -0
- package/dist/hardware/index.d.ts +3 -0
- package/dist/hardware/index.js +15 -0
- package/dist/hardware/router-parsers.d.ts +89 -0
- package/dist/hardware/router-parsers.js +234 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +26 -0
- package/dist/journal/codec.d.ts +44 -0
- package/dist/journal/codec.js +198 -0
- package/dist/journal/engine.d.ts +28 -0
- package/dist/journal/engine.js +68 -0
- package/dist/journal/entry.d.ts +74 -0
- package/dist/journal/entry.js +56 -0
- package/dist/journal/index.d.ts +6 -0
- package/dist/journal/index.js +6 -0
- package/dist/journal/legacy-ceraui.d.ts +73 -0
- package/dist/journal/legacy-ceraui.js +227 -0
- package/dist/journal/recovery.d.ts +58 -0
- package/dist/journal/recovery.js +117 -0
- package/dist/journal/store.d.ts +55 -0
- package/dist/journal/store.js +150 -0
- package/dist/location/fix-state.d.ts +53 -0
- package/dist/location/fix-state.js +75 -0
- package/dist/location/index.d.ts +2 -0
- package/dist/location/index.js +8 -0
- package/dist/location/nmea.d.ts +10 -0
- package/dist/location/nmea.js +89 -0
- package/dist/observations/envelope.d.ts +63 -0
- package/dist/observations/envelope.js +77 -0
- package/dist/observations/freshness.d.ts +28 -0
- package/dist/observations/freshness.js +76 -0
- package/dist/observations/index.d.ts +13 -0
- package/dist/observations/index.js +24 -0
- package/dist/observations/metric.d.ts +47 -0
- package/dist/observations/metric.js +73 -0
- package/dist/observations/model.d.ts +80 -0
- package/dist/observations/model.js +11 -0
- package/dist/observations/provenance.d.ts +94 -0
- package/dist/observations/provenance.js +67 -0
- package/dist/observations/raw.d.ts +42 -0
- package/dist/observations/raw.js +146 -0
- package/dist/observations/reading.d.ts +51 -0
- package/dist/observations/reading.js +65 -0
- package/dist/observations/sources/hilink.d.ts +10 -0
- package/dist/observations/sources/hilink.js +84 -0
- package/dist/observations/sources/modemmanager.d.ts +18 -0
- package/dist/observations/sources/modemmanager.js +239 -0
- package/dist/observations/sources/router-shared.d.ts +29 -0
- package/dist/observations/sources/router-shared.js +68 -0
- package/dist/observations/sources/ufi.d.ts +10 -0
- package/dist/observations/sources/ufi.js +111 -0
- package/dist/observations/sources/zte.d.ts +7 -0
- package/dist/observations/sources/zte.js +71 -0
- package/dist/observations/state-separation.d.ts +64 -0
- package/dist/observations/state-separation.js +52 -0
- package/dist/operations/contracts.d.ts +72 -0
- package/dist/operations/contracts.js +1 -0
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.js +1 -0
- package/dist/operations/operation-engine.d.ts +16 -0
- package/dist/operations/operation-engine.js +194 -0
- package/dist/ports/index.d.ts +12 -0
- package/{src/ports/index.ts → dist/ports/index.js} +12 -8
- package/dist/ports/location.d.ts +87 -0
- package/dist/ports/location.js +36 -0
- package/dist/ports/modem-manager.d.ts +89 -0
- package/dist/ports/modem-manager.js +9 -0
- package/dist/ports/mutation-admission.d.ts +27 -0
- package/dist/ports/mutation-admission.js +9 -0
- package/dist/ports/network-manager.d.ts +68 -0
- package/dist/ports/network-manager.js +13 -0
- package/{src/ports/observation.ts → dist/ports/observation.d.ts} +16 -27
- package/dist/ports/observation.js +7 -0
- package/dist/ports/ops.d.ts +44 -0
- package/dist/ports/ops.js +16 -0
- package/{src/ports/receipts.ts → dist/ports/receipts.d.ts} +5 -27
- package/dist/ports/receipts.js +9 -0
- package/dist/ports/reconcile.d.ts +33 -0
- package/dist/ports/reconcile.js +200 -0
- package/dist/ports/resource-ownership.d.ts +29 -0
- package/dist/ports/resource-ownership.js +1 -0
- package/dist/ports/router.d.ts +19 -0
- package/dist/ports/router.js +7 -0
- package/dist/ports/sms.d.ts +64 -0
- package/dist/ports/sms.js +24 -0
- package/dist/ports/uhubctl.d.ts +6 -0
- package/dist/ports/uhubctl.js +1 -0
- package/dist/providers/contracts.d.ts +124 -0
- package/dist/providers/contracts.js +10 -0
- package/dist/providers/huawei-hilink/index.d.ts +2 -0
- package/dist/providers/huawei-hilink/index.js +2 -0
- package/dist/providers/huawei-hilink/operations.d.ts +20 -0
- package/dist/providers/huawei-hilink/operations.js +56 -0
- package/dist/providers/huawei-hilink/provider.d.ts +52 -0
- package/dist/providers/huawei-hilink/provider.js +76 -0
- package/dist/providers/huawei-hilink/runtime.d.ts +22 -0
- package/dist/providers/huawei-hilink/runtime.js +171 -0
- package/dist/providers/huawei-hilink/session.d.ts +28 -0
- package/dist/providers/huawei-hilink/session.js +120 -0
- package/dist/providers/huawei-hilink/transport.d.ts +19 -0
- package/dist/providers/huawei-hilink/transport.js +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/matcher.d.ts +3 -0
- package/dist/providers/matcher.js +205 -0
- package/dist/providers/modem-manager/errors.d.ts +7 -0
- package/dist/providers/modem-manager/errors.js +37 -0
- package/dist/providers/modem-manager/generic-operations.d.ts +8 -0
- package/dist/providers/modem-manager/generic-operations.js +207 -0
- package/dist/providers/modem-manager/index.d.ts +3 -0
- package/dist/providers/modem-manager/index.js +3 -0
- package/dist/providers/modem-manager/module-operations.d.ts +21 -0
- package/dist/providers/modem-manager/module-operations.js +118 -0
- package/dist/providers/modem-manager/provider.d.ts +39 -0
- package/dist/providers/modem-manager/provider.js +152 -0
- package/dist/providers/modem-manager/snapshot.d.ts +5 -0
- package/dist/providers/modem-manager/snapshot.js +204 -0
- package/dist/providers/modem-manager/types.d.ts +135 -0
- package/dist/providers/modem-manager/types.js +1 -0
- package/dist/providers/network-manager/adapter.d.ts +71 -0
- package/dist/providers/network-manager/adapter.js +348 -0
- package/dist/providers/network-manager/index.d.ts +2 -0
- package/dist/providers/network-manager/index.js +2 -0
- package/dist/providers/network-manager/types.d.ts +163 -0
- package/dist/providers/network-manager/types.js +77 -0
- package/dist/providers/registry.d.ts +13 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/ufi-himi/index.d.ts +6 -0
- package/dist/providers/ufi-himi/index.js +6 -0
- package/dist/providers/ufi-himi/operations.d.ts +41 -0
- package/dist/providers/ufi-himi/operations.js +66 -0
- package/dist/providers/ufi-himi/prohibitions.d.ts +62 -0
- package/dist/providers/ufi-himi/prohibitions.js +88 -0
- package/dist/providers/ufi-himi/provider.d.ts +41 -0
- package/dist/providers/ufi-himi/provider.js +204 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.d.ts +32 -0
- package/dist/providers/ufi-himi/qualcomm-evidence.js +51 -0
- package/dist/providers/ufi-himi/session.d.ts +46 -0
- package/dist/providers/ufi-himi/session.js +92 -0
- package/dist/providers/ufi-himi/transport.d.ts +29 -0
- package/dist/providers/ufi-himi/transport.js +25 -0
- package/dist/providers/zte-goform/index.d.ts +2 -0
- package/dist/providers/zte-goform/index.js +2 -0
- package/dist/providers/zte-goform/provider.d.ts +51 -0
- package/dist/providers/zte-goform/provider.js +101 -0
- package/dist/providers/zte-goform/session.d.ts +17 -0
- package/dist/providers/zte-goform/session.js +133 -0
- package/dist/providers/zte-goform/transport.d.ts +16 -0
- package/dist/providers/zte-goform/transport.js +1 -0
- package/dist/radio/band-truth.d.ts +50 -0
- package/dist/radio/band-truth.js +92 -0
- package/dist/radio/index.d.ts +3 -0
- package/dist/radio/index.js +10 -0
- package/dist/radio/mode-combinations.d.ts +78 -0
- package/dist/radio/mode-combinations.js +198 -0
- package/dist/radio/mode-truth.d.ts +67 -0
- package/dist/radio/mode-truth.js +112 -0
- package/dist/redact.d.ts +15 -0
- package/dist/redact.js +189 -0
- package/dist/safety/composition-root.d.ts +28 -0
- package/dist/safety/composition-root.js +65 -0
- package/dist/safety/flock-resource-ownership.d.ts +11 -0
- package/dist/safety/flock-resource-ownership.js +151 -0
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/sms/dbus-messaging.d.ts +17 -0
- package/dist/sms/dbus-messaging.js +185 -0
- package/dist/sms/inbox-store.d.ts +10 -0
- package/dist/sms/inbox-store.js +82 -0
- package/dist/sms/index.d.ts +4 -0
- package/dist/sms/index.js +10 -0
- package/dist/sms/mmcli-parse.d.ts +54 -0
- package/dist/sms/mmcli-parse.js +224 -0
- package/dist/sms/normalize.d.ts +42 -0
- package/dist/sms/normalize.js +95 -0
- package/dist/testing/domain-fakes.d.ts +58 -0
- package/dist/testing/domain-fakes.js +98 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/provider-fakes.d.ts +45 -0
- package/dist/testing/provider-fakes.js +64 -0
- package/dist/transport/calls.d.ts +9 -0
- package/dist/transport/calls.js +88 -0
- package/dist/transport/codec.d.ts +3 -0
- package/dist/transport/codec.js +207 -0
- package/dist/transport/dbus-native.d.ts +57 -0
- package/dist/transport/dbus-native.js +17 -0
- package/dist/transport/errors.d.ts +21 -0
- package/{src/transport/errors.ts → dist/transport/errors.js} +34 -46
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/signals.d.ts +15 -0
- package/dist/transport/signals.js +123 -0
- package/dist/transport/signature.d.ts +7 -0
- package/dist/transport/signature.js +94 -0
- package/dist/transport/transport.d.ts +2 -0
- package/dist/transport/transport.js +202 -0
- package/dist/transport/types.d.ts +61 -0
- package/dist/transport/types.js +19 -0
- package/dist/usb-mode/catalog-schema.d.ts +139 -0
- package/dist/usb-mode/catalog-schema.js +97 -0
- package/dist/usb-mode/catalog.d.ts +21 -0
- package/{src/usb-mode/catalog.ts → dist/usb-mode/catalog.js} +10 -32
- package/dist/usb-mode/certified-catalog.json +67 -0
- package/dist/usb-mode/index.d.ts +5 -0
- package/dist/usb-mode/index.js +15 -0
- package/dist/usb-mode/ingestion.d.ts +111 -0
- package/dist/usb-mode/ingestion.js +187 -0
- package/dist/usb-mode/promotion-review.d.ts +21 -0
- package/dist/usb-mode/promotion-review.js +87 -0
- package/dist/usb-mode/usb-devices-parse.d.ts +36 -0
- package/dist/usb-mode/usb-devices-parse.js +157 -0
- package/dist/ussd/calls.d.ts +32 -0
- package/dist/ussd/calls.js +96 -0
- package/dist/ussd/index.d.ts +5 -0
- package/dist/ussd/index.js +12 -0
- package/dist/ussd/mm-ussd.d.ts +37 -0
- package/dist/ussd/mm-ussd.js +205 -0
- package/dist/ussd/refusal.d.ts +53 -0
- package/dist/ussd/refusal.js +154 -0
- package/dist/ussd/registration.d.ts +20 -0
- package/dist/ussd/registration.js +101 -0
- package/dist/ussd/session.d.ts +84 -0
- package/dist/ussd/session.js +163 -0
- package/package.json +38 -4
- package/src/backend/at-lease.test.ts +0 -106
- package/src/backend/at-lease.ts +0 -158
- package/src/backend/cell-info.test.ts +0 -154
- package/src/backend/cell-info.ts +0 -160
- package/src/backend/device-classifier.test.ts +0 -168
- package/src/backend/device-classifier.ts +0 -248
- package/src/backend/enrichment.ts +0 -96
- package/src/backend/features.test.ts +0 -162
- package/src/backend/features.ts +0 -179
- package/src/backend/identity-ladder.test.ts +0 -117
- package/src/backend/identity-ladder.ts +0 -221
- package/src/backend/identity-registry.test.ts +0 -89
- package/src/backend/identity-registry.ts +0 -151
- package/src/backend/index.ts +0 -221
- package/src/backend/lifecycle-interlock.ts +0 -38
- package/src/backend/managed-objects.ts +0 -108
- package/src/backend/mapping.ts +0 -160
- package/src/backend/mm-backend.ts +0 -191
- package/src/backend/mm-mutations.ts +0 -228
- package/src/backend/modem-actor.test.ts +0 -95
- package/src/backend/modem-actor.ts +0 -112
- package/src/backend/nm-auto-apn.ts +0 -161
- package/src/backend/nm-gsm-fields.ts +0 -122
- package/src/backend/nmcli-nm-port.ts +0 -228
- package/src/backend/nmcli-runner.ts +0 -52
- package/src/backend/observer.ts +0 -297
- package/src/backend/power-contract.test.ts +0 -40
- package/src/backend/power-contract.ts +0 -83
- package/src/backend/recovery-attribution.test.ts +0 -102
- package/src/backend/recovery-attribution.ts +0 -86
- package/src/backend/recovery-budget.test.ts +0 -64
- package/src/backend/recovery-budget.ts +0 -84
- package/src/backend/recovery-ladder.test.ts +0 -257
- package/src/backend/recovery-ladder.ts +0 -249
- package/src/backend/router-ethernet.test.ts +0 -71
- package/src/backend/router-ethernet.ts +0 -90
- package/src/backend/row-store.ts +0 -105
- package/src/backend/signal-setup.ts +0 -112
- package/src/backend/sim-unlock.ts +0 -193
- package/src/backend/transition-preconditions.ts +0 -149
- package/src/backend/usage/accounting.test.ts +0 -147
- package/src/backend/usage/accounting.ts +0 -123
- package/src/backend/usage/billing-cycle.test.ts +0 -62
- package/src/backend/usage/billing-cycle.ts +0 -45
- package/src/backend/usage/index.ts +0 -37
- package/src/backend/usage/proc-net-dev.test.ts +0 -56
- package/src/backend/usage/sampler.test.ts +0 -219
- package/src/backend/usage/sampler.ts +0 -228
- package/src/backend/usage/store.test.ts +0 -148
- package/src/backend/usage/store.ts +0 -177
- package/src/backend/usb-enumerator.test.ts +0 -87
- package/src/backend/usb-enumerator.ts +0 -181
- package/src/backend/usb-mode-transition.test.ts +0 -323
- package/src/backend/usb-mode-transition.ts +0 -253
- package/src/domain/brand.ts +0 -29
- package/src/domain/errors.ts +0 -77
- package/src/domain/guards.test.ts +0 -218
- package/src/domain/guards.ts +0 -144
- package/src/domain/identity.test.ts +0 -83
- package/src/domain/identity.ts +0 -165
- package/src/domain/index.ts +0 -12
- package/src/domain/snapshot.test.ts +0 -266
- package/src/domain/snapshot.ts +0 -120
- package/src/index.test.ts +0 -6
- package/src/index.ts +0 -15
- package/src/ports/README.md +0 -61
- package/src/ports/forbidden-surface.test.ts +0 -241
- package/src/ports/modem-manager.ts +0 -72
- package/src/ports/network-manager.ts +0 -87
- package/src/ports/ops.ts +0 -60
- package/src/ports/ops.type-test.ts +0 -39
- package/src/ports/receipts.test.ts +0 -153
- package/src/ports/reconcile.test.ts +0 -152
- package/src/ports/reconcile.ts +0 -338
- package/src/ports/router.ts +0 -29
- package/src/redact.test.ts +0 -82
- package/src/redact.ts +0 -73
- package/src/transport/README.md +0 -65
- package/src/transport/calls.ts +0 -113
- package/src/transport/characterization.test.ts +0 -260
- package/src/transport/codec.test.ts +0 -118
- package/src/transport/codec.ts +0 -240
- package/src/transport/conformance-python.test.ts +0 -152
- package/src/transport/conformance-same-lib.test.ts +0 -115
- package/src/transport/dbus-native-lib.d.ts +0 -19
- package/src/transport/dbus-native.ts +0 -85
- package/src/transport/index.ts +0 -30
- package/src/transport/no-library-leak.test.ts +0 -61
- package/src/transport/reliability.test.ts +0 -173
- package/src/transport/signals.ts +0 -150
- package/src/transport/signature.ts +0 -110
- package/src/transport/test-support/fake-service.ts +0 -168
- package/src/transport/test-support/independent-producer.py +0 -110
- package/src/transport/test-support/private-bus.ts +0 -66
- package/src/transport/transport.ts +0 -250
- package/src/transport/types.ts +0 -118
- package/src/usb-mode/catalog-schema.test.ts +0 -181
- package/src/usb-mode/catalog-schema.ts +0 -113
- package/src/usb-mode/certified-catalog.json +0 -67
- 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,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;
|