peerserver-client-react 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 +75 -0
- package/dist/index.d.mts +99 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +398 -0
- package/dist/index.mjs +363 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# peerserver-client-react
|
|
2
|
+
|
|
3
|
+
React hooks for [peerserver-client](https://www.npmjs.com/package/peerserver-client).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install peerserver-client peerserver-client-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { PeerProvider } from 'peerserver-client-react';
|
|
15
|
+
|
|
16
|
+
function App() {
|
|
17
|
+
return (
|
|
18
|
+
<PeerProvider config={{ url: 'wss://your-server.com/ws', alias: 'myuser' }}>
|
|
19
|
+
<Chat />
|
|
20
|
+
</PeerProvider>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Hooks
|
|
26
|
+
|
|
27
|
+
### `usePeerClient()`
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
const { connected, fingerprint, alias, error, reconnecting, broadcast, relay } = usePeerClient();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `useNamespace(namespace, appType?, version?)`
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
const { peers, joined, discover } = useNamespace('chat', 'chat', '1.0');
|
|
37
|
+
// peers: PeerInfo[] — auto-updates on join/leave
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `usePeer(fingerprint)`
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
const { connected, messages, streams, send, addStream, close } = usePeer(targetFingerprint);
|
|
44
|
+
send({ msg: 'hello' });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `useMatch(namespace)`
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
const { match, result, matching, connectToMatch } = useMatch('tictactoe');
|
|
51
|
+
await match({ skill: 'beginner' });
|
|
52
|
+
const peer = connectToMatch(); // connects to first matched peer
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `useBroadcast(namespace)`
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
const { messages, send } = useBroadcast('whiteboard');
|
|
59
|
+
send({ tool: 'pen', x: 100, y: 200 });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `useRelay()`
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
const { messages, send } = useRelay();
|
|
66
|
+
send(targetFingerprint, { text: 'hello' });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `useMediaStream()`
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
const { localStream, start, stop, attachToPeer, toggleAudio, toggleVideo } = useMediaStream();
|
|
73
|
+
await start({ video: true, audio: true });
|
|
74
|
+
attachToPeer(peer);
|
|
75
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import * as peerserver_client from 'peerserver-client';
|
|
4
|
+
import { ClientConfig, PeerClient, PeerInfo, DataChannelConfig, Peer, MatchResult } from 'peerserver-client';
|
|
5
|
+
|
|
6
|
+
interface PeerContextValue {
|
|
7
|
+
client: PeerClient | null;
|
|
8
|
+
connected: boolean;
|
|
9
|
+
fingerprint: string;
|
|
10
|
+
alias: string;
|
|
11
|
+
error: Error | null;
|
|
12
|
+
reconnecting: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare function PeerProvider({ config, children, autoConnect, }: {
|
|
15
|
+
config: ClientConfig;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
autoConnect?: boolean;
|
|
18
|
+
}): react_jsx_runtime.JSX.Element;
|
|
19
|
+
declare function usePeerContext(): PeerContextValue;
|
|
20
|
+
|
|
21
|
+
declare function usePeerClient(): {
|
|
22
|
+
client: peerserver_client.PeerClient | null;
|
|
23
|
+
connected: boolean;
|
|
24
|
+
fingerprint: string;
|
|
25
|
+
alias: string;
|
|
26
|
+
error: Error | null;
|
|
27
|
+
reconnecting: boolean;
|
|
28
|
+
relay: (to: string, payload: any) => void | undefined;
|
|
29
|
+
broadcast: (namespace: string, data: any) => void | undefined;
|
|
30
|
+
updateMetadata: (meta: Record<string, any>) => void | undefined;
|
|
31
|
+
disconnect: () => void | undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
declare function useNamespace(namespace: string, appType?: string, version?: string): {
|
|
35
|
+
peers: PeerInfo[];
|
|
36
|
+
joined: boolean;
|
|
37
|
+
discover: (limit?: number) => Promise<PeerInfo[]>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
declare function usePeer(fingerprint: string | null, options?: {
|
|
41
|
+
alias?: string;
|
|
42
|
+
autoConnect?: boolean;
|
|
43
|
+
channelConfig?: DataChannelConfig;
|
|
44
|
+
}): {
|
|
45
|
+
peer: Peer | null;
|
|
46
|
+
connectionState: string;
|
|
47
|
+
connected: boolean;
|
|
48
|
+
messages: any[];
|
|
49
|
+
streams: MediaStream[];
|
|
50
|
+
send: (data: any, channel?: string) => void;
|
|
51
|
+
addStream: (stream: MediaStream) => void;
|
|
52
|
+
removeStream: (stream: MediaStream) => void;
|
|
53
|
+
close: () => void;
|
|
54
|
+
clearMessages: () => void;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
declare function useMatch(namespace: string): {
|
|
58
|
+
match: (criteria?: Record<string, any>) => Promise<MatchResult | undefined>;
|
|
59
|
+
result: MatchResult | null;
|
|
60
|
+
matching: boolean;
|
|
61
|
+
error: Error | null;
|
|
62
|
+
connectToMatch: (index?: number) => peerserver_client.Peer | null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
interface BroadcastMessage {
|
|
66
|
+
from: string;
|
|
67
|
+
namespace: string;
|
|
68
|
+
data: any;
|
|
69
|
+
ts: number;
|
|
70
|
+
}
|
|
71
|
+
declare function useBroadcast(namespace: string, maxHistory?: number): {
|
|
72
|
+
messages: BroadcastMessage[];
|
|
73
|
+
send: (data: any) => void;
|
|
74
|
+
clear: () => void;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
interface RelayMessage {
|
|
78
|
+
from: string;
|
|
79
|
+
data: any;
|
|
80
|
+
ts: number;
|
|
81
|
+
}
|
|
82
|
+
declare function useRelay(maxHistory?: number): {
|
|
83
|
+
messages: RelayMessage[];
|
|
84
|
+
send: (to: string, data: any) => void;
|
|
85
|
+
clear: () => void;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
declare function useMediaStream(): {
|
|
89
|
+
localStream: MediaStream | null;
|
|
90
|
+
error: Error | null;
|
|
91
|
+
start: (constraints?: MediaStreamConstraints) => Promise<MediaStream | null>;
|
|
92
|
+
stop: () => void;
|
|
93
|
+
attachToPeer: (peer: Peer) => void;
|
|
94
|
+
detachFromPeer: (peer: Peer) => void;
|
|
95
|
+
toggleAudio: (enabled?: boolean) => void;
|
|
96
|
+
toggleVideo: (enabled?: boolean) => void;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export { PeerProvider, useBroadcast, useMatch, useMediaStream, useNamespace, usePeer, usePeerClient, usePeerContext, useRelay };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import * as peerserver_client from 'peerserver-client';
|
|
4
|
+
import { ClientConfig, PeerClient, PeerInfo, DataChannelConfig, Peer, MatchResult } from 'peerserver-client';
|
|
5
|
+
|
|
6
|
+
interface PeerContextValue {
|
|
7
|
+
client: PeerClient | null;
|
|
8
|
+
connected: boolean;
|
|
9
|
+
fingerprint: string;
|
|
10
|
+
alias: string;
|
|
11
|
+
error: Error | null;
|
|
12
|
+
reconnecting: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare function PeerProvider({ config, children, autoConnect, }: {
|
|
15
|
+
config: ClientConfig;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
autoConnect?: boolean;
|
|
18
|
+
}): react_jsx_runtime.JSX.Element;
|
|
19
|
+
declare function usePeerContext(): PeerContextValue;
|
|
20
|
+
|
|
21
|
+
declare function usePeerClient(): {
|
|
22
|
+
client: peerserver_client.PeerClient | null;
|
|
23
|
+
connected: boolean;
|
|
24
|
+
fingerprint: string;
|
|
25
|
+
alias: string;
|
|
26
|
+
error: Error | null;
|
|
27
|
+
reconnecting: boolean;
|
|
28
|
+
relay: (to: string, payload: any) => void | undefined;
|
|
29
|
+
broadcast: (namespace: string, data: any) => void | undefined;
|
|
30
|
+
updateMetadata: (meta: Record<string, any>) => void | undefined;
|
|
31
|
+
disconnect: () => void | undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
declare function useNamespace(namespace: string, appType?: string, version?: string): {
|
|
35
|
+
peers: PeerInfo[];
|
|
36
|
+
joined: boolean;
|
|
37
|
+
discover: (limit?: number) => Promise<PeerInfo[]>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
declare function usePeer(fingerprint: string | null, options?: {
|
|
41
|
+
alias?: string;
|
|
42
|
+
autoConnect?: boolean;
|
|
43
|
+
channelConfig?: DataChannelConfig;
|
|
44
|
+
}): {
|
|
45
|
+
peer: Peer | null;
|
|
46
|
+
connectionState: string;
|
|
47
|
+
connected: boolean;
|
|
48
|
+
messages: any[];
|
|
49
|
+
streams: MediaStream[];
|
|
50
|
+
send: (data: any, channel?: string) => void;
|
|
51
|
+
addStream: (stream: MediaStream) => void;
|
|
52
|
+
removeStream: (stream: MediaStream) => void;
|
|
53
|
+
close: () => void;
|
|
54
|
+
clearMessages: () => void;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
declare function useMatch(namespace: string): {
|
|
58
|
+
match: (criteria?: Record<string, any>) => Promise<MatchResult | undefined>;
|
|
59
|
+
result: MatchResult | null;
|
|
60
|
+
matching: boolean;
|
|
61
|
+
error: Error | null;
|
|
62
|
+
connectToMatch: (index?: number) => peerserver_client.Peer | null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
interface BroadcastMessage {
|
|
66
|
+
from: string;
|
|
67
|
+
namespace: string;
|
|
68
|
+
data: any;
|
|
69
|
+
ts: number;
|
|
70
|
+
}
|
|
71
|
+
declare function useBroadcast(namespace: string, maxHistory?: number): {
|
|
72
|
+
messages: BroadcastMessage[];
|
|
73
|
+
send: (data: any) => void;
|
|
74
|
+
clear: () => void;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
interface RelayMessage {
|
|
78
|
+
from: string;
|
|
79
|
+
data: any;
|
|
80
|
+
ts: number;
|
|
81
|
+
}
|
|
82
|
+
declare function useRelay(maxHistory?: number): {
|
|
83
|
+
messages: RelayMessage[];
|
|
84
|
+
send: (to: string, data: any) => void;
|
|
85
|
+
clear: () => void;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
declare function useMediaStream(): {
|
|
89
|
+
localStream: MediaStream | null;
|
|
90
|
+
error: Error | null;
|
|
91
|
+
start: (constraints?: MediaStreamConstraints) => Promise<MediaStream | null>;
|
|
92
|
+
stop: () => void;
|
|
93
|
+
attachToPeer: (peer: Peer) => void;
|
|
94
|
+
detachFromPeer: (peer: Peer) => void;
|
|
95
|
+
toggleAudio: (enabled?: boolean) => void;
|
|
96
|
+
toggleVideo: (enabled?: boolean) => void;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export { PeerProvider, useBroadcast, useMatch, useMediaStream, useNamespace, usePeer, usePeerClient, usePeerContext, useRelay };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
PeerProvider: () => PeerProvider,
|
|
24
|
+
useBroadcast: () => useBroadcast,
|
|
25
|
+
useMatch: () => useMatch,
|
|
26
|
+
useMediaStream: () => useMediaStream,
|
|
27
|
+
useNamespace: () => useNamespace,
|
|
28
|
+
usePeer: () => usePeer,
|
|
29
|
+
usePeerClient: () => usePeerClient,
|
|
30
|
+
usePeerContext: () => usePeerContext,
|
|
31
|
+
useRelay: () => useRelay
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(index_exports);
|
|
34
|
+
|
|
35
|
+
// src/provider.tsx
|
|
36
|
+
var import_react = require("react");
|
|
37
|
+
var import_peerserver_client = require("peerserver-client");
|
|
38
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
39
|
+
var PeerContext = (0, import_react.createContext)({
|
|
40
|
+
client: null,
|
|
41
|
+
connected: false,
|
|
42
|
+
fingerprint: "",
|
|
43
|
+
alias: "",
|
|
44
|
+
error: null,
|
|
45
|
+
reconnecting: false
|
|
46
|
+
});
|
|
47
|
+
function PeerProvider({
|
|
48
|
+
config,
|
|
49
|
+
children,
|
|
50
|
+
autoConnect = true
|
|
51
|
+
}) {
|
|
52
|
+
const clientRef = (0, import_react.useRef)(null);
|
|
53
|
+
const [connected, setConnected] = (0, import_react.useState)(false);
|
|
54
|
+
const [fingerprint, setFingerprint] = (0, import_react.useState)("");
|
|
55
|
+
const [alias, setAlias] = (0, import_react.useState)("");
|
|
56
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
57
|
+
const [reconnecting, setReconnecting] = (0, import_react.useState)(false);
|
|
58
|
+
(0, import_react.useEffect)(() => {
|
|
59
|
+
const client = new import_peerserver_client.PeerClient(config);
|
|
60
|
+
clientRef.current = client;
|
|
61
|
+
client.on("registered", (fp, a) => {
|
|
62
|
+
setFingerprint(fp);
|
|
63
|
+
setAlias(a);
|
|
64
|
+
setConnected(true);
|
|
65
|
+
setReconnecting(false);
|
|
66
|
+
setError(null);
|
|
67
|
+
});
|
|
68
|
+
client.on("disconnected", () => {
|
|
69
|
+
setConnected(false);
|
|
70
|
+
});
|
|
71
|
+
client.on("reconnecting", () => {
|
|
72
|
+
setReconnecting(true);
|
|
73
|
+
});
|
|
74
|
+
client.on("reconnected", () => {
|
|
75
|
+
setReconnecting(false);
|
|
76
|
+
setConnected(true);
|
|
77
|
+
});
|
|
78
|
+
client.on("error", (err) => {
|
|
79
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
80
|
+
});
|
|
81
|
+
if (autoConnect) {
|
|
82
|
+
client.connect().catch((e) => {
|
|
83
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return () => {
|
|
87
|
+
client.disconnect();
|
|
88
|
+
clientRef.current = null;
|
|
89
|
+
};
|
|
90
|
+
}, [config.url]);
|
|
91
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
92
|
+
PeerContext.Provider,
|
|
93
|
+
{
|
|
94
|
+
value: {
|
|
95
|
+
client: clientRef.current,
|
|
96
|
+
connected,
|
|
97
|
+
fingerprint,
|
|
98
|
+
alias,
|
|
99
|
+
error,
|
|
100
|
+
reconnecting
|
|
101
|
+
},
|
|
102
|
+
children
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
function usePeerContext() {
|
|
107
|
+
return (0, import_react.useContext)(PeerContext);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/use-peer-client.ts
|
|
111
|
+
function usePeerClient() {
|
|
112
|
+
const ctx = usePeerContext();
|
|
113
|
+
return {
|
|
114
|
+
client: ctx.client,
|
|
115
|
+
connected: ctx.connected,
|
|
116
|
+
fingerprint: ctx.fingerprint,
|
|
117
|
+
alias: ctx.alias,
|
|
118
|
+
error: ctx.error,
|
|
119
|
+
reconnecting: ctx.reconnecting,
|
|
120
|
+
relay: (to, payload) => ctx.client?.relay(to, payload),
|
|
121
|
+
broadcast: (namespace, data) => ctx.client?.broadcast(namespace, data),
|
|
122
|
+
updateMetadata: (meta) => ctx.client?.updateMetadata(meta),
|
|
123
|
+
disconnect: () => ctx.client?.disconnect()
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/use-namespace.ts
|
|
128
|
+
var import_react2 = require("react");
|
|
129
|
+
function useNamespace(namespace, appType, version) {
|
|
130
|
+
const { client, connected } = usePeerContext();
|
|
131
|
+
const [peers, setPeers] = (0, import_react2.useState)([]);
|
|
132
|
+
const [joined, setJoined] = (0, import_react2.useState)(false);
|
|
133
|
+
(0, import_react2.useEffect)(() => {
|
|
134
|
+
if (!client || !connected || !namespace) return;
|
|
135
|
+
client.join(namespace, appType, version).then((list) => {
|
|
136
|
+
setPeers(list);
|
|
137
|
+
setJoined(true);
|
|
138
|
+
});
|
|
139
|
+
const offJoined = client.on("peer_joined", (info) => {
|
|
140
|
+
setPeers((prev) => {
|
|
141
|
+
if (prev.some((p) => p.fingerprint === info.fingerprint)) return prev;
|
|
142
|
+
return [...prev, info];
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
const offLeft = client.on("peer_left", (fp) => {
|
|
146
|
+
setPeers((prev) => prev.filter((p) => p.fingerprint !== fp));
|
|
147
|
+
});
|
|
148
|
+
return () => {
|
|
149
|
+
offJoined();
|
|
150
|
+
offLeft();
|
|
151
|
+
client.leave(namespace);
|
|
152
|
+
setJoined(false);
|
|
153
|
+
setPeers([]);
|
|
154
|
+
};
|
|
155
|
+
}, [client, connected, namespace]);
|
|
156
|
+
const discover = (0, import_react2.useCallback)(
|
|
157
|
+
(limit) => client?.discover(namespace, limit) ?? Promise.resolve([]),
|
|
158
|
+
[client, namespace]
|
|
159
|
+
);
|
|
160
|
+
return { peers, joined, discover };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/use-peer.ts
|
|
164
|
+
var import_react3 = require("react");
|
|
165
|
+
function usePeer(fingerprint, options) {
|
|
166
|
+
const { client } = usePeerContext();
|
|
167
|
+
const [peer, setPeer] = (0, import_react3.useState)(null);
|
|
168
|
+
const [connectionState, setConnectionState] = (0, import_react3.useState)("new");
|
|
169
|
+
const [messages, setMessages] = (0, import_react3.useState)([]);
|
|
170
|
+
const [streams, setStreams] = (0, import_react3.useState)([]);
|
|
171
|
+
const maxMessages = (0, import_react3.useRef)(100);
|
|
172
|
+
(0, import_react3.useEffect)(() => {
|
|
173
|
+
if (!client || !fingerprint) return;
|
|
174
|
+
const autoConnect = options?.autoConnect ?? true;
|
|
175
|
+
let p;
|
|
176
|
+
const existing = client.getPeer(fingerprint);
|
|
177
|
+
if (existing) {
|
|
178
|
+
p = existing;
|
|
179
|
+
} else if (autoConnect) {
|
|
180
|
+
p = client.connectToPeer(fingerprint, options?.alias ?? "", options?.channelConfig);
|
|
181
|
+
} else {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
setPeer(p);
|
|
185
|
+
setConnectionState(p.connectionState);
|
|
186
|
+
const offConnected = p.on("connected", () => setConnectionState("connected"));
|
|
187
|
+
const offDisconnected = p.on("disconnected", (state) => setConnectionState(state));
|
|
188
|
+
const offData = p.on("data", (data, channel) => {
|
|
189
|
+
setMessages((prev) => {
|
|
190
|
+
const next = [...prev, { data, channel, ts: Date.now() }];
|
|
191
|
+
return next.length > maxMessages.current ? next.slice(-maxMessages.current) : next;
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
const offStream = p.on("stream", (stream) => {
|
|
195
|
+
setStreams((prev) => [...prev, stream]);
|
|
196
|
+
});
|
|
197
|
+
return () => {
|
|
198
|
+
offConnected();
|
|
199
|
+
offDisconnected();
|
|
200
|
+
offData();
|
|
201
|
+
offStream();
|
|
202
|
+
};
|
|
203
|
+
}, [client, fingerprint]);
|
|
204
|
+
const send = (0, import_react3.useCallback)(
|
|
205
|
+
(data, channel) => {
|
|
206
|
+
peer?.send(data, channel);
|
|
207
|
+
},
|
|
208
|
+
[peer]
|
|
209
|
+
);
|
|
210
|
+
const addStream = (0, import_react3.useCallback)(
|
|
211
|
+
(stream) => {
|
|
212
|
+
peer?.addStream(stream);
|
|
213
|
+
},
|
|
214
|
+
[peer]
|
|
215
|
+
);
|
|
216
|
+
const removeStream = (0, import_react3.useCallback)(
|
|
217
|
+
(stream) => {
|
|
218
|
+
peer?.removeStream(stream);
|
|
219
|
+
},
|
|
220
|
+
[peer]
|
|
221
|
+
);
|
|
222
|
+
const close = (0, import_react3.useCallback)(() => {
|
|
223
|
+
if (fingerprint) client?.closePeer(fingerprint);
|
|
224
|
+
setPeer(null);
|
|
225
|
+
setConnectionState("closed");
|
|
226
|
+
setMessages([]);
|
|
227
|
+
setStreams([]);
|
|
228
|
+
}, [client, fingerprint]);
|
|
229
|
+
const clearMessages = (0, import_react3.useCallback)(() => setMessages([]), []);
|
|
230
|
+
return {
|
|
231
|
+
peer,
|
|
232
|
+
connectionState,
|
|
233
|
+
connected: connectionState === "connected",
|
|
234
|
+
messages,
|
|
235
|
+
streams,
|
|
236
|
+
send,
|
|
237
|
+
addStream,
|
|
238
|
+
removeStream,
|
|
239
|
+
close,
|
|
240
|
+
clearMessages
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/use-match.ts
|
|
245
|
+
var import_react4 = require("react");
|
|
246
|
+
function useMatch(namespace) {
|
|
247
|
+
const { client } = usePeerContext();
|
|
248
|
+
const [result, setResult] = (0, import_react4.useState)(null);
|
|
249
|
+
const [matching, setMatching] = (0, import_react4.useState)(false);
|
|
250
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
251
|
+
const match = (0, import_react4.useCallback)(
|
|
252
|
+
async (criteria) => {
|
|
253
|
+
if (!client) return;
|
|
254
|
+
setMatching(true);
|
|
255
|
+
setError(null);
|
|
256
|
+
try {
|
|
257
|
+
const res = await client.match(namespace, criteria);
|
|
258
|
+
setResult(res);
|
|
259
|
+
return res;
|
|
260
|
+
} catch (e) {
|
|
261
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
262
|
+
} finally {
|
|
263
|
+
setMatching(false);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
[client, namespace]
|
|
267
|
+
);
|
|
268
|
+
const connectToMatch = (0, import_react4.useCallback)(
|
|
269
|
+
(index = 0) => {
|
|
270
|
+
if (!client || !result) return null;
|
|
271
|
+
const target = result.peers[index];
|
|
272
|
+
if (!target) return null;
|
|
273
|
+
return client.connectToPeer(target.fingerprint, target.alias);
|
|
274
|
+
},
|
|
275
|
+
[client, result]
|
|
276
|
+
);
|
|
277
|
+
return { match, result, matching, error, connectToMatch };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/use-broadcast.ts
|
|
281
|
+
var import_react5 = require("react");
|
|
282
|
+
function useBroadcast(namespace, maxHistory = 100) {
|
|
283
|
+
const { client } = usePeerContext();
|
|
284
|
+
const [messages, setMessages] = (0, import_react5.useState)([]);
|
|
285
|
+
const maxRef = (0, import_react5.useRef)(maxHistory);
|
|
286
|
+
maxRef.current = maxHistory;
|
|
287
|
+
(0, import_react5.useEffect)(() => {
|
|
288
|
+
if (!client) return;
|
|
289
|
+
const off = client.on("broadcast", (from, ns, data) => {
|
|
290
|
+
if (ns !== namespace) return;
|
|
291
|
+
setMessages((prev) => {
|
|
292
|
+
const next = [...prev, { from, namespace: ns, data, ts: Date.now() }];
|
|
293
|
+
return next.length > maxRef.current ? next.slice(-maxRef.current) : next;
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
return off;
|
|
297
|
+
}, [client, namespace]);
|
|
298
|
+
const send = (0, import_react5.useCallback)(
|
|
299
|
+
(data) => {
|
|
300
|
+
client?.broadcast(namespace, data);
|
|
301
|
+
},
|
|
302
|
+
[client, namespace]
|
|
303
|
+
);
|
|
304
|
+
const clear = (0, import_react5.useCallback)(() => setMessages([]), []);
|
|
305
|
+
return { messages, send, clear };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/use-relay.ts
|
|
309
|
+
var import_react6 = require("react");
|
|
310
|
+
function useRelay(maxHistory = 100) {
|
|
311
|
+
const { client } = usePeerContext();
|
|
312
|
+
const [messages, setMessages] = (0, import_react6.useState)([]);
|
|
313
|
+
const maxRef = (0, import_react6.useRef)(maxHistory);
|
|
314
|
+
maxRef.current = maxHistory;
|
|
315
|
+
(0, import_react6.useEffect)(() => {
|
|
316
|
+
if (!client) return;
|
|
317
|
+
const off = client.on("relay", (from, data) => {
|
|
318
|
+
setMessages((prev) => {
|
|
319
|
+
const next = [...prev, { from, data, ts: Date.now() }];
|
|
320
|
+
return next.length > maxRef.current ? next.slice(-maxRef.current) : next;
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
return off;
|
|
324
|
+
}, [client]);
|
|
325
|
+
const send = (0, import_react6.useCallback)(
|
|
326
|
+
(to, data) => {
|
|
327
|
+
client?.relay(to, data);
|
|
328
|
+
},
|
|
329
|
+
[client]
|
|
330
|
+
);
|
|
331
|
+
const clear = (0, import_react6.useCallback)(() => setMessages([]), []);
|
|
332
|
+
return { messages, send, clear };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/use-media-stream.ts
|
|
336
|
+
var import_react7 = require("react");
|
|
337
|
+
function useMediaStream() {
|
|
338
|
+
const [localStream, setLocalStream] = (0, import_react7.useState)(null);
|
|
339
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
340
|
+
const streamRef = (0, import_react7.useRef)(null);
|
|
341
|
+
const start = (0, import_react7.useCallback)(async (constraints) => {
|
|
342
|
+
try {
|
|
343
|
+
const stream = await navigator.mediaDevices.getUserMedia(
|
|
344
|
+
constraints ?? { audio: true, video: true }
|
|
345
|
+
);
|
|
346
|
+
streamRef.current = stream;
|
|
347
|
+
setLocalStream(stream);
|
|
348
|
+
setError(null);
|
|
349
|
+
return stream;
|
|
350
|
+
} catch (e) {
|
|
351
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}, []);
|
|
355
|
+
const stop = (0, import_react7.useCallback)(() => {
|
|
356
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
357
|
+
streamRef.current = null;
|
|
358
|
+
setLocalStream(null);
|
|
359
|
+
}, []);
|
|
360
|
+
const attachToPeer = (0, import_react7.useCallback)((peer) => {
|
|
361
|
+
if (streamRef.current) peer.addStream(streamRef.current);
|
|
362
|
+
}, []);
|
|
363
|
+
const detachFromPeer = (0, import_react7.useCallback)((peer) => {
|
|
364
|
+
if (streamRef.current) peer.removeStream(streamRef.current);
|
|
365
|
+
}, []);
|
|
366
|
+
const toggleAudio = (0, import_react7.useCallback)((enabled) => {
|
|
367
|
+
streamRef.current?.getAudioTracks().forEach((t) => {
|
|
368
|
+
t.enabled = enabled ?? !t.enabled;
|
|
369
|
+
});
|
|
370
|
+
}, []);
|
|
371
|
+
const toggleVideo = (0, import_react7.useCallback)((enabled) => {
|
|
372
|
+
streamRef.current?.getVideoTracks().forEach((t) => {
|
|
373
|
+
t.enabled = enabled ?? !t.enabled;
|
|
374
|
+
});
|
|
375
|
+
}, []);
|
|
376
|
+
return {
|
|
377
|
+
localStream,
|
|
378
|
+
error,
|
|
379
|
+
start,
|
|
380
|
+
stop,
|
|
381
|
+
attachToPeer,
|
|
382
|
+
detachFromPeer,
|
|
383
|
+
toggleAudio,
|
|
384
|
+
toggleVideo
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
388
|
+
0 && (module.exports = {
|
|
389
|
+
PeerProvider,
|
|
390
|
+
useBroadcast,
|
|
391
|
+
useMatch,
|
|
392
|
+
useMediaStream,
|
|
393
|
+
useNamespace,
|
|
394
|
+
usePeer,
|
|
395
|
+
usePeerClient,
|
|
396
|
+
usePeerContext,
|
|
397
|
+
useRelay
|
|
398
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// src/provider.tsx
|
|
2
|
+
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { PeerClient } from "peerserver-client";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
var PeerContext = createContext({
|
|
6
|
+
client: null,
|
|
7
|
+
connected: false,
|
|
8
|
+
fingerprint: "",
|
|
9
|
+
alias: "",
|
|
10
|
+
error: null,
|
|
11
|
+
reconnecting: false
|
|
12
|
+
});
|
|
13
|
+
function PeerProvider({
|
|
14
|
+
config,
|
|
15
|
+
children,
|
|
16
|
+
autoConnect = true
|
|
17
|
+
}) {
|
|
18
|
+
const clientRef = useRef(null);
|
|
19
|
+
const [connected, setConnected] = useState(false);
|
|
20
|
+
const [fingerprint, setFingerprint] = useState("");
|
|
21
|
+
const [alias, setAlias] = useState("");
|
|
22
|
+
const [error, setError] = useState(null);
|
|
23
|
+
const [reconnecting, setReconnecting] = useState(false);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const client = new PeerClient(config);
|
|
26
|
+
clientRef.current = client;
|
|
27
|
+
client.on("registered", (fp, a) => {
|
|
28
|
+
setFingerprint(fp);
|
|
29
|
+
setAlias(a);
|
|
30
|
+
setConnected(true);
|
|
31
|
+
setReconnecting(false);
|
|
32
|
+
setError(null);
|
|
33
|
+
});
|
|
34
|
+
client.on("disconnected", () => {
|
|
35
|
+
setConnected(false);
|
|
36
|
+
});
|
|
37
|
+
client.on("reconnecting", () => {
|
|
38
|
+
setReconnecting(true);
|
|
39
|
+
});
|
|
40
|
+
client.on("reconnected", () => {
|
|
41
|
+
setReconnecting(false);
|
|
42
|
+
setConnected(true);
|
|
43
|
+
});
|
|
44
|
+
client.on("error", (err) => {
|
|
45
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
46
|
+
});
|
|
47
|
+
if (autoConnect) {
|
|
48
|
+
client.connect().catch((e) => {
|
|
49
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return () => {
|
|
53
|
+
client.disconnect();
|
|
54
|
+
clientRef.current = null;
|
|
55
|
+
};
|
|
56
|
+
}, [config.url]);
|
|
57
|
+
return /* @__PURE__ */ jsx(
|
|
58
|
+
PeerContext.Provider,
|
|
59
|
+
{
|
|
60
|
+
value: {
|
|
61
|
+
client: clientRef.current,
|
|
62
|
+
connected,
|
|
63
|
+
fingerprint,
|
|
64
|
+
alias,
|
|
65
|
+
error,
|
|
66
|
+
reconnecting
|
|
67
|
+
},
|
|
68
|
+
children
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
function usePeerContext() {
|
|
73
|
+
return useContext(PeerContext);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/use-peer-client.ts
|
|
77
|
+
function usePeerClient() {
|
|
78
|
+
const ctx = usePeerContext();
|
|
79
|
+
return {
|
|
80
|
+
client: ctx.client,
|
|
81
|
+
connected: ctx.connected,
|
|
82
|
+
fingerprint: ctx.fingerprint,
|
|
83
|
+
alias: ctx.alias,
|
|
84
|
+
error: ctx.error,
|
|
85
|
+
reconnecting: ctx.reconnecting,
|
|
86
|
+
relay: (to, payload) => ctx.client?.relay(to, payload),
|
|
87
|
+
broadcast: (namespace, data) => ctx.client?.broadcast(namespace, data),
|
|
88
|
+
updateMetadata: (meta) => ctx.client?.updateMetadata(meta),
|
|
89
|
+
disconnect: () => ctx.client?.disconnect()
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/use-namespace.ts
|
|
94
|
+
import { useEffect as useEffect2, useState as useState2, useCallback } from "react";
|
|
95
|
+
function useNamespace(namespace, appType, version) {
|
|
96
|
+
const { client, connected } = usePeerContext();
|
|
97
|
+
const [peers, setPeers] = useState2([]);
|
|
98
|
+
const [joined, setJoined] = useState2(false);
|
|
99
|
+
useEffect2(() => {
|
|
100
|
+
if (!client || !connected || !namespace) return;
|
|
101
|
+
client.join(namespace, appType, version).then((list) => {
|
|
102
|
+
setPeers(list);
|
|
103
|
+
setJoined(true);
|
|
104
|
+
});
|
|
105
|
+
const offJoined = client.on("peer_joined", (info) => {
|
|
106
|
+
setPeers((prev) => {
|
|
107
|
+
if (prev.some((p) => p.fingerprint === info.fingerprint)) return prev;
|
|
108
|
+
return [...prev, info];
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
const offLeft = client.on("peer_left", (fp) => {
|
|
112
|
+
setPeers((prev) => prev.filter((p) => p.fingerprint !== fp));
|
|
113
|
+
});
|
|
114
|
+
return () => {
|
|
115
|
+
offJoined();
|
|
116
|
+
offLeft();
|
|
117
|
+
client.leave(namespace);
|
|
118
|
+
setJoined(false);
|
|
119
|
+
setPeers([]);
|
|
120
|
+
};
|
|
121
|
+
}, [client, connected, namespace]);
|
|
122
|
+
const discover = useCallback(
|
|
123
|
+
(limit) => client?.discover(namespace, limit) ?? Promise.resolve([]),
|
|
124
|
+
[client, namespace]
|
|
125
|
+
);
|
|
126
|
+
return { peers, joined, discover };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/use-peer.ts
|
|
130
|
+
import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
131
|
+
function usePeer(fingerprint, options) {
|
|
132
|
+
const { client } = usePeerContext();
|
|
133
|
+
const [peer, setPeer] = useState3(null);
|
|
134
|
+
const [connectionState, setConnectionState] = useState3("new");
|
|
135
|
+
const [messages, setMessages] = useState3([]);
|
|
136
|
+
const [streams, setStreams] = useState3([]);
|
|
137
|
+
const maxMessages = useRef2(100);
|
|
138
|
+
useEffect3(() => {
|
|
139
|
+
if (!client || !fingerprint) return;
|
|
140
|
+
const autoConnect = options?.autoConnect ?? true;
|
|
141
|
+
let p;
|
|
142
|
+
const existing = client.getPeer(fingerprint);
|
|
143
|
+
if (existing) {
|
|
144
|
+
p = existing;
|
|
145
|
+
} else if (autoConnect) {
|
|
146
|
+
p = client.connectToPeer(fingerprint, options?.alias ?? "", options?.channelConfig);
|
|
147
|
+
} else {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
setPeer(p);
|
|
151
|
+
setConnectionState(p.connectionState);
|
|
152
|
+
const offConnected = p.on("connected", () => setConnectionState("connected"));
|
|
153
|
+
const offDisconnected = p.on("disconnected", (state) => setConnectionState(state));
|
|
154
|
+
const offData = p.on("data", (data, channel) => {
|
|
155
|
+
setMessages((prev) => {
|
|
156
|
+
const next = [...prev, { data, channel, ts: Date.now() }];
|
|
157
|
+
return next.length > maxMessages.current ? next.slice(-maxMessages.current) : next;
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
const offStream = p.on("stream", (stream) => {
|
|
161
|
+
setStreams((prev) => [...prev, stream]);
|
|
162
|
+
});
|
|
163
|
+
return () => {
|
|
164
|
+
offConnected();
|
|
165
|
+
offDisconnected();
|
|
166
|
+
offData();
|
|
167
|
+
offStream();
|
|
168
|
+
};
|
|
169
|
+
}, [client, fingerprint]);
|
|
170
|
+
const send = useCallback2(
|
|
171
|
+
(data, channel) => {
|
|
172
|
+
peer?.send(data, channel);
|
|
173
|
+
},
|
|
174
|
+
[peer]
|
|
175
|
+
);
|
|
176
|
+
const addStream = useCallback2(
|
|
177
|
+
(stream) => {
|
|
178
|
+
peer?.addStream(stream);
|
|
179
|
+
},
|
|
180
|
+
[peer]
|
|
181
|
+
);
|
|
182
|
+
const removeStream = useCallback2(
|
|
183
|
+
(stream) => {
|
|
184
|
+
peer?.removeStream(stream);
|
|
185
|
+
},
|
|
186
|
+
[peer]
|
|
187
|
+
);
|
|
188
|
+
const close = useCallback2(() => {
|
|
189
|
+
if (fingerprint) client?.closePeer(fingerprint);
|
|
190
|
+
setPeer(null);
|
|
191
|
+
setConnectionState("closed");
|
|
192
|
+
setMessages([]);
|
|
193
|
+
setStreams([]);
|
|
194
|
+
}, [client, fingerprint]);
|
|
195
|
+
const clearMessages = useCallback2(() => setMessages([]), []);
|
|
196
|
+
return {
|
|
197
|
+
peer,
|
|
198
|
+
connectionState,
|
|
199
|
+
connected: connectionState === "connected",
|
|
200
|
+
messages,
|
|
201
|
+
streams,
|
|
202
|
+
send,
|
|
203
|
+
addStream,
|
|
204
|
+
removeStream,
|
|
205
|
+
close,
|
|
206
|
+
clearMessages
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/use-match.ts
|
|
211
|
+
import { useState as useState4, useCallback as useCallback3 } from "react";
|
|
212
|
+
function useMatch(namespace) {
|
|
213
|
+
const { client } = usePeerContext();
|
|
214
|
+
const [result, setResult] = useState4(null);
|
|
215
|
+
const [matching, setMatching] = useState4(false);
|
|
216
|
+
const [error, setError] = useState4(null);
|
|
217
|
+
const match = useCallback3(
|
|
218
|
+
async (criteria) => {
|
|
219
|
+
if (!client) return;
|
|
220
|
+
setMatching(true);
|
|
221
|
+
setError(null);
|
|
222
|
+
try {
|
|
223
|
+
const res = await client.match(namespace, criteria);
|
|
224
|
+
setResult(res);
|
|
225
|
+
return res;
|
|
226
|
+
} catch (e) {
|
|
227
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
228
|
+
} finally {
|
|
229
|
+
setMatching(false);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
[client, namespace]
|
|
233
|
+
);
|
|
234
|
+
const connectToMatch = useCallback3(
|
|
235
|
+
(index = 0) => {
|
|
236
|
+
if (!client || !result) return null;
|
|
237
|
+
const target = result.peers[index];
|
|
238
|
+
if (!target) return null;
|
|
239
|
+
return client.connectToPeer(target.fingerprint, target.alias);
|
|
240
|
+
},
|
|
241
|
+
[client, result]
|
|
242
|
+
);
|
|
243
|
+
return { match, result, matching, error, connectToMatch };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/use-broadcast.ts
|
|
247
|
+
import { useEffect as useEffect4, useState as useState5, useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
248
|
+
function useBroadcast(namespace, maxHistory = 100) {
|
|
249
|
+
const { client } = usePeerContext();
|
|
250
|
+
const [messages, setMessages] = useState5([]);
|
|
251
|
+
const maxRef = useRef3(maxHistory);
|
|
252
|
+
maxRef.current = maxHistory;
|
|
253
|
+
useEffect4(() => {
|
|
254
|
+
if (!client) return;
|
|
255
|
+
const off = client.on("broadcast", (from, ns, data) => {
|
|
256
|
+
if (ns !== namespace) return;
|
|
257
|
+
setMessages((prev) => {
|
|
258
|
+
const next = [...prev, { from, namespace: ns, data, ts: Date.now() }];
|
|
259
|
+
return next.length > maxRef.current ? next.slice(-maxRef.current) : next;
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
return off;
|
|
263
|
+
}, [client, namespace]);
|
|
264
|
+
const send = useCallback4(
|
|
265
|
+
(data) => {
|
|
266
|
+
client?.broadcast(namespace, data);
|
|
267
|
+
},
|
|
268
|
+
[client, namespace]
|
|
269
|
+
);
|
|
270
|
+
const clear = useCallback4(() => setMessages([]), []);
|
|
271
|
+
return { messages, send, clear };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/use-relay.ts
|
|
275
|
+
import { useEffect as useEffect5, useState as useState6, useCallback as useCallback5, useRef as useRef4 } from "react";
|
|
276
|
+
function useRelay(maxHistory = 100) {
|
|
277
|
+
const { client } = usePeerContext();
|
|
278
|
+
const [messages, setMessages] = useState6([]);
|
|
279
|
+
const maxRef = useRef4(maxHistory);
|
|
280
|
+
maxRef.current = maxHistory;
|
|
281
|
+
useEffect5(() => {
|
|
282
|
+
if (!client) return;
|
|
283
|
+
const off = client.on("relay", (from, data) => {
|
|
284
|
+
setMessages((prev) => {
|
|
285
|
+
const next = [...prev, { from, data, ts: Date.now() }];
|
|
286
|
+
return next.length > maxRef.current ? next.slice(-maxRef.current) : next;
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
return off;
|
|
290
|
+
}, [client]);
|
|
291
|
+
const send = useCallback5(
|
|
292
|
+
(to, data) => {
|
|
293
|
+
client?.relay(to, data);
|
|
294
|
+
},
|
|
295
|
+
[client]
|
|
296
|
+
);
|
|
297
|
+
const clear = useCallback5(() => setMessages([]), []);
|
|
298
|
+
return { messages, send, clear };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/use-media-stream.ts
|
|
302
|
+
import { useState as useState7, useCallback as useCallback6, useRef as useRef5 } from "react";
|
|
303
|
+
function useMediaStream() {
|
|
304
|
+
const [localStream, setLocalStream] = useState7(null);
|
|
305
|
+
const [error, setError] = useState7(null);
|
|
306
|
+
const streamRef = useRef5(null);
|
|
307
|
+
const start = useCallback6(async (constraints) => {
|
|
308
|
+
try {
|
|
309
|
+
const stream = await navigator.mediaDevices.getUserMedia(
|
|
310
|
+
constraints ?? { audio: true, video: true }
|
|
311
|
+
);
|
|
312
|
+
streamRef.current = stream;
|
|
313
|
+
setLocalStream(stream);
|
|
314
|
+
setError(null);
|
|
315
|
+
return stream;
|
|
316
|
+
} catch (e) {
|
|
317
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}, []);
|
|
321
|
+
const stop = useCallback6(() => {
|
|
322
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
323
|
+
streamRef.current = null;
|
|
324
|
+
setLocalStream(null);
|
|
325
|
+
}, []);
|
|
326
|
+
const attachToPeer = useCallback6((peer) => {
|
|
327
|
+
if (streamRef.current) peer.addStream(streamRef.current);
|
|
328
|
+
}, []);
|
|
329
|
+
const detachFromPeer = useCallback6((peer) => {
|
|
330
|
+
if (streamRef.current) peer.removeStream(streamRef.current);
|
|
331
|
+
}, []);
|
|
332
|
+
const toggleAudio = useCallback6((enabled) => {
|
|
333
|
+
streamRef.current?.getAudioTracks().forEach((t) => {
|
|
334
|
+
t.enabled = enabled ?? !t.enabled;
|
|
335
|
+
});
|
|
336
|
+
}, []);
|
|
337
|
+
const toggleVideo = useCallback6((enabled) => {
|
|
338
|
+
streamRef.current?.getVideoTracks().forEach((t) => {
|
|
339
|
+
t.enabled = enabled ?? !t.enabled;
|
|
340
|
+
});
|
|
341
|
+
}, []);
|
|
342
|
+
return {
|
|
343
|
+
localStream,
|
|
344
|
+
error,
|
|
345
|
+
start,
|
|
346
|
+
stop,
|
|
347
|
+
attachToPeer,
|
|
348
|
+
detachFromPeer,
|
|
349
|
+
toggleAudio,
|
|
350
|
+
toggleVideo
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
export {
|
|
354
|
+
PeerProvider,
|
|
355
|
+
useBroadcast,
|
|
356
|
+
useMatch,
|
|
357
|
+
useMediaStream,
|
|
358
|
+
useNamespace,
|
|
359
|
+
usePeer,
|
|
360
|
+
usePeerClient,
|
|
361
|
+
usePeerContext,
|
|
362
|
+
useRelay
|
|
363
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "peerserver-client-react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React hooks for peerserver-client",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": ">=17.0.0",
|
|
20
|
+
"peerserver-client": ">=1.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/react": "^18.2.0",
|
|
24
|
+
"react": "^18.2.0",
|
|
25
|
+
"tsup": "^8.0.0",
|
|
26
|
+
"typescript": "^5.4.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"webrtc",
|
|
30
|
+
"react",
|
|
31
|
+
"hooks",
|
|
32
|
+
"p2p",
|
|
33
|
+
"peerserver"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean --external react --external peerserver-client"
|
|
38
|
+
}
|
|
39
|
+
}
|