@symbiote-native/cellular 0.0.1
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/LICENSE +21 -0
- package/README.md +232 -0
- package/build/angular/index.d.ts +2 -0
- package/build/angular/index.js +4 -0
- package/build/angular/services/permissions.service/index.d.ts +8 -0
- package/build/angular/services/permissions.service/index.js +76 -0
- package/build/core/cellular.d.ts +40 -0
- package/build/core/cellular.js +116 -0
- package/build/core/index.d.ts +2 -0
- package/build/core/index.js +2 -0
- package/build/core/native-module.d.ts +13 -0
- package/build/core/native-module.js +3 -0
- package/build/core/types.d.ts +14 -0
- package/build/core/types.js +19 -0
- package/build/react/hooks/use-permissions/index.d.ts +6 -0
- package/build/react/hooks/use-permissions/index.js +28 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.js +4 -0
- package/build/vue/composables/use-permissions/index.d.ts +7 -0
- package/build/vue/composables/use-permissions/index.js +21 -0
- package/build/vue/index.d.ts +2 -0
- package/build/vue/index.js +4 -0
- package/build-ngc/angular/index.d.ts +2 -0
- package/build-ngc/angular/index.js +5 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/services/permissions.service/index.d.ts +11 -0
- package/build-ngc/angular/services/permissions.service/index.js +35 -0
- package/build-ngc/angular/services/permissions.service/index.js.map +1 -0
- package/build-ngc/core/cellular.d.ts +40 -0
- package/build-ngc/core/cellular.js +117 -0
- package/build-ngc/core/cellular.js.map +1 -0
- package/build-ngc/core/index.d.ts +2 -0
- package/build-ngc/core/index.js +3 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +13 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +14 -0
- package/build-ngc/core/types.js +20 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +107 -0
- package/src/angular/index.ts +4 -0
- package/src/angular/services/permissions.service/index.ts +35 -0
- package/src/angular/services/permissions.service/permissions.service.test.ts +109 -0
- package/src/core/cellular.test.ts +193 -0
- package/src/core/cellular.ts +131 -0
- package/src/core/index.ts +16 -0
- package/src/core/native-module.ts +24 -0
- package/src/core/types.ts +23 -0
- package/src/react/hooks/use-permissions/index.ts +39 -0
- package/src/react/hooks/use-permissions/use-permissions.test.tsx +115 -0
- package/src/react/index.ts +4 -0
- package/src/vue/composables/use-permissions/index.ts +34 -0
- package/src/vue/composables/use-permissions/use-permissions.test.ts +103 -0
- package/src/vue/index.ts +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# @symbiote-native/cellular
|
|
2
|
+
|
|
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,
|
|
6
|
+
React, Vue, and Angular, not just React. Like [`@symbiote-native/sensors`](../sensors) and
|
|
7
|
+
unlike this repo's plain-RN-module/view wrappers ([`@symbiote-native/slider`](../slider),
|
|
8
|
+
[`@symbiote-native/splash-screen`](../splash-screen)), `expo-cellular` is built on
|
|
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
|
|
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/`.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @symbiote-native/cellular
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Depends on `expo-cellular` and `expo-modules-core` directly (regular dependencies, pinned to
|
|
21
|
+
exact versions — never a caret range, since this package's `core/` is hand-ported against one
|
|
22
|
+
specific native API shape and a newer resolve could silently drift the two apart). Never install
|
|
23
|
+
`expo-cellular` yourself, and never add the `expo` meta-package to this project — it bundles its
|
|
24
|
+
own Metro/Babel pipeline that conflicts with this project's own.
|
|
25
|
+
|
|
26
|
+
## Required one-time step: native autolinking wiring
|
|
27
|
+
|
|
28
|
+
Unlike a plain RN native module, `expo-cellular`'s native code is discovered by
|
|
29
|
+
`expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism — this needs wiring
|
|
30
|
+
into the native host app **once**, covering this package and every other `expo-modules-core`
|
|
31
|
+
package with zero further changes:
|
|
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 |
|
|
40
|
+
|
|
41
|
+
Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
|
|
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).
|
|
45
|
+
|
|
46
|
+
## Shape
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
src/core/ cellular.ts (the free functions) + types.ts (CellularGeneration enum) +
|
|
50
|
+
native-module.ts, which resolves the native module by name through
|
|
51
|
+
expo-modules-core's requireNativeModule.
|
|
52
|
+
src/react/hooks/ @symbiote-native/cellular/react — usePermissions
|
|
53
|
+
src/vue/composables/ @symbiote-native/cellular/vue — usePermissions
|
|
54
|
+
src/angular/services/ @symbiote-native/cellular/angular — PermissionsService
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Each adapter's `usePermissions` hook/composable/service is a thin lifecycle wrapper (fetch on
|
|
58
|
+
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.
|
|
60
|
+
|
|
61
|
+
## Use it
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// React
|
|
65
|
+
import { useEffect, useState } from 'react';
|
|
66
|
+
import { CellularGeneration, getCarrierNameAsync, getCellularGenerationAsync } from '@symbiote-native/cellular';
|
|
67
|
+
import { usePermissions } from '@symbiote-native/cellular/react';
|
|
68
|
+
|
|
69
|
+
function CellularScreen() {
|
|
70
|
+
const [generation, setGeneration] = useState<CellularGeneration | null>(null);
|
|
71
|
+
const [carrierName, setCarrierName] = useState<string | null>(null);
|
|
72
|
+
const [status, requestPermission] = usePermissions();
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(([gen, carrier]) => {
|
|
76
|
+
setGeneration(gen);
|
|
77
|
+
setCarrierName(carrier);
|
|
78
|
+
});
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<>
|
|
83
|
+
<Text>{generation === null ? 'checking…' : CellularGeneration[generation]}</Text>
|
|
84
|
+
<Text>{carrierName ?? 'checking…'}</Text>
|
|
85
|
+
<Text>{status === null ? 'checking…' : status.status}</Text>
|
|
86
|
+
<Button title="Request permission" onPress={() => requestPermission()} />
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<!-- Vue -->
|
|
94
|
+
<script setup lang="ts">
|
|
95
|
+
import { onMounted, ref } from 'vue';
|
|
96
|
+
import { CellularGeneration, getCarrierNameAsync, getCellularGenerationAsync } from '@symbiote-native/cellular';
|
|
97
|
+
import { usePermissions } from '@symbiote-native/cellular/vue';
|
|
98
|
+
|
|
99
|
+
const generation = ref<CellularGeneration | null>(null);
|
|
100
|
+
const carrierName = ref<string | null>(null);
|
|
101
|
+
const { status, request: requestPermission } = usePermissions();
|
|
102
|
+
|
|
103
|
+
onMounted(() => {
|
|
104
|
+
Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(([gen, carrier]) => {
|
|
105
|
+
generation.value = gen;
|
|
106
|
+
carrierName.value = carrier;
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
</script>
|
|
110
|
+
<template>
|
|
111
|
+
<Text>{{ generation === null ? 'checking…' : CellularGeneration[generation] }}</Text>
|
|
112
|
+
<Text>{{ carrierName ?? 'checking…' }}</Text>
|
|
113
|
+
<Text>{{ status === null ? 'checking…' : status.status }}</Text>
|
|
114
|
+
<Button title="Request permission" :onPress="() => requestPermission()" />
|
|
115
|
+
</template>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// Angular
|
|
120
|
+
import { Component, inject, signal } from '@angular/core';
|
|
121
|
+
import { CellularGeneration, PermissionsService, getCarrierNameAsync, getCellularGenerationAsync } from '@symbiote-native/cellular/angular';
|
|
122
|
+
|
|
123
|
+
@Component({
|
|
124
|
+
selector: 'CellularScreen',
|
|
125
|
+
template: `
|
|
126
|
+
<Text>{{ generation() === null ? 'checking…' : CellularGeneration[generation()!] }}</Text>
|
|
127
|
+
<Text>{{ carrierName() ?? 'checking…' }}</Text>
|
|
128
|
+
<Text>{{ permissionStatus()?.status ?? 'checking…' }}</Text>
|
|
129
|
+
<Button title="Request permission" (press)="permissionsService.request()"></Button>
|
|
130
|
+
`,
|
|
131
|
+
})
|
|
132
|
+
export class CellularScreen {
|
|
133
|
+
readonly CellularGeneration = CellularGeneration;
|
|
134
|
+
private readonly permissionsService = inject(PermissionsService);
|
|
135
|
+
readonly permissionStatus = this.permissionsService.connect();
|
|
136
|
+
|
|
137
|
+
readonly generation = signal<CellularGeneration | null>(null);
|
|
138
|
+
readonly carrierName = signal<string | null>(null);
|
|
139
|
+
|
|
140
|
+
constructor() {
|
|
141
|
+
Promise.all([getCellularGenerationAsync(), getCarrierNameAsync()]).then(([generation, carrier]) => {
|
|
142
|
+
this.generation.set(generation);
|
|
143
|
+
this.carrierName.set(carrier);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
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`),
|
|
152
|
+
which also cover `allowsVoipAsync`/`getIsoCountryCodeAsync`/`getMobileCountryCodeAsync`/
|
|
153
|
+
`getMobileNetworkCodeAsync` and gate the Android-only fields behind `Platform.OS === 'android'`.
|
|
154
|
+
|
|
155
|
+
## API
|
|
156
|
+
|
|
157
|
+
Mostly stateless async functions, no event stream — plus one stateful pair,
|
|
158
|
+
`getPermissionsAsync`/`requestPermissionsAsync`, which gets its own lifecycle
|
|
159
|
+
hook/composable/service (`usePermissions`) so a component can re-check permission status
|
|
160
|
+
imperatively without re-wiring the fetch itself.
|
|
161
|
+
|
|
162
|
+
Carrier/SIM info and VoIP support are **Android only** — every one of those functions resolves
|
|
163
|
+
`null` on iOS without touching the native module at all, since iOS doesn't expose this
|
|
164
|
+
information. `getPermissionsAsync`/`requestPermissionsAsync` need no real permission grant on
|
|
165
|
+
iOS either (cellular info requires none there), so they resolve a plain `GRANTED` response
|
|
166
|
+
without a native call.
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
getCellularGenerationAsync(): Promise<CellularGeneration>
|
|
170
|
+
// UNKNOWN/CELLULAR_2G/CELLULAR_3G/CELLULAR_4G/CELLULAR_5G. Delegates to the native module on
|
|
171
|
+
// every platform — no iOS gate, unlike every other function below.
|
|
172
|
+
|
|
173
|
+
allowsVoipAsync(): Promise<boolean | null>
|
|
174
|
+
// @deprecated (upstream is deprecating this). null on iOS, native call on Android.
|
|
175
|
+
|
|
176
|
+
getIsoCountryCodeAsync(): Promise<string | null> // Android only — null on iOS.
|
|
177
|
+
getCarrierNameAsync(): Promise<string | null> // Android only — null on iOS.
|
|
178
|
+
getMobileCountryCodeAsync(): Promise<string | null> // Android only — null on iOS.
|
|
179
|
+
getMobileNetworkCodeAsync(): Promise<string | null> // Android only — null on iOS.
|
|
180
|
+
|
|
181
|
+
getPermissionsAsync(): Promise<PermissionResponse>
|
|
182
|
+
// Android delegates to the native module; every other platform resolves a plain GRANTED
|
|
183
|
+
// literal — no permission is needed there for cellular info.
|
|
184
|
+
|
|
185
|
+
requestPermissionsAsync(): Promise<PermissionResponse>
|
|
186
|
+
// Same split as getPermissionsAsync.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Plus `CellularGeneration` (enum) and `PermissionResponse`/`PermissionStatus`/
|
|
190
|
+
`PermissionExpiration` — the latter three re-exported straight from `expo-modules-core`, not
|
|
191
|
+
ported by hand.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { getCellularGenerationAsync, getCarrierNameAsync } from '@symbiote-native/cellular';
|
|
195
|
+
|
|
196
|
+
// framework-scoped entry points re-export the same free functions, plus usePermissions:
|
|
197
|
+
import { usePermissions } from '@symbiote-native/cellular/react';
|
|
198
|
+
import { usePermissions } from '@symbiote-native/cellular/vue';
|
|
199
|
+
import { PermissionsService } from '@symbiote-native/cellular/angular';
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### `usePermissions`
|
|
203
|
+
|
|
204
|
+
Fetches the current permission status on mount, then exposes `request`/`get` to re-check it
|
|
205
|
+
imperatively — every adapter follows the same shape as its sibling `PermissionsService`/
|
|
206
|
+
composable in `@symbiote-native/brightness`, the other package in this repo needing the same
|
|
207
|
+
get/request permission pair.
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
// React — a [status, request, get] tuple, mirroring upstream's own usePermissions hooks
|
|
211
|
+
const [status, requestPermission, getPermission] = usePermissions();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
// Vue — a { status, request, get } object; status is a Ref
|
|
216
|
+
const { status, request, get } = usePermissions();
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
// Angular — connect() returns a readonly Signal; request()/get() live on the service itself
|
|
221
|
+
readonly status = inject(PermissionsService).connect();
|
|
222
|
+
// later: inject(PermissionsService).request()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Test it
|
|
226
|
+
|
|
227
|
+
No Fabric/Descriptor angle at all — cellular info is a pure async-function + permission surface,
|
|
228
|
+
never a view. Tests inject a fake native-module object in place of the real
|
|
229
|
+
`requireNativeModule` resolution (`src/core/cellular.test.ts`,
|
|
230
|
+
`src/{react,vue,angular}/**/*.test.{ts,tsx}`, run via `vitest`) — no `installFabric()`, no
|
|
231
|
+
ViewConfig. Native rendering itself is verified on-device (see the parent
|
|
232
|
+
[README](../../README.md)).
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// @symbiote-native/cellular/angular: the Angular entry over the framework-agnostic core, plus
|
|
2
|
+
// PermissionsService — the only stateful surface this package needs a lifecycle wrapper for.
|
|
3
|
+
export { PermissionsService } from './services/permissions.service/index.js';
|
|
4
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Signal } from '@angular/core';
|
|
2
|
+
import { type PermissionResponse } from '../../../core';
|
|
3
|
+
export declare class PermissionsService {
|
|
4
|
+
private readonly status;
|
|
5
|
+
connect(): Signal<PermissionResponse | null>;
|
|
6
|
+
get(): Promise<PermissionResponse>;
|
|
7
|
+
request(): Promise<PermissionResponse>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
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
|
+
import { Injectable, signal } from '@angular/core';
|
|
41
|
+
import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core';
|
|
42
|
+
let PermissionsService = (() => {
|
|
43
|
+
let _classDecorators = [Injectable({ providedIn: 'root' })];
|
|
44
|
+
let _classDescriptor;
|
|
45
|
+
let _classExtraInitializers = [];
|
|
46
|
+
let _classThis;
|
|
47
|
+
var PermissionsService = class {
|
|
48
|
+
static { _classThis = this; }
|
|
49
|
+
static {
|
|
50
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
51
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
52
|
+
PermissionsService = _classThis = _classDescriptor.value;
|
|
53
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
54
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
55
|
+
}
|
|
56
|
+
status = signal(null);
|
|
57
|
+
connect() {
|
|
58
|
+
if (this.status() === null) {
|
|
59
|
+
void this.get();
|
|
60
|
+
}
|
|
61
|
+
return this.status.asReadonly();
|
|
62
|
+
}
|
|
63
|
+
async get() {
|
|
64
|
+
const response = await getPermissionsAsync();
|
|
65
|
+
this.status.set(response);
|
|
66
|
+
return response;
|
|
67
|
+
}
|
|
68
|
+
async request() {
|
|
69
|
+
const response = await requestPermissionsAsync();
|
|
70
|
+
this.status.set(response);
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return PermissionsService = _classThis;
|
|
75
|
+
})();
|
|
76
|
+
export { PermissionsService };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type PermissionResponse } from 'expo-modules-core';
|
|
2
|
+
import { CellularGeneration } from './types';
|
|
3
|
+
/** Gets the generation of the device's current connection. */
|
|
4
|
+
export declare function getCellularGenerationAsync(): Promise<CellularGeneration>;
|
|
5
|
+
/**
|
|
6
|
+
* Tells if the carrier of the SIM card allows to make VoIP calls on its network.
|
|
7
|
+
* @deprecated This method is deprecated and will be removed in a future SDK version.
|
|
8
|
+
* @platform android
|
|
9
|
+
*/
|
|
10
|
+
export declare function allowsVoipAsync(): Promise<boolean | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Gets the ISO country code of the current registered operator's MCC (Mobile Country Code).
|
|
13
|
+
* @platform android
|
|
14
|
+
*/
|
|
15
|
+
export declare function getIsoCountryCodeAsync(): Promise<string | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Gets the name of the user's cellular service provider.
|
|
18
|
+
* @platform android
|
|
19
|
+
*/
|
|
20
|
+
export declare function getCarrierNameAsync(): Promise<string | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Gets the mobile country code (MCC) of the device's current registered operator.
|
|
23
|
+
* @platform android
|
|
24
|
+
*/
|
|
25
|
+
export declare function getMobileCountryCodeAsync(): Promise<string | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Gets the mobile network code (MNC) of the device's current registered operator.
|
|
28
|
+
* @platform android
|
|
29
|
+
*/
|
|
30
|
+
export declare function getMobileNetworkCodeAsync(): Promise<string | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Checks user's permissions for accessing cellular information. Android only — iOS/web need no
|
|
33
|
+
* permission for cellular info, so this always resolves granted there.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getPermissionsAsync(): Promise<PermissionResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Asks the user to grant permissions for accessing cellular information. Android only — iOS/web
|
|
38
|
+
* need no permission for cellular info, so this always resolves granted there.
|
|
39
|
+
*/
|
|
40
|
+
export declare function requestPermissionsAsync(): Promise<PermissionResponse>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Hand-ported from .vendors/expo/packages/expo-cellular/src/Cellular.ts (sdk-57). Carrier/SIM
|
|
2
|
+
// info (ISO country code, carrier name, MCC/MNC) and allowsVoipAsync are Android-only upstream —
|
|
3
|
+
// each short-circuits to `null` on iOS before ever reaching the native module, since iOS exposes
|
|
4
|
+
// none of this. getPermissionsAsync/requestPermissionsAsync mirror that same split: Android
|
|
5
|
+
// delegates to the native module, every other platform needs no permission for cellular info at
|
|
6
|
+
// all and resolves a plain GRANTED literal. Upstream's web branch (navigator.connection) is out
|
|
7
|
+
// of scope — this repo has no web target for this package's native-module resolution path.
|
|
8
|
+
import { Platform, PermissionStatus, UnavailabilityError, } from 'expo-modules-core';
|
|
9
|
+
import { expoCellular } from './native-module';
|
|
10
|
+
import { CellularGeneration } from './types';
|
|
11
|
+
const NATIVE_MODULE_NAME = 'expo-cellular';
|
|
12
|
+
const GRANTED_PERMISSION_RESPONSE = {
|
|
13
|
+
status: PermissionStatus.GRANTED,
|
|
14
|
+
expires: 'never',
|
|
15
|
+
granted: true,
|
|
16
|
+
canAskAgain: true,
|
|
17
|
+
};
|
|
18
|
+
/** Gets the generation of the device's current connection. */
|
|
19
|
+
export async function getCellularGenerationAsync() {
|
|
20
|
+
if (!expoCellular.getCellularGenerationAsync) {
|
|
21
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getCellularGenerationAsync');
|
|
22
|
+
}
|
|
23
|
+
return expoCellular.getCellularGenerationAsync();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Tells if the carrier of the SIM card allows to make VoIP calls on its network.
|
|
27
|
+
* @deprecated This method is deprecated and will be removed in a future SDK version.
|
|
28
|
+
* @platform android
|
|
29
|
+
*/
|
|
30
|
+
export async function allowsVoipAsync() {
|
|
31
|
+
if (Platform.OS === 'ios') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!expoCellular.allowsVoipAsync) {
|
|
35
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'allowsVoipAsync');
|
|
36
|
+
}
|
|
37
|
+
return expoCellular.allowsVoipAsync();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Gets the ISO country code of the current registered operator's MCC (Mobile Country Code).
|
|
41
|
+
* @platform android
|
|
42
|
+
*/
|
|
43
|
+
export async function getIsoCountryCodeAsync() {
|
|
44
|
+
if (Platform.OS === 'ios') {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (!expoCellular.getIsoCountryCodeAsync) {
|
|
48
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getIsoCountryCodeAsync');
|
|
49
|
+
}
|
|
50
|
+
return expoCellular.getIsoCountryCodeAsync();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Gets the name of the user's cellular service provider.
|
|
54
|
+
* @platform android
|
|
55
|
+
*/
|
|
56
|
+
export async function getCarrierNameAsync() {
|
|
57
|
+
if (Platform.OS === 'ios') {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (!expoCellular.getCarrierNameAsync) {
|
|
61
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getCarrierNameAsync');
|
|
62
|
+
}
|
|
63
|
+
return expoCellular.getCarrierNameAsync();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets the mobile country code (MCC) of the device's current registered operator.
|
|
67
|
+
* @platform android
|
|
68
|
+
*/
|
|
69
|
+
export async function getMobileCountryCodeAsync() {
|
|
70
|
+
if (Platform.OS === 'ios') {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (!expoCellular.getMobileCountryCodeAsync) {
|
|
74
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getMobileCountryCodeAsync');
|
|
75
|
+
}
|
|
76
|
+
return expoCellular.getMobileCountryCodeAsync();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Gets the mobile network code (MNC) of the device's current registered operator.
|
|
80
|
+
* @platform android
|
|
81
|
+
*/
|
|
82
|
+
export async function getMobileNetworkCodeAsync() {
|
|
83
|
+
if (Platform.OS === 'ios') {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!expoCellular.getMobileNetworkCodeAsync) {
|
|
87
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getMobileNetworkCodeAsync');
|
|
88
|
+
}
|
|
89
|
+
return expoCellular.getMobileNetworkCodeAsync();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Checks user's permissions for accessing cellular information. Android only — iOS/web need no
|
|
93
|
+
* permission for cellular info, so this always resolves granted there.
|
|
94
|
+
*/
|
|
95
|
+
export async function getPermissionsAsync() {
|
|
96
|
+
if (Platform.OS === 'android') {
|
|
97
|
+
if (!expoCellular.getPermissionsAsync) {
|
|
98
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getPermissionsAsync');
|
|
99
|
+
}
|
|
100
|
+
return expoCellular.getPermissionsAsync();
|
|
101
|
+
}
|
|
102
|
+
return GRANTED_PERMISSION_RESPONSE;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Asks the user to grant permissions for accessing cellular information. Android only — iOS/web
|
|
106
|
+
* need no permission for cellular info, so this always resolves granted there.
|
|
107
|
+
*/
|
|
108
|
+
export async function requestPermissionsAsync() {
|
|
109
|
+
if (Platform.OS === 'android') {
|
|
110
|
+
if (!expoCellular.requestPermissionsAsync) {
|
|
111
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'requestPermissionsAsync');
|
|
112
|
+
}
|
|
113
|
+
return expoCellular.requestPermissionsAsync();
|
|
114
|
+
}
|
|
115
|
+
return GRANTED_PERMISSION_RESPONSE;
|
|
116
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { getCellularGenerationAsync, allowsVoipAsync, getIsoCountryCodeAsync, getCarrierNameAsync, getMobileCountryCodeAsync, getMobileNetworkCodeAsync, getPermissionsAsync, requestPermissionsAsync, } from './cellular';
|
|
2
|
+
export { CellularGeneration, PermissionStatus, type PermissionResponse, type PermissionExpiration, } from './types';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { getCellularGenerationAsync, allowsVoipAsync, getIsoCountryCodeAsync, getCarrierNameAsync, getMobileCountryCodeAsync, getMobileNetworkCodeAsync, getPermissionsAsync, requestPermissionsAsync, } from './cellular.js';
|
|
2
|
+
export { CellularGeneration, PermissionStatus, } from './types.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PermissionResponse } from 'expo-modules-core';
|
|
2
|
+
import type { CellularGeneration } from './types';
|
|
3
|
+
export type INativeCellularModule = {
|
|
4
|
+
getCellularGenerationAsync?(): Promise<CellularGeneration>;
|
|
5
|
+
allowsVoipAsync?(): Promise<boolean>;
|
|
6
|
+
getIsoCountryCodeAsync?(): Promise<string | null>;
|
|
7
|
+
getCarrierNameAsync?(): Promise<string | null>;
|
|
8
|
+
getMobileCountryCodeAsync?(): Promise<string | null>;
|
|
9
|
+
getMobileNetworkCodeAsync?(): Promise<string | null>;
|
|
10
|
+
getPermissionsAsync?(): Promise<PermissionResponse>;
|
|
11
|
+
requestPermissionsAsync?(): Promise<PermissionResponse>;
|
|
12
|
+
};
|
|
13
|
+
export declare const expoCellular: INativeCellularModule;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { PermissionStatus, type PermissionResponse, type PermissionExpiration, } from 'expo-modules-core';
|
|
2
|
+
/** Telecommunication technology used to establish a cellular connection. */
|
|
3
|
+
export declare enum CellularGeneration {
|
|
4
|
+
/** The device's connection generation could not be determined. */
|
|
5
|
+
UNKNOWN = 0,
|
|
6
|
+
/** 2nd generation (GPRS/EDGE-class) connection. */
|
|
7
|
+
CELLULAR_2G = 1,
|
|
8
|
+
/** 3rd generation (UMTS/HSPA-class) connection. */
|
|
9
|
+
CELLULAR_3G = 2,
|
|
10
|
+
/** 4th generation (LTE-class) connection. */
|
|
11
|
+
CELLULAR_4G = 3,
|
|
12
|
+
/** 5th generation (NR-class) connection. */
|
|
13
|
+
CELLULAR_5G = 4
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Hand-ported from .vendors/expo/packages/expo-cellular/src/Cellular.types.ts (sdk-57).
|
|
2
|
+
// PermissionResponse/PermissionStatus/PermissionExpiration come straight from
|
|
3
|
+
// expo-modules-core (which re-exports PermissionsInterface) — never from the `expo`
|
|
4
|
+
// meta-package, unlike upstream's own Cellular.ts.
|
|
5
|
+
export { PermissionStatus, } from 'expo-modules-core';
|
|
6
|
+
/** Telecommunication technology used to establish a cellular connection. */
|
|
7
|
+
export var CellularGeneration;
|
|
8
|
+
(function (CellularGeneration) {
|
|
9
|
+
/** The device's connection generation could not be determined. */
|
|
10
|
+
CellularGeneration[CellularGeneration["UNKNOWN"] = 0] = "UNKNOWN";
|
|
11
|
+
/** 2nd generation (GPRS/EDGE-class) connection. */
|
|
12
|
+
CellularGeneration[CellularGeneration["CELLULAR_2G"] = 1] = "CELLULAR_2G";
|
|
13
|
+
/** 3rd generation (UMTS/HSPA-class) connection. */
|
|
14
|
+
CellularGeneration[CellularGeneration["CELLULAR_3G"] = 2] = "CELLULAR_3G";
|
|
15
|
+
/** 4th generation (LTE-class) connection. */
|
|
16
|
+
CellularGeneration[CellularGeneration["CELLULAR_4G"] = 3] = "CELLULAR_4G";
|
|
17
|
+
/** 5th generation (NR-class) connection. */
|
|
18
|
+
CellularGeneration[CellularGeneration["CELLULAR_5G"] = 4] = "CELLULAR_5G";
|
|
19
|
+
})(CellularGeneration || (CellularGeneration = {}));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// React lifecycle wiring over the framework-agnostic core (core/cellular.ts) — fetches the
|
|
2
|
+
// current permission status on mount, then exposes request/get to re-check imperatively.
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core';
|
|
5
|
+
export function usePermissions() {
|
|
6
|
+
const isMounted = useRef(true);
|
|
7
|
+
const [status, setStatus] = useState(null);
|
|
8
|
+
const getPermission = useCallback(async () => {
|
|
9
|
+
const response = await getPermissionsAsync();
|
|
10
|
+
if (isMounted.current)
|
|
11
|
+
setStatus(response);
|
|
12
|
+
return response;
|
|
13
|
+
}, []);
|
|
14
|
+
const requestPermission = useCallback(async () => {
|
|
15
|
+
const response = await requestPermissionsAsync();
|
|
16
|
+
if (isMounted.current)
|
|
17
|
+
setStatus(response);
|
|
18
|
+
return response;
|
|
19
|
+
}, []);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
isMounted.current = true;
|
|
22
|
+
void getPermission();
|
|
23
|
+
return () => {
|
|
24
|
+
isMounted.current = false;
|
|
25
|
+
};
|
|
26
|
+
}, [getPermission]);
|
|
27
|
+
return [status, requestPermission, getPermission];
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// @symbiote-native/cellular/react: the React entry over the framework-agnostic core, plus
|
|
2
|
+
// usePermissions — the only stateful surface this package needs a lifecycle wrapper for.
|
|
3
|
+
export { usePermissions } from './hooks/use-permissions/index.js';
|
|
4
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Ref } from '@vue/runtime-core';
|
|
2
|
+
import { type PermissionResponse } from '../../../core';
|
|
3
|
+
export declare function usePermissions(): {
|
|
4
|
+
status: Ref<PermissionResponse | null>;
|
|
5
|
+
request: () => Promise<PermissionResponse>;
|
|
6
|
+
get: () => Promise<PermissionResponse>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Vue lifecycle wiring over the framework-agnostic core (core/cellular.ts) — fetches the
|
|
2
|
+
// current permission status on mount, then exposes request/get to re-check imperatively.
|
|
3
|
+
import { onMounted, ref } from '@vue/runtime-core';
|
|
4
|
+
import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core';
|
|
5
|
+
export function usePermissions() {
|
|
6
|
+
const status = ref(null);
|
|
7
|
+
const get = async () => {
|
|
8
|
+
const response = await getPermissionsAsync();
|
|
9
|
+
status.value = response;
|
|
10
|
+
return response;
|
|
11
|
+
};
|
|
12
|
+
const request = async () => {
|
|
13
|
+
const response = await requestPermissionsAsync();
|
|
14
|
+
status.value = response;
|
|
15
|
+
return response;
|
|
16
|
+
};
|
|
17
|
+
onMounted(() => {
|
|
18
|
+
void get();
|
|
19
|
+
});
|
|
20
|
+
return { status, request, get };
|
|
21
|
+
}
|