@symbiote-native/cellular 0.2.0 → 1.0.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 CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  A wrapper package for [SymbioteNative](../../README.md) that makes
4
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
- React, Vue, and Angular, not just React. Like [`@symbiote-native/sensors`](../sensors) and
5
+ generation, carrier/SIM info, and VoIP-support detection — reachable from **every** adapter:
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
@@ -30,18 +30,18 @@ Unlike a plain RN native module, `expo-cellular`'s native code is discovered by
30
30
  into the native host app **once**, covering this package and every other `expo-modules-core`
31
31
  package with zero further changes:
32
32
 
33
- | Platform | Touches |
34
- |---|---|
35
- | iOS | `ios/Podfile` — add `use_expo_modules!` |
36
- | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
37
- | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
38
- | 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) |
39
- | Android | `AndroidManifest.xml` — the `READ_PHONE_STATE` permission, needed to read carrier/SIM info |
33
+ | Platform | Touches |
34
+ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
35
+ | iOS | `ios/Podfile` — add `use_expo_modules!` |
36
+ | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
37
+ | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
38
+ | 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) |
39
+ | Android | `AndroidManifest.xml` — the `READ_PHONE_STATE` permission, needed to read carrier/SIM info |
40
40
 
41
41
  Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
42
42
  peer-dependency exclusion list, per-module permission strings — live in the
43
- `symbiote-expo-native-module` project skill. This wiring is already done for all four
44
- `examples/expo-*` canary apps (React, Vue SFC, Vue TSX, Angular).
43
+ `symbiote-expo-native-module` project skill. This wiring is already done for all six
44
+ `examples/expo-*` canary apps (React, Vue SFC, Vue TSX, Svelte, Solid, Angular).
45
45
 
46
46
  ## Shape
47
47
 
@@ -51,19 +51,26 @@ src/core/ cellular.ts (the free functions) + types.ts (CellularGene
51
51
  expo-modules-core's requireNativeModule.
52
52
  src/react/hooks/ @symbiote-native/cellular/react — usePermissions
53
53
  src/vue/composables/ @symbiote-native/cellular/vue — usePermissions
54
+ src/svelte/runes/ @symbiote-native/cellular/svelte — usePermissions
55
+ src/solid/primitives/ @symbiote-native/cellular/solid — createPermissions (Solid says
56
+ create*, not use*)
54
57
  src/angular/services/ @symbiote-native/cellular/angular — PermissionsService
55
58
  ```
56
59
 
57
60
  Each adapter's `usePermissions` hook/composable/service is a thin lifecycle wrapper (fetch on
58
61
  mount, expose `request`/`get` to re-check imperatively) over the same `core` functions — the
59
- permission-fetch logic is written once and shared by all three.
62
+ permission-fetch logic is written once and shared by every adapter.
60
63
 
61
64
  ## Use it
62
65
 
63
66
  ```tsx
64
67
  // React
65
68
  import { useEffect, useState } from 'react';
66
- import { CellularGeneration, getCarrierNameAsync, getCellularGenerationAsync } from '@symbiote-native/cellular';
69
+ import {
70
+ CellularGeneration,
71
+ getCarrierNameAsync,
72
+ getCellularGenerationAsync,
73
+ } from '@symbiote-native/cellular';
67
74
  import { usePermissions } from '@symbiote-native/cellular/react';
68
75
 
69
76
  function CellularScreen() {
@@ -72,15 +79,19 @@ function CellularScreen() {
72
79
  const [status, requestPermission] = usePermissions();
73
80
 
74
81
  useEffect(() => {
75
- Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(([gen, carrier]) => {
76
- setGeneration(gen);
77
- setCarrierName(carrier);
78
- });
82
+ Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(
83
+ ([gen, carrier]) => {
84
+ setGeneration(gen);
85
+ setCarrierName(carrier);
86
+ },
87
+ );
79
88
  }, []);
80
89
 
81
90
  return (
82
91
  <>
83
- <Text>{generation === null ? 'checking…' : CellularGeneration[generation]}</Text>
92
+ <Text>
93
+ {generation === null ? 'checking…' : CellularGeneration[generation]}
94
+ </Text>
84
95
  <Text>{carrierName ?? 'checking…'}</Text>
85
96
  <Text>{status === null ? 'checking…' : status.status}</Text>
86
97
  <Button title="Request permission" onPress={() => requestPermission()} />
@@ -93,7 +104,11 @@ function CellularScreen() {
93
104
  <!-- Vue -->
94
105
  <script setup lang="ts">
95
106
  import { onMounted, ref } from 'vue';
96
- import { CellularGeneration, getCarrierNameAsync, getCellularGenerationAsync } from '@symbiote-native/cellular';
107
+ import {
108
+ CellularGeneration,
109
+ getCarrierNameAsync,
110
+ getCellularGenerationAsync,
111
+ } from '@symbiote-native/cellular';
97
112
  import { usePermissions } from '@symbiote-native/cellular/vue';
98
113
 
99
114
  const generation = ref<CellularGeneration | null>(null);
@@ -101,32 +116,109 @@ const carrierName = ref<string | null>(null);
101
116
  const { status, request: requestPermission } = usePermissions();
102
117
 
103
118
  onMounted(() => {
104
- Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(([gen, carrier]) => {
105
- generation.value = gen;
106
- carrierName.value = carrier;
107
- });
119
+ Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(
120
+ ([gen, carrier]) => {
121
+ generation.value = gen;
122
+ carrierName.value = carrier;
123
+ },
124
+ );
108
125
  });
109
126
  </script>
110
127
  <template>
111
- <Text>{{ generation === null ? 'checking…' : CellularGeneration[generation] }}</Text>
128
+ <Text>{{
129
+ generation === null ? 'checking…' : CellularGeneration[generation]
130
+ }}</Text>
112
131
  <Text>{{ carrierName ?? 'checking…' }}</Text>
113
132
  <Text>{{ status === null ? 'checking…' : status.status }}</Text>
114
133
  <Button title="Request permission" :onPress="() => requestPermission()" />
115
134
  </template>
116
135
  ```
117
136
 
137
+ ```svelte
138
+ <!-- Svelte -->
139
+ <script lang="ts">
140
+ import {
141
+ CellularGeneration,
142
+ getCarrierNameAsync,
143
+ getCellularGenerationAsync,
144
+ usePermissions,
145
+ } from '@symbiote-native/cellular/svelte';
146
+
147
+ let generation = $state<CellularGeneration | null>(null);
148
+ let carrierName = $state<string | null>(null);
149
+ const permissions = usePermissions(); // boxed getter: permissions.status / .request()
150
+
151
+ Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(
152
+ ([gen, carrier]) => {
153
+ generation = gen;
154
+ carrierName = carrier;
155
+ },
156
+ );
157
+ </script>
158
+
159
+ <Text>{generation === null ? 'checking…' : CellularGeneration[generation]}</Text>
160
+ <Text>{carrierName ?? 'checking…'}</Text>
161
+ <Text>{permissions.status === null ? 'checking…' : permissions.status.status}</Text>
162
+ <Button title="Request permission" onPress={() => permissions.request()} />
163
+ ```
164
+
165
+ ```tsx
166
+ // Solid
167
+ import { createSignal } from 'solid-js';
168
+ import {
169
+ CellularGeneration,
170
+ getCarrierNameAsync,
171
+ getCellularGenerationAsync,
172
+ } from '@symbiote-native/cellular';
173
+ import { createPermissions } from '@symbiote-native/cellular/solid';
174
+
175
+ function CellularScreen() {
176
+ const [generation, setGeneration] = createSignal<CellularGeneration | null>(null);
177
+ const [carrierName, setCarrierName] = createSignal<string | null>(null);
178
+ const { status, request: requestPermission } = createPermissions();
179
+
180
+ Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(
181
+ ([gen, carrier]) => {
182
+ setGeneration(gen);
183
+ setCarrierName(carrier);
184
+ },
185
+ );
186
+
187
+ return (
188
+ <>
189
+ <Text>
190
+ {generation() === null ? 'checking…' : CellularGeneration[generation()!]}
191
+ </Text>
192
+ <Text>{carrierName() ?? 'checking…'}</Text>
193
+ <Text>{status() === null ? 'checking…' : status()!.status}</Text>
194
+ <Button title="Request permission" onPress={() => requestPermission()} />
195
+ </>
196
+ );
197
+ }
198
+ ```
199
+
118
200
  ```ts
119
201
  // Angular
120
202
  import { Component, inject, signal } from '@angular/core';
121
- import { CellularGeneration, PermissionsService, getCarrierNameAsync, getCellularGenerationAsync } from '@symbiote-native/cellular/angular';
203
+ import {
204
+ CellularGeneration,
205
+ PermissionsService,
206
+ getCarrierNameAsync,
207
+ getCellularGenerationAsync,
208
+ } from '@symbiote-native/cellular/angular';
122
209
 
123
210
  @Component({
124
211
  selector: 'CellularScreen',
125
212
  template: `
126
- <Text>{{ generation() === null ? 'checking…' : CellularGeneration[generation()!] }}</Text>
213
+ <Text>{{
214
+ generation() === null ? 'checking…' : CellularGeneration[generation()!]
215
+ }}</Text>
127
216
  <Text>{{ carrierName() ?? 'checking…' }}</Text>
128
217
  <Text>{{ permissionStatus()?.status ?? 'checking…' }}</Text>
129
- <Button title="Request permission" (press)="permissionsService.request()"></Button>
218
+ <Button
219
+ title="Request permission"
220
+ (press)="permissionsService.request()"
221
+ ></Button>
130
222
  `,
131
223
  })
132
224
  export class CellularScreen {
@@ -138,17 +230,20 @@ export class CellularScreen {
138
230
  readonly carrierName = signal<string | null>(null);
139
231
 
140
232
  constructor() {
141
- Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(([generation, carrier]) => {
142
- this.generation.set(generation);
143
- this.carrierName.set(carrier);
144
- });
233
+ Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(
234
+ ([generation, carrier]) => {
235
+ this.generation.set(generation);
236
+ this.carrierName.set(carrier);
237
+ },
238
+ );
145
239
  }
146
240
  }
147
241
  ```
148
242
 
149
243
  Trimmed from the real demo screens — `examples/expo-react/screens/CellularScreen.tsx` and its
150
- Vue SFC, Vue TSX, and Angular twins (`examples/expo-vue-sfc/screens/CellularScreen.vue`,
151
- `examples/expo-vue-tsx/screens/CellularScreen.tsx`, `examples/expo-angular/src/screens/CellularScreen.ts`),
244
+ Vue SFC, Vue TSX, Svelte, Solid, and Angular twins (`examples/expo-vue-sfc/screens/CellularScreen.vue`,
245
+ `examples/expo-vue-tsx/screens/CellularScreen.tsx`, `examples/expo-svelte/screens/CellularScreen.svelte`,
246
+ `examples/expo-solid/screens/CellularScreen.tsx`, `examples/expo-angular/src/screens/CellularScreen.ts`),
152
247
  which also cover `allowsVoipAsync`/`getIsoCountryCodeAsync`/`getMobileCountryCodeAsync`/
153
248
  `getMobileNetworkCodeAsync` and gate the Android-only fields behind `Platform.OS === 'android'`.
154
249
 
@@ -191,11 +286,16 @@ Plus `CellularGeneration` (enum) and `PermissionResponse`/`PermissionStatus`/
191
286
  ported by hand.
192
287
 
193
288
  ```ts
194
- import { getCellularGenerationAsync, getCarrierNameAsync } from '@symbiote-native/cellular';
289
+ import {
290
+ getCellularGenerationAsync,
291
+ getCarrierNameAsync,
292
+ } from '@symbiote-native/cellular';
195
293
 
196
294
  // framework-scoped entry points re-export the same free functions, plus usePermissions:
197
295
  import { usePermissions } from '@symbiote-native/cellular/react';
198
296
  import { usePermissions } from '@symbiote-native/cellular/vue';
297
+ import { usePermissions } from '@symbiote-native/cellular/svelte';
298
+ import { createPermissions } from '@symbiote-native/cellular/solid';
199
299
  import { PermissionsService } from '@symbiote-native/cellular/angular';
200
300
  ```
201
301
 
@@ -216,6 +316,16 @@ const [status, requestPermission, getPermission] = usePermissions();
216
316
  const { status, request, get } = usePermissions();
217
317
  ```
218
318
 
319
+ ```ts
320
+ // Svelte — the same object, boxed as getters: permissions.status
321
+ const permissions = usePermissions();
322
+ ```
323
+
324
+ ```ts
325
+ // Solid — createPermissions, and status/error are accessors: status()
326
+ const { status, request, get } = createPermissions();
327
+ ```
328
+
219
329
  ```ts
220
330
  // Angular — connect() returns a readonly Signal; request()/get() live on the service itself
221
331
  readonly status = inject(PermissionsService).connect();
@@ -227,6 +337,6 @@ readonly status = inject(PermissionsService).connect();
227
337
  No Fabric/Descriptor angle at all — cellular info is a pure async-function + permission surface,
228
338
  never a view. Tests inject a fake native-module object in place of the real
229
339
  `requireNativeModule` resolution (`src/core/cellular.test.ts`,
230
- `src/{react,vue,angular}/**/*.test.{ts,tsx}`, run via `vitest`) — no `installFabric()`, no
340
+ `src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`, run via `vitest`) — no `installFabric()`, no
231
341
  ViewConfig. Native rendering itself is verified on-device (see the parent
232
342
  [README](../../README.md)).
@@ -0,0 +1,2 @@
1
+ export { createPermissions } from './primitives/create-permissions';
2
+ export * from '../core';
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/cellular/solid: the Solid entry over the framework-agnostic core, plus
2
+ // createPermissions — the only stateful surface this package needs a lifecycle wrapper for. The
3
+ // lifecycle bucket is `primitives/` and the name is `create*` (never `hooks/`/`use*`, that is
4
+ // React's term and Solid's word for consuming an existing thing) — see
5
+ // adapters/solid/src/primitives for the same convention.
6
+ export { createPermissions } from './primitives/create-permissions.js';
7
+ export * from '../core/index.js';
@@ -0,0 +1,8 @@
1
+ import { type Accessor } from 'solid-js';
2
+ import { type PermissionResponse } from '../../core';
3
+ export declare function createPermissions(): {
4
+ status: Accessor<PermissionResponse | null>;
5
+ error: Accessor<Error | null>;
6
+ request: () => Promise<PermissionResponse>;
7
+ get: () => Promise<PermissionResponse>;
8
+ };
@@ -0,0 +1,55 @@
1
+ // Solid lifecycle wiring over the framework-agnostic core (core/cellular.ts) — the Solid twin of
2
+ // React's usePermissions hook, Vue's composable, Svelte's rune and Angular's PermissionsService.
3
+ //
4
+ // `primitives/` and `create*`, not `hooks/`/`use*`: Solid's ecosystem calls a composable reactive
5
+ // function a PRIMITIVE and reserves `use*` for consuming something that already exists. Full
6
+ // rationale in adapters/solid/src/primitives/create-color-scheme.ts's header.
7
+ //
8
+ // Returns ACCESSORS, never a snapshot: a Solid component body runs ONCE, so a plain returned value
9
+ // would freeze at "not fetched yet" and never move again.
10
+ //
11
+ // The initial fetch is fired SYNCHRONOUSLY from the primitive body rather than from a mount hook
12
+ // (React's useEffect, Vue's onMounted, Svelte's $effect) — same reason create-color-scheme
13
+ // subscribes synchronously: nothing can interleave between construction and the fetch starting, so
14
+ // there is no window in which a consumer sees a primitive that has not asked for status yet.
15
+ //
16
+ // That fetch has nobody to reject to, so its failure lands in `error` instead of escaping as an
17
+ // unhandled rejection: a null status with a non-null error is how a consumer tells "the fetch
18
+ // failed" from "not fetched yet". get()/request() still reject to their direct caller — only the
19
+ // automatic call is made safe.
20
+ //
21
+ // `onCleanup` drops the writes of a call still in flight when the owner is disposed — the Solid
22
+ // equivalent of React's isMounted ref. Outside a component / createRoot there is no owner to hang
23
+ // it on, so those late writes land instead (harmless: nothing reads a disposed signal).
24
+ import { createSignal, onCleanup } from 'solid-js';
25
+ import { getPermissionsAsync, requestPermissionsAsync, } from '../../core/index.js';
26
+ export function createPermissions() {
27
+ const [status, setStatus] = createSignal(null);
28
+ const [error, setError] = createSignal(null);
29
+ let isDisposed = false;
30
+ const record = (response) => {
31
+ if (isDisposed)
32
+ return;
33
+ setStatus(response);
34
+ setError(null);
35
+ };
36
+ const get = async () => {
37
+ const response = await getPermissionsAsync();
38
+ record(response);
39
+ return response;
40
+ };
41
+ const request = async () => {
42
+ const response = await requestPermissionsAsync();
43
+ record(response);
44
+ return response;
45
+ };
46
+ get().catch((cause) => {
47
+ if (isDisposed)
48
+ return;
49
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
50
+ });
51
+ onCleanup(() => {
52
+ isDisposed = true;
53
+ });
54
+ return { status, error, request, get };
55
+ }
@@ -19,7 +19,7 @@
19
19
  // same reason as `status`) instead of escaping as an unhandled rejection: a null status with a
20
20
  // non-null error is how a consumer tells "the fetch failed" from "not fetched yet".
21
21
  // get()/request() still reject to their direct caller — only the automatic call is made safe.
22
- import { getPermissionsAsync, requestPermissionsAsync } from '../../core/index.js';
22
+ import { getPermissionsAsync, requestPermissionsAsync, } from '../../core/index.js';
23
23
  export function usePermissions() {
24
24
  let status = $state(null);
25
25
  let error = $state(null);
@@ -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,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjF,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;uGAjCU,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,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 +1 @@
1
- {"version":3,"file":"cellular.js","sourceRoot":"","sources":["../../src/core/cellular.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cellular.js","sourceRoot":"","sources":["../../src/core/cellular.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAIxD,MAAM,yBAAyB,GAAG,cAAc,CAAC;AAmBjD,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAwB,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAIxD,MAAM,yBAAyB,GAAG,cAAc,CAAC;AAmBjD,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAC7C,yBAAyB,CAC1B,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@symbiote-native/cellular",
3
- "version": "0.2.0",
4
- "description": "expo-cellular wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, 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.",
3
+ "version": "1.0.0",
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": {
7
7
  "type": "git",
@@ -13,6 +13,18 @@
13
13
  "url": "https://github.com/OneEyed1366/symbiote-native/issues"
14
14
  },
15
15
  "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
16
+ "keywords": [
17
+ "react-native",
18
+ "symbiote-native",
19
+ "expo-cellular",
20
+ "react",
21
+ "vue",
22
+ "svelte",
23
+ "solid",
24
+ "angular",
25
+ "carrier-info",
26
+ "cellular-generation"
27
+ ],
16
28
  "type": "module",
17
29
  "main": "./build/core/index.js",
18
30
  "module": "./build/core/index.js",
@@ -34,6 +46,10 @@
34
46
  "types": "./build/svelte/index.d.ts",
35
47
  "default": "./build/svelte/index.js"
36
48
  },
49
+ "./solid": {
50
+ "types": "./build/solid/index.d.ts",
51
+ "default": "./build/solid/index.js"
52
+ },
37
53
  "./angular": {
38
54
  "types": "./build-ngc/angular/index.d.ts",
39
55
  "react-native": "./build-ngc/angular/index.js",
@@ -57,17 +73,19 @@
57
73
  "expo-modules-core": "57.0.5"
58
74
  },
59
75
  "peerDependencies": {
60
- "@symbiote-native/angular": ">=0.6.2",
61
- "@symbiote-native/engine": ">=0.1.7",
62
- "@symbiote-native/react": ">=0.2.8",
63
- "@symbiote-native/svelte": ">=0.1.0",
64
- "@symbiote-native/vue": ">=0.3.8",
65
76
  "@angular/core": ">=20",
66
77
  "@vue/runtime-core": "^3.5.13",
67
78
  "react": ">=19.0.0",
68
79
  "react-native": ">=0.86",
80
+ "solid-js": ">=1.9.0",
69
81
  "svelte": ">=5.56.0",
70
- "vue": ">=3.5.0"
82
+ "vue": ">=3.5.0",
83
+ "@symbiote-native/angular": "^1.0.0",
84
+ "@symbiote-native/engine": "^0.4.0",
85
+ "@symbiote-native/react": "^1.0.0",
86
+ "@symbiote-native/solid": "^1.0.0",
87
+ "@symbiote-native/svelte": "^1.0.0",
88
+ "@symbiote-native/vue": "^1.0.0"
71
89
  },
72
90
  "peerDependenciesMeta": {
73
91
  "@symbiote-native/angular": {
@@ -76,6 +94,9 @@
76
94
  "@symbiote-native/react": {
77
95
  "optional": true
78
96
  },
97
+ "@symbiote-native/solid": {
98
+ "optional": true
99
+ },
79
100
  "@symbiote-native/svelte": {
80
101
  "optional": true
81
102
  },
@@ -91,6 +112,9 @@
91
112
  "react": {
92
113
  "optional": true
93
114
  },
115
+ "solid-js": {
116
+ "optional": true
117
+ },
94
118
  "svelte": {
95
119
  "optional": true
96
120
  },
@@ -106,14 +130,16 @@
106
130
  "@types/react": "^19.2.0",
107
131
  "@vue/runtime-core": "^3.5.13",
108
132
  "react": "19.2.3",
133
+ "solid-js": "^1.9.14",
109
134
  "svelte": "^5.56.0",
110
135
  "typescript": "~6.0.0",
111
- "@symbiote-native/angular": "0.7.0",
112
- "@symbiote-native/engine": "0.2.0",
113
- "@symbiote-native/react": "0.3.0",
114
- "@symbiote-native/svelte": "0.2.0",
115
- "@symbiote-native/test-utils": "0.1.6",
116
- "@symbiote-native/vue": "0.4.0"
136
+ "@symbiote-native/angular": "1.0.0",
137
+ "@symbiote-native/engine": "0.4.0",
138
+ "@symbiote-native/react": "1.0.0",
139
+ "@symbiote-native/solid": "1.0.0",
140
+ "@symbiote-native/svelte": "1.0.0",
141
+ "@symbiote-native/test-utils": "0.3.0",
142
+ "@symbiote-native/vue": "1.0.0"
117
143
  },
118
144
  "scripts": {
119
145
  "typecheck": "tsc --build",
@@ -31,7 +31,9 @@ export class PermissionsService {
31
31
  if (!this.isAutoFetchStarted) {
32
32
  this.isAutoFetchStarted = true;
33
33
  this.get().catch((cause: unknown) => {
34
- this.fetchError.set(cause instanceof Error ? cause : new Error(String(cause)));
34
+ this.fetchError.set(
35
+ cause instanceof Error ? cause : new Error(String(cause)),
36
+ );
35
37
  });
36
38
  }
37
39
  return this.status.asReadonly();
@@ -26,7 +26,10 @@ const GRANTED_PERMISSION_RESPONSE: PermissionResponse = {
26
26
  /** Gets the generation of the device's current connection. */
27
27
  export async function getCellularGenerationAsync(): Promise<CellularGeneration> {
28
28
  if (!expoCellular.getCellularGenerationAsync) {
29
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getCellularGenerationAsync');
29
+ throw new UnavailabilityError(
30
+ NATIVE_MODULE_NAME,
31
+ 'getCellularGenerationAsync',
32
+ );
30
33
  }
31
34
  return expoCellular.getCellularGenerationAsync();
32
35
  }
@@ -83,7 +86,10 @@ export async function getMobileCountryCodeAsync(): Promise<string | null> {
83
86
  return null;
84
87
  }
85
88
  if (!expoCellular.getMobileCountryCodeAsync) {
86
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getMobileCountryCodeAsync');
89
+ throw new UnavailabilityError(
90
+ NATIVE_MODULE_NAME,
91
+ 'getMobileCountryCodeAsync',
92
+ );
87
93
  }
88
94
  return expoCellular.getMobileCountryCodeAsync();
89
95
  }
@@ -97,7 +103,10 @@ export async function getMobileNetworkCodeAsync(): Promise<string | null> {
97
103
  return null;
98
104
  }
99
105
  if (!expoCellular.getMobileNetworkCodeAsync) {
100
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getMobileNetworkCodeAsync');
106
+ throw new UnavailabilityError(
107
+ NATIVE_MODULE_NAME,
108
+ 'getMobileNetworkCodeAsync',
109
+ );
101
110
  }
102
111
  return expoCellular.getMobileNetworkCodeAsync();
103
112
  }
@@ -123,7 +132,10 @@ export async function getPermissionsAsync(): Promise<PermissionResponse> {
123
132
  export async function requestPermissionsAsync(): Promise<PermissionResponse> {
124
133
  if (Platform.OS === 'android') {
125
134
  if (!expoCellular.requestPermissionsAsync) {
126
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'requestPermissionsAsync');
135
+ throw new UnavailabilityError(
136
+ NATIVE_MODULE_NAME,
137
+ 'requestPermissionsAsync',
138
+ );
127
139
  }
128
140
  return expoCellular.requestPermissionsAsync();
129
141
  }
@@ -21,4 +21,6 @@ export type INativeCellularModule = {
21
21
  requestPermissionsAsync?(): Promise<PermissionResponse>;
22
22
  };
23
23
 
24
- export const expoCellular = requireNativeModule<INativeCellularModule>(EXPO_CELLULAR_MODULE_NAME);
24
+ export const expoCellular = requireNativeModule<INativeCellularModule>(
25
+ EXPO_CELLULAR_MODULE_NAME,
26
+ );
@@ -43,7 +43,8 @@ export function usePermissions(): [
43
43
  useEffect(() => {
44
44
  isMounted.current = true;
45
45
  getPermission().catch((cause: unknown) => {
46
- if (isMounted.current) setError(cause instanceof Error ? cause : new Error(String(cause)));
46
+ if (isMounted.current)
47
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
47
48
  });
48
49
  return () => {
49
50
  isMounted.current = false;
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/cellular/solid: the Solid entry over the framework-agnostic core, plus
2
+ // createPermissions — the only stateful surface this package needs a lifecycle wrapper for. The
3
+ // lifecycle bucket is `primitives/` and the name is `create*` (never `hooks/`/`use*`, that is
4
+ // React's term and Solid's word for consuming an existing thing) — see
5
+ // adapters/solid/src/primitives for the same convention.
6
+ export { createPermissions } from './primitives/create-permissions';
7
+ export * from '../core';
@@ -0,0 +1,69 @@
1
+ // Solid lifecycle wiring over the framework-agnostic core (core/cellular.ts) — the Solid twin of
2
+ // React's usePermissions hook, Vue's composable, Svelte's rune and Angular's PermissionsService.
3
+ //
4
+ // `primitives/` and `create*`, not `hooks/`/`use*`: Solid's ecosystem calls a composable reactive
5
+ // function a PRIMITIVE and reserves `use*` for consuming something that already exists. Full
6
+ // rationale in adapters/solid/src/primitives/create-color-scheme.ts's header.
7
+ //
8
+ // Returns ACCESSORS, never a snapshot: a Solid component body runs ONCE, so a plain returned value
9
+ // would freeze at "not fetched yet" and never move again.
10
+ //
11
+ // The initial fetch is fired SYNCHRONOUSLY from the primitive body rather than from a mount hook
12
+ // (React's useEffect, Vue's onMounted, Svelte's $effect) — same reason create-color-scheme
13
+ // subscribes synchronously: nothing can interleave between construction and the fetch starting, so
14
+ // there is no window in which a consumer sees a primitive that has not asked for status yet.
15
+ //
16
+ // That fetch has nobody to reject to, so its failure lands in `error` instead of escaping as an
17
+ // unhandled rejection: a null status with a non-null error is how a consumer tells "the fetch
18
+ // failed" from "not fetched yet". get()/request() still reject to their direct caller — only the
19
+ // automatic call is made safe.
20
+ //
21
+ // `onCleanup` drops the writes of a call still in flight when the owner is disposed — the Solid
22
+ // equivalent of React's isMounted ref. Outside a component / createRoot there is no owner to hang
23
+ // it on, so those late writes land instead (harmless: nothing reads a disposed signal).
24
+ import { createSignal, onCleanup, type Accessor } from 'solid-js';
25
+ import {
26
+ getPermissionsAsync,
27
+ requestPermissionsAsync,
28
+ type PermissionResponse,
29
+ } from '../../core';
30
+
31
+ export function createPermissions(): {
32
+ status: Accessor<PermissionResponse | null>;
33
+ error: Accessor<Error | null>;
34
+ request: () => Promise<PermissionResponse>;
35
+ get: () => Promise<PermissionResponse>;
36
+ } {
37
+ const [status, setStatus] = createSignal<PermissionResponse | null>(null);
38
+ const [error, setError] = createSignal<Error | null>(null);
39
+ let isDisposed = false;
40
+
41
+ const record = (response: PermissionResponse): void => {
42
+ if (isDisposed) return;
43
+ setStatus(response);
44
+ setError(null);
45
+ };
46
+
47
+ const get = async (): Promise<PermissionResponse> => {
48
+ const response = await getPermissionsAsync();
49
+ record(response);
50
+ return response;
51
+ };
52
+
53
+ const request = async (): Promise<PermissionResponse> => {
54
+ const response = await requestPermissionsAsync();
55
+ record(response);
56
+ return response;
57
+ };
58
+
59
+ get().catch((cause: unknown) => {
60
+ if (isDisposed) return;
61
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
62
+ });
63
+
64
+ onCleanup(() => {
65
+ isDisposed = true;
66
+ });
67
+
68
+ return { status, error, request, get };
69
+ }
@@ -19,7 +19,11 @@
19
19
  // same reason as `status`) instead of escaping as an unhandled rejection: a null status with a
20
20
  // non-null error is how a consumer tells "the fetch failed" from "not fetched yet".
21
21
  // get()/request() still reject to their direct caller — only the automatic call is made safe.
22
- import { getPermissionsAsync, requestPermissionsAsync, type PermissionResponse } from '../../core';
22
+ import {
23
+ getPermissionsAsync,
24
+ requestPermissionsAsync,
25
+ type PermissionResponse,
26
+ } from '../../core';
23
27
 
24
28
  export function usePermissions(): {
25
29
  readonly status: PermissionResponse | null;