bedrock-portal-nethernet 2.5.0 → 2.6.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 CHANGED
@@ -1,12 +1,12 @@
1
- # BedrockPortalNethernet
2
- [![NPM version](https://img.shields.io/npm/v/bedrock-portal-nethernet.svg)](http://npmjs.com/package/bedrock-portal-nethernet)
3
- [![Discord](https://img.shields.io/badge/chat-on%20discord-brightgreen.svg)](https://discord.gg/KTyd9HWuBD)
4
-
5
- A simple server implementation for the Bedrock Edition Nethernet protocol used by [BedrockPortal](https://github.com/LucienHH/bedrock-portal). Join our [Discord](https://discord.com/invite/KTyd9HWuBD) for support.
6
-
7
- Based on bedrock-protocol but stripped down to only the necessary components for the Nethernet protocol.
8
-
9
- ## Installation
10
- ```shell
11
- npm install bedrock-portal-nethernet
1
+ # BedrockPortalNethernet
2
+ [![NPM version](https://img.shields.io/npm/v/bedrock-portal-nethernet.svg)](http://npmjs.com/package/bedrock-portal-nethernet)
3
+ [![Discord](https://img.shields.io/badge/chat-on%20discord-brightgreen.svg)](https://discord.gg/KTyd9HWuBD)
4
+
5
+ A simple server implementation for the Bedrock Edition Nethernet protocol used by [BedrockPortal](https://github.com/LucienHH/bedrock-portal). Join our [Discord](https://discord.com/invite/KTyd9HWuBD) for support.
6
+
7
+ Based on bedrock-protocol but stripped down to only the necessary components for the Nethernet protocol.
8
+
9
+ ## Installation
10
+ ```shell
11
+ npm install bedrock-portal-nethernet
12
12
  ```
@@ -5,5 +5,5 @@ export type ServerIdentity = {
5
5
  };
6
6
  export declare function createServerIdentity(): ServerIdentity;
7
7
  export declare function signServerIdentity(sdp: string, identity: ServerIdentity): Promise<string>;
8
- export declare function verifyIdentity(sdp: string): Promise<Buffer<ArrayBufferLike>>;
8
+ export declare function verifyIdentity(sdp: string): Promise<NonSharedBuffer>;
9
9
  export declare function publicKeysEqual(encodedKey: string, expectedKey: Buffer): boolean;
package/dist/options.d.ts CHANGED
@@ -9,7 +9,7 @@ export type Options = {
9
9
  compressionThreshold: number;
10
10
  protocolVersion: number;
11
11
  };
12
- export declare const CURRENT_VERSION = "1.26.40";
12
+ export declare const CURRENT_VERSION = "1.26.45";
13
13
  export declare const defaultOptions: {
14
14
  version: string;
15
15
  autoInitPlayer: boolean;
package/dist/options.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.defaultOptions = exports.CURRENT_VERSION = void 0;
4
4
  const framer_1 = require("./transforms/framer");
5
5
  // Currently supported verson. Note, clients with newer versions can still connect as long as data is in minecraft-data
6
- exports.CURRENT_VERSION = '1.26.40';
6
+ exports.CURRENT_VERSION = '1.26.45';
7
7
  exports.defaultOptions = {
8
8
  // https://minecraft.wiki/w/Protocol_version#Bedrock_Edition_2
9
9
  version: exports.CURRENT_VERSION,
@@ -22,5 +22,5 @@ exports.defaultOptions = {
22
22
  // server: If true, only compress if a payload is larger than compressionThreshold
23
23
  compressionThreshold: 512,
24
24
  // server and client: The protocol version to use
25
- protocolVersion: 2168,
25
+ protocolVersion: 2169,
26
26
  };
package/dist/protocol.js CHANGED
@@ -3020,7 +3020,10 @@ exports.default = {
3020
3020
  },
3021
3021
  {
3022
3022
  name: 'filtered_custom_name',
3023
- type: 'string',
3023
+ type: [
3024
+ 'option',
3025
+ 'string',
3026
+ ],
3024
3027
  },
3025
3028
  {
3026
3029
  name: 'durability_correction',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bedrock-portal-nethernet",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "A simple server implementation for the Bedrock Edition Nethernet protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,7 +0,0 @@
1
- import { SignalEnvelope } from './envelope';
2
- import type { SignalInboxMessage, SignalOutboxMessage } from './transport';
3
- export declare class SignalCodec {
4
- decodeMessage(message: SignalInboxMessage): SignalEnvelope | null;
5
- encodeMessage(envelope: SignalEnvelope, networkId: bigint): SignalOutboxMessage;
6
- private parseInnerMessage;
7
- }
@@ -1,65 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SignalCodec = void 0;
4
- const crypto_1 = require("crypto");
5
- const envelope_1 = require("./envelope");
6
- const WEB_RTC_METHOD = 'Signaling_WebRtc_v1_0';
7
- class SignalCodec {
8
- decodeMessage(message) {
9
- const payload = this.parseInnerMessage(message.Message);
10
- if (!payload || payload.method !== WEB_RTC_METHOD) {
11
- return null;
12
- }
13
- try {
14
- return envelope_1.SignalEnvelope.parse(payload.params.message, {
15
- networkId: BigInt(payload.params.netherNetId),
16
- playerId: message.From,
17
- });
18
- }
19
- catch {
20
- return null;
21
- }
22
- }
23
- encodeMessage(envelope, networkId) {
24
- return {
25
- toPlayerId: envelope.peer.playerId,
26
- messageId: (0, crypto_1.randomUUID)(),
27
- message: JSON.stringify({
28
- jsonrpc: '2.0',
29
- method: WEB_RTC_METHOD,
30
- params: {
31
- netherNetId: networkId.toString(),
32
- message: envelope.serialize(),
33
- },
34
- }),
35
- };
36
- }
37
- parseInnerMessage(message) {
38
- let payload;
39
- try {
40
- payload = JSON.parse(message);
41
- }
42
- catch {
43
- return null;
44
- }
45
- if (!isJsonRpcSignalPayload(payload)) {
46
- return null;
47
- }
48
- return payload;
49
- }
50
- }
51
- exports.SignalCodec = SignalCodec;
52
- function isJsonRpcSignalPayload(value) {
53
- if (!value || typeof value !== 'object') {
54
- return false;
55
- }
56
- const payload = value;
57
- if (typeof payload.jsonrpc !== 'string' || typeof payload.method !== 'string') {
58
- return false;
59
- }
60
- if (!payload.params || typeof payload.params !== 'object') {
61
- return false;
62
- }
63
- const params = payload.params;
64
- return typeof params.netherNetId === 'string' && typeof params.message === 'string';
65
- }
@@ -1,20 +0,0 @@
1
- export declare const SignalType: {
2
- readonly ConnectRequest: "CONNECTREQUEST";
3
- readonly ConnectResponse: "CONNECTRESPONSE";
4
- readonly CandidateAdd: "CANDIDATEADD";
5
- readonly ConnectError: "CONNECTERROR";
6
- };
7
- export type SignalPeer = {
8
- networkId: bigint;
9
- playerId: string;
10
- };
11
- export declare class SignalEnvelope {
12
- readonly type: string;
13
- readonly connectionId: bigint;
14
- readonly payload: string;
15
- readonly peer: Readonly<SignalPeer>;
16
- constructor(type: string, connectionId: bigint, payload: string, peer: SignalPeer);
17
- serialize(): string;
18
- reply(type: string, payload: string): SignalEnvelope;
19
- static parse(message: string, peer: SignalPeer): SignalEnvelope | null;
20
- }
@@ -1,41 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SignalEnvelope = exports.SignalType = void 0;
4
- exports.SignalType = {
5
- ConnectRequest: 'CONNECTREQUEST',
6
- ConnectResponse: 'CONNECTRESPONSE',
7
- CandidateAdd: 'CANDIDATEADD',
8
- ConnectError: 'CONNECTERROR',
9
- };
10
- class SignalEnvelope {
11
- type;
12
- connectionId;
13
- payload;
14
- peer;
15
- constructor(type, connectionId, payload, peer) {
16
- this.type = type;
17
- this.connectionId = connectionId;
18
- this.payload = payload;
19
- this.peer = Object.freeze({ ...peer });
20
- }
21
- serialize() {
22
- const prefix = `${this.type} ${this.connectionId}`;
23
- return this.payload.length > 0 ? `${prefix} ${this.payload}` : prefix;
24
- }
25
- reply(type, payload) {
26
- return new SignalEnvelope(type, this.connectionId, payload, this.peer);
27
- }
28
- static parse(message, peer) {
29
- const [type, rawConnectionId, ...payload] = message.split(' ');
30
- if (!type || !rawConnectionId) {
31
- return null;
32
- }
33
- try {
34
- return new SignalEnvelope(type, BigInt(rawConnectionId), payload.join(' '), peer);
35
- }
36
- catch {
37
- return null;
38
- }
39
- }
40
- }
41
- exports.SignalEnvelope = SignalEnvelope;
@@ -1,44 +0,0 @@
1
- export type SignalingInboxEntry = {
2
- From: string;
3
- Message: string;
4
- Id: string;
5
- };
6
- export type SignalingOutboxEntry = {
7
- toPlayerId: string;
8
- messageId: string;
9
- message: string;
10
- };
11
- export type TurnAuthResponse = {
12
- TurnAuthServers?: Array<{
13
- Urls: string[];
14
- Username: string;
15
- Password: string;
16
- }>;
17
- };
18
- export type JsonRpcSignalingConnection = {
19
- url: string;
20
- headers: Record<string, string>;
21
- };
22
- export type JsonRpcSignalingCloseEvent = {
23
- code: number;
24
- reason: string;
25
- };
26
- export declare class JsonRpcSignalingTransport {
27
- onInboxEntries: (entries: SignalingInboxEntry[]) => void;
28
- onClose: (event: JsonRpcSignalingCloseEvent) => void;
29
- onError: (error: Error) => void;
30
- private ws;
31
- private pingInterval;
32
- private readonly rpc;
33
- constructor();
34
- get connected(): boolean;
35
- connect(connection: JsonRpcSignalingConnection): Promise<void>;
36
- close(code?: number, reason?: string): Promise<void>;
37
- requestTurnAuth(): Promise<any>;
38
- sendMessage(entry: SignalingOutboxEntry): Promise<void>;
39
- private handleJsonRpcMessage;
40
- private handleClose;
41
- private startPingLoop;
42
- private stopPingLoop;
43
- private normalizeError;
44
- }
@@ -1,174 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.JsonRpcSignalingTransport = void 0;
7
- const crypto_1 = require("crypto");
8
- const debug_1 = __importDefault(require("debug"));
9
- const JSONBig = require("json-bigint");
10
- const json_rpc_2_0_1 = require("json-rpc-2.0");
11
- const ws_1 = require("ws");
12
- const TURN_AUTH_METHOD = 'Signaling_TurnAuth_v1_0';
13
- const RECEIVE_MESSAGE_METHOD = 'Signaling_ReceiveMessage_v1_0';
14
- const DELIVERY_NOTIFICATION_METHOD = 'Signaling_DeliveryNotification_V1_0';
15
- const PING_METHOD = 'System_Ping_v1_0';
16
- const PONG_METHOD = 'System_Pong_v1_0';
17
- const SEND_CLIENT_MESSAGE_METHOD = 'Signaling_SendClientMessage_v1_0';
18
- const debug = (0, debug_1.default)('bedrock-portal-nethernet');
19
- const json = JSONBig({ useNativeBigInt: true });
20
- class JsonRpcSignalingTransport {
21
- onInboxEntries;
22
- onClose;
23
- onError;
24
- ws;
25
- pingInterval;
26
- rpc;
27
- constructor() {
28
- this.onInboxEntries = () => { };
29
- this.onClose = () => { };
30
- this.onError = () => { };
31
- this.ws = null;
32
- this.pingInterval = null;
33
- const server = new json_rpc_2_0_1.JSONRPCServer({
34
- errorListener: (message, payload) => {
35
- debug('JSON-RPC server error', message, payload);
36
- },
37
- });
38
- server.addMethod(RECEIVE_MESSAGE_METHOD, (params) => {
39
- const entries = Array.isArray(params) ? params : [params];
40
- this.onInboxEntries(entries);
41
- });
42
- server.addMethod(PING_METHOD, () => { });
43
- server.addMethod(PONG_METHOD, () => { });
44
- server.addMethod(DELIVERY_NOTIFICATION_METHOD, () => { });
45
- const client = new json_rpc_2_0_1.JSONRPCClient(async (payload) => {
46
- if (this.ws?.readyState !== ws_1.WebSocket.OPEN) {
47
- throw new Error('WebSocket not connected');
48
- }
49
- this.ws.send(json.stringify(payload));
50
- }, () => (0, crypto_1.randomUUID)());
51
- this.rpc = new json_rpc_2_0_1.JSONRPCServerAndClient(server, client, {
52
- errorListener: (message, payload) => {
53
- debug('JSON-RPC transport error', message, payload);
54
- },
55
- });
56
- }
57
- get connected() {
58
- return this.ws?.readyState === ws_1.WebSocket.OPEN;
59
- }
60
- async connect(connection) {
61
- if (this.ws?.readyState === ws_1.WebSocket.OPEN || this.ws?.readyState === ws_1.WebSocket.CONNECTING) {
62
- throw new Error('JSON-RPC signaling transport already connected');
63
- }
64
- const ws = new ws_1.WebSocket(connection.url, {
65
- headers: connection.headers,
66
- });
67
- this.ws = ws;
68
- let opened = false;
69
- await new Promise((resolve, reject) => {
70
- ws.onopen = () => {
71
- opened = true;
72
- debug('JSON-RPC signaling websocket connected');
73
- this.startPingLoop();
74
- resolve();
75
- };
76
- ws.onmessage = (event) => {
77
- this.handleJsonRpcMessage(event.data);
78
- };
79
- ws.onerror = (event) => {
80
- const error = this.normalizeError(event);
81
- debug('JSON-RPC signaling websocket error', error);
82
- this.onError(error);
83
- if (!opened) {
84
- reject(error);
85
- }
86
- };
87
- ws.onclose = (event) => {
88
- this.handleClose(ws, event.code, event.reason);
89
- if (!opened) {
90
- reject(new Error(`JSON-RPC signaling transport closed with code ${event.code} and reason ${event.reason}`));
91
- }
92
- };
93
- });
94
- }
95
- async close(code = 1000, reason = 'Normal Closure') {
96
- const ws = this.ws;
97
- this.stopPingLoop();
98
- this.rpc.rejectAllPendingRequests('JSON-RPC signaling transport closed');
99
- this.ws = null;
100
- if (!ws) {
101
- return;
102
- }
103
- ws.onmessage = null;
104
- ws.onerror = null;
105
- if (ws.readyState === ws_1.WebSocket.CLOSED) {
106
- ws.onclose = null;
107
- return;
108
- }
109
- await new Promise((resolve) => {
110
- ws.onclose = () => resolve();
111
- ws.close(code, reason);
112
- });
113
- }
114
- async requestTurnAuth() {
115
- return this.rpc.timeout(15000).request(TURN_AUTH_METHOD, {}, undefined);
116
- }
117
- async sendMessage(entry) {
118
- await this.rpc.timeout(15000).request(SEND_CLIENT_MESSAGE_METHOD, entry, undefined);
119
- }
120
- handleJsonRpcMessage(data) {
121
- if (typeof data !== 'string') {
122
- debug('Received non-string JSON-RPC message', data);
123
- return;
124
- }
125
- let payload;
126
- try {
127
- payload = json.parse(data);
128
- }
129
- catch (err) {
130
- debug('Received invalid JSON-RPC message', err);
131
- return;
132
- }
133
- debug('Received JSON-RPC message', payload);
134
- void this.rpc.receiveAndSend(payload, undefined, undefined).catch((err) => {
135
- debug('Failed to handle JSON-RPC message', err);
136
- });
137
- }
138
- handleClose(ws, code, reason) {
139
- debug(`JSON-RPC signaling websocket closed with code ${code} and reason ${reason}`);
140
- if (this.ws === ws) {
141
- this.ws = null;
142
- }
143
- this.stopPingLoop();
144
- this.rpc.rejectAllPendingRequests('JSON-RPC signaling transport closed');
145
- this.onClose({ code, reason });
146
- }
147
- startPingLoop() {
148
- this.stopPingLoop();
149
- this.pingInterval = setInterval(() => {
150
- if (!this.connected) {
151
- return;
152
- }
153
- void Promise.resolve(this.rpc.timeout(15000).request(PING_METHOD, [], undefined)).catch((err) => {
154
- debug('JSON-RPC signaling ping failed', err);
155
- });
156
- }, 5000);
157
- }
158
- stopPingLoop() {
159
- if (this.pingInterval) {
160
- clearInterval(this.pingInterval);
161
- this.pingInterval = null;
162
- }
163
- }
164
- normalizeError(event) {
165
- if (event.error instanceof Error) {
166
- return event.error;
167
- }
168
- if (typeof event.message === 'string' && event.message.length > 0) {
169
- return new Error(event.message);
170
- }
171
- return new Error('JSON-RPC signaling websocket error');
172
- }
173
- }
174
- exports.JsonRpcSignalingTransport = JsonRpcSignalingTransport;
@@ -1,15 +0,0 @@
1
- export type SignalRoute = {
2
- remoteNetworkId: bigint;
3
- remotePmsgId: string | null;
4
- };
5
- export type PartialSignalRoute = {
6
- remoteNetworkId?: bigint | null;
7
- remotePmsgId?: string | null;
8
- };
9
- export declare function createSignalRoute(remoteNetworkId?: bigint | null, remotePmsgId?: string | null): SignalRoute;
10
- export declare function mergeSignalRoute(primary: PartialSignalRoute, fallback?: PartialSignalRoute): SignalRoute;
11
- export declare function readSignalRoute(obj: Record<string, unknown>, networkIdKeys: string[], playerIdKeys: string[]): PartialSignalRoute;
12
- export declare function getStringValue(obj: Record<string, unknown>, keys: string[]): string | null;
13
- export declare function getBigIntValue(obj: Record<string, unknown>, keys: string[]): bigint | null;
14
- export declare function getRouteIdValue(obj: Record<string, unknown>, keys: string[]): string | null;
15
- export declare function parseJwtPayload(token: string): Record<string, unknown> | null;
@@ -1,80 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createSignalRoute = createSignalRoute;
4
- exports.mergeSignalRoute = mergeSignalRoute;
5
- exports.readSignalRoute = readSignalRoute;
6
- exports.getStringValue = getStringValue;
7
- exports.getBigIntValue = getBigIntValue;
8
- exports.getRouteIdValue = getRouteIdValue;
9
- exports.parseJwtPayload = parseJwtPayload;
10
- function createSignalRoute(remoteNetworkId = null, remotePmsgId = null) {
11
- return {
12
- remoteNetworkId: remoteNetworkId ?? 0n,
13
- remotePmsgId,
14
- };
15
- }
16
- function mergeSignalRoute(primary, fallback = {}) {
17
- return createSignalRoute(primary.remoteNetworkId ?? fallback.remoteNetworkId ?? null, primary.remotePmsgId ?? fallback.remotePmsgId ?? null);
18
- }
19
- function readSignalRoute(obj, networkIdKeys, playerIdKeys) {
20
- return {
21
- remoteNetworkId: getBigIntValue(obj, networkIdKeys),
22
- remotePmsgId: getRouteIdValue(obj, playerIdKeys),
23
- };
24
- }
25
- function getStringValue(obj, keys) {
26
- for (const key of keys) {
27
- const value = obj[key];
28
- if (typeof value === 'string') {
29
- return value;
30
- }
31
- }
32
- return null;
33
- }
34
- function getBigIntValue(obj, keys) {
35
- for (const key of keys) {
36
- const value = obj[key];
37
- if (typeof value === 'bigint') {
38
- return value;
39
- }
40
- if (typeof value === 'number' || typeof value === 'string') {
41
- const bigIntValue = tryBigInt(value);
42
- if (bigIntValue !== null) {
43
- return bigIntValue;
44
- }
45
- }
46
- }
47
- return null;
48
- }
49
- function getRouteIdValue(obj, keys) {
50
- for (const key of keys) {
51
- const value = obj[key];
52
- if (typeof value === 'string') {
53
- return value;
54
- }
55
- if (typeof value === 'number' || typeof value === 'bigint') {
56
- return String(value);
57
- }
58
- }
59
- return null;
60
- }
61
- function parseJwtPayload(token) {
62
- const [, payload] = token.split('.');
63
- if (!payload) {
64
- return null;
65
- }
66
- try {
67
- return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
68
- }
69
- catch {
70
- return null;
71
- }
72
- }
73
- function tryBigInt(value) {
74
- try {
75
- return BigInt(value);
76
- }
77
- catch {
78
- return null;
79
- }
80
- }
@@ -1,28 +0,0 @@
1
- import type { Authflow } from 'prismarine-auth';
2
- import type { IceServer } from 'node-datachannel';
3
- import { EventEmitter } from 'events';
4
- import { WebRtcSignal } from './webRtcSignal';
5
- export declare class SignalingClient extends EventEmitter {
6
- readonly netherNetId: bigint;
7
- localPlayerId: string | null;
8
- iceServers: IceServer[] | null;
9
- private readonly authflow;
10
- private readonly version;
11
- private readonly signalCodec;
12
- private readonly transport;
13
- private retryCount;
14
- private closed;
15
- private connectPromise;
16
- private reconnectPromise;
17
- constructor(authflow: Authflow, netherNetId: bigint, version: string);
18
- connect(): Promise<void>;
19
- close(): Promise<void>;
20
- sendSignal(signal: WebRtcSignal): Promise<void>;
21
- onSignal(listener: (signal: WebRtcSignal) => void): this;
22
- onConnectionError(listener: (error: Error) => void): this;
23
- private openSignalingSession;
24
- private handleTransportClose;
25
- private reconnect;
26
- private assertOpen;
27
- private emitConnectionError;
28
- }
@@ -1,161 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SignalingClient = void 0;
7
- const crypto_1 = require("crypto");
8
- const debug_1 = __importDefault(require("debug"));
9
- const events_1 = require("events");
10
- const webRtcSignalCodec_1 = require("./webRtcSignalCodec");
11
- const jsonRpcSignalingTransport_1 = require("./jsonRpcSignalingTransport");
12
- const turn_1 = require("./turn");
13
- const SIGNALING_SERVICE_URL = 'wss://signal.franchise.minecraft-services.net/ws/v1.0/messaging/connect';
14
- const SIGNAL_EVENT = 'signal';
15
- const CONNECTION_ERROR_EVENT = 'connectionError';
16
- const debug = (0, debug_1.default)('bedrock-portal-nethernet');
17
- class SignalingClient extends events_1.EventEmitter {
18
- netherNetId;
19
- localPlayerId;
20
- iceServers;
21
- authflow;
22
- version;
23
- signalCodec;
24
- transport;
25
- retryCount;
26
- closed;
27
- connectPromise;
28
- reconnectPromise;
29
- constructor(authflow, netherNetId, version) {
30
- super();
31
- this.authflow = authflow;
32
- this.netherNetId = netherNetId;
33
- this.version = version;
34
- this.localPlayerId = null;
35
- this.iceServers = null;
36
- this.signalCodec = new webRtcSignalCodec_1.WebRtcSignalCodec();
37
- this.transport = new jsonRpcSignalingTransport_1.JsonRpcSignalingTransport();
38
- this.retryCount = 0;
39
- this.closed = false;
40
- this.connectPromise = null;
41
- this.reconnectPromise = null;
42
- this.transport.onInboxEntries = (entries) => {
43
- for (const entry of entries) {
44
- const signal = this.signalCodec.decodeSignal(entry);
45
- if (!signal) {
46
- debug('Failed to decode WebRTC signaling message', entry);
47
- continue;
48
- }
49
- this.emit(SIGNAL_EVENT, signal);
50
- }
51
- };
52
- this.transport.onClose = (event) => {
53
- this.handleTransportClose(event);
54
- };
55
- this.transport.onError = (error) => {
56
- debug('JSON-RPC signaling transport error', error);
57
- };
58
- }
59
- async connect() {
60
- if (this.transport.connected) {
61
- throw new Error('Already connected to signaling service');
62
- }
63
- if (this.connectPromise) {
64
- return this.connectPromise;
65
- }
66
- this.closed = false;
67
- this.retryCount = 0;
68
- const promise = this.openSignalingSession().finally(() => {
69
- if (this.connectPromise === promise) {
70
- this.connectPromise = null;
71
- }
72
- });
73
- this.connectPromise = promise;
74
- return promise;
75
- }
76
- async close() {
77
- debug('Disconnecting from signaling service');
78
- this.closed = true;
79
- this.retryCount = 0;
80
- this.iceServers = null;
81
- this.localPlayerId = null;
82
- await this.transport.close();
83
- }
84
- async sendSignal(signal) {
85
- await this.transport.sendMessage(this.signalCodec.encodeSignal(signal, this.netherNetId));
86
- }
87
- onSignal(listener) {
88
- this.on(SIGNAL_EVENT, listener);
89
- return this;
90
- }
91
- onConnectionError(listener) {
92
- this.on(CONNECTION_ERROR_EVENT, listener);
93
- return this;
94
- }
95
- async openSignalingSession() {
96
- this.assertOpen();
97
- const xbl = await this.authflow.getMinecraftBedrockServicesToken({ version: this.version });
98
- this.assertOpen();
99
- this.localPlayerId = (0, turn_1.parseTokenSignalingPlayerId)(xbl.mcToken);
100
- debug('Fetched XBL token for signaling service');
101
- debug('Connecting to signaling service', SIGNALING_SERVICE_URL);
102
- await this.transport.connect({
103
- url: SIGNALING_SERVICE_URL,
104
- headers: {
105
- 'Authorization': xbl.mcToken,
106
- 'session-id': this.localPlayerId ?? (0, crypto_1.randomUUID)(),
107
- 'request-id': (0, crypto_1.randomUUID)(),
108
- },
109
- });
110
- this.assertOpen();
111
- this.iceServers = (0, turn_1.parseIceServersFromTurnAuth)(await this.transport.requestTurnAuth());
112
- }
113
- handleTransportClose(event) {
114
- debug(`Signaling service disconnected with code ${event.code} and reason ${event.reason}`);
115
- this.iceServers = null;
116
- if (this.closed) {
117
- return;
118
- }
119
- if (event.code === 1006) {
120
- if (this.retryCount >= 5) {
121
- this.emitConnectionError(new Error('Signaling service connection closed unexpectedly'));
122
- return;
123
- }
124
- this.retryCount += 1;
125
- void this.reconnect().catch((err) => {
126
- debug('Signaling service reconnect failed', err);
127
- this.emitConnectionError(err);
128
- });
129
- return;
130
- }
131
- if (event.code !== 1000) {
132
- this.emitConnectionError(new Error(`Signaling service connection closed with code ${event.code} and reason ${event.reason}`));
133
- }
134
- }
135
- async reconnect() {
136
- if (this.reconnectPromise) {
137
- return this.reconnectPromise;
138
- }
139
- const promise = this.openSignalingSession().finally(() => {
140
- if (this.reconnectPromise === promise) {
141
- this.reconnectPromise = null;
142
- }
143
- });
144
- this.reconnectPromise = promise;
145
- return promise;
146
- }
147
- assertOpen() {
148
- if (this.closed) {
149
- throw new Error('Signaling client is closed');
150
- }
151
- }
152
- emitConnectionError(err) {
153
- const error = err instanceof Error ? err : new Error(String(err));
154
- this.emit(CONNECTION_ERROR_EVENT, error);
155
- if (this.listenerCount('error') > 0) {
156
- this.emit('error', error);
157
- }
158
- return error;
159
- }
160
- }
161
- exports.SignalingClient = SignalingClient;
@@ -1,44 +0,0 @@
1
- export type SignalInboxMessage = {
2
- From: string;
3
- Message: string;
4
- Id: string;
5
- };
6
- export type SignalOutboxMessage = {
7
- toPlayerId: string;
8
- messageId: string;
9
- message: string;
10
- };
11
- export type TurnAuthResponse = {
12
- TurnAuthServers?: Array<{
13
- Urls: string[];
14
- Username: string;
15
- Password: string;
16
- }>;
17
- };
18
- export type SignalTransportConnection = {
19
- url: string;
20
- headers: Record<string, string>;
21
- };
22
- export type SignalTransportCloseEvent = {
23
- code: number;
24
- reason: string;
25
- };
26
- export declare class SignalTransport {
27
- onMessages: (messages: SignalInboxMessage[]) => void;
28
- onClose: (event: SignalTransportCloseEvent) => void;
29
- onError: (error: Error) => void;
30
- private ws;
31
- private pingInterval;
32
- private readonly rpc;
33
- constructor();
34
- get connected(): boolean;
35
- connect(connection: SignalTransportConnection): Promise<void>;
36
- close(code?: number, reason?: string): Promise<void>;
37
- requestTurnAuth(): Promise<any>;
38
- sendClientMessage(message: SignalOutboxMessage): Promise<void>;
39
- private handleMessage;
40
- private handleClose;
41
- private startPingLoop;
42
- private stopPingLoop;
43
- private normalizeError;
44
- }
@@ -1,174 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SignalTransport = void 0;
7
- const crypto_1 = require("crypto");
8
- const debug_1 = __importDefault(require("debug"));
9
- const JSONBig = require("json-bigint");
10
- const json_rpc_2_0_1 = require("json-rpc-2.0");
11
- const ws_1 = require("ws");
12
- const TURN_AUTH_METHOD = 'Signaling_TurnAuth_v1_0';
13
- const RECEIVE_MESSAGE_METHOD = 'Signaling_ReceiveMessage_v1_0';
14
- const DELIVERY_NOTIFICATION_METHOD = 'Signaling_DeliveryNotification_V1_0';
15
- const PING_METHOD = 'System_Ping_v1_0';
16
- const PONG_METHOD = 'System_Pong_v1_0';
17
- const SEND_CLIENT_MESSAGE_METHOD = 'Signaling_SendClientMessage_v1_0';
18
- const debug = (0, debug_1.default)('bedrock-portal-nethernet');
19
- const json = JSONBig({ useNativeBigInt: true });
20
- class SignalTransport {
21
- onMessages;
22
- onClose;
23
- onError;
24
- ws;
25
- pingInterval;
26
- rpc;
27
- constructor() {
28
- this.onMessages = () => { };
29
- this.onClose = () => { };
30
- this.onError = () => { };
31
- this.ws = null;
32
- this.pingInterval = null;
33
- const server = new json_rpc_2_0_1.JSONRPCServer({
34
- errorListener: (message, payload) => {
35
- debug('JSON-RPC server error', message, payload);
36
- },
37
- });
38
- server.addMethod(RECEIVE_MESSAGE_METHOD, (params) => {
39
- const messages = Array.isArray(params) ? params : [params];
40
- this.onMessages(messages);
41
- });
42
- server.addMethod(PING_METHOD, () => { });
43
- server.addMethod(PONG_METHOD, () => { });
44
- server.addMethod(DELIVERY_NOTIFICATION_METHOD, () => { });
45
- const client = new json_rpc_2_0_1.JSONRPCClient(async (payload) => {
46
- if (this.ws?.readyState !== ws_1.WebSocket.OPEN) {
47
- throw new Error('WebSocket not connected');
48
- }
49
- this.ws.send(json.stringify(payload));
50
- }, () => (0, crypto_1.randomUUID)());
51
- this.rpc = new json_rpc_2_0_1.JSONRPCServerAndClient(server, client, {
52
- errorListener: (message, payload) => {
53
- debug('JSON-RPC transport error', message, payload);
54
- },
55
- });
56
- }
57
- get connected() {
58
- return this.ws?.readyState === ws_1.WebSocket.OPEN;
59
- }
60
- async connect(connection) {
61
- if (this.ws?.readyState === ws_1.WebSocket.OPEN || this.ws?.readyState === ws_1.WebSocket.CONNECTING) {
62
- throw new Error('Signal transport already connected');
63
- }
64
- const ws = new ws_1.WebSocket(connection.url, {
65
- headers: connection.headers,
66
- });
67
- this.ws = ws;
68
- let opened = false;
69
- await new Promise((resolve, reject) => {
70
- ws.onopen = () => {
71
- opened = true;
72
- debug('Signal websocket connected');
73
- this.startPingLoop();
74
- resolve();
75
- };
76
- ws.onmessage = (event) => {
77
- this.handleMessage(event.data);
78
- };
79
- ws.onerror = (event) => {
80
- const error = this.normalizeError(event);
81
- debug('Signal websocket error', error);
82
- this.onError(error);
83
- if (!opened) {
84
- reject(error);
85
- }
86
- };
87
- ws.onclose = (event) => {
88
- this.handleClose(ws, event.code, event.reason);
89
- if (!opened) {
90
- reject(new Error(`Signal transport closed with code ${event.code} and reason ${event.reason}`));
91
- }
92
- };
93
- });
94
- }
95
- async close(code = 1000, reason = 'Normal Closure') {
96
- const ws = this.ws;
97
- this.stopPingLoop();
98
- this.rpc.rejectAllPendingRequests('Signal transport closed');
99
- this.ws = null;
100
- if (!ws) {
101
- return;
102
- }
103
- ws.onmessage = null;
104
- ws.onerror = null;
105
- if (ws.readyState === ws_1.WebSocket.CLOSED) {
106
- ws.onclose = null;
107
- return;
108
- }
109
- await new Promise((resolve) => {
110
- ws.onclose = () => resolve();
111
- ws.close(code, reason);
112
- });
113
- }
114
- async requestTurnAuth() {
115
- return this.rpc.timeout(15000).request(TURN_AUTH_METHOD, {}, undefined);
116
- }
117
- async sendClientMessage(message) {
118
- await this.rpc.timeout(15000).request(SEND_CLIENT_MESSAGE_METHOD, message, undefined);
119
- }
120
- handleMessage(data) {
121
- if (typeof data !== 'string') {
122
- debug('Received non-string message', data);
123
- return;
124
- }
125
- let payload;
126
- try {
127
- payload = json.parse(data);
128
- }
129
- catch (err) {
130
- debug('Received invalid JSON-RPC message', err);
131
- return;
132
- }
133
- debug('Received message', payload);
134
- void this.rpc.receiveAndSend(payload, undefined, undefined).catch((err) => {
135
- debug('Failed to handle JSON-RPC message', err);
136
- });
137
- }
138
- handleClose(ws, code, reason) {
139
- debug(`Signal websocket closed with code ${code} and reason ${reason}`);
140
- if (this.ws === ws) {
141
- this.ws = null;
142
- }
143
- this.stopPingLoop();
144
- this.rpc.rejectAllPendingRequests('Signal transport closed');
145
- this.onClose({ code, reason });
146
- }
147
- startPingLoop() {
148
- this.stopPingLoop();
149
- this.pingInterval = setInterval(() => {
150
- if (!this.connected) {
151
- return;
152
- }
153
- void Promise.resolve(this.rpc.timeout(15000).request(PING_METHOD, [], undefined)).catch((err) => {
154
- debug('Signal ping failed', err);
155
- });
156
- }, 5000);
157
- }
158
- stopPingLoop() {
159
- if (this.pingInterval) {
160
- clearInterval(this.pingInterval);
161
- this.pingInterval = null;
162
- }
163
- }
164
- normalizeError(event) {
165
- if (event.error instanceof Error) {
166
- return event.error;
167
- }
168
- if (typeof event.message === 'string' && event.message.length > 0) {
169
- return new Error(event.message);
170
- }
171
- return new Error('Signal websocket error');
172
- }
173
- }
174
- exports.SignalTransport = SignalTransport;
@@ -1,4 +0,0 @@
1
- import type { IceServer } from 'node-datachannel';
2
- import type { TurnAuthResponse } from './jsonRpcSignalingTransport';
3
- export declare function parseIceServersFromTurnAuth(data: TurnAuthResponse): IceServer[];
4
- export declare function parseTokenSignalingPlayerId(token: string): string | null;
@@ -1,83 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseIceServersFromTurnAuth = parseIceServersFromTurnAuth;
4
- exports.parseTokenSignalingPlayerId = parseTokenSignalingPlayerId;
5
- function parseIceServersFromTurnAuth(data) {
6
- const servers = [];
7
- for (const server of data.TurnAuthServers ?? []) {
8
- for (const url of server.Urls ?? []) {
9
- const parsed = parseIceServer(url);
10
- if (!parsed) {
11
- continue;
12
- }
13
- servers.push({
14
- ...parsed,
15
- username: server.Username,
16
- password: server.Password,
17
- });
18
- }
19
- }
20
- return servers;
21
- }
22
- function parseTokenSignalingPlayerId(token) {
23
- const jwt = token.split(' ')[1];
24
- if (!jwt) {
25
- return null;
26
- }
27
- const [, payload] = jwt.split('.');
28
- if (!payload) {
29
- return null;
30
- }
31
- try {
32
- const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
33
- return typeof parsed.pmid === 'string' ? parsed.pmid : null;
34
- }
35
- catch {
36
- return null;
37
- }
38
- }
39
- function parseIceServer(url) {
40
- const normalizedUrl = normalizeIceServerUrl(url);
41
- if (!normalizedUrl) {
42
- return null;
43
- }
44
- try {
45
- const parsed = new URL(normalizedUrl);
46
- if (!parsed.hostname || !parsed.port) {
47
- return null;
48
- }
49
- const port = Number.parseInt(parsed.port, 10);
50
- if (Number.isNaN(port)) {
51
- return null;
52
- }
53
- const relayType = resolveRelayType(parsed.protocol.slice(0, -1), parsed.searchParams.get('transport'));
54
- return relayType
55
- ? { hostname: parsed.hostname, port, relayType }
56
- : { hostname: parsed.hostname, port };
57
- }
58
- catch {
59
- return null;
60
- }
61
- }
62
- function normalizeIceServerUrl(url) {
63
- if (!/^(stun|turn|turns):/i.test(url)) {
64
- return null;
65
- }
66
- return url.replace(/^([a-z]+):/i, '$1://');
67
- }
68
- function resolveRelayType(protocol, transport) {
69
- if (protocol === 'stun') {
70
- return undefined;
71
- }
72
- if (protocol === 'turns') {
73
- return 'TurnTls';
74
- }
75
- const normalizedTransport = transport?.toLowerCase();
76
- if (normalizedTransport === 'tcp') {
77
- return 'TurnTcp';
78
- }
79
- if (normalizedTransport === 'tls') {
80
- return 'TurnTls';
81
- }
82
- return 'TurnUdp';
83
- }
@@ -1,20 +0,0 @@
1
- export declare const WebRtcSignalType: {
2
- readonly Offer: "CONNECTREQUEST";
3
- readonly Answer: "CONNECTRESPONSE";
4
- readonly IceCandidate: "CANDIDATEADD";
5
- readonly Error: "CONNECTERROR";
6
- };
7
- export type SignalingPeer = {
8
- netherNetId: bigint;
9
- playerId: string;
10
- };
11
- export declare class WebRtcSignal {
12
- readonly type: string;
13
- readonly connectionId: bigint;
14
- readonly payload: string;
15
- readonly peer: Readonly<SignalingPeer>;
16
- constructor(type: string, connectionId: bigint, payload: string, peer: SignalingPeer);
17
- serialize(): string;
18
- createReply(type: string, payload: string): WebRtcSignal;
19
- static parse(message: string, peer: SignalingPeer): WebRtcSignal | null;
20
- }
@@ -1,41 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WebRtcSignal = exports.WebRtcSignalType = void 0;
4
- exports.WebRtcSignalType = {
5
- Offer: 'CONNECTREQUEST',
6
- Answer: 'CONNECTRESPONSE',
7
- IceCandidate: 'CANDIDATEADD',
8
- Error: 'CONNECTERROR',
9
- };
10
- class WebRtcSignal {
11
- type;
12
- connectionId;
13
- payload;
14
- peer;
15
- constructor(type, connectionId, payload, peer) {
16
- this.type = type;
17
- this.connectionId = connectionId;
18
- this.payload = payload;
19
- this.peer = Object.freeze({ ...peer });
20
- }
21
- serialize() {
22
- const prefix = `${this.type} ${this.connectionId}`;
23
- return this.payload.length > 0 ? `${prefix} ${this.payload}` : prefix;
24
- }
25
- createReply(type, payload) {
26
- return new WebRtcSignal(type, this.connectionId, payload, this.peer);
27
- }
28
- static parse(message, peer) {
29
- const [type, rawConnectionId, ...payload] = message.split(' ');
30
- if (!type || !rawConnectionId) {
31
- return null;
32
- }
33
- try {
34
- return new WebRtcSignal(type, BigInt(rawConnectionId), payload.join(' '), peer);
35
- }
36
- catch {
37
- return null;
38
- }
39
- }
40
- }
41
- exports.WebRtcSignal = WebRtcSignal;
@@ -1,7 +0,0 @@
1
- import { WebRtcSignal } from './webRtcSignal';
2
- import type { SignalingInboxEntry, SignalingOutboxEntry } from './jsonRpcSignalingTransport';
3
- export declare class WebRtcSignalCodec {
4
- decodeSignal(entry: SignalingInboxEntry): WebRtcSignal | null;
5
- encodeSignal(signal: WebRtcSignal, localNetherNetId: bigint): SignalingOutboxEntry;
6
- private parseSignalPayload;
7
- }
@@ -1,65 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WebRtcSignalCodec = void 0;
4
- const crypto_1 = require("crypto");
5
- const webRtcSignal_1 = require("./webRtcSignal");
6
- const WEB_RTC_SIGNAL_METHOD = 'Signaling_WebRtc_v1_0';
7
- class WebRtcSignalCodec {
8
- decodeSignal(entry) {
9
- const payload = this.parseSignalPayload(entry.Message);
10
- if (!payload || payload.method !== WEB_RTC_SIGNAL_METHOD) {
11
- return null;
12
- }
13
- try {
14
- return webRtcSignal_1.WebRtcSignal.parse(payload.params.message, {
15
- netherNetId: BigInt(payload.params.netherNetId),
16
- playerId: entry.From,
17
- });
18
- }
19
- catch {
20
- return null;
21
- }
22
- }
23
- encodeSignal(signal, localNetherNetId) {
24
- return {
25
- toPlayerId: signal.peer.playerId,
26
- messageId: (0, crypto_1.randomUUID)(),
27
- message: JSON.stringify({
28
- jsonrpc: '2.0',
29
- method: WEB_RTC_SIGNAL_METHOD,
30
- params: {
31
- netherNetId: localNetherNetId.toString(),
32
- message: signal.serialize(),
33
- },
34
- }),
35
- };
36
- }
37
- parseSignalPayload(message) {
38
- let payload;
39
- try {
40
- payload = JSON.parse(message);
41
- }
42
- catch {
43
- return null;
44
- }
45
- if (!isJsonRpcWebRtcSignalPayload(payload)) {
46
- return null;
47
- }
48
- return payload;
49
- }
50
- }
51
- exports.WebRtcSignalCodec = WebRtcSignalCodec;
52
- function isJsonRpcWebRtcSignalPayload(value) {
53
- if (!value || typeof value !== 'object') {
54
- return false;
55
- }
56
- const payload = value;
57
- if (typeof payload.jsonrpc !== 'string' || typeof payload.method !== 'string') {
58
- return false;
59
- }
60
- if (!payload.params || typeof payload.params !== 'object') {
61
- return false;
62
- }
63
- const params = payload.params;
64
- return typeof params.netherNetId === 'string' && typeof params.message === 'string';
65
- }