peer-client 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/LICENSE +21 -0
- package/README.md +1086 -0
- package/dist/core/client.d.ts +41 -0
- package/dist/core/client.js +361 -0
- package/dist/core/emitter.d.ts +11 -0
- package/dist/core/emitter.js +46 -0
- package/dist/core/identity.d.ts +15 -0
- package/dist/core/identity.js +54 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +6 -0
- package/dist/core/peer.d.ts +29 -0
- package/dist/core/peer.js +234 -0
- package/dist/core/transport.d.ts +35 -0
- package/dist/core/transport.js +174 -0
- package/dist/core/types.d.ts +173 -0
- package/dist/core/types.js +28 -0
- package/dist/crypto.d.ts +32 -0
- package/dist/crypto.js +168 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/media.d.ts +63 -0
- package/dist/media.js +275 -0
- package/dist/react/Audio.d.ts +6 -0
- package/dist/react/Audio.js +18 -0
- package/dist/react/PeerProvider.d.ts +17 -0
- package/dist/react/PeerProvider.js +46 -0
- package/dist/react/PeerStatus.d.ts +7 -0
- package/dist/react/PeerStatus.js +20 -0
- package/dist/react/TransferProgress.d.ts +10 -0
- package/dist/react/TransferProgress.js +17 -0
- package/dist/react/Video.d.ts +7 -0
- package/dist/react/Video.js +18 -0
- package/dist/react/index.d.ts +19 -0
- package/dist/react/index.js +18 -0
- package/dist/react/useBroadcast.d.ts +12 -0
- package/dist/react/useBroadcast.js +21 -0
- package/dist/react/useCRDT.d.ts +8 -0
- package/dist/react/useCRDT.js +37 -0
- package/dist/react/useE2E.d.ts +11 -0
- package/dist/react/useE2E.js +62 -0
- package/dist/react/useFileTransfer.d.ts +24 -0
- package/dist/react/useFileTransfer.js +133 -0
- package/dist/react/useIdentity.d.ts +9 -0
- package/dist/react/useIdentity.js +63 -0
- package/dist/react/useMatch.d.ts +11 -0
- package/dist/react/useMatch.js +33 -0
- package/dist/react/useMedia.d.ts +13 -0
- package/dist/react/useMedia.js +89 -0
- package/dist/react/useNamespace.d.ts +7 -0
- package/dist/react/useNamespace.js +38 -0
- package/dist/react/usePeer.d.ts +6 -0
- package/dist/react/usePeer.js +49 -0
- package/dist/react/usePeerClient.d.ts +7 -0
- package/dist/react/usePeerClient.js +5 -0
- package/dist/react/useRelay.d.ts +11 -0
- package/dist/react/useRelay.js +19 -0
- package/dist/react/useRoom.d.ts +17 -0
- package/dist/react/useRoom.js +67 -0
- package/dist/react/useSync.d.ts +8 -0
- package/dist/react/useSync.js +45 -0
- package/dist/room.d.ts +44 -0
- package/dist/room.js +246 -0
- package/dist/sync.d.ts +45 -0
- package/dist/sync.js +333 -0
- package/dist/transfer.d.ts +49 -0
- package/dist/transfer.js +454 -0
- package/package.json +76 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from 'react';
|
|
2
|
+
import { usePeerContext } from './PeerProvider';
|
|
3
|
+
export function usePeer(fingerprint) {
|
|
4
|
+
const { client } = usePeerContext();
|
|
5
|
+
const [peer, setPeer] = useState(null);
|
|
6
|
+
const [connectionState, setConnectionState] = useState('new');
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!client || !fingerprint)
|
|
9
|
+
return;
|
|
10
|
+
const existing = client.getPeer(fingerprint);
|
|
11
|
+
if (existing) {
|
|
12
|
+
setPeer(existing);
|
|
13
|
+
setConnectionState(existing.connectionState);
|
|
14
|
+
}
|
|
15
|
+
const offJoined = client.on('peer_joined', (info) => {
|
|
16
|
+
if (info.fingerprint === fingerprint) {
|
|
17
|
+
const p = client.getPeer(fingerprint);
|
|
18
|
+
if (p) {
|
|
19
|
+
setPeer(p);
|
|
20
|
+
setConnectionState(p.connectionState);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const offLeft = client.on('peer_left', (fp) => {
|
|
25
|
+
if (fp === fingerprint) {
|
|
26
|
+
setPeer(null);
|
|
27
|
+
setConnectionState('closed');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return () => {
|
|
31
|
+
offJoined();
|
|
32
|
+
offLeft();
|
|
33
|
+
};
|
|
34
|
+
}, [client, fingerprint]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!peer)
|
|
37
|
+
return;
|
|
38
|
+
const offConnected = peer.on('connected', () => setConnectionState('connected'));
|
|
39
|
+
const offDisconnected = peer.on('disconnected', (s) => setConnectionState(s));
|
|
40
|
+
return () => {
|
|
41
|
+
offConnected();
|
|
42
|
+
offDisconnected();
|
|
43
|
+
};
|
|
44
|
+
}, [peer]);
|
|
45
|
+
const send = useCallback((data, channel) => {
|
|
46
|
+
peer?.send(data, channel);
|
|
47
|
+
}, [peer]);
|
|
48
|
+
return { peer, connectionState, send };
|
|
49
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from 'react';
|
|
2
|
+
import { usePeerContext } from './PeerProvider';
|
|
3
|
+
export function useRelay() {
|
|
4
|
+
const { client } = usePeerContext();
|
|
5
|
+
const [messages, setMessages] = useState([]);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!client)
|
|
8
|
+
return;
|
|
9
|
+
const off = client.on('relay', (from, payload) => {
|
|
10
|
+
setMessages((prev) => [...prev, { from, payload, ts: Date.now() }]);
|
|
11
|
+
});
|
|
12
|
+
return () => { off(); };
|
|
13
|
+
}, [client]);
|
|
14
|
+
const send = useCallback((to, payload) => {
|
|
15
|
+
client?.relay(to, payload);
|
|
16
|
+
}, [client]);
|
|
17
|
+
const clear = useCallback(() => setMessages([]), []);
|
|
18
|
+
return { messages, send, clear };
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DirectRoom, GroupRoom } from '../room';
|
|
2
|
+
import type { PeerInfo } from '../core/types';
|
|
3
|
+
interface RoomMessage {
|
|
4
|
+
data: any;
|
|
5
|
+
from: string;
|
|
6
|
+
ts: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function useRoom(roomId: string, type?: 'direct' | 'group', create?: boolean, maxSize?: number): {
|
|
9
|
+
joined: boolean;
|
|
10
|
+
peers: PeerInfo[];
|
|
11
|
+
messages: RoomMessage[];
|
|
12
|
+
send: (data: any, to?: string) => void;
|
|
13
|
+
clearMessages: () => void;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
room: DirectRoom | GroupRoom | null;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
2
|
+
import { usePeerContext } from './PeerProvider';
|
|
3
|
+
import { DirectRoom, GroupRoom } from '../room';
|
|
4
|
+
export function useRoom(roomId, type = 'direct', create = false, maxSize = 20) {
|
|
5
|
+
const { client } = usePeerContext();
|
|
6
|
+
const [peers, setPeers] = useState([]);
|
|
7
|
+
const [messages, setMessages] = useState([]);
|
|
8
|
+
const [joined, setJoined] = useState(false);
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const roomRef = useRef(null);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!client || !roomId)
|
|
13
|
+
return;
|
|
14
|
+
const room = type === 'group'
|
|
15
|
+
? new GroupRoom(client, roomId, maxSize)
|
|
16
|
+
: new DirectRoom(client, roomId);
|
|
17
|
+
roomRef.current = room;
|
|
18
|
+
room.on('peer_joined', (info) => {
|
|
19
|
+
setPeers((prev) => [...prev.filter((p) => p.fingerprint !== info.fingerprint), info]);
|
|
20
|
+
});
|
|
21
|
+
room.on('peer_left', (fp) => {
|
|
22
|
+
setPeers((prev) => prev.filter((p) => p.fingerprint !== fp));
|
|
23
|
+
});
|
|
24
|
+
room.on('data', (data, from) => {
|
|
25
|
+
setMessages((prev) => [...prev, { data, from, ts: Date.now() }]);
|
|
26
|
+
});
|
|
27
|
+
room.on('error', (e) => {
|
|
28
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
29
|
+
});
|
|
30
|
+
const init = async () => {
|
|
31
|
+
try {
|
|
32
|
+
if (create) {
|
|
33
|
+
await room.create();
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const peerList = await room.join();
|
|
37
|
+
const others = peerList.filter((p) => p.fingerprint !== client.fingerprint);
|
|
38
|
+
setPeers(others);
|
|
39
|
+
}
|
|
40
|
+
setJoined(true);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
init();
|
|
47
|
+
return () => {
|
|
48
|
+
room.close();
|
|
49
|
+
roomRef.current = null;
|
|
50
|
+
setJoined(false);
|
|
51
|
+
setPeers([]);
|
|
52
|
+
};
|
|
53
|
+
}, [client, roomId, type, maxSize, create]);
|
|
54
|
+
const send = useCallback((data, to) => {
|
|
55
|
+
const room = roomRef.current;
|
|
56
|
+
if (!room)
|
|
57
|
+
return;
|
|
58
|
+
if (room instanceof GroupRoom) {
|
|
59
|
+
room.send(data, to);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
room.send(data);
|
|
63
|
+
}
|
|
64
|
+
}, []);
|
|
65
|
+
const clearMessages = useCallback(() => setMessages([]), []);
|
|
66
|
+
return { joined, peers, messages, send, clearMessages, error, room: roomRef.current };
|
|
67
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SyncMode } from '../core/types';
|
|
2
|
+
export declare function useSync(roomId: string, mode?: SyncMode, merge?: (local: any, remote: any) => any): {
|
|
3
|
+
state: Record<string, any>;
|
|
4
|
+
set: (key: string, value: any) => void;
|
|
5
|
+
delete: (key: string) => void;
|
|
6
|
+
get: (key: string) => any;
|
|
7
|
+
error: Error | null;
|
|
8
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
2
|
+
import { usePeerContext } from './PeerProvider';
|
|
3
|
+
import { StateSync } from '../sync';
|
|
4
|
+
export function useSync(roomId, mode = 'lww', merge) {
|
|
5
|
+
const { client } = usePeerContext();
|
|
6
|
+
const [state, setState] = useState({});
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
const syncRef = useRef(null);
|
|
9
|
+
const mergeRef = useRef(merge);
|
|
10
|
+
mergeRef.current = merge;
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!client || !roomId)
|
|
13
|
+
return;
|
|
14
|
+
const sync = new StateSync(client, roomId, { mode, merge: mergeRef.current });
|
|
15
|
+
syncRef.current = sync;
|
|
16
|
+
sync.start();
|
|
17
|
+
sync.on('state_changed', () => {
|
|
18
|
+
setState(sync.getAll());
|
|
19
|
+
});
|
|
20
|
+
sync.on('error', (e) => {
|
|
21
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
22
|
+
});
|
|
23
|
+
return () => {
|
|
24
|
+
sync.destroy();
|
|
25
|
+
syncRef.current = null;
|
|
26
|
+
setState({});
|
|
27
|
+
};
|
|
28
|
+
}, [client, roomId, mode]);
|
|
29
|
+
const set = useCallback((key, value) => {
|
|
30
|
+
syncRef.current?.set(key, value);
|
|
31
|
+
setState((prev) => ({ ...prev, [key]: value }));
|
|
32
|
+
}, []);
|
|
33
|
+
const del = useCallback((key) => {
|
|
34
|
+
syncRef.current?.delete(key);
|
|
35
|
+
setState((prev) => {
|
|
36
|
+
const next = { ...prev };
|
|
37
|
+
delete next[key];
|
|
38
|
+
return next;
|
|
39
|
+
});
|
|
40
|
+
}, []);
|
|
41
|
+
const get = useCallback((key) => {
|
|
42
|
+
return syncRef.current?.get(key);
|
|
43
|
+
}, []);
|
|
44
|
+
return { state, set, delete: del, get, error };
|
|
45
|
+
}
|
package/dist/room.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Emitter } from './core/emitter';
|
|
2
|
+
import type { PeerClient } from './core/client';
|
|
3
|
+
import type { Peer } from './core/peer';
|
|
4
|
+
import type { PeerInfo } from './core/types';
|
|
5
|
+
type RoomEvent = 'peer_joined' | 'peer_left' | 'peer_connected' | 'data' | 'closed' | 'error';
|
|
6
|
+
export declare class DirectRoom extends Emitter<RoomEvent> {
|
|
7
|
+
private client;
|
|
8
|
+
private roomId;
|
|
9
|
+
private remotePeer;
|
|
10
|
+
private remoteFingerprint;
|
|
11
|
+
private _closed;
|
|
12
|
+
private cleanups;
|
|
13
|
+
constructor(client: PeerClient, roomId: string);
|
|
14
|
+
create(): Promise<void>;
|
|
15
|
+
join(): Promise<PeerInfo[]>;
|
|
16
|
+
private listen;
|
|
17
|
+
private connectTo;
|
|
18
|
+
send(data: any): void;
|
|
19
|
+
getPeer(): Peer | null;
|
|
20
|
+
getRemoteFingerprint(): string;
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
23
|
+
export declare class GroupRoom extends Emitter<RoomEvent> {
|
|
24
|
+
private client;
|
|
25
|
+
private roomId;
|
|
26
|
+
private maxSize;
|
|
27
|
+
private connectedPeers;
|
|
28
|
+
private relayPeers;
|
|
29
|
+
private _closed;
|
|
30
|
+
private cleanups;
|
|
31
|
+
constructor(client: PeerClient, roomId: string, maxSize?: number);
|
|
32
|
+
create(): Promise<void>;
|
|
33
|
+
join(): Promise<PeerInfo[]>;
|
|
34
|
+
private listen;
|
|
35
|
+
private connectTo;
|
|
36
|
+
private promoteRelayPeers;
|
|
37
|
+
send(data: any, to?: string): void;
|
|
38
|
+
broadcastViaServer(data: any): void;
|
|
39
|
+
kick(fingerprint: string): void;
|
|
40
|
+
getPeers(): Map<string, Peer>;
|
|
41
|
+
getPeerCount(): number;
|
|
42
|
+
close(): void;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
package/dist/room.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { Emitter } from './core/emitter';
|
|
2
|
+
import { LIMITS } from './core/types';
|
|
3
|
+
export class DirectRoom extends Emitter {
|
|
4
|
+
client;
|
|
5
|
+
roomId;
|
|
6
|
+
remotePeer = null;
|
|
7
|
+
remoteFingerprint = '';
|
|
8
|
+
_closed = false;
|
|
9
|
+
cleanups = [];
|
|
10
|
+
constructor(client, roomId) {
|
|
11
|
+
super();
|
|
12
|
+
this.client = client;
|
|
13
|
+
this.roomId = roomId;
|
|
14
|
+
}
|
|
15
|
+
async create() {
|
|
16
|
+
await this.client.createRoom(this.roomId, { maxSize: 2 });
|
|
17
|
+
this.listen();
|
|
18
|
+
}
|
|
19
|
+
async join() {
|
|
20
|
+
const peers = await this.client.joinRoom(this.roomId);
|
|
21
|
+
this.listen();
|
|
22
|
+
const remote = peers.find((p) => p.fingerprint !== this.client.fingerprint);
|
|
23
|
+
if (remote) {
|
|
24
|
+
this.connectTo(remote.fingerprint, remote.alias);
|
|
25
|
+
}
|
|
26
|
+
return peers;
|
|
27
|
+
}
|
|
28
|
+
listen() {
|
|
29
|
+
const offJoined = this.client.on('peer_joined', (info) => {
|
|
30
|
+
this.emit('peer_joined', info);
|
|
31
|
+
if (!this.remotePeer) {
|
|
32
|
+
this.connectTo(info.fingerprint, info.alias);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const offLeft = this.client.on('peer_left', (fp) => {
|
|
36
|
+
if (fp === this.remoteFingerprint) {
|
|
37
|
+
this.remotePeer = null;
|
|
38
|
+
this.remoteFingerprint = '';
|
|
39
|
+
this.emit('peer_left', fp);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const offRelay = this.client.on('relay', (from, payload) => {
|
|
43
|
+
if (from === this.remoteFingerprint && payload?._room === this.roomId) {
|
|
44
|
+
this.emit('data', payload.data, from);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const offKicked = this.client.on('kicked', (payload) => {
|
|
48
|
+
if (payload?.room_id === this.roomId) {
|
|
49
|
+
this.close();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
this.cleanups.push(offJoined, offLeft, offRelay, offKicked);
|
|
53
|
+
}
|
|
54
|
+
connectTo(fingerprint, alias) {
|
|
55
|
+
this.remoteFingerprint = fingerprint;
|
|
56
|
+
const peer = this.client.connectToPeer(fingerprint, alias);
|
|
57
|
+
this.remotePeer = peer;
|
|
58
|
+
peer.on('connected', () => this.emit('peer_connected', fingerprint));
|
|
59
|
+
peer.on('data', (data) => this.emit('data', data, fingerprint));
|
|
60
|
+
peer.on('disconnected', () => {
|
|
61
|
+
this.emit('peer_left', fingerprint);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
send(data) {
|
|
65
|
+
if (this._closed)
|
|
66
|
+
return;
|
|
67
|
+
if (this.remotePeer && this.remotePeer.connectionState === 'connected') {
|
|
68
|
+
try {
|
|
69
|
+
this.remotePeer.send(data);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
catch { }
|
|
73
|
+
}
|
|
74
|
+
if (this.remoteFingerprint) {
|
|
75
|
+
this.client.relay(this.remoteFingerprint, { _room: this.roomId, data });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
getPeer() {
|
|
79
|
+
return this.remotePeer;
|
|
80
|
+
}
|
|
81
|
+
getRemoteFingerprint() {
|
|
82
|
+
return this.remoteFingerprint;
|
|
83
|
+
}
|
|
84
|
+
close() {
|
|
85
|
+
if (this._closed)
|
|
86
|
+
return;
|
|
87
|
+
this._closed = true;
|
|
88
|
+
this.cleanups.forEach((fn) => fn());
|
|
89
|
+
this.cleanups = [];
|
|
90
|
+
if (this.remotePeer) {
|
|
91
|
+
this.remotePeer.close();
|
|
92
|
+
}
|
|
93
|
+
this.client.leave(this.roomId);
|
|
94
|
+
this.emit('closed');
|
|
95
|
+
this.removeAllListeners();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export class GroupRoom extends Emitter {
|
|
99
|
+
client;
|
|
100
|
+
roomId;
|
|
101
|
+
maxSize;
|
|
102
|
+
connectedPeers = new Map();
|
|
103
|
+
relayPeers = new Set();
|
|
104
|
+
_closed = false;
|
|
105
|
+
cleanups = [];
|
|
106
|
+
constructor(client, roomId, maxSize = LIMITS.MAX_GROUP_SIZE) {
|
|
107
|
+
super();
|
|
108
|
+
this.client = client;
|
|
109
|
+
this.roomId = roomId;
|
|
110
|
+
this.maxSize = Math.min(maxSize, LIMITS.MAX_GROUP_SIZE);
|
|
111
|
+
}
|
|
112
|
+
async create() {
|
|
113
|
+
await this.client.createRoom(this.roomId, { maxSize: this.maxSize });
|
|
114
|
+
this.listen();
|
|
115
|
+
}
|
|
116
|
+
async join() {
|
|
117
|
+
const peers = await this.client.joinRoom(this.roomId);
|
|
118
|
+
this.listen();
|
|
119
|
+
const others = peers.filter((p) => p.fingerprint !== this.client.fingerprint);
|
|
120
|
+
for (const p of others) {
|
|
121
|
+
this.connectTo(p.fingerprint, p.alias);
|
|
122
|
+
}
|
|
123
|
+
return peers;
|
|
124
|
+
}
|
|
125
|
+
listen() {
|
|
126
|
+
const offJoined = this.client.on('peer_joined', (info) => {
|
|
127
|
+
this.emit('peer_joined', info);
|
|
128
|
+
this.connectTo(info.fingerprint, info.alias);
|
|
129
|
+
});
|
|
130
|
+
const offLeft = this.client.on('peer_left', (fp) => {
|
|
131
|
+
const hadP2P = this.connectedPeers.has(fp);
|
|
132
|
+
this.connectedPeers.delete(fp);
|
|
133
|
+
this.relayPeers.delete(fp);
|
|
134
|
+
if (hadP2P) {
|
|
135
|
+
this.promoteRelayPeers();
|
|
136
|
+
}
|
|
137
|
+
this.emit('peer_left', fp);
|
|
138
|
+
});
|
|
139
|
+
const offRelay = this.client.on('relay', (from, payload) => {
|
|
140
|
+
if (payload?._room === this.roomId) {
|
|
141
|
+
this.emit('data', payload.data, from);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
const offBroadcast = this.client.on('broadcast', (from, ns, payload) => {
|
|
145
|
+
if (ns === this.roomId) {
|
|
146
|
+
this.emit('data', payload, from);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
const offKicked = this.client.on('kicked', (payload) => {
|
|
150
|
+
if (payload?.room_id === this.roomId) {
|
|
151
|
+
this.close();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
this.cleanups.push(offJoined, offLeft, offRelay, offBroadcast, offKicked);
|
|
155
|
+
}
|
|
156
|
+
connectTo(fingerprint, alias) {
|
|
157
|
+
if (this.connectedPeers.has(fingerprint) || this.relayPeers.has(fingerprint))
|
|
158
|
+
return;
|
|
159
|
+
if (this.connectedPeers.size >= LIMITS.RELAY_THRESHOLD) {
|
|
160
|
+
this.relayPeers.add(fingerprint);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const peer = this.client.connectToPeer(fingerprint, alias);
|
|
164
|
+
this.connectedPeers.set(fingerprint, peer);
|
|
165
|
+
peer.on('connected', () => this.emit('peer_connected', fingerprint));
|
|
166
|
+
peer.on('data', (data) => this.emit('data', data, fingerprint));
|
|
167
|
+
peer.on('disconnected', () => {
|
|
168
|
+
this.connectedPeers.delete(fingerprint);
|
|
169
|
+
this.relayPeers.add(fingerprint);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
promoteRelayPeers() {
|
|
173
|
+
if (this.connectedPeers.size >= LIMITS.RELAY_THRESHOLD)
|
|
174
|
+
return;
|
|
175
|
+
const available = LIMITS.RELAY_THRESHOLD - this.connectedPeers.size;
|
|
176
|
+
const toPromote = [...this.relayPeers].slice(0, available);
|
|
177
|
+
for (const fp of toPromote) {
|
|
178
|
+
this.relayPeers.delete(fp);
|
|
179
|
+
const peer = this.client.connectToPeer(fp);
|
|
180
|
+
this.connectedPeers.set(fp, peer);
|
|
181
|
+
peer.on('connected', () => this.emit('peer_connected', fp));
|
|
182
|
+
peer.on('data', (data) => this.emit('data', data, fp));
|
|
183
|
+
peer.on('disconnected', () => {
|
|
184
|
+
this.connectedPeers.delete(fp);
|
|
185
|
+
this.relayPeers.add(fp);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
send(data, to) {
|
|
190
|
+
if (this._closed)
|
|
191
|
+
return;
|
|
192
|
+
if (to) {
|
|
193
|
+
const peer = this.connectedPeers.get(to);
|
|
194
|
+
if (peer && peer.connectionState === 'connected') {
|
|
195
|
+
try {
|
|
196
|
+
peer.send(data);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
catch { }
|
|
200
|
+
}
|
|
201
|
+
this.client.relay(to, { _room: this.roomId, data });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this.connectedPeers.forEach((peer, fp) => {
|
|
205
|
+
if (peer.connectionState === 'connected') {
|
|
206
|
+
try {
|
|
207
|
+
peer.send(data);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
this.client.relay(fp, { _room: this.roomId, data });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this.client.relay(fp, { _room: this.roomId, data });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
this.relayPeers.forEach((fp) => {
|
|
218
|
+
this.client.relay(fp, { _room: this.roomId, data });
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
broadcastViaServer(data) {
|
|
222
|
+
this.client.broadcast(this.roomId, data);
|
|
223
|
+
}
|
|
224
|
+
kick(fingerprint) {
|
|
225
|
+
this.client.kick(this.roomId, fingerprint);
|
|
226
|
+
}
|
|
227
|
+
getPeers() {
|
|
228
|
+
return new Map(this.connectedPeers);
|
|
229
|
+
}
|
|
230
|
+
getPeerCount() {
|
|
231
|
+
return this.connectedPeers.size + this.relayPeers.size;
|
|
232
|
+
}
|
|
233
|
+
close() {
|
|
234
|
+
if (this._closed)
|
|
235
|
+
return;
|
|
236
|
+
this._closed = true;
|
|
237
|
+
this.cleanups.forEach((fn) => fn());
|
|
238
|
+
this.cleanups = [];
|
|
239
|
+
this.connectedPeers.forEach((p) => p.close());
|
|
240
|
+
this.connectedPeers.clear();
|
|
241
|
+
this.relayPeers.clear();
|
|
242
|
+
this.client.leave(this.roomId);
|
|
243
|
+
this.emit('closed');
|
|
244
|
+
this.removeAllListeners();
|
|
245
|
+
}
|
|
246
|
+
}
|
package/dist/sync.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Emitter } from './core/emitter';
|
|
2
|
+
import type { PeerClient } from './core/client';
|
|
3
|
+
import type { SyncConfig } from './core/types';
|
|
4
|
+
type SyncEvent = 'state_changed' | 'conflict' | 'synced' | 'error';
|
|
5
|
+
export declare class StateSync extends Emitter<SyncEvent> {
|
|
6
|
+
private client;
|
|
7
|
+
private state;
|
|
8
|
+
private mode;
|
|
9
|
+
private merge?;
|
|
10
|
+
private hlc;
|
|
11
|
+
private roomId;
|
|
12
|
+
private cleanups;
|
|
13
|
+
private tombstoneTimer;
|
|
14
|
+
constructor(client: PeerClient, roomId: string, config: SyncConfig);
|
|
15
|
+
start(): void;
|
|
16
|
+
private tick;
|
|
17
|
+
set(key: string, value: any): void;
|
|
18
|
+
get(key: string): any;
|
|
19
|
+
getAll(): Record<string, any>;
|
|
20
|
+
delete(key: string): void;
|
|
21
|
+
private handleRemoteUpdate;
|
|
22
|
+
private handleFullState;
|
|
23
|
+
private broadcastFullState;
|
|
24
|
+
private sendStateTo;
|
|
25
|
+
requestFullState(fingerprint: string): void;
|
|
26
|
+
private purgeTombstones;
|
|
27
|
+
destroy(): void;
|
|
28
|
+
}
|
|
29
|
+
export declare class CRDTSync extends Emitter<SyncEvent> {
|
|
30
|
+
private client;
|
|
31
|
+
private roomId;
|
|
32
|
+
private doc;
|
|
33
|
+
private cleanups;
|
|
34
|
+
private Yjs;
|
|
35
|
+
constructor(client: PeerClient, roomId: string, yjsModule: any);
|
|
36
|
+
getDoc(): any;
|
|
37
|
+
getMap(name?: string): any;
|
|
38
|
+
getText(name?: string): any;
|
|
39
|
+
getArray(name?: string): any;
|
|
40
|
+
start(): void;
|
|
41
|
+
private sendStateTo;
|
|
42
|
+
requestFullState(fingerprint: string): void;
|
|
43
|
+
destroy(): void;
|
|
44
|
+
}
|
|
45
|
+
export {};
|