@symbiote-native/cellular 3.0.1 → 3.0.3

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,15 +1,15 @@
1
1
  # @symbiote-native/cellular
2
2
 
3
3
  A wrapper package for [SymbioteNative](../../README.md) that makes
4
- [`expo-cellular`](https://docs.expo.dev/versions/latest/sdk/cellular/) — cellular connection
5
- generation, carrier/SIM info, and VoIP-support detection — reachable from **every** adapter:
4
+ [`expo-cellular`](https://docs.expo.dev/versions/latest/sdk/cellular/) - cellular connection
5
+ generation, carrier/SIM info, and VoIP-support detection - reachable from **every** adapter:
6
6
  React, Vue, Svelte, Solid, and Angular, not just React. Like [`@symbiote-native/sensors`](../sensors) and
7
7
  unlike this repo's plain-RN-module/view wrappers ([`@symbiote-native/slider`](../slider),
8
8
  [`@symbiote-native/splash-screen`](../splash-screen)), `expo-cellular` is built on
9
9
  `expo-modules-core`, so its native code autolinks straight out of `node_modules` via
10
- `expo-modules-autolinking` — no proxy `react-native.config.cjs`/podspec to ship. `expo-cellular`'s
10
+ `expo-modules-autolinking` - no proxy `react-native.config.cjs`/podspec to ship. `expo-cellular`'s
11
11
  own JS is never imported (it hard-imports the full `expo` meta-package, which this project never
12
- depends on) — every function is hand-ported into this package's own `core/`.
12
+ depends on) - every function is hand-ported into this package's own `core/`.
13
13
 
14
14
  ## Install
15
15
 
@@ -25,39 +25,39 @@ npx @symbiote-native/cli new my-app --cellular
25
25
  npx @symbiote-native/cli add --cellular
26
26
  ```
27
27
 
28
- Either way: installs `@symbiote-native/cellular` and wires the native autolinking automatically —
28
+ Either way: installs `@symbiote-native/cellular` and wires the native autolinking automatically,
29
29
  see [`@symbiote-native/cli`](../cli).
30
30
 
31
31
  <details>
32
- <summary>Manual install (no CLI — installing and wiring native autolinking by hand)</summary>
32
+ <summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
33
33
 
34
34
  ```bash
35
35
  npm install @symbiote-native/cellular
36
36
  ```
37
37
 
38
38
  Depends on `expo-cellular` and `expo-modules-core` directly (regular dependencies, pinned to
39
- exact versions — never a caret range, since this package's `core/` is hand-ported against one
39
+ exact versions - never a caret range, since this package's `core/` is hand-ported against one
40
40
  specific native API shape and a newer resolve could silently drift the two apart). Never install
41
- `expo-cellular` yourself, and never add the `expo` meta-package to this project — it bundles its
41
+ `expo-cellular` yourself, and never add the `expo` meta-package to this project - it bundles its
42
42
  own Metro/Babel pipeline that conflicts with this project's own.
43
43
 
44
44
  ### Required one-time step: native autolinking wiring
45
45
 
46
46
  Unlike a plain RN native module, `expo-cellular`'s native code is discovered by
47
- `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism — this needs wiring
47
+ `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism - this needs wiring
48
48
  into the native host app **once**, covering this package and every other `expo-modules-core`
49
49
  package with zero further changes:
50
50
 
51
51
  | Platform | Touches |
52
52
  | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
53
- | iOS | `ios/Podfile` — add `use_expo_modules!` |
54
- | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
55
- | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
56
- | Android | `MainApplication.kt` — Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
57
- | Android | `AndroidManifest.xml` — the `READ_PHONE_STATE` permission, needed to read carrier/SIM info |
58
-
59
- Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
60
- peer-dependency exclusion list, per-module permission strings — live in the
53
+ | iOS | `ios/Podfile` - add `use_expo_modules!` |
54
+ | iOS | `AppDelegate.swift` - Expo's runtime-bootstrap hook |
55
+ | Android | `settings.gradle` / `app/build.gradle` - resolve and include the Expo Gradle projects |
56
+ | Android | `MainApplication.kt` - Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
57
+ | Android | `AndroidManifest.xml` - the `READ_PHONE_STATE` permission, needed to read carrier/SIM info |
58
+
59
+ Full mechanics - the Podfile pieces that normally ship inside the `expo` package, the `expo`
60
+ peer-dependency exclusion list, per-module permission strings - live in the
61
61
  `symbiote-expo-native-module` project skill. This wiring is already done for all six
62
62
  `examples/expo-*` canary apps (React, Vue SFC, Vue TSX, Svelte, Solid, Angular).
63
63
 
@@ -69,16 +69,16 @@ peer-dependency exclusion list, per-module permission strings — live in the
69
69
  src/core/ cellular.ts (the free functions) + types.ts (CellularGeneration enum) +
70
70
  native-module.ts, which resolves the native module by name through
71
71
  expo-modules-core's requireNativeModule.
72
- src/react/hooks/ @symbiote-native/cellular/react — usePermissions
73
- src/vue/composables/ @symbiote-native/cellular/vue — usePermissions
74
- src/svelte/runes/ @symbiote-native/cellular/svelte — usePermissions
75
- src/solid/primitives/ @symbiote-native/cellular/solid — createPermissions (Solid says
72
+ src/react/hooks/ @symbiote-native/cellular/react - usePermissions
73
+ src/vue/composables/ @symbiote-native/cellular/vue - usePermissions
74
+ src/svelte/runes/ @symbiote-native/cellular/svelte - usePermissions
75
+ src/solid/primitives/ @symbiote-native/cellular/solid - createPermissions (Solid says
76
76
  create*, not use*)
77
- src/angular/services/ @symbiote-native/cellular/angular — PermissionsService
77
+ src/angular/services/ @symbiote-native/cellular/angular - PermissionsService
78
78
  ```
79
79
 
80
80
  Each adapter's `usePermissions` hook/composable/service is a thin lifecycle wrapper (fetch on
81
- mount, expose `request`/`get` to re-check imperatively) over the same `core` functions — the
81
+ mount, expose `request`/`get` to re-check imperatively) over the same `core` functions - the
82
82
  permission-fetch logic is written once and shared by every adapter.
83
83
 
84
84
  ## Use it
@@ -267,7 +267,7 @@ export class CellularScreen {
267
267
  }
268
268
  ```
269
269
 
270
- Trimmed from the real demo screens — `examples/expo-react/screens/CellularScreen.tsx` and its
270
+ Trimmed from the real demo screens - `examples/expo-react/screens/CellularScreen.tsx` and its
271
271
  Vue SFC, Vue TSX, Svelte, Solid, and Angular twins (`examples/expo-vue-sfc/screens/CellularScreen.vue`,
272
272
  `examples/expo-vue-tsx/screens/CellularScreen.tsx`, `examples/expo-svelte/screens/CellularScreen.svelte`,
273
273
  `examples/expo-solid/screens/CellularScreen.tsx`, `examples/expo-angular/src/screens/CellularScreen.ts`),
@@ -276,12 +276,12 @@ which also cover `allowsVoipAsync`/`getIsoCountryCodeAsync`/`getMobileCountryCod
276
276
 
277
277
  ## API
278
278
 
279
- Mostly stateless async functions, no event stream — plus one stateful pair,
279
+ Mostly stateless async functions, no event stream - plus one stateful pair,
280
280
  `getPermissionsAsync`/`requestPermissionsAsync`, which gets its own lifecycle
281
281
  hook/composable/service (`usePermissions`) so a component can re-check permission status
282
282
  imperatively without re-wiring the fetch itself.
283
283
 
284
- Carrier/SIM info and VoIP support are **Android only** — every one of those functions resolves
284
+ Carrier/SIM info and VoIP support are **Android only** - every one of those functions resolves
285
285
  `null` on iOS without touching the native module at all, since iOS doesn't expose this
286
286
  information. `getPermissionsAsync`/`requestPermissionsAsync` need no real permission grant on
287
287
  iOS either (cellular info requires none there), so they resolve a plain `GRANTED` response
@@ -290,26 +290,26 @@ without a native call.
290
290
  ```ts
291
291
  getCellularGenerationAsync(): Promise<CellularGeneration>
292
292
  // UNKNOWN/CELLULAR_2G/CELLULAR_3G/CELLULAR_4G/CELLULAR_5G. Delegates to the native module on
293
- // every platform — no iOS gate, unlike every other function below.
293
+ // every platform - no iOS gate, unlike every other function below.
294
294
 
295
295
  allowsVoipAsync(): Promise<boolean | null>
296
296
  // @deprecated (upstream is deprecating this). null on iOS, native call on Android.
297
297
 
298
- getIsoCountryCodeAsync(): Promise<string | null> // Android only — null on iOS.
299
- getCarrierNameAsync(): Promise<string | null> // Android only — null on iOS.
300
- getMobileCountryCodeAsync(): Promise<string | null> // Android only — null on iOS.
301
- getMobileNetworkCodeAsync(): Promise<string | null> // Android only — null on iOS.
298
+ getIsoCountryCodeAsync(): Promise<string | null> // Android only - null on iOS.
299
+ getCarrierNameAsync(): Promise<string | null> // Android only - null on iOS.
300
+ getMobileCountryCodeAsync(): Promise<string | null> // Android only - null on iOS.
301
+ getMobileNetworkCodeAsync(): Promise<string | null> // Android only - null on iOS.
302
302
 
303
303
  getPermissionsAsync(): Promise<PermissionResponse>
304
304
  // Android delegates to the native module; every other platform resolves a plain GRANTED
305
- // literal — no permission is needed there for cellular info.
305
+ // literal - no permission is needed there for cellular info.
306
306
 
307
307
  requestPermissionsAsync(): Promise<PermissionResponse>
308
308
  // Same split as getPermissionsAsync.
309
309
  ```
310
310
 
311
311
  Plus `CellularGeneration` (enum) and `PermissionResponse`/`PermissionStatus`/
312
- `PermissionExpiration` — the latter three re-exported straight from `expo-modules-core`, not
312
+ `PermissionExpiration` - the latter three re-exported straight from `expo-modules-core`, not
313
313
  ported by hand.
314
314
 
315
315
  ```ts
@@ -329,41 +329,50 @@ import { PermissionsService } from '@symbiote-native/cellular/angular';
329
329
  ### `usePermissions`
330
330
 
331
331
  Fetches the current permission status on mount, then exposes `request`/`get` to re-check it
332
- imperatively — every adapter follows the same shape as its sibling `PermissionsService`/
332
+ imperatively - every adapter follows the same shape as its sibling `PermissionsService`/
333
333
  composable in `@symbiote-native/brightness`, the other package in this repo needing the same
334
334
  get/request permission pair.
335
335
 
336
336
  ```tsx
337
- // React — a [status, request, get] tuple, mirroring upstream's own usePermissions hooks
337
+ // React - a [status, request, get] tuple, mirroring upstream's own usePermissions hooks
338
338
  const [status, requestPermission, getPermission] = usePermissions();
339
339
  ```
340
340
 
341
341
  ```ts
342
- // Vue — a { status, request, get } object; status is a Ref
342
+ // Vue - a { status, request, get } object; status is a Ref
343
343
  const { status, request, get } = usePermissions();
344
344
  ```
345
345
 
346
346
  ```ts
347
- // Svelte — the same object, boxed as getters: permissions.status
347
+ // Svelte - the same object, boxed as getters: permissions.status
348
348
  const permissions = usePermissions();
349
349
  ```
350
350
 
351
351
  ```ts
352
- // Solid — createPermissions, and status/error are accessors: status()
352
+ // Solid - createPermissions, and status/error are accessors: status()
353
353
  const { status, request, get } = createPermissions();
354
354
  ```
355
355
 
356
356
  ```ts
357
- // Angular — connect() returns a readonly Signal; request()/get() live on the service itself
357
+ // Angular - connect() returns a readonly Signal; request()/get() live on the service itself
358
358
  readonly status = inject(PermissionsService).connect();
359
359
  // later: inject(PermissionsService).request()
360
360
  ```
361
361
 
362
+ ## Common questions
363
+
364
+ - **Carrier name is `null`.** The user never configured a carrier.
365
+ - **`--` or `65535` on iOS 16.4+.** `CTCarrier` is deprecated with no replacement; treat as unknown.
366
+ - **Deprecated properties.** Use the async getters instead of `allowsVoip` and `carrier`.
367
+
368
+ Sources: [Expo docs: Cellular](https://docs.expo.dev/versions/v54.0.0/sdk/cellular/),
369
+ [iOS 16 CTCarrier deprecation](https://developer.apple.com/forums/thread/714876).
370
+
362
371
  ## Test it
363
372
 
364
- No Fabric/Descriptor angle at all — cellular info is a pure async-function + permission surface,
373
+ No Fabric/Descriptor angle at all - cellular info is a pure async-function + permission surface,
365
374
  never a view. Tests inject a fake native-module object in place of the real
366
375
  `requireNativeModule` resolution (`src/core/cellular.test.ts`,
367
- `src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`, run via `vitest`) — no `installFabric()`, no
376
+ `src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`, run via `vitest`) - no `installFabric()`, no
368
377
  ViewConfig. Native rendering itself is verified on-device (see the parent
369
378
  [README](../../README.md)).
@@ -1,12 +1,5 @@
1
- import { type Signal } from '@angular/core';
1
+ import { PermissionsServiceBase } from '@symbiote-native/angular';
2
2
  import { type PermissionResponse } from '../../../core';
3
- export declare class PermissionsService {
4
- private readonly status;
5
- private readonly fetchError;
6
- private isAutoFetchStarted;
7
- /** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
8
- readonly error: Signal<Error | null>;
9
- connect(): Signal<PermissionResponse | null>;
10
- get(): Promise<PermissionResponse>;
11
- request(): Promise<PermissionResponse>;
3
+ export declare class PermissionsService extends PermissionsServiceBase<PermissionResponse> {
4
+ constructor();
12
5
  }
@@ -1,3 +1,4 @@
1
+ // Angular twin of React's `usePermissions`: `inject(PermissionsService).connect()` gives a signal
1
2
  var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
3
  function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
4
  var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
@@ -32,60 +33,26 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
33
  }
33
34
  return useValue ? value : void 0;
34
35
  };
35
- // Angular twin of React's `usePermissions` hook and Vue's `usePermissions` composable —
36
- // see BatteryStateService's connect() pattern for the shape rationale.
37
- //
38
- // readonly status = inject(PermissionsService).connect();
39
- // // template: {{ status()?.granted }}
40
- //
41
- // The auto-fetch has nobody to reject to, so its failure lands in the `error` signal instead of
42
- // escaping as an unhandled rejection: a null status with a non-null error is how a consumer tells
43
- // "the fetch failed" from "not fetched yet". get()/request() still reject to their direct caller —
44
- // only the automatic call is made safe.
45
- import { Injectable, signal } from '@angular/core';
36
+ import { Injectable } from '@angular/core';
37
+ import { PermissionsServiceBase } from '@symbiote-native/angular';
46
38
  import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core/index.js';
47
39
  let PermissionsService = (() => {
48
40
  let _classDecorators = [Injectable({ providedIn: 'root' })];
49
41
  let _classDescriptor;
50
42
  let _classExtraInitializers = [];
51
43
  let _classThis;
52
- var PermissionsService = class {
44
+ let _classSuper = PermissionsServiceBase;
45
+ var PermissionsService = class extends _classSuper {
53
46
  static { _classThis = this; }
54
47
  static {
55
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
48
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
56
49
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
57
50
  PermissionsService = _classThis = _classDescriptor.value;
58
51
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
59
52
  __runInitializers(_classThis, _classExtraInitializers);
60
53
  }
61
- status = signal(null);
62
- fetchError = signal(null);
63
- isAutoFetchStarted = false;
64
- /** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
65
- error = this.fetchError.asReadonly();
66
- connect() {
67
- // Latched rather than guarded on `status() === null`: a failed fetch leaves status null
68
- // forever, so the old guard turned every later connect() into another native call — unbounded
69
- // once connect() is reached from a change-detected expression instead of a field initializer.
70
- if (!this.isAutoFetchStarted) {
71
- this.isAutoFetchStarted = true;
72
- this.get().catch((cause) => {
73
- this.fetchError.set(cause instanceof Error ? cause : new Error(String(cause)));
74
- });
75
- }
76
- return this.status.asReadonly();
77
- }
78
- async get() {
79
- const response = await getPermissionsAsync();
80
- this.status.set(response);
81
- this.fetchError.set(null);
82
- return response;
83
- }
84
- async request() {
85
- const response = await requestPermissionsAsync();
86
- this.status.set(response);
87
- this.fetchError.set(null);
88
- return response;
54
+ constructor() {
55
+ super(getPermissionsAsync, requestPermissionsAsync);
89
56
  }
90
57
  };
91
58
  return PermissionsService = _classThis;
@@ -1,15 +1,8 @@
1
- import { type Signal } from '@angular/core';
1
+ import { PermissionsServiceBase } from '@symbiote-native/angular';
2
2
  import { type PermissionResponse } from '../../../core';
3
3
  import * as i0 from "@angular/core";
4
- export declare class PermissionsService {
5
- private readonly status;
6
- private readonly fetchError;
7
- private isAutoFetchStarted;
8
- /** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
9
- readonly error: Signal<Error | null>;
10
- connect(): Signal<PermissionResponse | null>;
11
- get(): Promise<PermissionResponse>;
12
- request(): Promise<PermissionResponse>;
4
+ export declare class PermissionsService extends PermissionsServiceBase<PermissionResponse> {
5
+ constructor();
13
6
  static ɵfac: i0.ɵɵFactoryDeclaration<PermissionsService, never>;
14
7
  static ɵprov: i0.ɵɵInjectableDeclaration<PermissionsService>;
15
8
  }
@@ -1,47 +1,11 @@
1
- // Angular twin of React's `usePermissions` hook and Vue's `usePermissions` composable —
2
- // see BatteryStateService's connect() pattern for the shape rationale.
3
- //
4
- // readonly status = inject(PermissionsService).connect();
5
- // // template: {{ status()?.granted }}
6
- //
7
- // The auto-fetch has nobody to reject to, so its failure lands in the `error` signal instead of
8
- // escaping as an unhandled rejection: a null status with a non-null error is how a consumer tells
9
- // "the fetch failed" from "not fetched yet". get()/request() still reject to their direct caller —
10
- // only the automatic call is made safe.
11
- import { Injectable, signal } from '@angular/core';
1
+ // Angular twin of React's `usePermissions`: `inject(PermissionsService).connect()` gives a signal
2
+ import { Injectable } from '@angular/core';
3
+ import { PermissionsServiceBase } from '@symbiote-native/angular';
12
4
  import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core';
13
5
  import * as i0 from "@angular/core";
14
- export class PermissionsService {
15
- status = signal(null, /* @ts-ignore */
16
- ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
17
- fetchError = signal(null, /* @ts-ignore */
18
- ...(ngDevMode ? [{ debugName: "fetchError" }] : /* istanbul ignore next */ []));
19
- isAutoFetchStarted = false;
20
- /** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
21
- error = this.fetchError.asReadonly();
22
- connect() {
23
- // Latched rather than guarded on `status() === null`: a failed fetch leaves status null
24
- // forever, so the old guard turned every later connect() into another native call — unbounded
25
- // once connect() is reached from a change-detected expression instead of a field initializer.
26
- if (!this.isAutoFetchStarted) {
27
- this.isAutoFetchStarted = true;
28
- this.get().catch((cause) => {
29
- this.fetchError.set(cause instanceof Error ? cause : new Error(String(cause)));
30
- });
31
- }
32
- return this.status.asReadonly();
33
- }
34
- async get() {
35
- const response = await getPermissionsAsync();
36
- this.status.set(response);
37
- this.fetchError.set(null);
38
- return response;
39
- }
40
- async request() {
41
- const response = await requestPermissionsAsync();
42
- this.status.set(response);
43
- this.fetchError.set(null);
44
- return response;
6
+ export class PermissionsService extends PermissionsServiceBase {
7
+ constructor() {
8
+ super(getPermissionsAsync, requestPermissionsAsync);
45
9
  }
46
10
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
47
11
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PermissionsService, providedIn: 'root' });
@@ -49,5 +13,5 @@ export class PermissionsService {
49
13
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PermissionsService, decorators: [{
50
14
  type: Injectable,
51
15
  args: [{ providedIn: 'root' }]
52
- }] });
16
+ }], ctorParameters: () => [] });
53
17
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/permissions.service/index.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,uEAAuE;AACvE,EAAE;AACF,4DAA4D;AAC5D,yCAAyC;AACzC,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,mGAAmG;AACnG,wCAAwC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,uBAAuB,GAExB,MAAM,eAAe,CAAC;;AAGvB,MAAM,OAAO,kBAAkB;IACZ,MAAM,GAAG,MAAM,CAA4B,IAAI;+EAAC,CAAC;IACjD,UAAU,GAAG,MAAM,CAAe,IAAI;mFAAC,CAAC;IACjD,kBAAkB,GAAG,KAAK,CAAC;IAEnC,gGAAgG;IACvF,KAAK,GAAyB,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;IAEpE,OAAO;QACL,wFAAwF;QACxF,8FAA8F;QAC9F,8FAA8F;QAC9F,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAC/B,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAClC,IAAI,CAAC,UAAU,CAAC,GAAG,CACjB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,GAAG;QACP,MAAM,QAAQ,GAAG,MAAM,mBAAmB,EAAE,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,QAAQ,GAAG,MAAM,uBAAuB,EAAE,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;uGAnCU,kBAAkB;2GAAlB,kBAAkB,cADL,MAAM;;2FACnB,kBAAkB;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/permissions.service/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAElG,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EACL,mBAAmB,EACnB,uBAAuB,GAExB,MAAM,eAAe,CAAC;;AAGvB,MAAM,OAAO,kBAAmB,SAAQ,sBAA0C;IAChF;QACE,KAAK,CAAC,mBAAmB,EAAE,uBAAuB,CAAC,CAAC;IACtD,CAAC;uGAHU,kBAAkB;2GAAlB,kBAAkB,cADL,MAAM;;2FACnB,kBAAkB;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/cellular",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "expo-cellular wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Cellular generation (2G/3G/4G/5G), carrier/SIM info on Android (ISO country code, carrier name, MCC/MNC), VoIP-support detection, and the permission get/request pair gating carrier info on Android.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "expo-cellular": "57.0.1",
73
- "expo-modules-core": "57.0.5"
73
+ "expo-modules-core": "57.0.20"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@angular/core": ">=20",
@@ -80,12 +80,12 @@
80
80
  "solid-js": ">=1.9.0",
81
81
  "svelte": ">=5.56.0",
82
82
  "vue": ">=3.5.0",
83
- "@symbiote-native/angular": "^3.1.1",
84
- "@symbiote-native/engine": "^1.3.0",
85
- "@symbiote-native/react": "^3.0.3",
86
- "@symbiote-native/solid": "^3.0.3",
87
- "@symbiote-native/svelte": "^3.0.3",
88
- "@symbiote-native/vue": "^3.0.3"
83
+ "@symbiote-native/angular": "^3.2.0",
84
+ "@symbiote-native/engine": "^1.5.0",
85
+ "@symbiote-native/react": "^3.2.0",
86
+ "@symbiote-native/solid": "^3.1.0",
87
+ "@symbiote-native/svelte": "^3.1.0",
88
+ "@symbiote-native/vue": "^3.2.0"
89
89
  },
90
90
  "peerDependenciesMeta": {
91
91
  "@symbiote-native/angular": {
@@ -133,13 +133,13 @@
133
133
  "solid-js": "^1.9.14",
134
134
  "svelte": "^5.56.0",
135
135
  "typescript": "~6.0.0",
136
- "@symbiote-native/angular": "3.1.1",
137
- "@symbiote-native/engine": "1.3.0",
138
- "@symbiote-native/react": "3.0.3",
139
- "@symbiote-native/solid": "3.0.3",
140
- "@symbiote-native/svelte": "3.0.3",
141
- "@symbiote-native/test-utils": "0.4.3",
142
- "@symbiote-native/vue": "3.0.3"
136
+ "@symbiote-native/angular": "3.2.0",
137
+ "@symbiote-native/engine": "1.5.0",
138
+ "@symbiote-native/react": "3.2.0",
139
+ "@symbiote-native/solid": "3.1.0",
140
+ "@symbiote-native/svelte": "3.1.0",
141
+ "@symbiote-native/test-utils": "0.4.6",
142
+ "@symbiote-native/vue": "3.2.0"
143
143
  },
144
144
  "scripts": {
145
145
  "typecheck": "tsc --build",
@@ -1,14 +1,7 @@
1
- // Angular twin of React's `usePermissions` hook and Vue's `usePermissions` composable —
2
- // see BatteryStateService's connect() pattern for the shape rationale.
3
- //
4
- // readonly status = inject(PermissionsService).connect();
5
- // // template: {{ status()?.granted }}
6
- //
7
- // The auto-fetch has nobody to reject to, so its failure lands in the `error` signal instead of
8
- // escaping as an unhandled rejection: a null status with a non-null error is how a consumer tells
9
- // "the fetch failed" from "not fetched yet". get()/request() still reject to their direct caller —
10
- // only the automatic call is made safe.
11
- import { Injectable, signal, type Signal } from '@angular/core';
1
+ // Angular twin of React's `usePermissions`: `inject(PermissionsService).connect()` gives a signal
2
+
3
+ import { Injectable } from '@angular/core';
4
+ import { PermissionsServiceBase } from '@symbiote-native/angular';
12
5
  import {
13
6
  getPermissionsAsync,
14
7
  requestPermissionsAsync,
@@ -16,40 +9,8 @@ import {
16
9
  } from '../../../core';
17
10
 
18
11
  @Injectable({ providedIn: 'root' })
19
- export class PermissionsService {
20
- private readonly status = signal<PermissionResponse | null>(null);
21
- private readonly fetchError = signal<Error | null>(null);
22
- private isAutoFetchStarted = false;
23
-
24
- /** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
25
- readonly error: Signal<Error | null> = this.fetchError.asReadonly();
26
-
27
- connect(): Signal<PermissionResponse | null> {
28
- // Latched rather than guarded on `status() === null`: a failed fetch leaves status null
29
- // forever, so the old guard turned every later connect() into another native call — unbounded
30
- // once connect() is reached from a change-detected expression instead of a field initializer.
31
- if (!this.isAutoFetchStarted) {
32
- this.isAutoFetchStarted = true;
33
- this.get().catch((cause: unknown) => {
34
- this.fetchError.set(
35
- cause instanceof Error ? cause : new Error(String(cause)),
36
- );
37
- });
38
- }
39
- return this.status.asReadonly();
40
- }
41
-
42
- async get(): Promise<PermissionResponse> {
43
- const response = await getPermissionsAsync();
44
- this.status.set(response);
45
- this.fetchError.set(null);
46
- return response;
47
- }
48
-
49
- async request(): Promise<PermissionResponse> {
50
- const response = await requestPermissionsAsync();
51
- this.status.set(response);
52
- this.fetchError.set(null);
53
- return response;
12
+ export class PermissionsService extends PermissionsServiceBase<PermissionResponse> {
13
+ constructor() {
14
+ super(getPermissionsAsync, requestPermissionsAsync);
54
15
  }
55
16
  }