@helixdev/helix-sdk 0.1.1-staging.7 → 0.1.1-staging.9
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/dist/index.d.ts +19 -2
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -1
- package/dist/multiplayer-contract/credential.d.ts +34 -0
- package/dist/multiplayer-contract/credential.js +5 -0
- package/dist/multiplayer-contract/credential.js.map +1 -0
- package/dist/multiplayer-contract/index.d.ts +10 -0
- package/dist/multiplayer-contract/index.js +26 -0
- package/dist/multiplayer-contract/index.js.map +1 -0
- package/dist/multiplayer-contract/messages.d.ts +171 -0
- package/dist/multiplayer-contract/messages.js +57 -0
- package/dist/multiplayer-contract/messages.js.map +1 -0
- package/dist/multiplayer-contract/room.d.ts +92 -0
- package/dist/multiplayer-contract/room.js +17 -0
- package/dist/multiplayer-contract/room.js.map +1 -0
- package/dist/multiplayer-contract/state.d.ts +46 -0
- package/dist/multiplayer-contract/state.js +15 -0
- package/dist/multiplayer-contract/state.js.map +1 -0
- package/dist/multiplayer.d.ts +96 -0
- package/dist/multiplayer.js +247 -0
- package/dist/multiplayer.js.map +1 -0
- package/dist/protocol.d.ts +11 -1
- package/dist/protocol.js +3 -2
- package/dist/protocol.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +14 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type Vec3 = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
z: number;
|
|
5
|
+
};
|
|
6
|
+
/** Pinned units. Linear in meters / meters-per-second; every angle is degrees (see the *Deg suffixes). */
|
|
7
|
+
export declare const UNITS: {
|
|
8
|
+
readonly position: "meters";
|
|
9
|
+
readonly velocity: "meters/second";
|
|
10
|
+
readonly angles: "degrees";
|
|
11
|
+
};
|
|
12
|
+
/** Stable per-player identity (set at join from the room credential; not part of the per-tick churn). */
|
|
13
|
+
export interface PlayerIdentity {
|
|
14
|
+
/** Opaque per-connection room playerKey (= the state.players map key). NOT the credential `sub` — that stays server-side (§4). */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Sanitized display name for nameplates (sanitized before it enters room state — see plan H4). */
|
|
17
|
+
displayName: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The per-tick replicated kinematic + animation parameters for one player. The room holds the
|
|
21
|
+
* authoritative copy (a @colyseus/schema mirror of these fields); the NetworkDriver maps each field
|
|
22
|
+
* onto the named character blackboard param. FIRST DRAFT — the exact injected set is confirmed by
|
|
23
|
+
* spike S2 (engine headless-replica write-path); changing the field set bumps CONTRACT_VERSION.
|
|
24
|
+
*/
|
|
25
|
+
export interface PlayerReplicaState {
|
|
26
|
+
/** World feet position, METERS. Interpolated by the remote replica. */
|
|
27
|
+
position: Vec3;
|
|
28
|
+
/** Body facing, DEGREES. The adapter converts to radians for body.setFacingYaw. */
|
|
29
|
+
facingYawDeg: number;
|
|
30
|
+
/** Horizontal planar speed, M/S. → blackboard 'speed' (blend-space input). */
|
|
31
|
+
speed: number;
|
|
32
|
+
/** Signed movement direction relative to facing, DEGREES (−180..180). → blackboard 'direction'. */
|
|
33
|
+
moveDirectionDeg: number;
|
|
34
|
+
/** Vertical velocity, M/S (+up). → blackboard 'verticalVelocity' (jump/fall states). */
|
|
35
|
+
verticalVelocity: number;
|
|
36
|
+
/** Ground contact. → blackboard 'isGrounded' (grounded/air transitions). */
|
|
37
|
+
grounded: boolean;
|
|
38
|
+
/** Crouch stance. → routes the locomotion anim graph to its crouched states. */
|
|
39
|
+
crouched: boolean;
|
|
40
|
+
/** Aim yaw, DEGREES (world). → blackboard 'cameraYaw' (aim-additive source). */
|
|
41
|
+
aimYawDeg: number;
|
|
42
|
+
/** Aim pitch, DEGREES. → blackboard 'cameraPitch' (aim-additive source). */
|
|
43
|
+
aimPitchDeg: number;
|
|
44
|
+
/** Ability ids currently active (e.g. ['locomotion','fly']). Drives remote ability activation. */
|
|
45
|
+
activeAbilities: string[];
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Replicated player state — the parameters the room broadcasts and the engine's NetworkDriver feeds
|
|
2
|
+
// into the character blackboard. We replicate PARAMETERS, never bone transforms: animation runs fully
|
|
3
|
+
// client-side off these values (see character-architecture §3).
|
|
4
|
+
//
|
|
5
|
+
// ALL ANGLES ARE DEGREES — every angular field is suffixed *Deg. One unit on the wire, so there is no
|
|
6
|
+
// radians/degrees ambiguity to forget. The engine mixes units internally (the body is radians, the
|
|
7
|
+
// animation params are degrees); the engine adapter owns the single deg↔rad conversion at the body
|
|
8
|
+
// boundary (body.setFacingYaw) — the contract itself stays all-degrees.
|
|
9
|
+
/** Pinned units. Linear in meters / meters-per-second; every angle is degrees (see the *Deg suffixes). */
|
|
10
|
+
export const UNITS = {
|
|
11
|
+
position: 'meters',
|
|
12
|
+
velocity: 'meters/second',
|
|
13
|
+
angles: 'degrees',
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/multiplayer-contract/state.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,sGAAsG;AACtG,gEAAgE;AAChE,EAAE;AACF,sGAAsG;AACtG,mGAAmG;AACnG,mGAAmG;AACnG,wEAAwE;AAIxE,0GAA0G;AAC1G,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,eAAe;IACzB,MAAM,EAAE,SAAS;CACT,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type StateMessage, type RoomState, type PlayerState, type EntityState, type RoomVarValue, type Vec3 } from './multiplayer-contract';
|
|
2
|
+
export type ReplicaInput = Omit<StateMessage, 'seq'>;
|
|
3
|
+
/** Options for joinRoom — only needed where the API base can't be derived from the token (local dev/tests). */
|
|
4
|
+
export interface JoinRoomOptions {
|
|
5
|
+
/** Override the platform API base URL. Default: the world_session token's `iss`, else configure({ apiBaseUrl }). */
|
|
6
|
+
apiBaseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
/** The curated live-room surface handed to world authors. Colyseus's API, re-exposed under Helix.*. */
|
|
9
|
+
export interface HelixRoom {
|
|
10
|
+
readonly roomId: string;
|
|
11
|
+
readonly sessionId: string;
|
|
12
|
+
/** Authoritative shared state (a @colyseus/schema instance shaped like the contract RoomState). */
|
|
13
|
+
readonly state: RoomState;
|
|
14
|
+
/** Fires after every applied patch from the server. */
|
|
15
|
+
onStateChange(cb: (state: RoomState) => void): void;
|
|
16
|
+
/** Subscribe to a collection's adds (Tier-2 seam: players today, entities reserved). Fires for existing entries too. */
|
|
17
|
+
onAdd(collection: 'players', cb: (player: PlayerState, id: string) => void): void;
|
|
18
|
+
onAdd(collection: 'entities', cb: (entity: EntityState, id: string) => void): void;
|
|
19
|
+
onRemove(collection: 'players', cb: (player: PlayerState, id: string) => void): void;
|
|
20
|
+
onRemove(collection: 'entities', cb: (entity: EntityState, id: string) => void): void;
|
|
21
|
+
/** A world-authored server→client message (the room broadcasts these for declared events). */
|
|
22
|
+
onMessage<Payload = unknown>(type: string, cb: (payload: Payload) => void): void;
|
|
23
|
+
/** Per-tick predicted state. Coalesced + flushed at MESSAGE_RATE.stateHz, seq-tagged (D3/D4). */
|
|
24
|
+
sendState(input: ReplicaInput): void;
|
|
25
|
+
/** Activate/deactivate a built-in ability (sent immediately; server enforces the rate ceiling). */
|
|
26
|
+
sendAbility(ability: string, active: boolean): void;
|
|
27
|
+
/** A world-authored declared action (the generic extensibility channel). */
|
|
28
|
+
sendAction(name: string, args?: Record<string, RoomVarValue>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Upload an owner-authoritative entity's client-simulated state (Tier 2 Phase 4.6c). Call only for an entity
|
|
31
|
+
* whose `controller` is this client (state.entities[id].controller === sessionId); the room rejects others.
|
|
32
|
+
* Sent immediately, per-entity seq-tagged; the room gates the position against the kind's maxSpeed + clamps vars.
|
|
33
|
+
*/
|
|
34
|
+
uploadEntity(entityId: string, input: {
|
|
35
|
+
position: Vec3;
|
|
36
|
+
vars?: Record<string, RoomVarValue>;
|
|
37
|
+
}): void;
|
|
38
|
+
/**
|
|
39
|
+
* Upload MANY owner-authoritative entities in ONE message (Tier 2 Phase 4.10). Prefer this over per-entity
|
|
40
|
+
* uploadEntity when hosting multiple entities: the per-connection rate cap counts messages, so N single uploads
|
|
41
|
+
* spend N tokens/tick and starve, while one batch spends one. Position is sent as a compact [x,y,z] tuple; each
|
|
42
|
+
* entry is gated/clamped server-side exactly like uploadEntity. Pass only entities this client controls.
|
|
43
|
+
*/
|
|
44
|
+
uploadEntities(entities: ReadonlyArray<{
|
|
45
|
+
id: string;
|
|
46
|
+
position: Vec3;
|
|
47
|
+
vars?: Record<string, RoomVarValue>;
|
|
48
|
+
epoch?: number;
|
|
49
|
+
}>): void;
|
|
50
|
+
/** Sent-but-unacknowledged states — the reconciliation tail the engine NetworkDriver replays (D4). */
|
|
51
|
+
pendingInputs(): readonly StateMessage[];
|
|
52
|
+
/** Prune the reconciliation buffer once the server confirms it processed up to `seq` (D4). */
|
|
53
|
+
acknowledge(seq: number): void;
|
|
54
|
+
/** Connection dropped; the client is auto-reconnecting (built into @colyseus/sdk). */
|
|
55
|
+
onDrop(cb: () => void): void;
|
|
56
|
+
/** Auto-reconnection succeeded. */
|
|
57
|
+
onReconnect(cb: () => void): void;
|
|
58
|
+
/** Left for good (kicked, room disposed, or after we leave) — `code` is the close code. */
|
|
59
|
+
onLeave(cb: (code: number) => void): void;
|
|
60
|
+
/** Leave intentionally (no reconnection grace held server-side). */
|
|
61
|
+
leave(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
export interface MultiplayerHost {
|
|
64
|
+
isInitialized(): boolean;
|
|
65
|
+
getToken(): string | null;
|
|
66
|
+
getWorldId(): string | null;
|
|
67
|
+
}
|
|
68
|
+
export declare class HelixMultiplayer {
|
|
69
|
+
private readonly host;
|
|
70
|
+
private apiBaseUrl;
|
|
71
|
+
private room;
|
|
72
|
+
private callbacks;
|
|
73
|
+
private seq;
|
|
74
|
+
private entitySeq;
|
|
75
|
+
private entityBatchSeq;
|
|
76
|
+
private pendingInput;
|
|
77
|
+
private flushTimer;
|
|
78
|
+
private inputBuffer;
|
|
79
|
+
private worldId;
|
|
80
|
+
constructor(host: MultiplayerHost);
|
|
81
|
+
configure(options: {
|
|
82
|
+
apiBaseUrl: string;
|
|
83
|
+
}): void;
|
|
84
|
+
joinRoom(worldId?: string, options?: JoinRoomOptions): Promise<HelixRoom>;
|
|
85
|
+
private requestJoin;
|
|
86
|
+
private makeHandle;
|
|
87
|
+
private startFlush;
|
|
88
|
+
private flush;
|
|
89
|
+
private persistReconnect;
|
|
90
|
+
private readReconnect;
|
|
91
|
+
private reconnectWsUrl;
|
|
92
|
+
private clearReconnect;
|
|
93
|
+
private tryReconnect;
|
|
94
|
+
private leave;
|
|
95
|
+
private teardown;
|
|
96
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// Helix.multiplayer — the client half of HELIX multiplayer (plan pillar D). Wraps the official
|
|
2
|
+
// Colyseus browser client (@colyseus/sdk, schema v4 — the renamed colyseus.js, paired with the 0.17
|
|
3
|
+
// room server) so world authors never import it directly. Flow: POST the platform
|
|
4
|
+
// /api/v1/instant-worlds/:worldId/join with the world_session token → { wsUrl, roomName, credential }
|
|
5
|
+
// → Client.joinOrCreate(token) → a curated HelixRoom handle. The engine NetworkDriver consumes the
|
|
6
|
+
// room state; this module owns the connect handshake, the throttled seq-tagged send path (D3), the
|
|
7
|
+
// reconciliation buffer (D4), and teardown/reconnection wiring (D5).
|
|
8
|
+
import { ClientMessageType, MESSAGE_RATE, } from './multiplayer-contract';
|
|
9
|
+
// ~12s of inputs at 10Hz — generous reconciliation window; bounded so a missing server ack can't grow it forever.
|
|
10
|
+
const INPUT_BUFFER_MAX = 120;
|
|
11
|
+
// sessionStorage key (per world) for the colyseus reconnectionToken + its wsUrl. sessionStorage is per-tab and
|
|
12
|
+
// SURVIVES A RELOAD but not a tab close — exactly the lifetime we want: a reload resumes the held seat; a new
|
|
13
|
+
// tab / post-close start does a fresh join. This is what makes a reload RECONNECT (bypassing the room's
|
|
14
|
+
// onAuth one-seat-per-account gate, which would otherwise reject the reload's fresh join during the grace
|
|
15
|
+
// window) instead of falling back to single-player.
|
|
16
|
+
const RECONNECT_KEY = (worldId) => `helix:mp:reconnect:${worldId}`;
|
|
17
|
+
export class HelixMultiplayer {
|
|
18
|
+
host;
|
|
19
|
+
apiBaseUrl = null;
|
|
20
|
+
room = null;
|
|
21
|
+
callbacks = null;
|
|
22
|
+
seq = 0;
|
|
23
|
+
entitySeq = {}; // per-entity monotonic upload seq (4.6c owner-entity channel)
|
|
24
|
+
entityBatchSeq = 0; // per-connection monotonic batch seq (4.10 owner-entity batch channel)
|
|
25
|
+
pendingInput = null;
|
|
26
|
+
flushTimer = null;
|
|
27
|
+
inputBuffer = [];
|
|
28
|
+
worldId = null; // the joined world (the reconnect-token storage key)
|
|
29
|
+
constructor(host) {
|
|
30
|
+
this.host = host;
|
|
31
|
+
}
|
|
32
|
+
// Pin the platform API base once (local dev / staging where the token issuer isn't the live host).
|
|
33
|
+
configure(options) {
|
|
34
|
+
this.apiBaseUrl = options.apiBaseUrl.replace(/\/$/, '');
|
|
35
|
+
}
|
|
36
|
+
// Join (or create) this world's room. worldId defaults to the current world. Requires login: a guest
|
|
37
|
+
// has no world_session and can't reach /join — multiplayer is logged-in only (single-player for guests).
|
|
38
|
+
async joinRoom(worldId, options = {}) {
|
|
39
|
+
if (!this.host.isInitialized())
|
|
40
|
+
throw new Error('Helix: call Helix.init() before Helix.multiplayer.joinRoom()');
|
|
41
|
+
if (this.room)
|
|
42
|
+
throw new Error('Helix.multiplayer: already in a room — call leave() first');
|
|
43
|
+
const token = this.host.getToken();
|
|
44
|
+
if (!token)
|
|
45
|
+
throw new Error('Helix.multiplayer: multiplayer requires login (no session) — call Helix.auth.requestLogin() first');
|
|
46
|
+
const id = worldId ?? this.host.getWorldId();
|
|
47
|
+
if (!id)
|
|
48
|
+
throw new Error('Helix.multiplayer: no world id — pass joinRoom(worldId) when running outside a HELIX shell');
|
|
49
|
+
const apiBase = options.apiBaseUrl?.replace(/\/$/, '') ?? this.apiBaseUrl ?? issuerOf(token);
|
|
50
|
+
if (!apiBase)
|
|
51
|
+
throw new Error('Helix.multiplayer: cannot resolve the API base URL — call Helix.multiplayer.configure({ apiBaseUrl })');
|
|
52
|
+
const colyseus = (await import('@colyseus/sdk'));
|
|
53
|
+
// RESUME FIRST: on a reload, a seat persisted in sessionStorage is still held in the server's grace window —
|
|
54
|
+
// reconnect to it (resumes the same seat + its vars, bypassing onAuth) instead of a fresh join that onAuth
|
|
55
|
+
// would reject as a duplicate. On any failure (grace expired / clean despawn / new tab) fall back to /join.
|
|
56
|
+
let room = await this.tryReconnect(colyseus, id);
|
|
57
|
+
if (!room) {
|
|
58
|
+
const reservation = await this.requestJoin(apiBase, id, token);
|
|
59
|
+
const client = new colyseus.Client(reservation.wsUrl);
|
|
60
|
+
// buildId is the matchmaking filter (room filterBy(['buildId'])) — it groups players per active build.
|
|
61
|
+
// The room re-validates it against the verified credential in onAuth, so a spoofed value can't cross builds.
|
|
62
|
+
room = await client.joinOrCreate(reservation.roomName, { token: reservation.credential, buildId: reservation.buildId });
|
|
63
|
+
this.persistReconnect(id, reservation.wsUrl, room.reconnectionToken);
|
|
64
|
+
}
|
|
65
|
+
this.worldId = id;
|
|
66
|
+
this.room = room;
|
|
67
|
+
this.callbacks = colyseus.getStateCallbacks(room);
|
|
68
|
+
this.startFlush();
|
|
69
|
+
// Keep the persisted token fresh as colyseus rotates it (e.g. after an in-page auto-reconnect).
|
|
70
|
+
room.onReconnect(() => this.persistReconnect(id, this.reconnectWsUrl(id), room.reconnectionToken));
|
|
71
|
+
room.onLeave(() => this.teardown());
|
|
72
|
+
return this.makeHandle(room);
|
|
73
|
+
}
|
|
74
|
+
async requestJoin(apiBase, worldId, token) {
|
|
75
|
+
const res = await fetch(`${apiBase}/api/v1/instant-worlds/${encodeURIComponent(worldId)}/join`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { authorization: `Bearer ${token}` },
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
const detail = await res.json().catch(() => null);
|
|
81
|
+
const message = (detail && typeof detail.message === 'string' && detail.message) || `HTTP ${res.status}`;
|
|
82
|
+
throw new Error(`Helix.multiplayer: join failed — ${message}`);
|
|
83
|
+
}
|
|
84
|
+
return (await res.json());
|
|
85
|
+
}
|
|
86
|
+
makeHandle(room) {
|
|
87
|
+
const collection = (name) => {
|
|
88
|
+
if (!this.callbacks)
|
|
89
|
+
throw new Error('Helix.multiplayer: room not ready');
|
|
90
|
+
return this.callbacks(room.state)[name];
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
roomId: room.roomId,
|
|
94
|
+
sessionId: room.sessionId,
|
|
95
|
+
get state() {
|
|
96
|
+
return room.state;
|
|
97
|
+
},
|
|
98
|
+
onStateChange: (cb) => void room.onStateChange((s) => cb(s)),
|
|
99
|
+
onAdd: (name, cb) => void collection(name).onAdd((v, k) => cb(v, k), true),
|
|
100
|
+
onRemove: (name, cb) => void collection(name).onRemove((v, k) => cb(v, k)),
|
|
101
|
+
onMessage: (type, cb) => void room.onMessage(type, (p) => cb(p)),
|
|
102
|
+
sendState: (input) => {
|
|
103
|
+
this.pendingInput = input;
|
|
104
|
+
},
|
|
105
|
+
sendAbility: (ability, active) => room.send(ClientMessageType.Ability, { ability, active }),
|
|
106
|
+
sendAction: (name, args) => room.send(ClientMessageType.Action, { name, args }),
|
|
107
|
+
uploadEntity: (entityId, input) => {
|
|
108
|
+
const seq = (this.entitySeq[entityId] = (this.entitySeq[entityId] ?? 0) + 1);
|
|
109
|
+
room.send(ClientMessageType.EntityState, { entity: entityId, seq, position: input.position, vars: input.vars });
|
|
110
|
+
},
|
|
111
|
+
uploadEntities: (entities) => {
|
|
112
|
+
if (entities.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
const seq = ++this.entityBatchSeq;
|
|
115
|
+
const states = entities.map((e) => ({
|
|
116
|
+
e: e.id,
|
|
117
|
+
p: [e.position.x, e.position.y, e.position.z],
|
|
118
|
+
...(e.vars !== undefined ? { v: e.vars } : {}),
|
|
119
|
+
...(e.epoch !== undefined ? { ep: e.epoch } : {}),
|
|
120
|
+
}));
|
|
121
|
+
room.send(ClientMessageType.EntityStateBatch, { seq, states });
|
|
122
|
+
},
|
|
123
|
+
pendingInputs: () => this.inputBuffer.slice(),
|
|
124
|
+
acknowledge: (seq) => {
|
|
125
|
+
this.inputBuffer = this.inputBuffer.filter((m) => m.seq > seq);
|
|
126
|
+
},
|
|
127
|
+
onDrop: (cb) => void room.onDrop(() => cb()),
|
|
128
|
+
onReconnect: (cb) => void room.onReconnect(() => cb()),
|
|
129
|
+
onLeave: (cb) => void room.onLeave((code) => cb(code)),
|
|
130
|
+
leave: () => this.leave(),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// D3: coalesce per-frame sendState calls and flush the latest at most stateHz times/second, tagging a
|
|
134
|
+
// monotonic seq. D4: buffer each sent state (bounded) so the engine can replay the unacknowledged tail.
|
|
135
|
+
startFlush() {
|
|
136
|
+
const periodMs = Math.round(1000 / MESSAGE_RATE.stateHz);
|
|
137
|
+
this.flushTimer = setInterval(() => this.flush(), periodMs);
|
|
138
|
+
}
|
|
139
|
+
flush() {
|
|
140
|
+
if (!this.pendingInput || !this.room)
|
|
141
|
+
return;
|
|
142
|
+
const message = { ...this.pendingInput, seq: ++this.seq };
|
|
143
|
+
this.pendingInput = null;
|
|
144
|
+
this.room.send(ClientMessageType.State, message);
|
|
145
|
+
this.inputBuffer.push(message);
|
|
146
|
+
if (this.inputBuffer.length > INPUT_BUFFER_MAX)
|
|
147
|
+
this.inputBuffer.shift();
|
|
148
|
+
}
|
|
149
|
+
// D5 RECONNECTION (sessionStorage-backed): persist / read / clear the colyseus reconnectionToken + its wsUrl
|
|
150
|
+
// per world. sessionStorage is per-tab and survives a RELOAD but not a close — so a reload resumes the held
|
|
151
|
+
// seat (below) while a new tab does a fresh join. We deliberately do NOT consent-leave on tab unload anymore:
|
|
152
|
+
// letting the socket drop holds the seat in the server's grace window so a reload can RECONNECT to it (and
|
|
153
|
+
// keep its vars), instead of a fresh join that the room's one-seat-per-account onAuth gate would reject. A
|
|
154
|
+
// genuine close just lets the grace window expire (the seat despawns after RECONNECT_GRACE_SEC).
|
|
155
|
+
persistReconnect(worldId, wsUrl, reconnectionToken) {
|
|
156
|
+
if (typeof sessionStorage === 'undefined' || !wsUrl || !reconnectionToken)
|
|
157
|
+
return;
|
|
158
|
+
try {
|
|
159
|
+
sessionStorage.setItem(RECONNECT_KEY(worldId), JSON.stringify({ wsUrl, reconnectionToken }));
|
|
160
|
+
}
|
|
161
|
+
catch { /* storage unavailable */ }
|
|
162
|
+
}
|
|
163
|
+
readReconnect(worldId) {
|
|
164
|
+
if (typeof sessionStorage === 'undefined')
|
|
165
|
+
return null;
|
|
166
|
+
try {
|
|
167
|
+
const raw = sessionStorage.getItem(RECONNECT_KEY(worldId));
|
|
168
|
+
const v = raw ? JSON.parse(raw) : null;
|
|
169
|
+
return v?.wsUrl && v.reconnectionToken ? { wsUrl: v.wsUrl, reconnectionToken: v.reconnectionToken } : null;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
reconnectWsUrl(worldId) {
|
|
176
|
+
return this.readReconnect(worldId)?.wsUrl ?? '';
|
|
177
|
+
}
|
|
178
|
+
clearReconnect(worldId) {
|
|
179
|
+
if (typeof sessionStorage === 'undefined')
|
|
180
|
+
return;
|
|
181
|
+
try {
|
|
182
|
+
sessionStorage.removeItem(RECONNECT_KEY(worldId));
|
|
183
|
+
}
|
|
184
|
+
catch { /* ignore */ }
|
|
185
|
+
}
|
|
186
|
+
// Resume a grace-held seat (a reload). Returns the resumed room, or null to fall through to a fresh join
|
|
187
|
+
// (no saved token / grace expired / seat despawned / stale wsUrl — reconnect() rejects and we drop the token).
|
|
188
|
+
async tryReconnect(colyseus, worldId) {
|
|
189
|
+
const saved = this.readReconnect(worldId);
|
|
190
|
+
if (!saved)
|
|
191
|
+
return null;
|
|
192
|
+
try {
|
|
193
|
+
const client = new colyseus.Client(saved.wsUrl);
|
|
194
|
+
const room = await client.reconnect(saved.reconnectionToken);
|
|
195
|
+
this.persistReconnect(worldId, saved.wsUrl, room.reconnectionToken); // the token may rotate on resume
|
|
196
|
+
return room;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
this.clearReconnect(worldId);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Leave for good: consented (frees the seat immediately, no grace) + drop the saved token so we don't try to
|
|
204
|
+
// resume a seat we deliberately left. (A reload does NOT call this — it just unloads.)
|
|
205
|
+
async leave() {
|
|
206
|
+
const room = this.room;
|
|
207
|
+
if (this.worldId)
|
|
208
|
+
this.clearReconnect(this.worldId);
|
|
209
|
+
this.teardown();
|
|
210
|
+
if (room)
|
|
211
|
+
await room.leave(true);
|
|
212
|
+
}
|
|
213
|
+
// Reset in-memory state. Called on a drop too (onLeave) — it does NOT clear the saved reconnect token, so a
|
|
214
|
+
// later reload can still resume the grace-held seat.
|
|
215
|
+
teardown() {
|
|
216
|
+
if (this.flushTimer)
|
|
217
|
+
clearInterval(this.flushTimer);
|
|
218
|
+
this.flushTimer = null;
|
|
219
|
+
this.room = null;
|
|
220
|
+
this.callbacks = null;
|
|
221
|
+
this.pendingInput = null;
|
|
222
|
+
this.inputBuffer = [];
|
|
223
|
+
this.worldId = null;
|
|
224
|
+
this.seq = 0;
|
|
225
|
+
this.entitySeq = {};
|
|
226
|
+
this.entityBatchSeq = 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Read the `iss` claim from a JWT without verifying (the world_session issuer is the platform API
|
|
230
|
+
// origin). Verification isn't this side's job; the room verifies the credential against JWKS. Returns
|
|
231
|
+
// null if there's no usable http(s) issuer — in which case the world must configure({ apiBaseUrl }).
|
|
232
|
+
// NOTE: the new backend does not set `iss` on the world_session yet, so configure()/options is the
|
|
233
|
+
// resolution path until the join lane adds it (tracked in MIGRATION.md, backend step).
|
|
234
|
+
function issuerOf(token) {
|
|
235
|
+
const segment = token.split('.')[1];
|
|
236
|
+
if (!segment)
|
|
237
|
+
return null;
|
|
238
|
+
try {
|
|
239
|
+
const json = atob(segment.replace(/-/g, '+').replace(/_/g, '/'));
|
|
240
|
+
const iss = JSON.parse(json).iss;
|
|
241
|
+
return typeof iss === 'string' && /^https?:\/\//.test(iss) ? iss.replace(/\/$/, '') : null;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=multiplayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multiplayer.js","sourceRoot":"","sources":["../src/multiplayer.ts"],"names":[],"mappings":"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"}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const PROTOCOL_VERSION =
|
|
1
|
+
export declare const PROTOCOL_VERSION = 2;
|
|
2
2
|
export type HelixUser = {
|
|
3
3
|
id: string;
|
|
4
4
|
username: string;
|
|
@@ -15,18 +15,28 @@ export type HelixSession = {
|
|
|
15
15
|
scopes: string[];
|
|
16
16
|
user: HelixUser;
|
|
17
17
|
};
|
|
18
|
+
export type DebugLogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
19
|
+
export type DebugLogEntry = {
|
|
20
|
+
level: DebugLogLevel;
|
|
21
|
+
args: string[];
|
|
22
|
+
t: number;
|
|
23
|
+
};
|
|
18
24
|
export type WorldToShellMessage = {
|
|
19
25
|
type: 'helix:ready';
|
|
20
26
|
protocolVersion: number;
|
|
21
27
|
} | {
|
|
22
28
|
type: 'helix:request-login';
|
|
23
29
|
requestId: string;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'helix:log';
|
|
32
|
+
entry: DebugLogEntry;
|
|
24
33
|
};
|
|
25
34
|
export type ShellToWorldMessage = {
|
|
26
35
|
type: 'helix:init';
|
|
27
36
|
protocolVersion: number;
|
|
28
37
|
world: HelixWorldContext;
|
|
29
38
|
session: HelixSession | null;
|
|
39
|
+
debug?: boolean;
|
|
30
40
|
} | {
|
|
31
41
|
type: 'helix:session';
|
|
32
42
|
session: HelixSession | null;
|
package/dist/protocol.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// postMessage protocol between a world (sandboxed iframe) and the HELIX shell
|
|
2
2
|
// (the play page, or a local `helix dev` shell). This file IS the wire contract
|
|
3
|
-
// — both sides import it; version any breaking change.
|
|
4
|
-
|
|
3
|
+
// — both sides import it; version any breaking change. (v2 is additive: helix:init
|
|
4
|
+
// gained `debug`, and helix:log carries world logs to the shell when debug is on.)
|
|
5
|
+
export const PROTOCOL_VERSION = 2;
|
|
5
6
|
export function isShellMessage(data) {
|
|
6
7
|
return (typeof data === 'object' &&
|
|
7
8
|
data !== null &&
|
package/dist/protocol.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,mFAAmF;AACnF,mFAAmF;AAEnF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AA4DlC,MAAM,UAAU,cAAc,CAAC,IAAa;IAC1C,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,OAAQ,IAA2B,CAAC,IAAI,KAAK,QAAQ;QACrD,CAAE,IAAyB,CAAC,IAAI,KAAK,YAAY;YAC9C,IAAyB,CAAC,IAAI,KAAK,eAAe;YAClD,IAAyB,CAAC,IAAI,KAAK,oBAAoB,CAAC,CAC5D,CAAC;AACJ,CAAC"}
|