@bloopjs/web 0.0.44 → 0.0.45

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.
@@ -0,0 +1,12 @@
1
+ import type { BrokerMessage } from "./protocol.ts";
2
+ export type RoomEvents = {
3
+ onBrokerMessage: (message: BrokerMessage) => void;
4
+ onPeerIdAssign: (peerId: string) => void;
5
+ onPeerConnected: (peerId: string) => void;
6
+ onPeerDisconnected: (peerId: string) => void;
7
+ onDataChannelOpen: (peerId: string, reliable: boolean, channel: RTCDataChannel) => void;
8
+ onMessage: (peerId: string, data: Uint8Array, reliable: boolean) => void;
9
+ onDataChannelClose: (peerId: string, reliable: boolean) => void;
10
+ };
11
+ export declare function joinRoom(brokerUrl: string, _roomId: string, cbs: RoomEvents): void;
12
+ //# sourceMappingURL=broker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../src/netcode/broker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AASnD,MAAM,MAAM,UAAU,GAAG;IACvB,eAAe,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAClD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7C,iBAAiB,EAAE,CACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,cAAc,KACpB,IAAI,CAAC;IACV,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACzE,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;CACjE,CAAC;AAEF,wBAAgB,QAAQ,CACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,UAAU,QA0GhB"}
@@ -0,0 +1,37 @@
1
+ import type { PeerId } from "./protocol.ts";
2
+ export type Log = {
3
+ source: "webrtc" | "ws" | "local" | "rollback";
4
+ /** absolute frame number since the start of the sim */
5
+ frame_number: number;
6
+ /** relative frame number since the start of the current session */
7
+ match_frame: number | null;
8
+ /** unix timestamp */
9
+ timestamp: number;
10
+ severity: LogSeverity;
11
+ label?: string;
12
+ json?: any;
13
+ direction?: LogDirection;
14
+ from?: PeerId;
15
+ to?: PeerId;
16
+ reliable?: boolean;
17
+ packet?: {
18
+ size: number;
19
+ bytes: Uint8Array;
20
+ };
21
+ };
22
+ export type LogOpts = Partial<Log> & {
23
+ source: Log["source"];
24
+ };
25
+ export type LogDirection = "inbound" | "outbound";
26
+ export type LogSeverity = "debug" | "log" | "warn" | "error";
27
+ export type OnLogCallback = (log: Log) => void;
28
+ export type Logger = {
29
+ onLog: OnLogCallback | null;
30
+ matchFrame: number;
31
+ frameNumber: number;
32
+ log(opts: LogOpts): void;
33
+ warn(opts: LogOpts): void;
34
+ error(opts: LogOpts): void;
35
+ };
36
+ export declare const logger: Logger;
37
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/netcode/logs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,MAAM,GAAG,GAAG;IAChB,MAAM,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO,GAAG,UAAU,CAAC;IAC/C,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,GAAG,CAAC;IAEX,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,UAAU,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG;IACnC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;AAE/C,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAE5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IAEpB,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5B,CAAA;AAED,eAAO,MAAM,MAAM,EAAE,MAmCpB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export type { PeerId, BrokerMessage, PeerMessage } from "./protocol.ts";
2
+ export type { Log, LogOpts, LogDirection, LogSeverity, OnLogCallback, } from "./logs.ts";
3
+ export type { WebRtcPipe } from "./transport.ts";
4
+ export type { RoomEvents } from "./broker.ts";
5
+ export { PacketType } from "./protocol.ts";
6
+ export { logger } from "./logs.ts";
7
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/netcode/mod.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EACV,GAAG,EACH,OAAO,EACP,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,42 @@
1
+ export type PeerId = string;
2
+ export declare enum PacketType {
3
+ None = 0,
4
+ Inputs = 1
5
+ }
6
+ export type BrokerMessage = {
7
+ type: "welcome";
8
+ yourId: PeerId;
9
+ peerIds: PeerId[];
10
+ serverId: string;
11
+ } | {
12
+ type: "message:json";
13
+ peerId: PeerId;
14
+ message: PeerMessage;
15
+ } | {
16
+ type: "message:string";
17
+ peerId: PeerId;
18
+ message: string;
19
+ } | {
20
+ type: "message:buffer";
21
+ peerId: PeerId;
22
+ message: ArrayBuffer;
23
+ } | {
24
+ type: "peer:connect";
25
+ peerId: PeerId;
26
+ } | {
27
+ type: "peer:disconnect";
28
+ peerId: PeerId;
29
+ };
30
+ export type PeerMessage = {
31
+ type: "offer";
32
+ target: string;
33
+ payload: string;
34
+ } | {
35
+ type: "answer";
36
+ target: string;
37
+ payload: string;
38
+ } | {
39
+ type: "message";
40
+ payload: any;
41
+ };
42
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/netcode/protocol.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAK5B,oBAAY,UAAU;IACpB,IAAI,IAAI;IACR,MAAM,IAAI;CACX;AAED,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;CACtB,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;CACtB,GACD;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,MAAM,MAAM,WAAW,GACnB;IACE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;CACd,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { WebSocket } from "partysocket";
2
+ export type WebRtcPipe = {
3
+ peerConnection: RTCPeerConnection;
4
+ reliable: RTCDataChannel;
5
+ unreliable: RTCDataChannel;
6
+ peerId: string;
7
+ };
8
+ export declare function connect(ws: WebSocket, peerId: string,
9
+ /** defaults to 10s */
10
+ timeoutMs?: number): Promise<WebRtcPipe>;
11
+ export declare function logErrors(dc: RTCDataChannel): Promise<void>;
12
+ export declare function logPeerConnection(pc: RTCPeerConnection, peerId: string): Promise<void>;
13
+ export declare function gatherIce(pc: RTCPeerConnection, timeoutMs: number): Promise<boolean | Error>;
14
+ export declare function waitForAnswer(ws: WebSocket, pc: RTCPeerConnection, timeoutMs: number): Promise<void>;
15
+ export declare function listenForOffers(ws: WebSocket, cb: (pipe: WebRtcPipe) => void): void;
16
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/netcode/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAQ7C,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,iBAAiB,CAAC;IAClC,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,cAAc,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,OAAO,CAC3B,EAAE,EAAE,SAAS,EACb,MAAM,EAAE,MAAM;AACd,sBAAsB;AACtB,SAAS,GAAE,MAAc,GACxB,OAAO,CAAC,UAAU,CAAC,CAiCrB;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,cAAc,iBA6BjD;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,iBAc5E;AAED,wBAAsB,SAAS,CAC7B,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,CAkB1B;AAED,wBAAsB,aAAa,CACjC,EAAE,EAAE,SAAS,EACb,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,QAwE5E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloopjs/web",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
4
4
  "author": "Neil Sarkar",
5
5
  "type": "module",
6
6
  "repository": {
@@ -33,7 +33,8 @@
33
33
  "typescript": "^5"
34
34
  },
35
35
  "dependencies": {
36
- "@bloopjs/bloop": "0.0.44",
37
- "@bloopjs/engine": "0.0.44"
36
+ "@bloopjs/bloop": "0.0.45",
37
+ "@bloopjs/engine": "0.0.45",
38
+ "partysocket": "^1.1.6"
38
39
  }
39
40
  }
package/src/App.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { type Bloop, type MountOpts, mount, type Sim } from "@bloopjs/bloop";
2
2
  import type { Key } from "@bloopjs/engine";
3
3
  import { mouseButtonCodeToMouseButton } from "@bloopjs/engine";
4
+ import {
5
+ joinRoom as joinRoomInternal,
6
+ type RoomEvents,
7
+ } from "./netcode/broker";
8
+ import { logger } from "./netcode/logs.ts";
4
9
 
5
10
  export type StartOptions = {
6
11
  /** A bloop game instance */
@@ -13,8 +18,12 @@ export type StartOptions = {
13
18
  startPaused?: boolean;
14
19
  /** Whether the sim should be recording to tape from initialization, defaults to true */
15
20
  startRecording?: boolean;
21
+ /** URL for the WebRTC signaling broker (e.g. "wss://broker.example.com/ws") */
22
+ brokerUrl?: string;
16
23
  };
17
24
 
25
+ const DEFAULT_BROKER_URL = "wss://webrtc-divine-glade-8064.fly.dev/ws";
26
+
18
27
  /** Start a bloop game on the web */
19
28
  export async function start(opts: StartOptions): Promise<App> {
20
29
  if (!opts.sim) {
@@ -26,29 +35,46 @@ export async function start(opts: StartOptions): Promise<App> {
26
35
  opts.sim = sim;
27
36
  }
28
37
 
29
- const app = new App(opts.sim, opts.game);
38
+ const app = new App(
39
+ opts.sim,
40
+ opts.game,
41
+ opts.brokerUrl ?? DEFAULT_BROKER_URL,
42
+ );
30
43
  return app;
31
44
  }
32
45
 
46
+ /**
47
+ * The main application class for running a bloop game in the browser
48
+ *
49
+ * This class handles translating browser events and APIs to bloopjs and the wasm engine.
50
+ *
51
+ * Usually instantiated with the start() function
52
+ */
33
53
  export class App {
34
54
  #sim: Sim;
35
55
  game: Bloop<any>;
56
+ /** URL for the WebRTC signaling broker */
57
+ readonly brokerUrl: string;
36
58
  /** RequestAnimationFrame handle for cancelling */
37
59
  #rafHandle: number | null = null;
38
60
  #unsubscribe: UnsubscribeFn | null = null;
39
61
  #now: number = performance.now();
40
62
 
41
- constructor(sim: Sim, game: Bloop<any>) {
63
+ constructor(sim: Sim, game: Bloop<any>, brokerUrl: string) {
42
64
  this.#sim = sim;
43
65
  this.game = game;
66
+ this.brokerUrl = brokerUrl;
44
67
 
45
68
  this.game.hooks.beforeFrame = (frame: number) => {
69
+ logger.frameNumber = this.#sim.time.frame;
70
+ logger.matchFrame = this.#sim.wasm.get_match_frame();
46
71
  this.beforeFrame.notify(frame);
47
72
  };
48
73
 
49
74
  this.subscribe();
50
75
  }
51
76
 
77
+ /** The simulation instance associated with this app */
52
78
  get sim(): Sim {
53
79
  return this.#sim;
54
80
  }
@@ -57,7 +83,14 @@ export class App {
57
83
  this.#sim = sim;
58
84
  }
59
85
 
86
+ /** Join a multiplayer room via the broker */
87
+ joinRoom(roomId: string, callbacks: RoomEvents): void {
88
+ joinRoomInternal(this.brokerUrl, roomId, callbacks);
89
+ }
90
+
91
+ /** Event listeners for before a frame is processed */
60
92
  beforeFrame: ReturnType<typeof createListener> = createListener<[number]>();
93
+ /** Event listeners for after a frame is processed */
61
94
  afterFrame: ReturnType<typeof createListener> = createListener<[number]>();
62
95
 
63
96
  /** Subscribe to the browser events and start the render loop */
@@ -176,11 +209,23 @@ export class App {
176
209
  this.afterFrame.unsubscribeAll();
177
210
  }
178
211
 
212
+ /**
213
+ * Accept Hot Module Replacement when running in a vite dev server
214
+ *
215
+ * @example
216
+ *
217
+ * ```ts
218
+ * import.meta.hot?.accept("./game", async (newModule) => {
219
+ * await app.acceptHmr(newModule?.game, {
220
+ * wasmUrl: monorepoWasmUrl,
221
+ * });
222
+ * ```
223
+ */
179
224
  async acceptHmr(module: any, opts?: Partial<MountOpts>): Promise<void> {
180
225
  const game = (module.game ?? module) as Bloop<any>;
181
226
  if (!game.hooks) {
182
227
  throw new Error(
183
- `HMR: missing game.hooks export on module: ${JSON.stringify(module)}`
228
+ `HMR: missing game.hooks export on module: ${JSON.stringify(module)}`,
184
229
  );
185
230
  }
186
231
 
package/src/mod.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { App, start } from "./App.ts";
2
+ export * from "./netcode/mod.ts";
@@ -0,0 +1,136 @@
1
+ import { WebSocket } from "partysocket";
2
+ import { logger } from "./logs.ts";
3
+ import type { BrokerMessage } from "./protocol.ts";
4
+ import {
5
+ connect,
6
+ listenForOffers,
7
+ logErrors,
8
+ logPeerConnection,
9
+ type WebRtcPipe,
10
+ } from "./transport.ts";
11
+
12
+ export type RoomEvents = {
13
+ onBrokerMessage: (message: BrokerMessage) => void;
14
+ onPeerIdAssign: (peerId: string) => void;
15
+ onPeerConnected: (peerId: string) => void;
16
+ onPeerDisconnected: (peerId: string) => void;
17
+
18
+ onDataChannelOpen: (
19
+ peerId: string,
20
+ reliable: boolean,
21
+ channel: RTCDataChannel
22
+ ) => void;
23
+ onMessage: (peerId: string, data: Uint8Array, reliable: boolean) => void;
24
+ onDataChannelClose: (peerId: string, reliable: boolean) => void;
25
+ };
26
+
27
+ export function joinRoom(
28
+ brokerUrl: string,
29
+ _roomId: string,
30
+ cbs: RoomEvents
31
+ ) {
32
+ const broker = new WebSocket(brokerUrl);
33
+
34
+ broker.addEventListener("open", () => {
35
+ logger.log({
36
+ source: "ws",
37
+ label: "Connection opened",
38
+ });
39
+ });
40
+
41
+ broker.addEventListener("close", (event) => {
42
+ logger.warn({
43
+ source: "ws",
44
+ label: "Connection closed",
45
+ json: event,
46
+ });
47
+ });
48
+
49
+ broker.addEventListener("error", (event) => {
50
+ logger.error({
51
+ source: "ws",
52
+ label: "Connection error",
53
+ json: event,
54
+ });
55
+ });
56
+
57
+ const pipes: Map<string, WebRtcPipe> = new Map();
58
+
59
+ let ourId = "";
60
+
61
+ broker.addEventListener("message", async (event) => {
62
+ try {
63
+ const envelope = JSON.parse(event.data) as BrokerMessage;
64
+ logger.log({
65
+ source: "ws",
66
+ direction: "inbound",
67
+ json: envelope,
68
+ });
69
+
70
+ switch (envelope.type) {
71
+ case "welcome":
72
+ ourId = envelope.yourId;
73
+ cbs.onPeerIdAssign(envelope.yourId);
74
+ for (const peerId of envelope.peerIds) {
75
+ if (peerId === ourId) continue;
76
+ cbs.onPeerConnected(peerId);
77
+ }
78
+ break;
79
+ case "message:json":
80
+ break;
81
+ case "peer:connect": {
82
+ const pipe = await connect(broker, envelope.peerId);
83
+ registerPipe(pipe, cbs);
84
+ cbs.onPeerConnected(envelope.peerId);
85
+ break;
86
+ }
87
+ case "peer:disconnect":
88
+ cbs.onPeerDisconnected(envelope.peerId);
89
+ break;
90
+ default:
91
+ logger.warn({
92
+ source: "ws",
93
+ label: `Unknown message type: ${envelope.type}`,
94
+ json: envelope,
95
+ });
96
+ }
97
+ } catch (e) {
98
+ logger.error({
99
+ source: "ws",
100
+ label: "Failed to parse json",
101
+ json: {
102
+ data: event.data,
103
+ error: e,
104
+ },
105
+ });
106
+ }
107
+ });
108
+
109
+ listenForOffers(broker, (pipe) => {
110
+ registerPipe(pipe, cbs);
111
+ });
112
+
113
+ function registerPipe(pipe: WebRtcPipe, cbs: RoomEvents) {
114
+ logErrors(pipe.reliable);
115
+ logErrors(pipe.unreliable);
116
+ logPeerConnection(pipe.peerConnection, ourId);
117
+
118
+ cbs.onDataChannelOpen(pipe.peerId, true, pipe.reliable);
119
+ cbs.onDataChannelOpen(pipe.peerId, false, pipe.unreliable);
120
+
121
+ pipe.reliable.onmessage = (event) => {
122
+ cbs.onMessage(pipe.peerId, event.data, true);
123
+ };
124
+ pipe.reliable.onclose = () => {
125
+ cbs.onDataChannelClose(pipe.peerId, true);
126
+ };
127
+
128
+ pipe.unreliable.onmessage = (event) => {
129
+ cbs.onMessage(pipe.peerId, event.data, false);
130
+ };
131
+ pipe.unreliable.onclose = () => {
132
+ cbs.onDataChannelClose(pipe.peerId, false);
133
+ };
134
+ pipes.set(pipe.peerId, pipe);
135
+ }
136
+ }
@@ -0,0 +1,81 @@
1
+ import type { PeerId } from "./protocol.ts";
2
+
3
+ export type Log = {
4
+ source: "webrtc" | "ws" | "local" | "rollback";
5
+ /** absolute frame number since the start of the sim */
6
+ frame_number: number;
7
+ /** relative frame number since the start of the current session */
8
+ match_frame: number | null;
9
+ /** unix timestamp */
10
+ timestamp: number;
11
+ severity: LogSeverity;
12
+ label?: string;
13
+ json?: any;
14
+ // webrtc stuff
15
+ direction?: LogDirection;
16
+ from?: PeerId;
17
+ to?: PeerId;
18
+ reliable?: boolean;
19
+ packet?: {
20
+ size: number;
21
+ bytes: Uint8Array;
22
+ };
23
+ };
24
+
25
+ export type LogOpts = Partial<Log> & {
26
+ source: Log["source"];
27
+ };
28
+
29
+ export type LogDirection = "inbound" | "outbound";
30
+
31
+ export type LogSeverity = "debug" | "log" | "warn" | "error";
32
+
33
+ export type OnLogCallback = (log: Log) => void;
34
+
35
+ export type Logger = {
36
+ onLog: OnLogCallback | null;
37
+
38
+ matchFrame: number;
39
+ frameNumber: number;
40
+
41
+ log(opts: LogOpts): void;
42
+ warn(opts: LogOpts): void;
43
+ error(opts: LogOpts): void;
44
+ }
45
+
46
+ export const logger: Logger = {
47
+ onLog: null as OnLogCallback | null,
48
+
49
+ matchFrame: -1,
50
+ frameNumber: -1,
51
+
52
+ log(opts: LogOpts) {
53
+ this.onLog?.({
54
+ ...opts,
55
+ frame_number: this.frameNumber,
56
+ match_frame: this.matchFrame >= 0 ? this.matchFrame : null,
57
+ timestamp: Date.now(),
58
+ severity: "log",
59
+ });
60
+ },
61
+
62
+ warn(opts: LogOpts) {
63
+ this.onLog?.({
64
+ ...opts,
65
+ frame_number: this.frameNumber,
66
+ match_frame: this.matchFrame >= 0 ? this.matchFrame : null,
67
+ timestamp: Date.now(),
68
+ severity: "warn",
69
+ });
70
+ },
71
+
72
+ error(opts: LogOpts) {
73
+ this.onLog?.({
74
+ ...opts,
75
+ frame_number: this.frameNumber,
76
+ match_frame: this.matchFrame >= 0 ? this.matchFrame : null,
77
+ timestamp: Date.now(),
78
+ severity: "error",
79
+ });
80
+ },
81
+ };
@@ -0,0 +1,14 @@
1
+ // biome-ignore assist/source/organizeImports: organized by hand
2
+ export type { PeerId, BrokerMessage, PeerMessage } from "./protocol.ts";
3
+ export type {
4
+ Log,
5
+ LogOpts,
6
+ LogDirection,
7
+ LogSeverity,
8
+ OnLogCallback,
9
+ } from "./logs.ts";
10
+ export type { WebRtcPipe } from "./transport.ts";
11
+ export type { RoomEvents } from "./broker.ts";
12
+
13
+ export { PacketType } from "./protocol.ts";
14
+ export { logger } from "./logs.ts";
@@ -0,0 +1,56 @@
1
+ export type PeerId = string;
2
+
3
+ // TODO: bring the websocket server from neilsarkar/labs into this repo
4
+
5
+ // Wire format packet types
6
+ export enum PacketType {
7
+ None = 0,
8
+ Inputs = 1,
9
+ }
10
+
11
+ export type BrokerMessage =
12
+ | {
13
+ type: "welcome";
14
+ yourId: PeerId;
15
+ peerIds: PeerId[];
16
+ serverId: string;
17
+ }
18
+ | {
19
+ type: "message:json";
20
+ peerId: PeerId;
21
+ message: PeerMessage;
22
+ }
23
+ | {
24
+ type: "message:string";
25
+ peerId: PeerId;
26
+ message: string;
27
+ }
28
+ | {
29
+ type: "message:buffer";
30
+ peerId: PeerId;
31
+ message: ArrayBuffer;
32
+ }
33
+ | {
34
+ type: "peer:connect";
35
+ peerId: PeerId;
36
+ }
37
+ | {
38
+ type: "peer:disconnect";
39
+ peerId: PeerId;
40
+ };
41
+
42
+ export type PeerMessage =
43
+ | {
44
+ type: "offer";
45
+ target: string;
46
+ payload: string;
47
+ }
48
+ | {
49
+ type: "answer";
50
+ target: string;
51
+ payload: string;
52
+ }
53
+ | {
54
+ type: "message";
55
+ payload: any;
56
+ };