@flighthq/connectivity 0.5.0-next.1944.cd6b6e7 → 0.5.1-edge.638.d4dbd4e

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @flighthq/connectivity
2
2
 
3
- Connectivity status and online/offline change signals over a swappable web/native backend
3
+ Explicit connectivity status, change signals, and reachability operations
4
4
 
5
5
  ## Install
6
6
 
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/connectivity`. The `
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/connectivity@0.5.0-next.1944.cd6b6e7](https://github.com/flighthq/flight/tree/cd6b6e71eb472b3a4249830a7743333f019fa7b7/packages/connectivity)
17
- - [License](https://github.com/flighthq/flight/blob/cd6b6e71eb472b3a4249830a7743333f019fa7b7/LICENSE.md)
16
+ - [Source for @flighthq/connectivity@0.5.1-edge.638.d4dbd4e](https://github.com/flighthq/flight/tree/d4dbd4e347dc8be10b180c85a766ab50333dcb51/packages/connectivity)
17
+ - [License](https://github.com/flighthq/flight/blob/d4dbd4e347dc8be10b180c85a766ab50333dcb51/LICENSE.md)
@@ -1,20 +1,13 @@
1
- import type { BackendExplanation, Connectivity, ConnectivityBackend, ConnectivityReachability, ConnectivityReachabilityOptions, ConnectivityStatus } from '@flighthq/types/contract';
2
- export declare function attachConnectivity(net: Connectivity): void;
1
+ import type { Connectivity, ConnectivityReachability, ConnectivityReachabilityOptions, ConnectivityStatus, HasConnectivityChange, HasConnectivityReachability, HasConnectivityStatus } from '@flighthq/types/contract';
2
+ export declare function attachConnectivity(host: HasConnectivityStatus & HasConnectivityChange, connectivity: Connectivity): boolean;
3
3
  export declare function createConnectivity(): Connectivity;
4
- export declare function createConnectivityStatus(): ConnectivityStatus;
5
- export declare function createWebConnectivityBackend(): ConnectivityBackend;
6
- export declare function detachConnectivity(net: Connectivity): void;
7
- export declare function detectConnectivityReachability(options: Readonly<ConnectivityReachabilityOptions>, out: ConnectivityReachability): Promise<ConnectivityReachability>;
8
- export declare function disposeConnectivity(net: Connectivity): void;
9
- export declare function explainConnectivityBackend(): BackendExplanation;
10
- export declare function getConnectivityBackend(): ConnectivityBackend;
11
- export declare function getConnectivityStatus(out: ConnectivityStatus): ConnectivityStatus;
4
+ export declare function destroyConnectivity(host: HasConnectivityChange): void;
5
+ export declare function detachConnectivity(connectivity: Connectivity): void;
6
+ export declare function detectConnectivityReachability(host: HasConnectivityReachability, options: Readonly<ConnectivityReachabilityOptions>, out: ConnectivityReachability): Promise<ConnectivityReachability>;
7
+ export declare function disposeConnectivity(connectivity: Connectivity): void;
8
+ export declare function getConnectivityOnline(host: HasConnectivityStatus): boolean | null;
9
+ export declare function getConnectivityStatus(host: HasConnectivityStatus, out: ConnectivityStatus): ConnectivityStatus;
12
10
  export declare function hasConnectivityStatusChanged(a: Readonly<ConnectivityStatus>, b: Readonly<ConnectivityStatus>): boolean;
13
- export declare function installConnectivityHostBackend(backend: ConnectivityBackend): void;
14
- export declare function isConnectivityMetered(): boolean;
15
- export declare function isConnectivityOnline(): boolean;
16
- export declare function isConnectivitySaveDataEnabled(): boolean;
17
- export declare function observeConnectivityHostResult(operation: string, succeeded: boolean): void;
18
- export declare function resetConnectivityBackendForTest(): void;
19
- export declare function setConnectivityBackend(backend: ConnectivityBackend | null): void;
11
+ export declare function isConnectivityMetered(host: HasConnectivityStatus): boolean;
12
+ export declare function isConnectivitySaveDataEnabled(host: HasConnectivityStatus): boolean;
20
13
  //# sourceMappingURL=connectivity.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connectivity.d.ts","sourceRoot":"","sources":["../src/connectivity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EAEnB,wBAAwB,EACxB,+BAA+B,EAC/B,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAElC,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAwB1D;AAED,wBAAgB,kBAAkB,IAAI,YAAY,CAQjD;AAED,wBAAgB,wBAAwB,IAAI,kBAAkB,CAW7D;AAID,wBAAgB,4BAA4B,IAAI,mBAAmB,CAuDlE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAM1D;AAED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,QAAQ,CAAC,+BAA+B,CAAC,EAClD,GAAG,EAAE,wBAAwB,GAC5B,OAAO,CAAC,wBAAwB,CAAC,CAcnC;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAE3D;AAED,wBAAgB,0BAA0B,IAAI,kBAAkB,CAa/D;AAED,wBAAgB,sBAAsB,IAAI,mBAAmB,CAE5D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,kBAAkB,GAAG,kBAAkB,CAEjF;AAED,wBAAgB,4BAA4B,CAC1C,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAC/B,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAC9B,OAAO,CAWT;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAMjF;AAED,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAgB,6BAA6B,IAAI,OAAO,CAEvD;AAED,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAKzF;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAKtD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAAG,IAAI,CAEhF"}
1
+ {"version":3,"file":"connectivity.d.ts","sourceRoot":"","sources":["../src/connectivity.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,EACtB,MAAM,0BAA0B,CAAC;AAMlC,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,qBAAqB,GAAG,qBAAqB,EACnD,YAAY,EAAE,YAAY,GACzB,OAAO,CA2BT;AAED,wBAAgB,kBAAkB,IAAI,YAAY,CAQjD;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAErE;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAKnE;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,2BAA2B,EACjC,OAAO,EAAE,QAAQ,CAAC,+BAA+B,CAAC,EAClD,GAAG,EAAE,wBAAwB,GAC5B,OAAO,CAAC,wBAAwB,CAAC,CAEnC;AAID,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAOpE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,GAAG,IAAI,CAEjF;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,EAAE,GAAG,EAAE,kBAAkB,GAAG,kBAAkB,CAE9G;AAED,wBAAgB,4BAA4B,CAC1C,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAC/B,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,GAC9B,OAAO,CAWT;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAE1E;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAElF"}
@@ -1,155 +1,79 @@
1
- import { createSignal, emitSignal } from '@flighthq/signals/contract';
2
- export function attachConnectivity(net) {
3
- detachConnectivity(net);
4
- const backend = getConnectivityBackend();
5
- const initial = backend.getStatus(_scratch);
1
+ import { createEntity } from '@flighthq/entity/contract';
2
+ import { clearSignal, createSignal, emitSignal } from '@flighthq/signals/contract';
3
+ // Starts raw host-change delivery and turns it into the five core-owned diff signals. Status and
4
+ // change are separate Host witnesses: a snapshot command cannot stand in for an event subscription.
5
+ // Re-attaching always consumes the prior provider's exact unsubscribe before touching the new one.
6
+ // Returns false when the change provider cannot establish a real subscription.
7
+ export function attachConnectivity(host, connectivity) {
8
+ detachConnectivity(connectivity);
9
+ const statusBackend = host.connectivity.status;
10
+ const initial = statusBackend.getStatus(connectivityStatusOut());
6
11
  let wasOnline = initial.online;
7
12
  let wasType = initial.type;
8
13
  let wasMetered = initial.metered;
9
- const unsubscribe = backend.subscribe(() => {
10
- const status = backend.getStatus(_scratch);
11
- emitSignal(net.onChange, status);
14
+ const unsubscribe = host.connectivity.change.subscribe(() => {
15
+ const status = statusBackend.getStatus(connectivityStatusOut());
16
+ emitSignal(connectivity.onChange, status);
12
17
  if (status.online !== wasOnline) {
13
18
  wasOnline = status.online;
14
- emitSignal(status.online ? net.onOnline : net.onOffline);
19
+ if (status.online === true)
20
+ emitSignal(connectivity.onOnline);
21
+ else if (status.online === false)
22
+ emitSignal(connectivity.onOffline);
15
23
  }
16
24
  if (status.type !== wasType) {
17
25
  wasType = status.type;
18
- emitSignal(net.onConnectionTypeChange, status.type);
26
+ emitSignal(connectivity.onConnectionTypeChange, status.type);
19
27
  }
20
28
  if (status.metered !== wasMetered) {
21
29
  wasMetered = status.metered;
22
- emitSignal(net.onMeteredChange, status.metered);
30
+ emitSignal(connectivity.onMeteredChange, status.metered);
23
31
  }
24
32
  });
25
- _subscriptions.set(net, unsubscribe);
33
+ if (unsubscribe === null)
34
+ return false;
35
+ _subscriptions.set(connectivity, unsubscribe);
36
+ return true;
26
37
  }
27
38
  export function createConnectivity() {
28
- return {
39
+ return createEntity({
29
40
  onChange: createSignal(),
30
41
  onConnectionTypeChange: createSignal(),
31
42
  onMeteredChange: createSignal(),
32
43
  onOffline: createSignal(),
33
44
  onOnline: createSignal(),
34
- };
35
- }
36
- export function createConnectivityStatus() {
37
- return {
38
- downlink: -1,
39
- downlinkMax: -1,
40
- effectiveType: '',
41
- metered: false,
42
- online: false,
43
- rtt: -1,
44
- saveData: false,
45
- type: 'unknown',
46
- };
47
- }
48
- // Builds the default web backend over navigator.onLine, the Network Information API, and the window
49
- // online/offline events. Degrades to online=true / type 'unknown' where the APIs are absent.
50
- export function createWebConnectivityBackend() {
51
- return {
52
- getStatus(out) {
53
- const nav = typeof navigator !== 'undefined' ? navigator : null;
54
- out.online = nav?.onLine ?? true;
55
- const conn = getWebConnection();
56
- out.type = mapWebConnectionType(conn?.type);
57
- out.downlink = typeof conn?.downlink === 'number' ? conn.downlink : -1;
58
- out.downlinkMax = typeof conn?.downlinkMax === 'number' ? conn.downlinkMax : -1;
59
- out.effectiveType = typeof conn?.effectiveType === 'string' ? conn.effectiveType : '';
60
- out.rtt = typeof conn?.rtt === 'number' ? conn.rtt : -1;
61
- out.saveData = conn?.saveData === true;
62
- out.metered = out.saveData || out.type === 'cellular';
63
- return out;
64
- },
65
- async detectReachability(options, out) {
66
- if (typeof fetch === 'undefined') {
67
- out.reachable = false;
68
- out.latency = -1;
69
- return out;
70
- }
71
- const timeout = options.timeout ?? 5000;
72
- const controller = new AbortController();
73
- const timerId = setTimeout(() => controller.abort(), timeout);
74
- const combinedSignal = options.signal ? anyAbortSignal(options.signal, controller.signal) : controller.signal;
75
- const start = Date.now();
76
- try {
77
- const response = await fetch(options.url, {
78
- method: 'HEAD',
79
- cache: 'no-store',
80
- signal: combinedSignal,
81
- });
82
- clearTimeout(timerId);
83
- out.reachable = response.ok;
84
- out.latency = Date.now() - start;
85
- }
86
- catch {
87
- clearTimeout(timerId);
88
- out.reachable = false;
89
- out.latency = -1;
90
- }
91
- return out;
92
- },
93
- subscribe(listener) {
94
- if (typeof window === 'undefined')
95
- return () => { };
96
- window.addEventListener('online', listener);
97
- window.addEventListener('offline', listener);
98
- const conn = getWebConnection();
99
- conn?.addEventListener?.('change', listener);
100
- return () => {
101
- window.removeEventListener('online', listener);
102
- window.removeEventListener('offline', listener);
103
- conn?.removeEventListener?.('change', listener);
104
- };
105
- },
106
- };
45
+ });
107
46
  }
108
- export function detachConnectivity(net) {
109
- const unsubscribe = _subscriptions.get(net);
110
- if (unsubscribe !== undefined) {
111
- unsubscribe();
112
- _subscriptions.delete(net);
113
- }
47
+ // Terminal provider teardown. This is deliberately separate from per-entity detach: a caller that
48
+ // releases one subscription must not destroy a shared host provider.
49
+ export function destroyConnectivity(host) {
50
+ host.connectivity.change.destroy();
114
51
  }
115
- export async function detectConnectivityReachability(options, out) {
116
- const backend = getConnectivityBackend();
117
- if (backend.detectReachability !== undefined) {
118
- return backend.detectReachability(options, out);
119
- }
120
- // Fallback: use the web backend's implementation directly
121
- if (_cachedWebBackend === null)
122
- _cachedWebBackend = createWebConnectivityBackend();
123
- const webBackend = _cachedWebBackend;
124
- if (webBackend.detectReachability !== undefined) {
125
- return webBackend.detectReachability(options, out);
126
- }
127
- out.reachable = false;
128
- out.latency = -1;
129
- return out;
52
+ export function detachConnectivity(connectivity) {
53
+ const unsubscribe = _subscriptions.get(connectivity);
54
+ if (unsubscribe === undefined)
55
+ return;
56
+ _subscriptions.delete(connectivity);
57
+ unsubscribe();
130
58
  }
131
- export function disposeConnectivity(net) {
132
- detachConnectivity(net);
59
+ export function detectConnectivityReachability(host, options, out) {
60
+ return host.connectivity.reachability.detectReachability(options, out);
133
61
  }
134
- export function explainConnectivityBackend() {
135
- if (_custom !== null) {
136
- return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
137
- }
138
- if (_host !== null) {
139
- return {
140
- conflict: _hostConflict,
141
- layer: 'host',
142
- operation: _hostObservation !== null ? _hostObservation.operation : null,
143
- viability: _hostObservation !== null ? _hostObservation.viability : 'unobserved',
144
- };
145
- }
146
- return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
62
+ // Disposes only the caller-owned event entity. Provider teardown is terminal and explicit through
63
+ // destroyConnectivity because one provider can fan out to multiple Connectivity entities.
64
+ export function disposeConnectivity(connectivity) {
65
+ detachConnectivity(connectivity);
66
+ clearSignal(connectivity.onChange);
67
+ clearSignal(connectivity.onConnectionTypeChange);
68
+ clearSignal(connectivity.onMeteredChange);
69
+ clearSignal(connectivity.onOffline);
70
+ clearSignal(connectivity.onOnline);
147
71
  }
148
- export function getConnectivityBackend() {
149
- return _custom ?? _host ?? _sentinel;
72
+ export function getConnectivityOnline(host) {
73
+ return host.connectivity.status.getStatus(connectivityStatusOut()).online;
150
74
  }
151
- export function getConnectivityStatus(out) {
152
- return getConnectivityBackend().getStatus(out);
75
+ export function getConnectivityStatus(host, out) {
76
+ return host.connectivity.status.getStatus(out);
153
77
  }
154
78
  export function hasConnectivityStatusChanged(a, b) {
155
79
  return (a.online !== b.online ||
@@ -161,107 +85,27 @@ export function hasConnectivityStatusChanged(a, b) {
161
85
  a.saveData !== b.saveData ||
162
86
  a.metered !== b.metered);
163
87
  }
164
- export function installConnectivityHostBackend(backend) {
165
- if (_host !== null) {
166
- if (_host !== backend)
167
- _hostConflict = true;
168
- return;
169
- }
170
- _host = backend;
171
- }
172
- export function isConnectivityMetered() {
173
- return getConnectivityBackend().getStatus(_scratch).metered;
174
- }
175
- export function isConnectivityOnline() {
176
- return getConnectivityBackend().getStatus(_scratch).online;
88
+ export function isConnectivityMetered(host) {
89
+ return host.connectivity.status.getStatus(connectivityStatusOut()).metered;
177
90
  }
178
- export function isConnectivitySaveDataEnabled() {
179
- return getConnectivityBackend().getStatus(_scratch).saveData;
91
+ export function isConnectivitySaveDataEnabled(host) {
92
+ return host.connectivity.status.getStatus(connectivityStatusOut()).saveData;
180
93
  }
181
- export function observeConnectivityHostResult(operation, succeeded) {
182
- _hostObservation = {
183
- operation,
184
- viability: succeeded ? 'available' : 'runtime-api-unavailable',
94
+ // ConnectivityStatus is a backend-produced query/out snapshot, not a user-created identity. Keep its
95
+ // sentinel allocation package-private so the public create* vocabulary remains Entity-only.
96
+ function connectivityStatusOut() {
97
+ return {
98
+ downlink: -1,
99
+ downlinkMax: -1,
100
+ effectiveType: '',
101
+ metered: false,
102
+ online: null,
103
+ rtt: -1,
104
+ saveData: false,
105
+ type: 'unknown',
185
106
  };
186
107
  }
187
- export function resetConnectivityBackendForTest() {
188
- _custom = null;
189
- _host = null;
190
- _hostConflict = false;
191
- _hostObservation = null;
192
- }
193
- export function setConnectivityBackend(backend) {
194
- _custom = backend;
195
- }
196
- let _custom = null;
197
- let _host = null;
198
- let _hostConflict = false;
199
- let _hostObservation = null;
200
- const _scratch = createConnectivityStatus();
201
- const _sentinel = {
202
- getStatus(out) {
203
- out.online = false;
204
- out.type = 'unknown';
205
- out.downlink = -1;
206
- out.downlinkMax = -1;
207
- out.effectiveType = '';
208
- out.rtt = -1;
209
- out.saveData = false;
210
- out.metered = false;
211
- return out;
212
- },
213
- async detectReachability(_options, out) {
214
- out.reachable = false;
215
- out.latency = -1;
216
- return out;
217
- },
218
- subscribe() {
219
- return () => { };
220
- },
221
- };
108
+ // Each entity retains the exact release returned by the provider it attached to. Deleting the entry
109
+ // before invoking the release also makes a re-entrant detach idempotent.
222
110
  const _subscriptions = new WeakMap();
223
- // Combines two AbortSignals: aborts when either fires.
224
- function anyAbortSignal(a, b) {
225
- if (typeof AbortSignal !== 'undefined' && 'any' in AbortSignal) {
226
- return AbortSignal.any([a, b]);
227
- }
228
- const controller = new AbortController();
229
- const onAbort = () => {
230
- controller.abort();
231
- a.removeEventListener('abort', onAbort);
232
- b.removeEventListener('abort', onAbort);
233
- };
234
- a.addEventListener('abort', onAbort, { once: true });
235
- b.addEventListener('abort', onAbort, { once: true });
236
- return controller.signal;
237
- }
238
- function getWebConnection() {
239
- if (typeof navigator === 'undefined')
240
- return null;
241
- const nav = navigator;
242
- return nav.connection ?? null;
243
- }
244
- function mapWebConnectionType(type) {
245
- switch (type) {
246
- case 'bluetooth':
247
- return 'bluetooth';
248
- case 'cellular':
249
- return 'cellular';
250
- case 'ethernet':
251
- return 'ethernet';
252
- case 'none':
253
- return 'none';
254
- case 'other':
255
- return 'other';
256
- case 'vpn':
257
- return 'vpn';
258
- case 'wifi':
259
- return 'wifi';
260
- case 'wimax':
261
- return 'wimax';
262
- default:
263
- return 'unknown';
264
- }
265
- }
266
- let _cachedWebBackend = null;
267
111
  //# sourceMappingURL=connectivity.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"connectivity.js","sourceRoot":"","sources":["../src/connectivity.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connectivity.js","sourceRoot":"","sources":["../src/connectivity.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { explainConnectivityBackend, attachConnectivity, createConnectivity, detachConnectivity, detectConnectivityReachability, disposeConnectivity, getConnectivityStatus, hasConnectivityStatusChanged, isConnectivityMetered, isConnectivityOnline, isConnectivitySaveDataEnabled, } from './contract';
1
+ export { attachConnectivity, createConnectivity, detachConnectivity, detectConnectivityReachability, disposeConnectivity, destroyConnectivity, getConnectivityOnline, getConnectivityStatus, hasConnectivityStatusChanged, isConnectivityMetered, isConnectivitySaveDataEnabled, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,8BAA8B,EAC9B,mBAAmB,EACnB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,GAC9B,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,8BAA8B,EAC9B,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { explainConnectivityBackend, attachConnectivity, createConnectivity, detachConnectivity, detectConnectivityReachability, disposeConnectivity, getConnectivityStatus, hasConnectivityStatusChanged, isConnectivityMetered, isConnectivityOnline, isConnectivitySaveDataEnabled, } from './contract';
1
+ export { attachConnectivity, createConnectivity, detachConnectivity, detectConnectivityReachability, disposeConnectivity, destroyConnectivity, getConnectivityOnline, getConnectivityStatus, hasConnectivityStatusChanged, isConnectivityMetered, isConnectivitySaveDataEnabled, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,8BAA8B,EAC9B,mBAAmB,EACnB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,GAC9B,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,8BAA8B,EAC9B,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,YAAY,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/connectivity",
3
- "version": "0.5.0-next.1944.cd6b6e7",
3
+ "version": "0.5.1-edge.638.d4dbd4e",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,12 +38,13 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/signals": "0.5.0-next.1944.cd6b6e7",
42
- "@flighthq/types": "0.5.0-next.1944.cd6b6e7"
41
+ "@flighthq/entity": "0.5.1-edge.638.d4dbd4e",
42
+ "@flighthq/signals": "0.5.1-edge.638.d4dbd4e",
43
+ "@flighthq/types": "0.5.1-edge.638.d4dbd4e"
43
44
  },
44
45
  "devDependencies": {
45
46
  "typescript": "^5.3.0"
46
47
  },
47
- "description": "Connectivity status and online/offline change signals over a swappable web/native backend",
48
+ "description": "Explicit connectivity status, change signals, and reachability operations",
48
49
  "sideEffects": false
49
50
  }
@@ -1,453 +1,320 @@
1
- import { connectSignal } from '@flighthq/signals/contract';
2
- import type { ConnectivityBackend, ConnectivityReachability, ConnectivityStatus } from '@flighthq/types/contract';
1
+ import { createEntity } from '@flighthq/entity/contract';
2
+ import { connectSignal, hasSignalSlots } from '@flighthq/signals/contract';
3
+ import type {
4
+ ConnectivityChangeBackend,
5
+ ConnectivityReachabilityBackend,
6
+ ConnectivityStatus,
7
+ ConnectivityStatusBackend,
8
+ } from '@flighthq/types/contract';
9
+ import { EntityRuntimeKey } from '@flighthq/types/contract';
3
10
 
4
11
  import {
5
12
  attachConnectivity,
6
13
  createConnectivity,
7
- createConnectivityStatus,
8
- createWebConnectivityBackend,
9
14
  detachConnectivity,
10
15
  detectConnectivityReachability,
11
16
  disposeConnectivity,
12
- getConnectivityBackend,
17
+ destroyConnectivity,
18
+ getConnectivityOnline,
13
19
  getConnectivityStatus,
14
20
  hasConnectivityStatusChanged,
15
21
  isConnectivityMetered,
16
- isConnectivityOnline,
17
22
  isConnectivitySaveDataEnabled,
18
- setConnectivityBackend,
19
- explainConnectivityBackend,
20
- installConnectivityHostBackend,
21
- observeConnectivityHostResult,
22
- resetConnectivityBackendForTest,
23
23
  } from './connectivity';
24
24
 
25
- function fakeBackend(
26
- overrides?: Partial<ConnectivityStatus>,
27
- ): ConnectivityBackend & { status: ConnectivityStatus; fire: () => void } {
28
- let listener: (() => void) | null = null;
29
- const status: ConnectivityStatus = {
25
+ interface FakeConnectivityProvider extends ConnectivityStatusBackend, ConnectivityChangeBackend {
26
+ readonly activeSubscriptions: () => number;
27
+ readonly destroyCalls: () => number;
28
+ fire(): void;
29
+ readonly status: ConnectivityStatus;
30
+ readonly unsubscribeCalls: () => number;
31
+ }
32
+
33
+ function fakeProvider(
34
+ overrides: Partial<ConnectivityStatus> = {},
35
+ subscriptionAvailable = true,
36
+ ): FakeConnectivityProvider {
37
+ const listeners = new Set<() => void>();
38
+ const current = status({
30
39
  downlink: 10,
31
40
  downlinkMax: 100,
32
41
  effectiveType: '4g',
33
- metered: false,
34
42
  online: true,
35
43
  rtt: 50,
36
- saveData: false,
37
44
  type: 'wifi',
38
45
  ...overrides,
39
- };
40
- return {
41
- status,
42
- getStatus(out) {
43
- out.online = status.online;
44
- out.type = status.type;
45
- out.downlink = status.downlink;
46
- out.downlinkMax = status.downlinkMax;
47
- out.effectiveType = status.effectiveType;
48
- out.rtt = status.rtt;
49
- out.saveData = status.saveData;
50
- out.metered = status.metered;
51
- return out;
52
- },
53
- subscribe(l) {
54
- listener = l;
55
- return () => {
56
- listener = null;
57
- };
58
- },
59
- fire() {
60
- listener?.();
46
+ });
47
+ let destroys = 0;
48
+ let unsubscribes = 0;
49
+ let destroyed = false;
50
+ return Object.assign(
51
+ createEntity({
52
+ destroy() {
53
+ if (destroyed) return;
54
+ destroyed = true;
55
+ destroys++;
56
+ listeners.clear();
57
+ },
58
+ getStatus(out: ConnectivityStatus) {
59
+ Object.assign(out, current);
60
+ return out;
61
+ },
62
+ subscribe(listener: () => void) {
63
+ if (!subscriptionAvailable || destroyed) return null;
64
+ listeners.add(listener);
65
+ let active = true;
66
+ return () => {
67
+ if (!active) return;
68
+ active = false;
69
+ unsubscribes++;
70
+ listeners.delete(listener);
71
+ };
72
+ },
73
+ }),
74
+ {
75
+ activeSubscriptions: () => listeners.size,
76
+ destroyCalls: () => destroys,
77
+ fire: () => {
78
+ for (const listener of [...listeners]) listener();
79
+ },
80
+ status: current,
81
+ unsubscribeCalls: () => unsubscribes,
61
82
  },
62
- };
83
+ );
63
84
  }
64
85
 
65
- afterEach(() => setConnectivityBackend(null));
86
+ function hostFor(provider: FakeConnectivityProvider) {
87
+ return { connectivity: { change: provider, status: provider } };
88
+ }
89
+
90
+ function status(overrides: Partial<ConnectivityStatus> = {}): ConnectivityStatus {
91
+ return {
92
+ downlink: -1,
93
+ downlinkMax: -1,
94
+ effectiveType: '',
95
+ metered: false,
96
+ online: null,
97
+ rtt: -1,
98
+ saveData: false,
99
+ type: 'unknown',
100
+ ...overrides,
101
+ };
102
+ }
66
103
 
67
104
  describe('attachConnectivity', () => {
68
- it('emits onChange and onOffline on transition to offline', () => {
69
- const backend = fakeBackend();
70
- setConnectivityBackend(backend);
71
- const net = createConnectivity();
72
- let changes = 0;
105
+ it('returns true and emits core diffs from a raw provider change', () => {
106
+ const provider = fakeProvider();
107
+ const connectivity = createConnectivity();
108
+ const changes: ConnectivityStatus[] = [];
109
+ const types: string[] = [];
110
+ const metered: boolean[] = [];
73
111
  let offline = 0;
74
- connectSignal(net.onChange, () => changes++);
75
- connectSignal(net.onOffline, () => offline++);
76
- attachConnectivity(net);
77
- backend.status.online = false;
78
- (backend as ReturnType<typeof fakeBackend>).fire();
79
- expect(changes).toBe(1);
80
- expect(offline).toBe(1);
81
- });
112
+ connectSignal(connectivity.onChange, (value) => changes.push(value as ConnectivityStatus));
113
+ connectSignal(connectivity.onConnectionTypeChange, (value) => types.push(value));
114
+ connectSignal(connectivity.onMeteredChange, (value) => metered.push(value));
115
+ connectSignal(connectivity.onOffline, () => offline++);
82
116
 
83
- it('emits onOnline on transition to online', () => {
84
- const backend = fakeBackend({ online: false });
85
- setConnectivityBackend(backend);
86
- const net = createConnectivity();
87
- let online = 0;
88
- connectSignal(net.onOnline, () => online++);
89
- attachConnectivity(net);
90
- backend.status.online = true;
91
- (backend as ReturnType<typeof fakeBackend>).fire();
92
- expect(online).toBe(1);
93
- });
117
+ expect(attachConnectivity(hostFor(provider), connectivity)).toBe(true);
118
+ Object.assign(provider.status, { metered: true, online: false, type: 'cellular' as const });
119
+ provider.fire();
94
120
 
95
- it('emits onConnectionTypeChange on type transition', () => {
96
- const backend = fakeBackend();
97
- setConnectivityBackend(backend);
98
- const net = createConnectivity();
99
- const types: string[] = [];
100
- connectSignal(net.onConnectionTypeChange, (t) => types.push(t));
101
- attachConnectivity(net);
102
- backend.status.type = 'cellular';
103
- (backend as ReturnType<typeof fakeBackend>).fire();
121
+ expect(changes).toHaveLength(1);
122
+ expect(changes[0]).toMatchObject({ metered: true, online: false, type: 'cellular' });
104
123
  expect(types).toEqual(['cellular']);
124
+ expect(metered).toEqual([true]);
125
+ expect(offline).toBe(1);
105
126
  });
106
127
 
107
- it('does not emit onConnectionTypeChange when type is unchanged', () => {
108
- const backend = fakeBackend();
109
- setConnectivityBackend(backend);
110
- const net = createConnectivity();
111
- let count = 0;
112
- connectSignal(net.onConnectionTypeChange, () => count++);
113
- attachConnectivity(net);
114
- // fire without changing type
115
- (backend as ReturnType<typeof fakeBackend>).fire();
116
- expect(count).toBe(0);
128
+ it('treats unknown as unmeasured and emits offline only after a measured transition', () => {
129
+ const provider = fakeProvider({ online: null });
130
+ const connectivity = createConnectivity();
131
+ let offline = 0;
132
+ connectSignal(connectivity.onOffline, () => offline++);
133
+ attachConnectivity(hostFor(provider), connectivity);
134
+ provider.fire();
135
+ expect(offline).toBe(0);
136
+ provider.status.online = false;
137
+ provider.fire();
138
+ expect(offline).toBe(1);
117
139
  });
118
140
 
119
- it('emits onMeteredChange on metered transition', () => {
120
- const backend = fakeBackend();
121
- setConnectivityBackend(backend);
122
- const net = createConnectivity();
123
- const metered: boolean[] = [];
124
- connectSignal(net.onMeteredChange, (m) => metered.push(m));
125
- attachConnectivity(net);
126
- backend.status.metered = true;
127
- (backend as ReturnType<typeof fakeBackend>).fire();
128
- expect(metered).toEqual([true]);
141
+ it('returns false instead of retaining a silent no-op subscription', () => {
142
+ const provider = fakeProvider({}, false);
143
+ expect(attachConnectivity(hostFor(provider), createConnectivity())).toBe(false);
144
+ expect(provider.activeSubscriptions()).toBe(0);
129
145
  });
130
146
 
131
- it('is idempotent — replaces prior subscription', () => {
132
- const backend = fakeBackend();
133
- setConnectivityBackend(backend);
134
- const net = createConnectivity();
147
+ it('detaches A before attaching B and never delivers from the old origin', () => {
148
+ const a = fakeProvider();
149
+ const b = fakeProvider();
150
+ const connectivity = createConnectivity();
135
151
  let changes = 0;
136
- connectSignal(net.onChange, () => changes++);
137
- attachConnectivity(net);
138
- attachConnectivity(net);
139
- (backend as ReturnType<typeof fakeBackend>).fire();
152
+ connectSignal(connectivity.onChange, () => changes++);
153
+ attachConnectivity(hostFor(a), connectivity);
154
+ attachConnectivity(hostFor(b), connectivity);
155
+ expect(a.unsubscribeCalls()).toBe(1);
156
+ expect(a.activeSubscriptions()).toBe(0);
157
+ expect(b.activeSubscriptions()).toBe(1);
158
+ a.fire();
159
+ expect(changes).toBe(0);
160
+ b.fire();
140
161
  expect(changes).toBe(1);
141
162
  });
142
- });
143
163
 
144
- describe('createConnectivity', () => {
145
- it('creates an entity with five signals', () => {
146
- const net = createConnectivity();
147
- expect(net.onChange).toBeDefined();
148
- expect(net.onConnectionTypeChange).toBeDefined();
149
- expect(net.onMeteredChange).toBeDefined();
150
- expect(net.onOnline).toBeDefined();
151
- expect(net.onOffline).toBeDefined();
164
+ it('emits distinct status snapshots rather than mutating a retained prior payload', () => {
165
+ const provider = fakeProvider();
166
+ const connectivity = createConnectivity();
167
+ const changes: Readonly<ConnectivityStatus>[] = [];
168
+ connectSignal(connectivity.onChange, (value) => changes.push(value));
169
+ attachConnectivity(hostFor(provider), connectivity);
170
+ provider.fire();
171
+ provider.status.type = 'cellular';
172
+ provider.fire();
173
+ expect(changes[0]).not.toBe(changes[1]);
174
+ expect(changes[0]?.type).toBe('wifi');
175
+ expect(changes[1]?.type).toBe('cellular');
152
176
  });
153
177
  });
154
178
 
155
- describe('createConnectivityStatus', () => {
156
- it('allocates a zeroed status with all fields at sentinel values', () => {
157
- expect(createConnectivityStatus()).toEqual({
158
- downlink: -1,
159
- downlinkMax: -1,
160
- effectiveType: '',
161
- metered: false,
162
- online: false,
163
- rtt: -1,
164
- saveData: false,
165
- type: 'unknown',
166
- });
179
+ describe('createConnectivity', () => {
180
+ it('returns an Entity carrying all five core-owned signals', () => {
181
+ const connectivity = createConnectivity();
182
+ expect(EntityRuntimeKey in connectivity).toBe(true);
183
+ expect(Object.keys(connectivity).sort()).toEqual([
184
+ 'onChange',
185
+ 'onConnectionTypeChange',
186
+ 'onMeteredChange',
187
+ 'onOffline',
188
+ 'onOnline',
189
+ ]);
167
190
  });
168
191
  });
169
192
 
170
- describe('createWebConnectivityBackend', () => {
171
- it('reads a status without throwing', () => {
172
- const out = createConnectivityStatus();
173
- const result = createWebConnectivityBackend().getStatus(out);
174
- expect(typeof result.online).toBe('boolean');
175
- expect(typeof result.metered).toBe('boolean');
176
- expect(typeof result.saveData).toBe('boolean');
177
- });
178
-
179
- it('returns sentinel rtt and downlinkMax when connection is absent', () => {
180
- const out = createConnectivityStatus();
181
- createWebConnectivityBackend().getStatus(out);
182
- // In jsdom, navigator.connection is absent so rtt/downlinkMax default to -1
183
- expect(out.rtt).toBe(-1);
184
- expect(out.downlinkMax).toBe(-1);
193
+ describe('destroyConnectivity', () => {
194
+ it('uses the supplied change provider and provider teardown is terminal/idempotent', () => {
195
+ const provider = fakeProvider();
196
+ const host = hostFor(provider);
197
+ destroyConnectivity(host);
198
+ destroyConnectivity(host);
199
+ expect(provider.destroyCalls()).toBe(1);
200
+ expect(provider.subscribe(() => {})).toBeNull();
185
201
  });
186
202
  });
187
203
 
188
204
  describe('detachConnectivity', () => {
189
- it('stops further delivery', () => {
190
- const backend = fakeBackend();
191
- setConnectivityBackend(backend);
192
- const net = createConnectivity();
193
- let changes = 0;
194
- connectSignal(net.onChange, () => changes++);
195
- attachConnectivity(net);
196
- detachConnectivity(net);
197
- (backend as ReturnType<typeof fakeBackend>).fire();
198
- expect(changes).toBe(0);
199
- });
200
-
201
- it('is safe to call when not attached', () => {
202
- const net = createConnectivity();
203
- expect(() => detachConnectivity(net)).not.toThrow();
205
+ it('consumes the exact unsubscribe once', () => {
206
+ const provider = fakeProvider();
207
+ const connectivity = createConnectivity();
208
+ attachConnectivity(hostFor(provider), connectivity);
209
+ detachConnectivity(connectivity);
210
+ detachConnectivity(connectivity);
211
+ expect(provider.unsubscribeCalls()).toBe(1);
204
212
  });
205
213
  });
206
214
 
207
215
  describe('detectConnectivityReachability', () => {
208
- // A backend without detectReachability falls through to the web backend's own implementation, which
209
- // calls fetch against options.url. BOTH CASES BELOW STUB fetch, because leaving it alone made this a
210
- // real request to the public internet from a unit test: 4.1s measured here against a 5000ms default
211
- // timeout, which full-suite load pushes it over. Egress is not what makes that a hazard — a sandboxed
212
- // runner with no outbound network fails it every time rather than intermittently, so the current
213
- // flakiness is a property of this machine's network rather than of the test. `unstubGlobals` restores
214
- // the real fetch between tests.
215
- //
216
- // The assertion is the sentinel itself — reachable false AND latency -1 — rather than the shape of
217
- // the value. A `typeof result.reachable === 'boolean'` check passes on a live success too, so it held
218
- // for a test whose name promises a sentinel while checking nothing that distinguishes one.
219
- it('returns a sentinel when fetch is unavailable (SSR/jsdom guard)', async () => {
220
- setConnectivityBackend(fakeBackend());
221
- vi.stubGlobal('fetch', undefined);
222
- const out: ConnectivityReachability = { latency: 0, reachable: true };
223
- const result = await detectConnectivityReachability({ url: 'https://example.com' }, out);
224
- expect(result).toBe(out);
225
- expect(result.reachable).toBe(false);
226
- expect(result.latency).toBe(-1);
227
- });
228
-
229
- // The fallback's other sentinel path: fetch exists and the HEAD request rejects. Same contract, and
230
- // it was unpinned — the absent-fetch branch could have returned the sentinel while this one did not.
231
- it('returns a sentinel when the HEAD request fails', async () => {
232
- setConnectivityBackend(fakeBackend());
233
- vi.stubGlobal('fetch', () => Promise.reject(new Error('network unreachable')));
234
- const out: ConnectivityReachability = { latency: 0, reachable: true };
235
- const result = await detectConnectivityReachability({ url: 'https://example.com' }, out);
236
- expect(result).toBe(out);
237
- expect(result.reachable).toBe(false);
238
- expect(result.latency).toBe(-1);
239
- });
240
-
241
- it('uses the backend detectReachability when available', async () => {
242
- const probeBackend: ConnectivityBackend = {
243
- ...fakeBackend(),
244
- async detectReachability(_opts, out) {
216
+ it('dispatches reachability only to the supplied reachability slot', async () => {
217
+ let calls = 0;
218
+ const reachability = createEntity({
219
+ async detectReachability(_options, out) {
220
+ calls++;
221
+ out.latency = 7;
245
222
  out.reachable = true;
246
- out.latency = 42;
247
223
  return out;
248
224
  },
249
- };
250
- setConnectivityBackend(probeBackend);
251
- const out: ConnectivityReachability = { latency: 0, reachable: false };
252
- const result = await detectConnectivityReachability({ url: 'https://example.com' }, out);
253
- expect(result.reachable).toBe(true);
254
- expect(result.latency).toBe(42);
225
+ } satisfies Omit<ConnectivityReachabilityBackend, typeof EntityRuntimeKey>);
226
+ const out = { latency: -1, reachable: false };
227
+ const result = await detectConnectivityReachability(
228
+ { connectivity: { reachability } },
229
+ { url: 'https://example.invalid' },
230
+ out,
231
+ );
232
+ expect(result).toBe(out);
233
+ expect(result).toEqual({ latency: 7, reachable: true });
234
+ expect(calls).toBe(1);
255
235
  });
256
236
  });
257
237
 
258
238
  describe('disposeConnectivity', () => {
259
- it('detaches the subscription', () => {
260
- const backend = fakeBackend();
261
- setConnectivityBackend(backend);
262
- const net = createConnectivity();
263
- attachConnectivity(net);
264
- expect(() => disposeConnectivity(net)).not.toThrow();
265
- });
266
-
267
- it('is safe to call when not attached', () => {
268
- const net = createConnectivity();
269
- expect(() => disposeConnectivity(net)).not.toThrow();
270
- });
271
- });
272
-
273
- describe('explainConnectivityBackend', () => {
274
- afterEach(() => resetConnectivityBackendForTest());
275
-
276
- it('reports host-not-enabled when no backend is installed', () => {
277
- resetConnectivityBackendForTest();
278
- const explanation = explainConnectivityBackend();
279
- expect(explanation.layer).toBe('host-not-enabled');
280
- expect(explanation.conflict).toBe(false);
281
- expect(explanation.viability).toBe('unobserved');
282
- });
283
-
284
- it('reports custom layer when a custom backend is set', () => {
285
- setConnectivityBackend(fakeBackend());
286
- expect(explainConnectivityBackend().layer).toBe('custom');
287
- });
288
-
289
- it('reports host layer when a host backend is installed', () => {
290
- installConnectivityHostBackend(fakeBackend());
291
- expect(explainConnectivityBackend().layer).toBe('host');
292
- });
293
-
294
- it('reports conflict when two different host backends are installed', () => {
295
- installConnectivityHostBackend(fakeBackend());
296
- installConnectivityHostBackend(fakeBackend());
297
- expect(explainConnectivityBackend().conflict).toBe(true);
239
+ it('unsubscribes once and clears listeners from every signal', () => {
240
+ const provider = fakeProvider();
241
+ const connectivity = createConnectivity();
242
+ attachConnectivity(hostFor(provider), connectivity);
243
+ connectSignal(connectivity.onChange, () => {});
244
+ connectSignal(connectivity.onConnectionTypeChange, () => {});
245
+ connectSignal(connectivity.onMeteredChange, () => {});
246
+ connectSignal(connectivity.onOffline, () => {});
247
+ connectSignal(connectivity.onOnline, () => {});
248
+ disposeConnectivity(connectivity);
249
+ disposeConnectivity(connectivity);
250
+ expect(provider.unsubscribeCalls()).toBe(1);
251
+ expect([
252
+ hasSignalSlots(connectivity.onChange),
253
+ hasSignalSlots(connectivity.onConnectionTypeChange),
254
+ hasSignalSlots(connectivity.onMeteredChange),
255
+ hasSignalSlots(connectivity.onOffline),
256
+ hasSignalSlots(connectivity.onOnline),
257
+ ]).toEqual([false, false, false, false, false]);
298
258
  });
299
259
  });
300
260
 
301
- describe('getConnectivityBackend', () => {
302
- it('falls back to a web backend', () => {
303
- expect(getConnectivityBackend()).not.toBeNull();
261
+ describe('getConnectivityOnline', () => {
262
+ it('preserves unknown and measured online states', () => {
263
+ expect(getConnectivityOnline(hostFor(fakeProvider({ online: null })))).toBeNull();
264
+ expect(getConnectivityOnline(hostFor(fakeProvider({ online: true })))).toBe(true);
304
265
  });
305
266
  });
306
267
 
307
268
  describe('getConnectivityStatus', () => {
308
- it('fills the out parameter from the backend', () => {
309
- setConnectivityBackend(fakeBackend());
310
- const out = createConnectivityStatus();
311
- expect(getConnectivityStatus(out)).toBe(out);
269
+ it('reads status and level conveniences only from the supplied host', () => {
270
+ const provider = fakeProvider({ metered: true, online: false, saveData: true });
271
+ const host = hostFor(provider);
272
+ const out = status();
273
+ expect(getConnectivityStatus(host, out)).toBe(out);
312
274
  expect(out.type).toBe('wifi');
313
- expect(out.rtt).toBe(50);
314
- expect(out.downlinkMax).toBe(100);
315
275
  });
316
276
  });
317
277
 
318
278
  describe('hasConnectivityStatusChanged', () => {
319
- it('returns false for equal statuses', () => {
320
- const a = createConnectivityStatus();
321
- const b = createConnectivityStatus();
322
- expect(hasConnectivityStatusChanged(a, b)).toBe(false);
323
- });
324
-
325
- it('returns true when online differs', () => {
326
- const a = { ...createConnectivityStatus(), online: true };
327
- const b = createConnectivityStatus();
328
- expect(hasConnectivityStatusChanged(a, b)).toBe(true);
329
- });
330
-
331
- it('returns true when type differs', () => {
332
- const a = { ...createConnectivityStatus(), type: 'wifi' as const };
333
- const b = createConnectivityStatus();
334
- expect(hasConnectivityStatusChanged(a, b)).toBe(true);
335
- });
336
-
337
- it('returns true when rtt differs', () => {
338
- const a = { ...createConnectivityStatus(), rtt: 30 };
339
- const b = createConnectivityStatus();
340
- expect(hasConnectivityStatusChanged(a, b)).toBe(true);
341
- });
342
-
343
- it('returns true when saveData differs', () => {
344
- const a = { ...createConnectivityStatus(), saveData: true };
345
- const b = createConnectivityStatus();
346
- expect(hasConnectivityStatusChanged(a, b)).toBe(true);
347
- });
348
-
349
- it('returns true when metered differs', () => {
350
- const a = { ...createConnectivityStatus(), metered: true };
351
- const b = createConnectivityStatus();
352
- expect(hasConnectivityStatusChanged(a, b)).toBe(true);
353
- });
354
-
355
- it('is alias-safe — same object returns false', () => {
356
- const s = createConnectivityStatus();
357
- expect(hasConnectivityStatusChanged(s, s)).toBe(false);
358
- });
359
- });
360
-
361
- describe('installConnectivityHostBackend', () => {
362
- afterEach(() => resetConnectivityBackendForTest());
363
-
364
- it('installs a host backend that getConnectivityBackend returns', () => {
365
- const backend = fakeBackend();
366
- installConnectivityHostBackend(backend);
367
- expect(getConnectivityBackend()).toBe(backend);
368
- });
369
-
370
- it('is first-host-wins: a second different backend sets conflict', () => {
371
- const first = fakeBackend();
372
- const second = fakeBackend();
373
- installConnectivityHostBackend(first);
374
- installConnectivityHostBackend(second);
375
- expect(getConnectivityBackend()).toBe(first);
376
- expect(explainConnectivityBackend().conflict).toBe(true);
279
+ it('returns false for equal or aliased snapshots', () => {
280
+ const a = status();
281
+ expect(hasConnectivityStatusChanged(a, { ...a })).toBe(false);
282
+ expect(hasConnectivityStatusChanged(a, a)).toBe(false);
283
+ });
284
+
285
+ it('detects every status axis', () => {
286
+ const base = status();
287
+ const variants: ConnectivityStatus[] = [
288
+ status({ online: true }),
289
+ status({ type: 'wifi' }),
290
+ status({ downlink: 1 }),
291
+ status({ downlinkMax: 1 }),
292
+ status({ effectiveType: '4g' }),
293
+ status({ rtt: 1 }),
294
+ status({ saveData: true }),
295
+ status({ metered: true }),
296
+ ];
297
+ expect(variants.map((variant) => hasConnectivityStatusChanged(base, variant))).toEqual([
298
+ true,
299
+ true,
300
+ true,
301
+ true,
302
+ true,
303
+ true,
304
+ true,
305
+ true,
306
+ ]);
377
307
  });
378
308
  });
379
309
 
380
310
  describe('isConnectivityMetered', () => {
381
- it('returns false for a non-metered connection', () => {
382
- setConnectivityBackend(fakeBackend({ metered: false }));
383
- expect(isConnectivityMetered()).toBe(false);
384
- });
385
-
386
- it('returns true for a metered connection', () => {
387
- setConnectivityBackend(fakeBackend({ metered: true }));
388
- expect(isConnectivityMetered()).toBe(true);
389
- });
390
- });
391
-
392
- describe('isConnectivityOnline', () => {
393
- it('reflects the backend online flag', () => {
394
- const backend = fakeBackend({ online: false });
395
- setConnectivityBackend(backend);
396
- expect(isConnectivityOnline()).toBe(false);
397
- });
398
-
399
- it('returns true when the backend is online', () => {
400
- setConnectivityBackend(fakeBackend({ online: true }));
401
- expect(isConnectivityOnline()).toBe(true);
311
+ it('reads the metered level from the supplied status witness', () => {
312
+ expect(isConnectivityMetered(hostFor(fakeProvider({ metered: true })))).toBe(true);
402
313
  });
403
314
  });
404
315
 
405
316
  describe('isConnectivitySaveDataEnabled', () => {
406
- it('returns false when saveData is off', () => {
407
- setConnectivityBackend(fakeBackend({ saveData: false }));
408
- expect(isConnectivitySaveDataEnabled()).toBe(false);
409
- });
410
-
411
- it('returns true when saveData is on', () => {
412
- setConnectivityBackend(fakeBackend({ saveData: true }));
413
- expect(isConnectivitySaveDataEnabled()).toBe(true);
414
- });
415
- });
416
-
417
- describe('observeConnectivityHostResult', () => {
418
- afterEach(() => resetConnectivityBackendForTest());
419
-
420
- it('records a successful observation', () => {
421
- installConnectivityHostBackend(fakeBackend());
422
- observeConnectivityHostResult('getStatus', true);
423
- const explanation = explainConnectivityBackend();
424
- expect(explanation.operation).toBe('getStatus');
425
- expect(explanation.viability).toBe('available');
426
- });
427
-
428
- it('records a failed observation', () => {
429
- installConnectivityHostBackend(fakeBackend());
430
- observeConnectivityHostResult('getStatus', false);
431
- expect(explainConnectivityBackend().viability).toBe('runtime-api-unavailable');
432
- });
433
- });
434
-
435
- describe('resetConnectivityBackendForTest', () => {
436
- it('clears all backend slots', () => {
437
- setConnectivityBackend(fakeBackend());
438
- installConnectivityHostBackend(fakeBackend());
439
- observeConnectivityHostResult('getStatus', true);
440
- resetConnectivityBackendForTest();
441
- expect(explainConnectivityBackend().layer).toBe('host-not-enabled');
442
- expect(explainConnectivityBackend().conflict).toBe(false);
443
- expect(explainConnectivityBackend().viability).toBe('unobserved');
444
- });
445
- });
446
-
447
- describe('setConnectivityBackend', () => {
448
- it('clears back to the web fallback when passed null', () => {
449
- setConnectivityBackend(fakeBackend());
450
- setConnectivityBackend(null);
451
- expect(getConnectivityBackend()).not.toBeNull();
317
+ it('reads the save-data level from the supplied status witness', () => {
318
+ expect(isConnectivitySaveDataEnabled(hostFor(fakeProvider({ saveData: true })))).toBe(true);
452
319
  });
453
320
  });