@minip2p/react-native 0.1.1 → 0.3.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.
Files changed (37) hide show
  1. package/README.md +25 -83
  2. package/android/src/main/jniLibs/arm64-v8a/libminip2p_ffi.so +0 -0
  3. package/android/src/main/jniLibs/x86_64/libminip2p_ffi.so +0 -0
  4. package/build/Minip2pFfi.xcframework/ios-arm64/libminip2p_ffi.a +0 -0
  5. package/build/Minip2pFfi.xcframework/ios-arm64-simulator/libminip2p_ffi.a +0 -0
  6. package/cpp/generated/minip2p_ffi.cpp +45 -2
  7. package/cpp/generated/minip2p_ffi.hpp +2 -0
  8. package/lib/module/adapter.js +16 -8
  9. package/lib/module/adapter.js.map +1 -1
  10. package/lib/module/generated/minip2p_ffi-ffi.js.map +1 -1
  11. package/lib/module/generated/minip2p_ffi.js +159 -46
  12. package/lib/module/generated/minip2p_ffi.js.map +1 -1
  13. package/lib/module/hook-lifecycle.js +116 -0
  14. package/lib/module/hook-lifecycle.js.map +1 -0
  15. package/lib/module/hooks.js +56 -0
  16. package/lib/module/hooks.js.map +1 -0
  17. package/lib/module/index.js +2 -1
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/typescript/src/adapter.d.ts +4 -4
  20. package/lib/typescript/src/adapter.d.ts.map +1 -1
  21. package/lib/typescript/src/generated/minip2p_ffi-ffi.d.ts +3 -1
  22. package/lib/typescript/src/generated/minip2p_ffi-ffi.d.ts.map +1 -1
  23. package/lib/typescript/src/generated/minip2p_ffi.d.ts +89 -3
  24. package/lib/typescript/src/generated/minip2p_ffi.d.ts.map +1 -1
  25. package/lib/typescript/src/hook-lifecycle.d.ts +38 -0
  26. package/lib/typescript/src/hook-lifecycle.d.ts.map +1 -0
  27. package/lib/typescript/src/hooks.d.ts +49 -0
  28. package/lib/typescript/src/hooks.d.ts.map +1 -0
  29. package/lib/typescript/src/index.d.ts +2 -1
  30. package/lib/typescript/src/index.d.ts.map +1 -1
  31. package/package.json +3 -2
  32. package/src/adapter.ts +40 -25
  33. package/src/generated/minip2p_ffi-ffi.ts +3 -1
  34. package/src/generated/minip2p_ffi.ts +199 -55
  35. package/src/hook-lifecycle.ts +152 -0
  36. package/src/hooks.ts +102 -0
  37. package/src/index.ts +8 -1
@@ -0,0 +1,152 @@
1
+ /* oxlint-disable promise/prefer-await-to-callbacks -- This module coordinates synchronous lifecycle callbacks. */
2
+
3
+ import { ClosedError } from "@minip2p/core";
4
+ import type { CloseReason, Unsubscribe } from "@minip2p/core";
5
+
6
+ export interface HookEndpoint {
7
+ close: () => void;
8
+ listenAddrs: () => readonly string[];
9
+ onClose: (handler: (reason: CloseReason) => void) => Unsubscribe;
10
+ peerId: () => string;
11
+ setActive: (active: boolean) => void;
12
+ }
13
+
14
+ export interface AppStateSource {
15
+ readonly currentState: string | null | undefined;
16
+ addEventListener: (
17
+ type: "change",
18
+ handler: (state: string) => void
19
+ ) => { remove: () => void };
20
+ }
21
+
22
+ /** @internal */
23
+ export const bindAppStateSource = (
24
+ endpoint: Pick<HookEndpoint, "setActive">,
25
+ source: AppStateSource
26
+ ): Unsubscribe => {
27
+ let active = true;
28
+ try {
29
+ endpoint.setActive(source.currentState === "active");
30
+ } catch (error) {
31
+ if (error instanceof ClosedError) {
32
+ return () => {
33
+ active = false;
34
+ };
35
+ }
36
+ throw error;
37
+ }
38
+ const subscription = source.addEventListener("change", (state) => {
39
+ if (!active) {
40
+ return;
41
+ }
42
+ try {
43
+ endpoint.setActive(state === "active");
44
+ } catch (error) {
45
+ if (!(error instanceof ClosedError)) {
46
+ throw error;
47
+ }
48
+ active = false;
49
+ subscription.remove();
50
+ }
51
+ });
52
+ return () => {
53
+ active = false;
54
+ subscription.remove();
55
+ };
56
+ };
57
+
58
+ export type LifecycleState<Endpoint extends HookEndpoint> =
59
+ | {
60
+ readonly status: "running";
61
+ readonly endpoint: Endpoint;
62
+ readonly peerId: string;
63
+ readonly listenAddrs: readonly string[];
64
+ }
65
+ | { readonly status: "closed" }
66
+ | { readonly status: "failed"; readonly error: unknown };
67
+
68
+ /** @internal */
69
+ export const mountEndpointLifecycle = <Endpoint extends HookEndpoint>(options: {
70
+ readonly bindAppState: (endpoint: Endpoint) => Unsubscribe;
71
+ readonly create: () => Endpoint;
72
+ readonly notify: (state: LifecycleState<Endpoint>) => void;
73
+ readonly schedule?: (callback: () => void) => void;
74
+ }): { readonly close: () => void; readonly cleanup: () => void } => {
75
+ const schedule = options.schedule ?? ((callback) => setTimeout(callback, 0));
76
+ let active = true;
77
+ let generation = 0;
78
+ let endpoint: Endpoint | undefined;
79
+ let removeClose: Unsubscribe | undefined;
80
+ let removeAppState: Unsubscribe | undefined;
81
+
82
+ try {
83
+ const created = options.create();
84
+ endpoint = created;
85
+ removeClose = created.onClose((reason) => {
86
+ if (!active) {
87
+ return;
88
+ }
89
+ generation += 1;
90
+ removeAppState?.();
91
+ removeAppState = undefined;
92
+ endpoint = undefined;
93
+ options.notify(
94
+ reason.reason === "driverFailed"
95
+ ? { error: reason.error, status: "failed" }
96
+ : { status: "closed" }
97
+ );
98
+ });
99
+ const unbindAppState = options.bindAppState(created);
100
+ if (endpoint === created) {
101
+ removeAppState = unbindAppState;
102
+ } else {
103
+ unbindAppState();
104
+ }
105
+ const running = {
106
+ endpoint: created,
107
+ listenAddrs: created.listenAddrs(),
108
+ peerId: created.peerId(),
109
+ status: "running" as const,
110
+ };
111
+ const scheduledGeneration = generation;
112
+ schedule(() => {
113
+ if (
114
+ active &&
115
+ generation === scheduledGeneration &&
116
+ endpoint === created
117
+ ) {
118
+ options.notify(running);
119
+ }
120
+ });
121
+ } catch (error) {
122
+ removeClose?.();
123
+ removeClose = undefined;
124
+ removeAppState?.();
125
+ removeAppState = undefined;
126
+ endpoint?.close();
127
+ endpoint = undefined;
128
+ const scheduledGeneration = generation;
129
+ schedule(() => {
130
+ if (active && generation === scheduledGeneration) {
131
+ options.notify({ error, status: "failed" });
132
+ }
133
+ });
134
+ }
135
+
136
+ return {
137
+ cleanup: () => {
138
+ active = false;
139
+ generation += 1;
140
+ removeClose?.();
141
+ removeClose = undefined;
142
+ removeAppState?.();
143
+ removeAppState = undefined;
144
+ const current = endpoint;
145
+ endpoint = undefined;
146
+ current?.close();
147
+ },
148
+ close: () => {
149
+ endpoint?.close();
150
+ },
151
+ };
152
+ };
package/src/hooks.ts ADDED
@@ -0,0 +1,102 @@
1
+ import type {
2
+ DriverFailedError,
3
+ Minip2pConfig,
4
+ Unsubscribe,
5
+ } from "@minip2p/core";
6
+ import { useCallback, useEffect, useRef, useState } from "react";
7
+ import { AppState } from "react-native";
8
+
9
+ import { Minip2p } from "./adapter";
10
+ import { bindAppStateSource, mountEndpointLifecycle } from "./hook-lifecycle";
11
+
12
+ /** Lifecycle state returned by {@link useMinip2p}. */
13
+ export type Minip2pHookState =
14
+ | {
15
+ readonly status: "starting";
16
+ readonly endpoint?: undefined;
17
+ readonly peerId?: undefined;
18
+ readonly listenAddrs?: undefined;
19
+ readonly error?: undefined;
20
+ }
21
+ | {
22
+ readonly status: "running";
23
+ readonly endpoint: Minip2p;
24
+ readonly peerId: string;
25
+ readonly listenAddrs: readonly string[];
26
+ readonly error?: undefined;
27
+ }
28
+ | {
29
+ readonly status: "closed";
30
+ readonly endpoint?: undefined;
31
+ readonly peerId?: undefined;
32
+ readonly listenAddrs?: undefined;
33
+ readonly error?: undefined;
34
+ }
35
+ | {
36
+ readonly status: "failed";
37
+ readonly endpoint?: undefined;
38
+ readonly peerId?: undefined;
39
+ readonly listenAddrs?: undefined;
40
+ readonly error: unknown;
41
+ };
42
+
43
+ /** Hook lifecycle state plus an idempotent endpoint shutdown callback. */
44
+ export type UseMinip2pResult = Minip2pHookState & {
45
+ /** Closes the currently owned endpoint, if one exists. */
46
+ readonly close: () => void;
47
+ };
48
+
49
+ /** Mirrors the current and future React Native AppState into an endpoint. */
50
+ export const bindAppState = (
51
+ endpoint: Pick<Minip2p, "setActive">
52
+ ): Unsubscribe => bindAppStateSource(endpoint, AppState);
53
+
54
+ /**
55
+ * Owns a native endpoint for a committed component lifetime.
56
+ *
57
+ * Keep `createConfig` pure and stable for the component lifetime. Endpoint
58
+ * construction happens only in the committed effect.
59
+ */
60
+ export const useMinip2p = (
61
+ createConfig: () => Minip2pConfig
62
+ ): UseMinip2pResult => {
63
+ const endpointRef = useRef<Minip2p | null>(null);
64
+ const configFactory = useRef(createConfig);
65
+ const [state, setState] = useState<Minip2pHookState>({
66
+ status: "starting",
67
+ });
68
+ const close = useCallback(() => {
69
+ endpointRef.current?.close();
70
+ }, []);
71
+
72
+ useEffect(() => {
73
+ const lifecycle = mountEndpointLifecycle({
74
+ bindAppState,
75
+ create: () => {
76
+ const endpoint = Minip2p.create(configFactory.current());
77
+ endpointRef.current = endpoint;
78
+ return endpoint;
79
+ },
80
+ notify: (next) => {
81
+ endpointRef.current = next.status === "running" ? next.endpoint : null;
82
+ setState(next);
83
+ },
84
+ });
85
+ return () => {
86
+ endpointRef.current = null;
87
+ lifecycle.cleanup();
88
+ };
89
+ }, []);
90
+
91
+ return { ...state, close };
92
+ };
93
+
94
+ /** Narrows the failed hook state for consumers that want a native failure. */
95
+ export const isDriverFailure = (
96
+ state: Minip2pHookState
97
+ ): state is Extract<Minip2pHookState, { status: "failed" }> & {
98
+ error: DriverFailedError;
99
+ } =>
100
+ state.status === "failed" &&
101
+ state.error instanceof Error &&
102
+ state.error.name === "DriverFailedError";
package/src/index.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  export * from "@minip2p/core";
2
2
  export {
3
- MiniP2p,
3
+ Minip2p,
4
4
  circuitAddress,
5
5
  generateSecretKey,
6
6
  peerIdFromSecretKey,
7
7
  } from "./adapter";
8
+ export {
9
+ bindAppState,
10
+ isDriverFailure,
11
+ useMinip2p,
12
+ type Minip2pHookState,
13
+ type UseMinip2pResult,
14
+ } from "./hooks";