@symbiote-native/network 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 +139 -36
- package/build/angular/services/network-state.service/index.js +2 -2
- package/build/core/index.d.ts +1 -1
- package/build/core/index.js +1 -1
- package/build/react/hooks/use-network-state/index.js +1 -1
- package/build/solid/index.d.ts +2 -0
- package/build/solid/index.js +5 -0
- package/build/solid/primitives/create-network-state.d.ts +3 -0
- package/build/solid/primitives/create-network-state.js +32 -0
- package/build-ngc/angular/services/network-state.service/index.js +2 -2
- package/build-ngc/angular/services/network-state.service/index.js.map +1 -1
- package/build-ngc/core/index.d.ts +1 -1
- package/build-ngc/core/index.js +1 -1
- package/build-ngc/core/index.js.map +1 -1
- package/build-ngc/core/native-module.js.map +1 -1
- package/build-ngc/core/network.js.map +1 -1
- package/package.json +40 -14
- package/src/angular/services/network-state.service/index.ts +16 -3
- package/src/core/index.ts +5 -1
- package/src/core/native-module.ts +3 -1
- package/src/core/network.ts +4 -1
- package/src/react/hooks/use-network-state/index.ts +8 -2
- package/src/solid/index.ts +6 -0
- package/src/solid/primitives/create-network-state.ts +43 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Port of [`expo-network`](https://docs.expo.dev/versions/latest/sdk/network/) for
|
|
4
4
|
[SymbioteNative](../../README.md) — network connection state (type/connected/internet-reachable)
|
|
5
5
|
with a change listener, device IP address, and airplane-mode detection, reachable from every
|
|
6
|
-
adapter (React, Vue, Angular), not just React.
|
|
6
|
+
adapter (React, Vue, Svelte, Solid, Angular), not just React.
|
|
7
7
|
|
|
8
8
|
Built the same way as [`@symbiote-native/battery`](../battery) and
|
|
9
9
|
[`@symbiote-native/local-auth`](../local-auth), an `expo-modules-core`-based wrapper (see the
|
|
@@ -31,12 +31,12 @@ into the native host app **once**, covering this package and every other `expo-m
|
|
|
31
31
|
package (`@symbiote-native/battery`, `@symbiote-native/sensors`, `@symbiote-native/local-auth`)
|
|
32
32
|
with zero further changes:
|
|
33
33
|
|
|
34
|
-
| Platform | Touches
|
|
35
|
-
|
|
36
|
-
| iOS
|
|
37
|
-
| iOS
|
|
38
|
-
| Android
|
|
39
|
-
| Android
|
|
34
|
+
| Platform | Touches |
|
|
35
|
+
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| iOS | `ios/Podfile` — add `use_expo_modules!` |
|
|
37
|
+
| iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
|
|
38
|
+
| Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
|
|
39
|
+
| 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) |
|
|
40
40
|
|
|
41
41
|
Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
|
|
42
42
|
peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. Reference
|
|
@@ -56,37 +56,50 @@ src/core/ network.ts — get*Async functions + addNetworkStateList
|
|
|
56
56
|
NetworkStateEvent, hand-ported from Network.types.ts.
|
|
57
57
|
src/react/hooks/ @symbiote-native/network/react — useNetworkState
|
|
58
58
|
src/vue/composables/ @symbiote-native/network/vue — useNetworkState (same name)
|
|
59
|
+
src/svelte/runes/ @symbiote-native/network/svelte — useNetworkState (same name)
|
|
60
|
+
src/solid/primitives/ @symbiote-native/network/solid — createNetworkState (returns an
|
|
61
|
+
Accessor; Solid reserves `use*` for consuming existing state)
|
|
59
62
|
src/angular/services/ @symbiote-native/network/angular — NetworkStateService (`.connect()`
|
|
60
63
|
returns a Signal)
|
|
61
64
|
```
|
|
62
65
|
|
|
63
|
-
Each adapter's hook/composable/service is a thin lifecycle wrapper (seed from the
|
|
64
|
-
`getNetworkStateAsync()` call, subscribe to `addNetworkStateListener`, unsubscribe on
|
|
65
|
-
the same `core` functions — the subscription logic is written once and shared by all
|
|
66
|
+
Each adapter's hook/composable/rune/primitive/service is a thin lifecycle wrapper (seed from the
|
|
67
|
+
one-shot `getNetworkStateAsync()` call, subscribe to `addNetworkStateListener`, unsubscribe on
|
|
68
|
+
unmount) over the same `core` functions — the subscription logic is written once and shared by all
|
|
69
|
+
of them.
|
|
66
70
|
|
|
67
71
|
## Use it
|
|
68
72
|
|
|
69
73
|
```tsx
|
|
70
74
|
// React — examples/expo-react/screens/NetworkScreen.tsx
|
|
71
75
|
import { useEffect, useState } from 'react';
|
|
72
|
-
import {
|
|
76
|
+
import {
|
|
77
|
+
getIpAddressAsync,
|
|
78
|
+
isAirplaneModeEnabledAsync,
|
|
79
|
+
} from '@symbiote-native/network';
|
|
73
80
|
import { useNetworkState } from '@symbiote-native/network/react';
|
|
74
81
|
|
|
75
82
|
function NetworkScreen() {
|
|
76
|
-
const networkState = useNetworkState();
|
|
83
|
+
const networkState = useNetworkState(); // { type, isConnected, isInternetReachable }
|
|
77
84
|
const [ipAddress, setIpAddress] = useState<string | null>(null);
|
|
78
85
|
const [isAirplaneMode, setIsAirplaneMode] = useState<boolean | null>(null);
|
|
79
86
|
|
|
80
87
|
useEffect(() => {
|
|
81
|
-
Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
|
|
89
|
+
([ip, airplaneMode]) => {
|
|
90
|
+
setIpAddress(ip);
|
|
91
|
+
setIsAirplaneMode(airplaneMode);
|
|
92
|
+
},
|
|
93
|
+
);
|
|
85
94
|
}, [networkState]);
|
|
86
95
|
|
|
87
96
|
return (
|
|
88
97
|
<>
|
|
89
|
-
<Text>
|
|
98
|
+
<Text>
|
|
99
|
+
{networkState.isConnected
|
|
100
|
+
? `Connected via ${networkState.type}`
|
|
101
|
+
: 'Offline'}
|
|
102
|
+
</Text>
|
|
90
103
|
<Text>{ipAddress ?? 'checking…'}</Text>
|
|
91
104
|
<Text>{isAirplaneMode ? 'Airplane mode: On' : 'Airplane mode: Off'}</Text>
|
|
92
105
|
</>
|
|
@@ -99,30 +112,68 @@ function NetworkScreen() {
|
|
|
99
112
|
<script setup lang="ts">
|
|
100
113
|
import { ref, watch } from 'vue';
|
|
101
114
|
import { Text } from '@symbiote-native/vue';
|
|
102
|
-
import {
|
|
115
|
+
import {
|
|
116
|
+
getIpAddressAsync,
|
|
117
|
+
isAirplaneModeEnabledAsync,
|
|
118
|
+
} from '@symbiote-native/network';
|
|
103
119
|
import { useNetworkState } from '@symbiote-native/network/vue';
|
|
104
120
|
|
|
105
|
-
const networkState = useNetworkState();
|
|
121
|
+
const networkState = useNetworkState(); // Ref<NetworkState>
|
|
106
122
|
const ipAddress = ref<string | null>(null);
|
|
107
123
|
const isAirplaneMode = ref<boolean | null>(null);
|
|
108
124
|
|
|
109
125
|
watch(
|
|
110
126
|
networkState,
|
|
111
127
|
() => {
|
|
112
|
-
void Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
void Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
|
|
129
|
+
([ip, airplaneMode]) => {
|
|
130
|
+
ipAddress.value = ip;
|
|
131
|
+
isAirplaneMode.value = airplaneMode;
|
|
132
|
+
},
|
|
133
|
+
);
|
|
116
134
|
},
|
|
117
135
|
{ immediate: true },
|
|
118
136
|
);
|
|
119
137
|
</script>
|
|
120
138
|
<template>
|
|
121
|
-
<Text>{{
|
|
139
|
+
<Text>{{
|
|
140
|
+
networkState.isConnected ? `Connected via ${networkState.type}` : 'Offline'
|
|
141
|
+
}}</Text>
|
|
122
142
|
<Text>{{ ipAddress ?? 'checking…' }}</Text>
|
|
123
143
|
</template>
|
|
124
144
|
```
|
|
125
145
|
|
|
146
|
+
```svelte
|
|
147
|
+
<!-- Svelte -->
|
|
148
|
+
<script lang="ts">
|
|
149
|
+
import {
|
|
150
|
+
getIpAddressAsync,
|
|
151
|
+
isAirplaneModeEnabledAsync,
|
|
152
|
+
} from '@symbiote-native/network';
|
|
153
|
+
import { useNetworkState } from '@symbiote-native/network/svelte';
|
|
154
|
+
|
|
155
|
+
const networkState = useNetworkState(); // { readonly current: NetworkState }
|
|
156
|
+
let ipAddress = $state<string | null>(null);
|
|
157
|
+
let isAirplaneMode = $state<boolean | null>(null);
|
|
158
|
+
|
|
159
|
+
$effect(() => {
|
|
160
|
+
networkState.current; // establishes the dependency, so this re-runs on network change
|
|
161
|
+
void Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
|
|
162
|
+
([ip, airplaneMode]) => {
|
|
163
|
+
ipAddress = ip;
|
|
164
|
+
isAirplaneMode = airplaneMode;
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
</script>
|
|
169
|
+
<Text>
|
|
170
|
+
{networkState.current.isConnected
|
|
171
|
+
? `Connected via ${networkState.current.type}`
|
|
172
|
+
: 'Offline'}
|
|
173
|
+
</Text>
|
|
174
|
+
<Text>{ipAddress ?? 'checking…'}</Text>
|
|
175
|
+
```
|
|
176
|
+
|
|
126
177
|
```ts
|
|
127
178
|
// Angular — examples/expo-angular/src/screens/NetworkScreen.ts
|
|
128
179
|
import { Component, effect, inject, signal } from '@angular/core';
|
|
@@ -145,15 +196,59 @@ export class NetworkScreen {
|
|
|
145
196
|
constructor() {
|
|
146
197
|
effect(() => {
|
|
147
198
|
this.networkState();
|
|
148
|
-
Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
|
|
149
|
-
|
|
150
|
-
|
|
199
|
+
Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
|
|
200
|
+
([ip]) => {
|
|
201
|
+
this.ipAddress.set(ip);
|
|
202
|
+
},
|
|
203
|
+
);
|
|
151
204
|
});
|
|
152
205
|
}
|
|
153
206
|
}
|
|
154
207
|
```
|
|
155
208
|
|
|
156
|
-
|
|
209
|
+
```tsx
|
|
210
|
+
// Solid — the accessor is CALLED; a Solid component body runs once, so a snapshot would freeze.
|
|
211
|
+
import { createSignal, onMount } from 'solid-js';
|
|
212
|
+
import { Text } from '@symbiote-native/solid';
|
|
213
|
+
import {
|
|
214
|
+
getIpAddressAsync,
|
|
215
|
+
isAirplaneModeEnabledAsync,
|
|
216
|
+
} from '@symbiote-native/network';
|
|
217
|
+
import { createNetworkState } from '@symbiote-native/network/solid';
|
|
218
|
+
|
|
219
|
+
function NetworkScreen() {
|
|
220
|
+
const networkState = createNetworkState(); // Accessor<NetworkState>
|
|
221
|
+
const [ipAddress, setIpAddress] = createSignal<string | null>(null);
|
|
222
|
+
const [isAirplaneMode, setIsAirplaneMode] = createSignal<boolean | null>(
|
|
223
|
+
null,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
onMount(() => {
|
|
227
|
+
void Promise.all([getIpAddressAsync(), isAirplaneModeEnabledAsync()]).then(
|
|
228
|
+
([ip, airplaneMode]) => {
|
|
229
|
+
setIpAddress(ip);
|
|
230
|
+
setIsAirplaneMode(airplaneMode);
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<>
|
|
237
|
+
<Text>
|
|
238
|
+
{networkState().isConnected
|
|
239
|
+
? `Connected via ${networkState().type}`
|
|
240
|
+
: 'Offline'}
|
|
241
|
+
</Text>
|
|
242
|
+
<Text>{ipAddress() ?? 'checking…'}</Text>
|
|
243
|
+
<Text>
|
|
244
|
+
{isAirplaneMode() ? 'Airplane mode: On' : 'Airplane mode: Off'}
|
|
245
|
+
</Text>
|
|
246
|
+
</>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Each of the demo screens above also re-fetches the IP address and airplane-mode card
|
|
157
252
|
whenever the live network state changes (toggling Wi-Fi/airplane mode on the device updates both
|
|
158
253
|
cards together) — see the linked files for the full version, including the connection-type label
|
|
159
254
|
switch and the layout around it.
|
|
@@ -174,29 +269,37 @@ Plus `NetworkStateType` (enum: `NONE`/`UNKNOWN`/`CELLULAR`/`WIFI`/`BLUETOOTH`/`E
|
|
|
174
269
|
`VPN`/`OTHER`), `NetworkState`, `NetworkStateEvent` — ported from upstream's `Network.types.ts`.
|
|
175
270
|
|
|
176
271
|
```ts
|
|
177
|
-
import {
|
|
272
|
+
import {
|
|
273
|
+
getNetworkStateAsync,
|
|
274
|
+
addNetworkStateListener,
|
|
275
|
+
} from '@symbiote-native/network';
|
|
178
276
|
|
|
179
277
|
// framework-scoped entry points re-export the same free functions, plus a lifecycle
|
|
180
278
|
// hook/composable/service:
|
|
181
279
|
import { useNetworkState } from '@symbiote-native/network/react';
|
|
182
280
|
import { useNetworkState } from '@symbiote-native/network/vue';
|
|
281
|
+
import { useNetworkState } from '@symbiote-native/network/svelte';
|
|
282
|
+
import { createNetworkState } from '@symbiote-native/network/solid';
|
|
183
283
|
import { NetworkStateService } from '@symbiote-native/network/angular';
|
|
184
284
|
```
|
|
185
285
|
|
|
186
|
-
`useNetworkState` seeds its initial value from a one-shot
|
|
187
|
-
subscribes to `addNetworkStateListener` for updates, and
|
|
188
|
-
upstream's own `useNetworkState`.
|
|
286
|
+
`useNetworkState` (Solid: `createNetworkState`) seeds its initial value from a one-shot
|
|
287
|
+
`getNetworkStateAsync()` call, then subscribes to `addNetworkStateListener` for updates, and
|
|
288
|
+
unsubscribes on unmount — mirroring upstream's own `useNetworkState`. The Solid primitive returns
|
|
289
|
+
an `Accessor<NetworkState>` and subscribes from its body rather than an effect, so nothing can slip
|
|
290
|
+
between the seed and the subscription.
|
|
189
291
|
|
|
190
292
|
## Test it
|
|
191
293
|
|
|
192
294
|
No Fabric/Descriptor angle at all — network is a pure async-function + `EventEmitter` listener
|
|
193
295
|
surface, never a view. Tests inject a fake native-module object in place of the real
|
|
194
296
|
`requireNativeModule` resolution (`src/core/network.test.ts`,
|
|
195
|
-
`src/{react,vue,angular}/**/*.test.{ts,tsx}`, `vitest`), the same pattern
|
|
297
|
+
`src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`, `vitest`), the same pattern
|
|
196
298
|
`@symbiote-native/battery`/`@symbiote-native/sensors`/`@symbiote-native/local-auth` use — no
|
|
197
299
|
`installFabric()`, no ViewConfig. Native rendering itself is verified on-device — see the parent
|
|
198
300
|
[README](../../README.md).
|
|
199
301
|
|
|
200
|
-
The Android/iOS native wiring is done across all
|
|
201
|
-
(`examples/expo-react`, `examples/expo-vue-sfc`, `examples/expo-vue-tsx`, `examples/expo-
|
|
202
|
-
this package isn't yet in the public, non-Expo
|
|
302
|
+
The Android/iOS native wiring is done across all six `examples/expo-*` canary apps
|
|
303
|
+
(`examples/expo-react`, `examples/expo-vue-sfc`, `examples/expo-vue-tsx`, `examples/expo-svelte`,
|
|
304
|
+
`examples/expo-solid`, `examples/expo-angular`); this package isn't yet in the public, non-Expo
|
|
305
|
+
`examples/react` canary.
|
|
@@ -32,8 +32,8 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
32
32
|
}
|
|
33
33
|
return useValue ? value : void 0;
|
|
34
34
|
};
|
|
35
|
-
import { effect, inject, Injectable, Injector, signal } from '@angular/core';
|
|
36
|
-
import { addNetworkStateListener, getNetworkStateAsync } from '../../../core/index.js';
|
|
35
|
+
import { effect, inject, Injectable, Injector, signal, } from '@angular/core';
|
|
36
|
+
import { addNetworkStateListener, getNetworkStateAsync, } from '../../../core/index.js';
|
|
37
37
|
// Angular twin of React's `useNetworkState` hook and Vue's `useNetworkState` composable.
|
|
38
38
|
//
|
|
39
39
|
// readonly networkState = inject(NetworkStateService).connect();
|
package/build/core/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, } from './network';
|
|
2
|
-
export { NetworkStateType, type NetworkState, type NetworkStateEvent } from './types';
|
|
2
|
+
export { NetworkStateType, type NetworkState, type NetworkStateEvent, } from './types';
|
|
3
3
|
export type { EventSubscription } from 'expo-modules-core';
|
package/build/core/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, } from './network.js';
|
|
2
|
-
export { NetworkStateType } from './types.js';
|
|
2
|
+
export { NetworkStateType, } from './types.js';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// value with a one-shot getNetworkStateAsync() call before the first native event fires, matching
|
|
3
3
|
// upstream's own useNetworkState.
|
|
4
4
|
import { useEffect, useState } from 'react';
|
|
5
|
-
import { addNetworkStateListener, getNetworkStateAsync } from '../../../core/index.js';
|
|
5
|
+
import { addNetworkStateListener, getNetworkStateAsync, } from '../../../core/index.js';
|
|
6
6
|
export function useNetworkState() {
|
|
7
7
|
const [networkState, setNetworkState] = useState({});
|
|
8
8
|
useEffect(() => {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @symbiote-native/network/solid: the Solid entry over the framework-agnostic core — mirrors the
|
|
2
|
+
// lifecycle-bucket naming convention of adapters/solid/src/primitives (never `hooks`/`composables`/
|
|
3
|
+
// `runes`, those are React's, Vue's and Svelte's terms).
|
|
4
|
+
export { createNetworkState } from './primitives/create-network-state.js';
|
|
5
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// createNetworkState — the Solid twin of React's `useNetworkState` hook, Vue's composable and
|
|
2
|
+
// Svelte's rune, over the framework-agnostic core (core/network.ts). Seeds with a one-shot
|
|
3
|
+
// getNetworkStateAsync() call before the first native event fires, matching upstream's own
|
|
4
|
+
// useNetworkState.
|
|
5
|
+
//
|
|
6
|
+
// `primitives/` and `create*`, never `hooks/`+`use*`: Solid's ecosystem calls a composable
|
|
7
|
+
// reactive function a PRIMITIVE and reserves `use*` for consuming something that already exists.
|
|
8
|
+
// Full rationale in adapters/solid/src/primitives/create-color-scheme.ts.
|
|
9
|
+
//
|
|
10
|
+
// Returns an ACCESSOR, never a snapshot: a Solid component body runs ONCE, so a returned object
|
|
11
|
+
// would pin the caller to the empty seed forever.
|
|
12
|
+
//
|
|
13
|
+
// Outside a component or `createRoot` there is no owner for `onCleanup` — Solid warns and the
|
|
14
|
+
// native listener lives for the process. The accessor still tracks; only the teardown is lost.
|
|
15
|
+
import { createSignal, onCleanup } from 'solid-js';
|
|
16
|
+
import { addNetworkStateListener, getNetworkStateAsync, } from '../../core/index.js';
|
|
17
|
+
export function createNetworkState() {
|
|
18
|
+
const [networkState, setNetworkState] = createSignal({});
|
|
19
|
+
getNetworkStateAsync().then(state => {
|
|
20
|
+
setNetworkState(state);
|
|
21
|
+
});
|
|
22
|
+
// Subscribed from the primitive body, not from an effect: React/Vue/Svelte start listening a
|
|
23
|
+
// tick after their seed runs, so a change landing in that window is lost. Both statements here
|
|
24
|
+
// run in one synchronous tick and nothing can interleave.
|
|
25
|
+
const subscription = addNetworkStateListener(event => {
|
|
26
|
+
setNetworkState(event);
|
|
27
|
+
});
|
|
28
|
+
onCleanup(() => {
|
|
29
|
+
subscription.remove();
|
|
30
|
+
});
|
|
31
|
+
return networkState;
|
|
32
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { effect, inject, Injectable, Injector, signal } from '@angular/core';
|
|
2
|
-
import { addNetworkStateListener, getNetworkStateAsync } from '../../../core';
|
|
1
|
+
import { effect, inject, Injectable, Injector, signal, } from '@angular/core';
|
|
2
|
+
import { addNetworkStateListener, getNetworkStateAsync, } from '../../../core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
// Angular twin of React's `useNetworkState` hook and Vue's `useNetworkState` composable.
|
|
5
5
|
//
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/network-state.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/network-state.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,GAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,uBAAuB,EACvB,oBAAoB,GAErB,MAAM,eAAe,CAAC;;AAEvB,yFAAyF;AACzF,EAAE;AACF,mEAAmE;AACnE,2CAA2C;AAE3C,MAAM,OAAO,mBAAmB;IACb,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE7C,OAAO;QACL,MAAM,YAAY,GAAG,MAAM,CAAe,EAAE;yFAAC,CAAC;QAE9C,MAAM,CACJ,SAAS,CAAC,EAAE;YACV,oBAAoB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9D,MAAM,YAAY,GAAG,uBAAuB,CAAC,KAAK,CAAC,EAAE,CACnD,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CACxB,CAAC;YACF,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;QACzC,CAAC,EACD,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAC5B,CAAC;QAEF,OAAO,YAAY,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC;uGAlBU,mBAAmB;2GAAnB,mBAAmB,cADN,MAAM;;2FACnB,mBAAmB;kBAD/B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { getNetworkStateAsync, getIpAddressAsync, isAirplaneModeEnabledAsync, addNetworkStateListener, } from './network';
|
|
2
|
-
export { NetworkStateType, type NetworkState, type NetworkStateEvent } from './types';
|
|
2
|
+
export { NetworkStateType, type NetworkState, type NetworkStateEvent, } from './types';
|
|
3
3
|
export type { EventSubscription } from 'expo-modules-core';
|
package/build-ngc/core/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,GAGjB,MAAM,SAAS,CAAC"}
|
|
@@ -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,wBAAwB,GAAG,aAAa,CAAC;AAc/C,MAAM,CAAC,MAAM,WAAW,GAAG,mBAAmB,
|
|
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,wBAAwB,GAAG,aAAa,CAAC;AAc/C,MAAM,CAAC,MAAM,WAAW,GAAG,mBAAmB,CAC5C,wBAAwB,CACzB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/core/network.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,0FAA0F;AAC1F,2DAA2D;AAC3D,OAAO,EAAE,mBAAmB,EAA0B,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC;QACtC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,WAAW,CAAC,oBAAoB,EAAE,CAAC;AAC5C,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC;QACnC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,WAAW,CAAC,iBAAiB,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC;QAC5C,MAAM,IAAI,mBAAmB,
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/core/network.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,0FAA0F;AAC1F,2DAA2D;AAC3D,OAAO,EAAE,mBAAmB,EAA0B,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC;QACtC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,WAAW,CAAC,oBAAoB,EAAE,CAAC;AAC5C,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC;QACnC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,WAAW,CAAC,iBAAiB,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,IAAI,CAAC,WAAW,CAAC,0BAA0B,EAAE,CAAC;QAC5C,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,4BAA4B,CAC7B,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,0BAA0B,EAAE,CAAC;AAClD,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,uBAAuB,CACrC,QAA4C;IAE5C,OAAO,WAAW,CAAC,WAAW,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;AACpE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/network",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "expo-network wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Network state (type/connected/internet-reachable) with a change listener, device IP address, and airplane-mode detection.",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "expo-network wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Network state (type/connected/internet-reachable) with a change listener, device IP address, and airplane-mode detection.",
|
|
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-network",
|
|
20
|
+
"react",
|
|
21
|
+
"vue",
|
|
22
|
+
"svelte",
|
|
23
|
+
"solid",
|
|
24
|
+
"angular",
|
|
25
|
+
"connectivity",
|
|
26
|
+
"network-state"
|
|
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.
|
|
112
|
-
"@symbiote-native/engine": "0.
|
|
113
|
-
"@symbiote-native/react": "0.
|
|
114
|
-
"@symbiote-native/
|
|
115
|
-
"@symbiote-native/
|
|
116
|
-
"@symbiote-native/
|
|
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",
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
effect,
|
|
3
|
+
inject,
|
|
4
|
+
Injectable,
|
|
5
|
+
Injector,
|
|
6
|
+
signal,
|
|
7
|
+
type Signal,
|
|
8
|
+
} from '@angular/core';
|
|
9
|
+
import {
|
|
10
|
+
addNetworkStateListener,
|
|
11
|
+
getNetworkStateAsync,
|
|
12
|
+
type NetworkState,
|
|
13
|
+
} from '../../../core';
|
|
3
14
|
|
|
4
15
|
// Angular twin of React's `useNetworkState` hook and Vue's `useNetworkState` composable.
|
|
5
16
|
//
|
|
@@ -15,7 +26,9 @@ export class NetworkStateService {
|
|
|
15
26
|
effect(
|
|
16
27
|
onCleanup => {
|
|
17
28
|
getNetworkStateAsync().then(state => networkState.set(state));
|
|
18
|
-
const subscription = addNetworkStateListener(event =>
|
|
29
|
+
const subscription = addNetworkStateListener(event =>
|
|
30
|
+
networkState.set(event),
|
|
31
|
+
);
|
|
19
32
|
onCleanup(() => subscription.remove());
|
|
20
33
|
},
|
|
21
34
|
{ injector: this.injector },
|
package/src/core/index.ts
CHANGED
|
@@ -4,5 +4,9 @@ export {
|
|
|
4
4
|
isAirplaneModeEnabledAsync,
|
|
5
5
|
addNetworkStateListener,
|
|
6
6
|
} from './network';
|
|
7
|
-
export {
|
|
7
|
+
export {
|
|
8
|
+
NetworkStateType,
|
|
9
|
+
type NetworkState,
|
|
10
|
+
type NetworkStateEvent,
|
|
11
|
+
} from './types';
|
|
8
12
|
export type { EventSubscription } from 'expo-modules-core';
|
|
@@ -16,4 +16,6 @@ export type INativeNetworkModule = {
|
|
|
16
16
|
): EventSubscription;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export const expoNetwork = requireNativeModule<INativeNetworkModule>(
|
|
19
|
+
export const expoNetwork = requireNativeModule<INativeNetworkModule>(
|
|
20
|
+
EXPO_NETWORK_MODULE_NAME,
|
|
21
|
+
);
|
package/src/core/network.ts
CHANGED
|
@@ -29,7 +29,10 @@ export async function getIpAddressAsync(): Promise<string> {
|
|
|
29
29
|
*/
|
|
30
30
|
export async function isAirplaneModeEnabledAsync(): Promise<boolean> {
|
|
31
31
|
if (!expoNetwork.isAirplaneModeEnabledAsync) {
|
|
32
|
-
throw new UnavailabilityError(
|
|
32
|
+
throw new UnavailabilityError(
|
|
33
|
+
NATIVE_MODULE_NAME,
|
|
34
|
+
'isAirplaneModeEnabledAsync',
|
|
35
|
+
);
|
|
33
36
|
}
|
|
34
37
|
return expoNetwork.isAirplaneModeEnabledAsync();
|
|
35
38
|
}
|
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
// value with a one-shot getNetworkStateAsync() call before the first native event fires, matching
|
|
3
3
|
// upstream's own useNetworkState.
|
|
4
4
|
import { useEffect, useState } from 'react';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
addNetworkStateListener,
|
|
7
|
+
getNetworkStateAsync,
|
|
8
|
+
type NetworkState,
|
|
9
|
+
} from '../../../core';
|
|
6
10
|
|
|
7
11
|
export function useNetworkState(): NetworkState {
|
|
8
12
|
const [networkState, setNetworkState] = useState<NetworkState>({});
|
|
9
13
|
|
|
10
14
|
useEffect(() => {
|
|
11
15
|
getNetworkStateAsync().then(setNetworkState);
|
|
12
|
-
const subscription = addNetworkStateListener(event =>
|
|
16
|
+
const subscription = addNetworkStateListener(event =>
|
|
17
|
+
setNetworkState(event),
|
|
18
|
+
);
|
|
13
19
|
return () => subscription.remove();
|
|
14
20
|
}, []);
|
|
15
21
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @symbiote-native/network/solid: the Solid entry over the framework-agnostic core — mirrors the
|
|
2
|
+
// lifecycle-bucket naming convention of adapters/solid/src/primitives (never `hooks`/`composables`/
|
|
3
|
+
// `runes`, those are React's, Vue's and Svelte's terms).
|
|
4
|
+
|
|
5
|
+
export { createNetworkState } from './primitives/create-network-state';
|
|
6
|
+
export * from '../core';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// createNetworkState — the Solid twin of React's `useNetworkState` hook, Vue's composable and
|
|
2
|
+
// Svelte's rune, over the framework-agnostic core (core/network.ts). Seeds with a one-shot
|
|
3
|
+
// getNetworkStateAsync() call before the first native event fires, matching upstream's own
|
|
4
|
+
// useNetworkState.
|
|
5
|
+
//
|
|
6
|
+
// `primitives/` and `create*`, never `hooks/`+`use*`: Solid's ecosystem calls a composable
|
|
7
|
+
// reactive function a PRIMITIVE and reserves `use*` for consuming something that already exists.
|
|
8
|
+
// Full rationale in adapters/solid/src/primitives/create-color-scheme.ts.
|
|
9
|
+
//
|
|
10
|
+
// Returns an ACCESSOR, never a snapshot: a Solid component body runs ONCE, so a returned object
|
|
11
|
+
// would pin the caller to the empty seed forever.
|
|
12
|
+
//
|
|
13
|
+
// Outside a component or `createRoot` there is no owner for `onCleanup` — Solid warns and the
|
|
14
|
+
// native listener lives for the process. The accessor still tracks; only the teardown is lost.
|
|
15
|
+
|
|
16
|
+
import { createSignal, onCleanup, type Accessor } from 'solid-js';
|
|
17
|
+
import {
|
|
18
|
+
addNetworkStateListener,
|
|
19
|
+
getNetworkStateAsync,
|
|
20
|
+
type EventSubscription,
|
|
21
|
+
type NetworkState,
|
|
22
|
+
} from '../../core';
|
|
23
|
+
|
|
24
|
+
export function createNetworkState(): Accessor<NetworkState> {
|
|
25
|
+
const [networkState, setNetworkState] = createSignal<NetworkState>({});
|
|
26
|
+
|
|
27
|
+
getNetworkStateAsync().then(state => {
|
|
28
|
+
setNetworkState(state);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Subscribed from the primitive body, not from an effect: React/Vue/Svelte start listening a
|
|
32
|
+
// tick after their seed runs, so a change landing in that window is lost. Both statements here
|
|
33
|
+
// run in one synchronous tick and nothing can interleave.
|
|
34
|
+
const subscription: EventSubscription = addNetworkStateListener(event => {
|
|
35
|
+
setNetworkState(event);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
onCleanup(() => {
|
|
39
|
+
subscription.remove();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return networkState;
|
|
43
|
+
}
|