@antha/multiplayer-core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-CC0 +121 -0
- package/LICENSE-MIT +21 -0
- package/README.md +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/multiplayer-api/errors.d.ts +13 -0
- package/dist/multiplayer-api/errors.js +15 -0
- package/dist/multiplayer-api/multiplayer-api.d.ts +215 -0
- package/dist/multiplayer-api/multiplayer-api.js +123 -0
- package/dist/multiplayer-api/multiplayer-client.d.ts +25 -0
- package/dist/multiplayer-api/multiplayer-client.js +18 -0
- package/dist/multiplayer-api/multiplayer-controller.d.ts +417 -0
- package/dist/multiplayer-api/multiplayer-controller.js +331 -0
- package/dist/multiplayer-api/multiplayer-socket-messages.d.ts +141 -0
- package/dist/multiplayer-api/multiplayer-socket-messages.js +114 -0
- package/dist/multiplayer-id.d.ts +59 -0
- package/dist/multiplayer-id.js +38 -0
- package/dist/room-handler-server/mock-room-handler-server-api-client.d.ts +47 -0
- package/dist/room-handler-server/mock-room-handler-server-api-client.js +107 -0
- package/dist/room-handler-server/multiplayer-room-handler.d.ts +121 -0
- package/dist/room-handler-server/multiplayer-room-handler.js +216 -0
- package/dist/webrtc/web-rtc-communication.d.ts +64 -0
- package/dist/webrtc/web-rtc-communication.js +54 -0
- package/dist/webrtc/webrtc-controller.d.ts +104 -0
- package/dist/webrtc/webrtc-controller.js +170 -0
- package/dist/webrtc/webrtc-multiplayer-controller.d.ts +263 -0
- package/dist/webrtc/webrtc-multiplayer-controller.js +397 -0
- package/package.json +62 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { type JsonCompatibleValue } from '@augment-vir/common';
|
|
2
|
+
import { ListenTarget } from 'typed-event-target';
|
|
3
|
+
import { type ClientId } from '../multiplayer-id.js';
|
|
4
|
+
import { type WebrtcAnswer, type WebrtcOffer } from './web-rtc-communication.js';
|
|
5
|
+
/**
|
|
6
|
+
* Converts an `RTCSessionDescription` into a plain object so that shape validation works correctly.
|
|
7
|
+
* Browser `RTCSessionDescription` objects expose `type` and `sdp` as prototype getters rather than
|
|
8
|
+
* own data properties, which causes `Object.getOwnPropertyNames`-based checks (used by TypeBox /
|
|
9
|
+
* object-shape-tester) to miss them.
|
|
10
|
+
*
|
|
11
|
+
* @category Internal
|
|
12
|
+
*/
|
|
13
|
+
export declare function toPlainSessionDescription(description: Readonly<RTCSessionDescription>): {
|
|
14
|
+
type: RTCSdpType;
|
|
15
|
+
sdp: string;
|
|
16
|
+
};
|
|
17
|
+
declare const WebrtcMessageEvent_base: (new (eventInitDict: {
|
|
18
|
+
bubbles?: boolean;
|
|
19
|
+
cancelable?: boolean;
|
|
20
|
+
composed?: boolean;
|
|
21
|
+
detail: any;
|
|
22
|
+
}) => import("typed-event-target").TypedCustomEvent<any, "webrtc-message">) & Pick<{
|
|
23
|
+
new (type: string, eventInitDict?: EventInit): Event;
|
|
24
|
+
prototype: Event;
|
|
25
|
+
readonly NONE: 0;
|
|
26
|
+
readonly CAPTURING_PHASE: 1;
|
|
27
|
+
readonly AT_TARGET: 2;
|
|
28
|
+
readonly BUBBLING_PHASE: 3;
|
|
29
|
+
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<any, "webrtc-message">, "type">;
|
|
30
|
+
/**
|
|
31
|
+
* An event that is omitted from {@link WebrtcController} when a WebRTC message is received.
|
|
32
|
+
*
|
|
33
|
+
* @category Internal
|
|
34
|
+
*/
|
|
35
|
+
export declare class WebrtcMessageEvent<MessageData extends JsonCompatibleValue> extends WebrtcMessageEvent_base {
|
|
36
|
+
detail: MessageData;
|
|
37
|
+
}
|
|
38
|
+
declare const WebrtcConnectEvent_base: (new (eventInitDict: {
|
|
39
|
+
bubbles?: boolean;
|
|
40
|
+
cancelable?: boolean;
|
|
41
|
+
composed?: boolean;
|
|
42
|
+
detail: boolean;
|
|
43
|
+
}) => import("typed-event-target").TypedCustomEvent<boolean, "webrtc-connect">) & Pick<{
|
|
44
|
+
new (type: string, eventInitDict?: EventInit): Event;
|
|
45
|
+
prototype: Event;
|
|
46
|
+
readonly NONE: 0;
|
|
47
|
+
readonly CAPTURING_PHASE: 1;
|
|
48
|
+
readonly AT_TARGET: 2;
|
|
49
|
+
readonly BUBBLING_PHASE: 3;
|
|
50
|
+
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<boolean, "webrtc-connect">, "type">;
|
|
51
|
+
/**
|
|
52
|
+
* An event that is omitted from {@link WebrtcController} when the WebRTC connection is made or lost.
|
|
53
|
+
* `event.detail` is `true` if the connection has been made; `false` if the connection was lost.
|
|
54
|
+
*
|
|
55
|
+
* @category Internal
|
|
56
|
+
*/
|
|
57
|
+
export declare class WebrtcConnectEvent extends WebrtcConnectEvent_base {
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* All events fro {@link WebrtcController}.
|
|
61
|
+
*
|
|
62
|
+
* @category Internal
|
|
63
|
+
*/
|
|
64
|
+
export type WebrtcEvents<MessageData extends JsonCompatibleValue> = WebrtcMessageEvent<MessageData> | WebrtcConnectEvent;
|
|
65
|
+
/**
|
|
66
|
+
* A single peer-to-peer WebRTC controller. It is to be used like this:
|
|
67
|
+
*
|
|
68
|
+
* 1. Call {@link WebrtcController.createOffer} to start off a WebRTC handshake. Send this offer to
|
|
69
|
+
* another WebRTC connection or {@link WebrtcController} instance.
|
|
70
|
+
* 2. Call {@link WebrtcController.createAnswer} to accept someone else's WebRTC handshake offer and
|
|
71
|
+
* create a WebRTC handshake answer. Send this answer back to whoever sent the WebRTC offer.
|
|
72
|
+
* 3. Call {@link WebrtcController.acceptAnswer} on the {@link WebrtcController} instance that first
|
|
73
|
+
* created the offer.
|
|
74
|
+
*
|
|
75
|
+
* If everything went well, after those 3 steps you'll now have a live WebRTC connection!
|
|
76
|
+
*
|
|
77
|
+
* @category Internal
|
|
78
|
+
*/
|
|
79
|
+
export declare class WebrtcController<MessageData extends JsonCompatibleValue> extends ListenTarget<WebrtcEvents<MessageData>> {
|
|
80
|
+
readonly clientId: ClientId;
|
|
81
|
+
private dataChannel;
|
|
82
|
+
private connection;
|
|
83
|
+
/** Indicates whether the WebRTC connection is live or not. */
|
|
84
|
+
readonly isConnected: boolean;
|
|
85
|
+
constructor(clientId: ClientId);
|
|
86
|
+
/** Create a WebRTC offer. This is the first step in the WebRTC handshake process. */
|
|
87
|
+
createOffer(stunServerUrls: ReadonlyArray<string>): Promise<WebrtcOffer>;
|
|
88
|
+
/** Accepts a WebRTC answer. This is the third (and last) step in the WebRTC handshake process. */
|
|
89
|
+
acceptAnswer(rawAnswer: string | Readonly<RTCSessionDescriptionInit>): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Accepts a WebRTC offer and creates a WebRTC answer. This is the second step in the WebRTC
|
|
92
|
+
* handshake process.
|
|
93
|
+
*/
|
|
94
|
+
createAnswer(rawOffer: string | Readonly<RTCSessionDescriptionInit>, stunServerUrls: ReadonlyArray<string>): Promise<WebrtcAnswer>;
|
|
95
|
+
/**
|
|
96
|
+
* Send a message to the other peer in the WebRTC connection. This will throw an error if the
|
|
97
|
+
* connection has not been established yet.
|
|
98
|
+
*/
|
|
99
|
+
sendMessage(data: Readonly<MessageData>): void;
|
|
100
|
+
destroy(): void;
|
|
101
|
+
private handleDataChannel;
|
|
102
|
+
private createConnection;
|
|
103
|
+
}
|
|
104
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { assert, assertWrap, check } from '@augment-vir/assert';
|
|
2
|
+
import { addPrefix, DeferredPromise, makeWritable, wrapInTry, } from '@augment-vir/common';
|
|
3
|
+
import { assertValidShape } from 'object-shape-tester';
|
|
4
|
+
import { defineTypedCustomEvent, ListenTarget } from 'typed-event-target';
|
|
5
|
+
import { webrtcAnswerShape, webrtcOfferShape, } from './web-rtc-communication.js';
|
|
6
|
+
/**
|
|
7
|
+
* Converts an `RTCSessionDescription` into a plain object so that shape validation works correctly.
|
|
8
|
+
* Browser `RTCSessionDescription` objects expose `type` and `sdp` as prototype getters rather than
|
|
9
|
+
* own data properties, which causes `Object.getOwnPropertyNames`-based checks (used by TypeBox /
|
|
10
|
+
* object-shape-tester) to miss them.
|
|
11
|
+
*
|
|
12
|
+
* @category Internal
|
|
13
|
+
*/
|
|
14
|
+
export function toPlainSessionDescription(description) {
|
|
15
|
+
return {
|
|
16
|
+
type: description.type,
|
|
17
|
+
sdp: description.sdp,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* An event that is omitted from {@link WebrtcController} when a WebRTC message is received.
|
|
22
|
+
*
|
|
23
|
+
* @category Internal
|
|
24
|
+
*/
|
|
25
|
+
export class WebrtcMessageEvent extends defineTypedCustomEvent()('webrtc-message') {
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* An event that is omitted from {@link WebrtcController} when the WebRTC connection is made or lost.
|
|
29
|
+
* `event.detail` is `true` if the connection has been made; `false` if the connection was lost.
|
|
30
|
+
*
|
|
31
|
+
* @category Internal
|
|
32
|
+
*/
|
|
33
|
+
export class WebrtcConnectEvent extends defineTypedCustomEvent()('webrtc-connect') {
|
|
34
|
+
}
|
|
35
|
+
function formatStunServerUrls(stunServerUrls) {
|
|
36
|
+
return stunServerUrls.map((stunServerUrl) => {
|
|
37
|
+
return {
|
|
38
|
+
urls: addPrefix({
|
|
39
|
+
value: stunServerUrl,
|
|
40
|
+
prefix: 'stun:',
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A single peer-to-peer WebRTC controller. It is to be used like this:
|
|
47
|
+
*
|
|
48
|
+
* 1. Call {@link WebrtcController.createOffer} to start off a WebRTC handshake. Send this offer to
|
|
49
|
+
* another WebRTC connection or {@link WebrtcController} instance.
|
|
50
|
+
* 2. Call {@link WebrtcController.createAnswer} to accept someone else's WebRTC handshake offer and
|
|
51
|
+
* create a WebRTC handshake answer. Send this answer back to whoever sent the WebRTC offer.
|
|
52
|
+
* 3. Call {@link WebrtcController.acceptAnswer} on the {@link WebrtcController} instance that first
|
|
53
|
+
* created the offer.
|
|
54
|
+
*
|
|
55
|
+
* If everything went well, after those 3 steps you'll now have a live WebRTC connection!
|
|
56
|
+
*
|
|
57
|
+
* @category Internal
|
|
58
|
+
*/
|
|
59
|
+
export class WebrtcController extends ListenTarget {
|
|
60
|
+
clientId;
|
|
61
|
+
dataChannel;
|
|
62
|
+
connection;
|
|
63
|
+
/** Indicates whether the WebRTC connection is live or not. */
|
|
64
|
+
isConnected = false;
|
|
65
|
+
constructor(clientId) {
|
|
66
|
+
super();
|
|
67
|
+
this.clientId = clientId;
|
|
68
|
+
}
|
|
69
|
+
/** Create a WebRTC offer. This is the first step in the WebRTC handshake process. */
|
|
70
|
+
async createOffer(stunServerUrls) {
|
|
71
|
+
const candidatePromise = this.createConnection(stunServerUrls);
|
|
72
|
+
assert.isDefined(this.connection);
|
|
73
|
+
this.handleDataChannel(this.connection.createDataChannel('chat'));
|
|
74
|
+
await this.connection.setLocalDescription(await this.connection.createOffer());
|
|
75
|
+
await candidatePromise;
|
|
76
|
+
const offer = toPlainSessionDescription(assertWrap.isDefined(this.connection.localDescription));
|
|
77
|
+
assertValidShape(offer, webrtcOfferShape);
|
|
78
|
+
return offer;
|
|
79
|
+
}
|
|
80
|
+
/** Accepts a WebRTC answer. This is the third (and last) step in the WebRTC handshake process. */
|
|
81
|
+
async acceptAnswer(rawAnswer) {
|
|
82
|
+
const answer = check.isString(rawAnswer) ? JSON.parse(rawAnswer) : rawAnswer;
|
|
83
|
+
assert.isDefined(this.connection);
|
|
84
|
+
assertValidShape(answer, webrtcAnswerShape);
|
|
85
|
+
await this.connection.setRemoteDescription(answer);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Accepts a WebRTC offer and creates a WebRTC answer. This is the second step in the WebRTC
|
|
89
|
+
* handshake process.
|
|
90
|
+
*/
|
|
91
|
+
async createAnswer(rawOffer, stunServerUrls) {
|
|
92
|
+
const offer = check.isString(rawOffer)
|
|
93
|
+
? JSON.parse(rawOffer)
|
|
94
|
+
: rawOffer;
|
|
95
|
+
const candidatePromise = this.createConnection(stunServerUrls);
|
|
96
|
+
assert.isDefined(this.connection);
|
|
97
|
+
this.connection.addEventListener('datachannel', (event) => {
|
|
98
|
+
this.handleDataChannel(event.channel);
|
|
99
|
+
});
|
|
100
|
+
await this.connection.setRemoteDescription(offer);
|
|
101
|
+
await this.connection.setLocalDescription(await this.connection.createAnswer());
|
|
102
|
+
if (stunServerUrls.length) {
|
|
103
|
+
await candidatePromise;
|
|
104
|
+
}
|
|
105
|
+
const answer = toPlainSessionDescription(assertWrap.isDefined(this.connection.localDescription));
|
|
106
|
+
assertValidShape(answer, webrtcAnswerShape);
|
|
107
|
+
return answer;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Send a message to the other peer in the WebRTC connection. This will throw an error if the
|
|
111
|
+
* connection has not been established yet.
|
|
112
|
+
*/
|
|
113
|
+
sendMessage(data) {
|
|
114
|
+
assert.isTrue(this.isConnected, `There is no WebRTC connection to send a message to from ${this.clientId}.`);
|
|
115
|
+
assert.isDefined(this.dataChannel, `There is no WebRTC connection to send a message to from ${this.clientId}.`);
|
|
116
|
+
this.dataChannel.send(JSON.stringify(data));
|
|
117
|
+
}
|
|
118
|
+
destroy() {
|
|
119
|
+
this.dataChannel?.close();
|
|
120
|
+
this.connection?.close();
|
|
121
|
+
super.destroy();
|
|
122
|
+
}
|
|
123
|
+
handleDataChannel(dataChannel) {
|
|
124
|
+
this.dataChannel?.close();
|
|
125
|
+
this.dataChannel = dataChannel;
|
|
126
|
+
this.dataChannel.addEventListener('open', () => {
|
|
127
|
+
makeWritable(this).isConnected = true;
|
|
128
|
+
this.dispatch(new WebrtcConnectEvent({
|
|
129
|
+
detail: true,
|
|
130
|
+
}));
|
|
131
|
+
});
|
|
132
|
+
this.dataChannel.addEventListener('closing', () => {
|
|
133
|
+
makeWritable(this).isConnected = false;
|
|
134
|
+
this.dispatch(new WebrtcConnectEvent({
|
|
135
|
+
detail: false,
|
|
136
|
+
}));
|
|
137
|
+
});
|
|
138
|
+
this.dataChannel.addEventListener('message', (event) => {
|
|
139
|
+
const detail = wrapInTry(() => (check.isString(event.data) ? JSON.parse(event.data) : event.data), {
|
|
140
|
+
fallbackValue: event.data,
|
|
141
|
+
});
|
|
142
|
+
this.dispatch(new WebrtcMessageEvent({
|
|
143
|
+
detail,
|
|
144
|
+
}));
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
createConnection(stunServerUrls) {
|
|
148
|
+
if (this.connection) {
|
|
149
|
+
throw new Error('Connection already created!');
|
|
150
|
+
}
|
|
151
|
+
const deferredIceCandidatePromise = new DeferredPromise();
|
|
152
|
+
const iceCandidateListener = (event) => {
|
|
153
|
+
// all candidates are done
|
|
154
|
+
if (!event.candidate) {
|
|
155
|
+
assert.isDefined(this.connection);
|
|
156
|
+
deferredIceCandidatePromise.resolve();
|
|
157
|
+
this.connection.removeEventListener('icecandidate', iceCandidateListener);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
this.connection = new RTCPeerConnection({
|
|
161
|
+
iceServers: formatStunServerUrls(stunServerUrls),
|
|
162
|
+
});
|
|
163
|
+
this.connection.addEventListener('icecandidate', iceCandidateListener);
|
|
164
|
+
/**
|
|
165
|
+
* This must be awaited so the candidate list can finish populating before we present the
|
|
166
|
+
* offer to the user.
|
|
167
|
+
*/
|
|
168
|
+
return deferredIceCandidatePromise.promise;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { type JsonCompatibleValue, type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type RequireExactlyOne } from 'type-fest';
|
|
3
|
+
import { ListenTarget } from 'typed-event-target';
|
|
4
|
+
import { type MultiplayerConnectClientMessage } from '../multiplayer-api/multiplayer-api.js';
|
|
5
|
+
import { type MultiplayerApiClient } from '../multiplayer-api/multiplayer-client.js';
|
|
6
|
+
import { type ClientId } from '../multiplayer-id.js';
|
|
7
|
+
import { MultiplayerWebSocketMessageType } from './web-rtc-communication.js';
|
|
8
|
+
declare const WebrtcMultiplayerMessageEvent_base: (new (eventInitDict: {
|
|
9
|
+
bubbles?: boolean;
|
|
10
|
+
cancelable?: boolean;
|
|
11
|
+
composed?: boolean;
|
|
12
|
+
detail: any;
|
|
13
|
+
}) => import("typed-event-target").TypedCustomEvent<any, "webrtc-multiplayer-message">) & Pick<{
|
|
14
|
+
new (type: string, eventInitDict?: EventInit): Event;
|
|
15
|
+
prototype: Event;
|
|
16
|
+
readonly NONE: 0;
|
|
17
|
+
readonly CAPTURING_PHASE: 1;
|
|
18
|
+
readonly AT_TARGET: 2;
|
|
19
|
+
readonly BUBBLING_PHASE: 3;
|
|
20
|
+
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<any, "webrtc-multiplayer-message">, "type">;
|
|
21
|
+
/**
|
|
22
|
+
* An event that is omitted from {@link WebrtcController} when a WebRTC message is received.
|
|
23
|
+
*
|
|
24
|
+
* @category Internal
|
|
25
|
+
*/
|
|
26
|
+
export declare class WebrtcMultiplayerMessageEvent<MessageData extends JsonCompatibleValue> extends WebrtcMultiplayerMessageEvent_base {
|
|
27
|
+
readonly sourceClientId: ClientId;
|
|
28
|
+
detail: MessageData;
|
|
29
|
+
constructor(sourceClientId: ClientId, detail: MessageData);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Type for data in {@link WebrtcMultiplayerConnectionUpdateEvent}.
|
|
33
|
+
*
|
|
34
|
+
* @category Internal
|
|
35
|
+
*/
|
|
36
|
+
export type MultiplayerConnectionUpdate = RequireExactlyOne<{
|
|
37
|
+
newHost: ClientId;
|
|
38
|
+
newMember: ClientId;
|
|
39
|
+
lostHost: ClientId;
|
|
40
|
+
lostMember: ClientId;
|
|
41
|
+
}>;
|
|
42
|
+
declare const WebrtcMultiplayerConnectionUpdateEvent_base: (new (eventInitDict: {
|
|
43
|
+
bubbles?: boolean;
|
|
44
|
+
cancelable?: boolean;
|
|
45
|
+
composed?: boolean;
|
|
46
|
+
detail: ((Required<Pick<{
|
|
47
|
+
newHost: ClientId;
|
|
48
|
+
newMember: ClientId;
|
|
49
|
+
lostHost: ClientId;
|
|
50
|
+
lostMember: ClientId;
|
|
51
|
+
}, "newHost">> & Partial<Record<"newMember" | "lostHost" | "lostMember", never>>) | (Required<Pick<{
|
|
52
|
+
newHost: ClientId;
|
|
53
|
+
newMember: ClientId;
|
|
54
|
+
lostHost: ClientId;
|
|
55
|
+
lostMember: ClientId;
|
|
56
|
+
}, "newMember">> & Partial<Record<"newHost" | "lostHost" | "lostMember", never>>) | (Required<Pick<{
|
|
57
|
+
newHost: ClientId;
|
|
58
|
+
newMember: ClientId;
|
|
59
|
+
lostHost: ClientId;
|
|
60
|
+
lostMember: ClientId;
|
|
61
|
+
}, "lostHost">> & Partial<Record<"newHost" | "newMember" | "lostMember", never>>) | (Required<Pick<{
|
|
62
|
+
newHost: ClientId;
|
|
63
|
+
newMember: ClientId;
|
|
64
|
+
lostHost: ClientId;
|
|
65
|
+
lostMember: ClientId;
|
|
66
|
+
}, "lostMember">> & Partial<Record<"newHost" | "newMember" | "lostHost", never>>)) & Omit<{
|
|
67
|
+
newHost: ClientId;
|
|
68
|
+
newMember: ClientId;
|
|
69
|
+
lostHost: ClientId;
|
|
70
|
+
lostMember: ClientId;
|
|
71
|
+
}, "newHost" | "newMember" | "lostHost" | "lostMember">;
|
|
72
|
+
}) => import("typed-event-target").TypedCustomEvent<((Required<Pick<{
|
|
73
|
+
newHost: ClientId;
|
|
74
|
+
newMember: ClientId;
|
|
75
|
+
lostHost: ClientId;
|
|
76
|
+
lostMember: ClientId;
|
|
77
|
+
}, "newHost">> & Partial<Record<"newMember" | "lostHost" | "lostMember", never>>) | (Required<Pick<{
|
|
78
|
+
newHost: ClientId;
|
|
79
|
+
newMember: ClientId;
|
|
80
|
+
lostHost: ClientId;
|
|
81
|
+
lostMember: ClientId;
|
|
82
|
+
}, "newMember">> & Partial<Record<"newHost" | "lostHost" | "lostMember", never>>) | (Required<Pick<{
|
|
83
|
+
newHost: ClientId;
|
|
84
|
+
newMember: ClientId;
|
|
85
|
+
lostHost: ClientId;
|
|
86
|
+
lostMember: ClientId;
|
|
87
|
+
}, "lostHost">> & Partial<Record<"newHost" | "newMember" | "lostMember", never>>) | (Required<Pick<{
|
|
88
|
+
newHost: ClientId;
|
|
89
|
+
newMember: ClientId;
|
|
90
|
+
lostHost: ClientId;
|
|
91
|
+
lostMember: ClientId;
|
|
92
|
+
}, "lostMember">> & Partial<Record<"newHost" | "newMember" | "lostHost", never>>)) & Omit<{
|
|
93
|
+
newHost: ClientId;
|
|
94
|
+
newMember: ClientId;
|
|
95
|
+
lostHost: ClientId;
|
|
96
|
+
lostMember: ClientId;
|
|
97
|
+
}, "newHost" | "newMember" | "lostHost" | "lostMember">, "webrtc-multiplayer-connection-update">) & Pick<{
|
|
98
|
+
new (type: string, eventInitDict?: EventInit): Event;
|
|
99
|
+
prototype: Event;
|
|
100
|
+
readonly NONE: 0;
|
|
101
|
+
readonly CAPTURING_PHASE: 1;
|
|
102
|
+
readonly AT_TARGET: 2;
|
|
103
|
+
readonly BUBBLING_PHASE: 3;
|
|
104
|
+
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<((Required<Pick<{
|
|
105
|
+
newHost: ClientId;
|
|
106
|
+
newMember: ClientId;
|
|
107
|
+
lostHost: ClientId;
|
|
108
|
+
lostMember: ClientId;
|
|
109
|
+
}, "newHost">> & Partial<Record<"newMember" | "lostHost" | "lostMember", never>>) | (Required<Pick<{
|
|
110
|
+
newHost: ClientId;
|
|
111
|
+
newMember: ClientId;
|
|
112
|
+
lostHost: ClientId;
|
|
113
|
+
lostMember: ClientId;
|
|
114
|
+
}, "newMember">> & Partial<Record<"newHost" | "lostHost" | "lostMember", never>>) | (Required<Pick<{
|
|
115
|
+
newHost: ClientId;
|
|
116
|
+
newMember: ClientId;
|
|
117
|
+
lostHost: ClientId;
|
|
118
|
+
lostMember: ClientId;
|
|
119
|
+
}, "lostHost">> & Partial<Record<"newHost" | "newMember" | "lostMember", never>>) | (Required<Pick<{
|
|
120
|
+
newHost: ClientId;
|
|
121
|
+
newMember: ClientId;
|
|
122
|
+
lostHost: ClientId;
|
|
123
|
+
lostMember: ClientId;
|
|
124
|
+
}, "lostMember">> & Partial<Record<"newHost" | "newMember" | "lostHost", never>>)) & Omit<{
|
|
125
|
+
newHost: ClientId;
|
|
126
|
+
newMember: ClientId;
|
|
127
|
+
lostHost: ClientId;
|
|
128
|
+
lostMember: ClientId;
|
|
129
|
+
}, "newHost" | "newMember" | "lostHost" | "lostMember">, "webrtc-multiplayer-connection-update">, "type">;
|
|
130
|
+
/**
|
|
131
|
+
* An event that is omitted from {@link WebrtcMultiplayerController} when the multiplayer room host
|
|
132
|
+
* is updated.
|
|
133
|
+
*
|
|
134
|
+
* @category Internal
|
|
135
|
+
*/
|
|
136
|
+
export declare class WebrtcMultiplayerConnectionUpdateEvent extends WebrtcMultiplayerConnectionUpdateEvent_base {
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A helper for creating a new empty room.
|
|
140
|
+
*
|
|
141
|
+
* @category Internal
|
|
142
|
+
*/
|
|
143
|
+
export declare function createNewRoom(params?: Readonly<PartialWithUndefined<Omit<RoomInput, 'roomId'>>>): RoomInput;
|
|
144
|
+
/**
|
|
145
|
+
* Room selection input for {@link WebrtcMultiplayerController}.
|
|
146
|
+
*
|
|
147
|
+
* @category Internal
|
|
148
|
+
*/
|
|
149
|
+
export type RoomInput = Pick<Extract<MultiplayerConnectClientMessage, {
|
|
150
|
+
type: MultiplayerWebSocketMessageType.Offer;
|
|
151
|
+
}>, 'roomPassword' | 'roomId' | 'roomName'>;
|
|
152
|
+
/**
|
|
153
|
+
* This is used to check if a new WebRTC connection should be allowed. This will only be triggered
|
|
154
|
+
* on a room host client. Return `true`
|
|
155
|
+
*
|
|
156
|
+
* @category Internal
|
|
157
|
+
*/
|
|
158
|
+
export type ShouldAllowConnectionCheck<Controller> = (data: {
|
|
159
|
+
connectingClientId: ClientId;
|
|
160
|
+
controller: Controller;
|
|
161
|
+
}) => MaybePromise<boolean>;
|
|
162
|
+
/**
|
|
163
|
+
* A controller that connects to the multiplayer api and establishes a WebRTC connection to the
|
|
164
|
+
* selected room, or, if the room does not exist yet, creates the room and becomes the host.
|
|
165
|
+
*
|
|
166
|
+
* Make sure, after constructing this class, to call
|
|
167
|
+
* {@link WebrtcMultiplayerController.initConnection} when you're ready to being the connection.
|
|
168
|
+
*
|
|
169
|
+
* @category Internal
|
|
170
|
+
*/
|
|
171
|
+
export declare class WebrtcMultiplayerController<MessageData extends JsonCompatibleValue = any> extends ListenTarget<WebrtcMultiplayerMessageEvent<MessageData> | WebrtcMultiplayerConnectionUpdateEvent> {
|
|
172
|
+
private readonly gameId;
|
|
173
|
+
private readonly multiplayerApiClient;
|
|
174
|
+
readonly stunServerUrls: ReadonlyArray<string>;
|
|
175
|
+
readonly multiplayerRoom: Readonly<RoomInput>;
|
|
176
|
+
/** The randomized client id for this controller and client. */
|
|
177
|
+
readonly clientId: ClientId;
|
|
178
|
+
/**
|
|
179
|
+
* This is fired when a WebRTC peer attempts to connect to the host client (this will only
|
|
180
|
+
* be fired if your client is the host). Return `true` to accept the connection. Return
|
|
181
|
+
* `false` to reject it.
|
|
182
|
+
*
|
|
183
|
+
* @default accept all connections
|
|
184
|
+
*/
|
|
185
|
+
private readonly shouldAllowConnectionCheck;
|
|
186
|
+
readonly hostClientId: ClientId | undefined;
|
|
187
|
+
/**
|
|
188
|
+
* Connections between multiple WebRTC peers.
|
|
189
|
+
*
|
|
190
|
+
* A connection with the current client's id is the init connection.
|
|
191
|
+
*/
|
|
192
|
+
private connections;
|
|
193
|
+
private webSocket;
|
|
194
|
+
private readonly clientSecret;
|
|
195
|
+
readonly isDestroyed: boolean;
|
|
196
|
+
constructor(gameId: string, multiplayerApiClient: Readonly<MultiplayerApiClient>, stunServerUrls: ReadonlyArray<string>, multiplayerRoom: Readonly<RoomInput>,
|
|
197
|
+
/** The randomized client id for this controller and client. */
|
|
198
|
+
clientId?: ClientId,
|
|
199
|
+
/**
|
|
200
|
+
* This is fired when a WebRTC peer attempts to connect to the host client (this will only
|
|
201
|
+
* be fired if your client is the host). Return `true` to accept the connection. Return
|
|
202
|
+
* `false` to reject it.
|
|
203
|
+
*
|
|
204
|
+
* @default accept all connections
|
|
205
|
+
*/
|
|
206
|
+
shouldAllowConnectionCheck?: ShouldAllowConnectionCheck<WebrtcMultiplayerController<MessageData>>);
|
|
207
|
+
/**
|
|
208
|
+
* If this controller is the host, it'll behave differently:
|
|
209
|
+
*
|
|
210
|
+
* - Hosts hold WebRTC connections to all clients (non-hosts only hold a WebRTC connection to the
|
|
211
|
+
* host).
|
|
212
|
+
* - Hosts hold a WebSocket connection to the signal server so they can receive more clients at
|
|
213
|
+
* any time.
|
|
214
|
+
*/
|
|
215
|
+
isHost(): boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Get all connected client ids.
|
|
218
|
+
*
|
|
219
|
+
* - For host clients, this indicates how many member clients are connected to the host client,
|
|
220
|
+
* _not_ including the host itself.
|
|
221
|
+
* - For non-host clients, this only lists the local connection used to reach the host.
|
|
222
|
+
*
|
|
223
|
+
* For host clients, this does ont include the host client id whereas
|
|
224
|
+
* {@link WebrtcMultiplayerController.getAllClientIds} does.
|
|
225
|
+
*/
|
|
226
|
+
getConnectedClientIds(): ClientId[];
|
|
227
|
+
/**
|
|
228
|
+
* Get all room client ids.
|
|
229
|
+
*
|
|
230
|
+
* - For host clients, this indicates how many clients are connected to the room, including the
|
|
231
|
+
* host client itself.
|
|
232
|
+
* - For non-host clients, this includes the member client and the host client once connected.
|
|
233
|
+
*
|
|
234
|
+
* For host clients, this includes the host client id whereas
|
|
235
|
+
* {@link WebrtcMultiplayerController.getConnectedClientIds} does not.
|
|
236
|
+
*/
|
|
237
|
+
getAllClientIds(): ClientId[];
|
|
238
|
+
/** Indicates whether ths client is connected to a multiplayer room. */
|
|
239
|
+
isConnected(): boolean;
|
|
240
|
+
/** Destroy this controller and clean everything up. */
|
|
241
|
+
destroy(): void;
|
|
242
|
+
/**
|
|
243
|
+
* Send a message to the room participants.
|
|
244
|
+
*
|
|
245
|
+
* - If the current client is the room host, this message is sent to all other room clients.
|
|
246
|
+
* - If the current client is just a room member (not the host), the message is sent to the host.
|
|
247
|
+
*/
|
|
248
|
+
sendMessage(data: Readonly<MessageData>): void;
|
|
249
|
+
/** Send a message to just a single client. This is only allowed on a host client. */
|
|
250
|
+
sendToOnlyOneClient(clientId: ClientId, data: Readonly<MessageData>): void;
|
|
251
|
+
/**
|
|
252
|
+
* Call this to connect to the multiplayer server.
|
|
253
|
+
*
|
|
254
|
+
* @returns Whether or not the connection was initialized (it won't be initialized, for example,
|
|
255
|
+
* if the WebRTC connections already exist).
|
|
256
|
+
*/
|
|
257
|
+
initConnection(): Promise<boolean>;
|
|
258
|
+
private sendHostPing;
|
|
259
|
+
private connectionQueue;
|
|
260
|
+
private setupWebSocket;
|
|
261
|
+
private createNewConnection;
|
|
262
|
+
}
|
|
263
|
+
export {};
|