@smoregg/sdk 0.4.1 → 0.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 +199 -0
- package/dist/cjs/SmoreHost.cjs +306 -0
- package/dist/cjs/SmoreHost.cjs.map +1 -0
- package/dist/cjs/SmorePlayer.cjs +229 -0
- package/dist/cjs/SmorePlayer.cjs.map +1 -0
- package/dist/cjs/components/IframeGameBridge.cjs +115 -0
- package/dist/cjs/components/IframeGameBridge.cjs.map +1 -0
- package/dist/cjs/context/RoomProvider.cjs +3 -3
- package/dist/cjs/context/RoomProvider.cjs.map +1 -1
- package/dist/cjs/controller.cjs +379 -0
- package/dist/cjs/controller.cjs.map +1 -0
- package/dist/cjs/hooks/useGameHost.cjs +86 -13
- package/dist/cjs/hooks/useGameHost.cjs.map +1 -1
- package/dist/cjs/hooks/useGamePlayer.cjs +60 -4
- package/dist/cjs/hooks/useGamePlayer.cjs.map +1 -1
- package/dist/cjs/iframe/index.cjs +50 -316
- package/dist/cjs/iframe/index.cjs.map +1 -1
- package/dist/cjs/index.cjs +8 -22
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/screen.cjs +526 -0
- package/dist/cjs/screen.cjs.map +1 -0
- package/dist/cjs/testing.cjs +257 -0
- package/dist/cjs/testing.cjs.map +1 -0
- package/dist/cjs/transport/protocol.cjs.map +1 -1
- package/dist/cjs/utils/connectionMonitor.cjs +77 -0
- package/dist/cjs/utils/connectionMonitor.cjs.map +1 -0
- package/dist/cjs/utils/preloadAssets.cjs +66 -0
- package/dist/cjs/utils/preloadAssets.cjs.map +1 -0
- package/dist/cjs/utils/serverTime.cjs +43 -0
- package/dist/cjs/utils/serverTime.cjs.map +1 -0
- package/dist/esm/SmoreHost.js +304 -0
- package/dist/esm/SmoreHost.js.map +1 -0
- package/dist/esm/SmorePlayer.js +227 -0
- package/dist/esm/SmorePlayer.js.map +1 -0
- package/dist/esm/components/IframeGameBridge.js +113 -0
- package/dist/esm/components/IframeGameBridge.js.map +1 -0
- package/dist/esm/context/RoomProvider.js +3 -3
- package/dist/esm/context/RoomProvider.js.map +1 -1
- package/dist/esm/controller.js +376 -0
- package/dist/esm/controller.js.map +1 -0
- package/dist/esm/hooks/useGameHost.js +87 -14
- package/dist/esm/hooks/useGameHost.js.map +1 -1
- package/dist/esm/hooks/useGamePlayer.js +61 -5
- package/dist/esm/hooks/useGamePlayer.js.map +1 -1
- package/dist/esm/iframe/index.js +51 -314
- package/dist/esm/iframe/index.js.map +1 -1
- package/dist/esm/index.js +3 -8
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/screen.js +523 -0
- package/dist/esm/screen.js.map +1 -0
- package/dist/esm/testing.js +254 -0
- package/dist/esm/testing.js.map +1 -0
- package/dist/esm/transport/protocol.js.map +1 -1
- package/dist/esm/utils/connectionMonitor.js +75 -0
- package/dist/esm/utils/connectionMonitor.js.map +1 -0
- package/dist/esm/utils/preloadAssets.js +63 -0
- package/dist/esm/utils/preloadAssets.js.map +1 -0
- package/dist/esm/utils/serverTime.js +41 -0
- package/dist/esm/utils/serverTime.js.map +1 -0
- package/dist/types/SmoreHost.d.ts +187 -0
- package/dist/types/SmoreHost.d.ts.map +1 -0
- package/dist/types/SmorePlayer.d.ts +146 -0
- package/dist/types/SmorePlayer.d.ts.map +1 -0
- package/dist/types/components/IframeGameBridge.d.ts +2 -2
- package/dist/types/components/IframeGameBridge.d.ts.map +1 -1
- package/dist/types/components/index.d.ts +2 -4
- package/dist/types/components/index.d.ts.map +1 -1
- package/dist/types/context/RoomProvider.d.ts +3 -3
- package/dist/types/context/RoomProvider.d.ts.map +1 -1
- package/dist/types/controller.d.ts +78 -0
- package/dist/types/controller.d.ts.map +1 -0
- package/dist/types/hooks/useGameHost.d.ts +33 -7
- package/dist/types/hooks/useGameHost.d.ts.map +1 -1
- package/dist/types/hooks/useGamePlayer.d.ts +29 -3
- package/dist/types/hooks/useGamePlayer.d.ts.map +1 -1
- package/dist/types/iframe/index.d.ts +10 -10
- package/dist/types/iframe/index.d.ts.map +1 -1
- package/dist/types/iframe/vanilla.d.ts +12 -4
- package/dist/types/iframe/vanilla.d.ts.map +1 -1
- package/dist/types/index.d.ts +36 -21
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/screen.d.ts +79 -0
- package/dist/types/screen.d.ts.map +1 -0
- package/dist/types/testing.d.ts +61 -0
- package/dist/types/testing.d.ts.map +1 -0
- package/dist/types/transport/protocol.d.ts +2 -5
- package/dist/types/transport/protocol.d.ts.map +1 -1
- package/dist/types/types.d.ts +869 -4
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/connectionMonitor.d.ts +57 -0
- package/dist/types/utils/connectionMonitor.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +7 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/dist/types/utils/preloadAssets.d.ts +29 -0
- package/dist/types/utils/preloadAssets.d.ts.map +1 -0
- package/dist/types/utils/serverTime.d.ts +28 -0
- package/dist/types/utils/serverTime.d.ts.map +1 -0
- package/dist/umd/smore-sdk-iframe.umd.js +54 -317
- package/dist/umd/smore-sdk-iframe.umd.js.map +1 -1
- package/dist/umd/smore-sdk-iframe.umd.min.js +1 -1
- package/dist/umd/smore-sdk-iframe.umd.min.js.map +1 -1
- package/dist/umd/smore-sdk-vanilla.umd.js +1166 -117
- package/dist/umd/smore-sdk-vanilla.umd.js.map +1 -1
- package/dist/umd/smore-sdk-vanilla.umd.min.js +1 -1
- package/dist/umd/smore-sdk-vanilla.umd.min.js.map +1 -1
- package/dist/umd/smore-sdk.umd.js +1139 -602
- package/dist/umd/smore-sdk.umd.js.map +1 -1
- package/dist/umd/smore-sdk.umd.min.js +1 -1
- package/dist/umd/smore-sdk.umd.min.js.map +1 -1
- package/package.json +1 -26
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# @smoregg/sdk
|
|
2
|
+
|
|
3
|
+
S'MORE Game SDK - Build party games with React for the S'MORE platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @smoregg/sdk
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @smoregg/sdk
|
|
11
|
+
# or
|
|
12
|
+
yarn add @smoregg/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### Screen Game (TV/Display)
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { SmoreScreen } from '@smoregg/sdk';
|
|
21
|
+
|
|
22
|
+
function MyGame() {
|
|
23
|
+
const screen = new SmoreScreen({
|
|
24
|
+
gameId: 'my-game',
|
|
25
|
+
listeners: {
|
|
26
|
+
tap: (playerIndex, data) => {
|
|
27
|
+
console.log(`Player ${playerIndex} tapped!`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return <div>Game UI</div>;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Controller App (Mobile)
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { SmoreController } from '@smoregg/sdk';
|
|
40
|
+
|
|
41
|
+
function MyController() {
|
|
42
|
+
const controller = new SmoreController({
|
|
43
|
+
gameId: 'my-game',
|
|
44
|
+
listeners: {
|
|
45
|
+
'state-update': (state) => {
|
|
46
|
+
console.log('Game state:', state);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<button onClick={() => controller.send('tap', {})}>
|
|
53
|
+
TAP
|
|
54
|
+
</button>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
- **Screen/Controller Communication**: Simple event-based messaging
|
|
62
|
+
- **React Hooks**: `useGameHost`, `useGamePlayer` for React apps
|
|
63
|
+
- **TypeScript Support**: Full type definitions included
|
|
64
|
+
- **Testing Utilities**: Mock Screen and Controller for unit testing
|
|
65
|
+
- **Multiple Formats**: ESM, CJS, and UMD builds
|
|
66
|
+
- **Zero Dependencies**: Peer dependencies only (React, Socket.IO)
|
|
67
|
+
|
|
68
|
+
## API Reference
|
|
69
|
+
|
|
70
|
+
### SmoreScreen
|
|
71
|
+
|
|
72
|
+
Screen-side game controller.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const screen = new SmoreScreen({
|
|
76
|
+
gameId: string;
|
|
77
|
+
listeners?: Record<string, (playerIndex: number, data: any) => void>;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Methods
|
|
81
|
+
screen.broadcast(event: string, data: any): void
|
|
82
|
+
screen.sendToPlayer(playerIndex: number, event: string, data: any): void
|
|
83
|
+
screen.on(event: string, callback: Function): void
|
|
84
|
+
screen.off(event: string, callback: Function): void
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### SmoreController
|
|
88
|
+
|
|
89
|
+
Controller-side app.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const controller = new SmoreController({
|
|
93
|
+
gameId: string;
|
|
94
|
+
listeners?: Record<string, (data: any) => void>;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Methods
|
|
98
|
+
controller.send(event: string, data: any): void
|
|
99
|
+
controller.on(event: string, callback: Function): void
|
|
100
|
+
controller.off(event: string, callback: Function): void
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### React Hooks
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { useGameHost, useGamePlayer } from '@smoregg/sdk';
|
|
107
|
+
|
|
108
|
+
// Screen
|
|
109
|
+
const { room, broadcast, sendToPlayer } = useGameHost({
|
|
110
|
+
gameId: 'my-game',
|
|
111
|
+
onInput: {
|
|
112
|
+
tap: (playerIndex, data) => { /* ... */ }
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Controller
|
|
117
|
+
const { emit, room } = useGamePlayer({
|
|
118
|
+
gameId: 'my-game',
|
|
119
|
+
listeners: {
|
|
120
|
+
'state-update': (state) => { /* ... */ }
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Backward Compatibility
|
|
126
|
+
|
|
127
|
+
The old `SmoreHost` and `SmorePlayer` names are still available as aliases:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// These still work (deprecated)
|
|
131
|
+
import { SmoreHost, SmorePlayer } from '@smoregg/sdk';
|
|
132
|
+
|
|
133
|
+
// Prefer the new names
|
|
134
|
+
import { SmoreScreen, SmoreController } from '@smoregg/sdk';
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Testing
|
|
138
|
+
|
|
139
|
+
The SDK provides comprehensive testing utilities for unit testing your game logic:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { createMockScreen, createMockController } from '@smoregg/sdk';
|
|
143
|
+
|
|
144
|
+
// Create mock screen with players
|
|
145
|
+
const screen = createMockScreen<MyEvents>({
|
|
146
|
+
controllers: [
|
|
147
|
+
{ playerIndex: 0, nickname: 'Player 1', connected: true },
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Simulate player input
|
|
152
|
+
screen.simulateEvent(0, 'tap', { x: 100, y: 200 });
|
|
153
|
+
|
|
154
|
+
// Check what was broadcast
|
|
155
|
+
expect(screen.getBroadcasts()).toContainEqual({
|
|
156
|
+
event: 'score-update',
|
|
157
|
+
data: { scores: { 0: 10 } },
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
See [docs/testing.md](./docs/testing.md) for the full testing guide.
|
|
162
|
+
|
|
163
|
+
## Types
|
|
164
|
+
|
|
165
|
+
All types are exported from the main package:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import type {
|
|
169
|
+
Player,
|
|
170
|
+
PlayerDTO,
|
|
171
|
+
CharacterAppearance,
|
|
172
|
+
GameMetadata,
|
|
173
|
+
GameState,
|
|
174
|
+
InputCallback
|
|
175
|
+
} from '@smoregg/sdk';
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Building
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Development
|
|
182
|
+
pnpm typecheck
|
|
183
|
+
|
|
184
|
+
# Build all formats (ESM, CJS, UMD)
|
|
185
|
+
pnpm build
|
|
186
|
+
|
|
187
|
+
# Clean
|
|
188
|
+
pnpm clean
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Publishing
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
pnpm publish:npm
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT (C) S'MORE Team
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var DirectTransport = require('./transport/DirectTransport.cjs');
|
|
4
|
+
var PostMessageTransport = require('./transport/PostMessageTransport.cjs');
|
|
5
|
+
var protocol = require('./transport/protocol.cjs');
|
|
6
|
+
|
|
7
|
+
const SYSTEM_PREFIX = "smore:";
|
|
8
|
+
const SYSTEM_EVENTS = {
|
|
9
|
+
PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,
|
|
10
|
+
PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`,
|
|
11
|
+
GAME_OVER: `${SYSTEM_PREFIX}game-over`
|
|
12
|
+
};
|
|
13
|
+
const EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;
|
|
14
|
+
function validateEventName(event) {
|
|
15
|
+
if (!EVENT_NAME_REGEX.test(event)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`[SmoreHost] Invalid event name "${event}". Event names must:
|
|
18
|
+
- Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)
|
|
19
|
+
- Start and end with a letter (no leading/trailing - or _)`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class SmoreHost {
|
|
24
|
+
transport = null;
|
|
25
|
+
config;
|
|
26
|
+
_players = [];
|
|
27
|
+
_roomCode = "";
|
|
28
|
+
_leaderIndex = -1;
|
|
29
|
+
_isReady = false;
|
|
30
|
+
_isDestroyed = false;
|
|
31
|
+
boundMessageHandler = null;
|
|
32
|
+
registeredHandlers = [];
|
|
33
|
+
constructor(config = {}) {
|
|
34
|
+
this.config = config;
|
|
35
|
+
if (config.listeners) {
|
|
36
|
+
for (const event of Object.keys(config.listeners)) {
|
|
37
|
+
validateEventName(event);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (config.socket) {
|
|
41
|
+
this.initBundled(config);
|
|
42
|
+
} else {
|
|
43
|
+
this.initIframe(config);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Initialization
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
initBundled(config) {
|
|
50
|
+
if (!config.socket) {
|
|
51
|
+
throw new Error("[SmoreHost] socket is required for bundled games");
|
|
52
|
+
}
|
|
53
|
+
this.transport = new DirectTransport.DirectTransport(config.socket);
|
|
54
|
+
this._roomCode = config.roomCode || "";
|
|
55
|
+
this._players = config.players || [];
|
|
56
|
+
this._leaderIndex = config.leaderIndex ?? -1;
|
|
57
|
+
this.setupEventHandlers();
|
|
58
|
+
this._isReady = true;
|
|
59
|
+
this.config.onReady?.();
|
|
60
|
+
}
|
|
61
|
+
initIframe(config) {
|
|
62
|
+
const parentOrigin = config.parentOrigin || "*";
|
|
63
|
+
window.parent.postMessage({ type: "smore:ready" }, parentOrigin);
|
|
64
|
+
this.boundMessageHandler = (e) => {
|
|
65
|
+
if (parentOrigin !== "*" && e.origin !== parentOrigin) return;
|
|
66
|
+
const msg = e.data;
|
|
67
|
+
if (!protocol.isSmoreMessage(msg)) return;
|
|
68
|
+
if (msg.type === "smore:init") {
|
|
69
|
+
const initData = msg.payload;
|
|
70
|
+
if (initData.side !== "host") {
|
|
71
|
+
console.error("[SmoreHost] Received init for wrong side:", initData.side);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.transport = new PostMessageTransport.PostMessageTransport(parentOrigin);
|
|
75
|
+
this._roomCode = initData.roomCode;
|
|
76
|
+
this._players = this.mapPlayersFromInit(initData.players);
|
|
77
|
+
this._leaderIndex = this.findLeaderIndex(initData.players, initData.leaderId);
|
|
78
|
+
this.setupEventHandlers();
|
|
79
|
+
this._isReady = true;
|
|
80
|
+
this.config.onReady?.();
|
|
81
|
+
} else if (msg.type === "smore:update") {
|
|
82
|
+
const updateData = msg.payload;
|
|
83
|
+
if (updateData.players) {
|
|
84
|
+
this._players = this.mapPlayersFromInit(updateData.players);
|
|
85
|
+
}
|
|
86
|
+
if (updateData.leaderId !== void 0) {
|
|
87
|
+
this._leaderIndex = this.findLeaderIndex(this._players, updateData.leaderId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
window.addEventListener("message", this.boundMessageHandler);
|
|
92
|
+
}
|
|
93
|
+
mapPlayersFromInit(players) {
|
|
94
|
+
return players.map((p, index) => ({
|
|
95
|
+
playerIndex: p.playerIndex ?? index,
|
|
96
|
+
nickname: p.nickname || `Player ${index + 1}`,
|
|
97
|
+
connected: p.connected !== false,
|
|
98
|
+
appearance: p.appearance
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
findLeaderIndex(players, leaderId) {
|
|
102
|
+
if (!leaderId) return -1;
|
|
103
|
+
const idx = players.findIndex((p) => p.sessionId === leaderId);
|
|
104
|
+
return idx >= 0 ? idx : -1;
|
|
105
|
+
}
|
|
106
|
+
setupEventHandlers() {
|
|
107
|
+
if (!this.transport) return;
|
|
108
|
+
this.registerHandler(SYSTEM_EVENTS.PLAYER_JOIN, (data) => {
|
|
109
|
+
const playerIndex = data.player?.playerIndex;
|
|
110
|
+
if (playerIndex !== void 0) {
|
|
111
|
+
this.config.onPlayerJoin?.(playerIndex);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
this.registerHandler(SYSTEM_EVENTS.PLAYER_LEAVE, (data) => {
|
|
115
|
+
if (data.playerIndex !== void 0) {
|
|
116
|
+
this.config.onPlayerLeave?.(data.playerIndex);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
this.registerHandler("room:player-joined", (data) => {
|
|
120
|
+
const playerIndex = data?.player?.playerIndex ?? data?.playerIndex;
|
|
121
|
+
if (playerIndex !== void 0) {
|
|
122
|
+
this.config.onPlayerJoin?.(playerIndex);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
this.registerHandler("room:player-left", (data) => {
|
|
126
|
+
const playerIndex = data?.playerIndex ?? data?.player?.playerIndex;
|
|
127
|
+
if (playerIndex !== void 0) {
|
|
128
|
+
this.config.onPlayerLeave?.(playerIndex);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
if (this.config.listeners) {
|
|
132
|
+
for (const [event, handler] of Object.entries(this.config.listeners)) {
|
|
133
|
+
if (!handler) continue;
|
|
134
|
+
this.registerHandler(event, (data) => {
|
|
135
|
+
const { playerIndex, ...rest } = data;
|
|
136
|
+
if (playerIndex !== void 0) {
|
|
137
|
+
handler(playerIndex, rest);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
registerHandler(event, handler) {
|
|
144
|
+
if (!this.transport) return;
|
|
145
|
+
this.transport.on(event, handler);
|
|
146
|
+
this.registeredHandlers.push({ event, handler });
|
|
147
|
+
}
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Public Properties
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
/**
|
|
152
|
+
* Get all players in the room.
|
|
153
|
+
* Returns a copy to prevent external mutation.
|
|
154
|
+
*/
|
|
155
|
+
get players() {
|
|
156
|
+
return [...this._players];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the room code.
|
|
160
|
+
*/
|
|
161
|
+
get roomCode() {
|
|
162
|
+
return this._roomCode;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get the leader's player index (-1 if no leader).
|
|
166
|
+
*/
|
|
167
|
+
get leaderIndex() {
|
|
168
|
+
return this._leaderIndex;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check if the host is initialized and ready.
|
|
172
|
+
*/
|
|
173
|
+
get isReady() {
|
|
174
|
+
return this._isReady;
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Public Methods
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
/**
|
|
180
|
+
* Broadcast an event to all players.
|
|
181
|
+
*
|
|
182
|
+
* @param event - Event name (no colons allowed)
|
|
183
|
+
* @param data - Optional data payload
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* host.broadcast('phase-update', { phase: 'playing' });
|
|
188
|
+
* host.broadcast('timer-tick', { remaining: 30 });
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
broadcast(event, data) {
|
|
192
|
+
this.ensureReady("broadcast");
|
|
193
|
+
validateEventName(event);
|
|
194
|
+
this.transport.emit(event, data);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Send an event to a specific player.
|
|
198
|
+
*
|
|
199
|
+
* @param playerIndex - Target player index (0, 1, 2, ...)
|
|
200
|
+
* @param event - Event name (no colons allowed)
|
|
201
|
+
* @param data - Optional data payload
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```ts
|
|
205
|
+
* host.sendToPlayer(0, 'your-turn', { timeLimit: 30 });
|
|
206
|
+
* host.sendToPlayer(1, 'wait', { message: 'Not your turn' });
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
sendToPlayer(playerIndex, event, data) {
|
|
210
|
+
this.ensureReady("sendToPlayer");
|
|
211
|
+
validateEventName(event);
|
|
212
|
+
this.transport.emit(event, {
|
|
213
|
+
targetPlayerIndex: playerIndex,
|
|
214
|
+
...data && typeof data === "object" ? data : { data }
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Signal game over with results.
|
|
219
|
+
* This will broadcast the game over event to all players.
|
|
220
|
+
*
|
|
221
|
+
* @param results - Game results (scores, winner, etc.)
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```ts
|
|
225
|
+
* host.gameOver({
|
|
226
|
+
* scores: { 0: 100, 1: 75, 2: 50 },
|
|
227
|
+
* winner: 0,
|
|
228
|
+
* });
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
gameOver(results) {
|
|
232
|
+
this.ensureReady("gameOver");
|
|
233
|
+
this.transport.emit(SYSTEM_EVENTS.GAME_OVER, { results });
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Add a listener for a specific event after construction.
|
|
237
|
+
*
|
|
238
|
+
* @param event - Event name (no colons allowed)
|
|
239
|
+
* @param handler - Handler function (playerIndex, data) => void
|
|
240
|
+
* @returns Cleanup function to remove the listener
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* const cleanup = host.on('tap', (playerIndex, data) => {
|
|
245
|
+
* console.log(`Player ${playerIndex} tapped`);
|
|
246
|
+
* });
|
|
247
|
+
*
|
|
248
|
+
* // Later
|
|
249
|
+
* cleanup();
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
on(event, handler) {
|
|
253
|
+
validateEventName(event);
|
|
254
|
+
const wrappedHandler = (data) => {
|
|
255
|
+
const { playerIndex, ...rest } = data;
|
|
256
|
+
if (playerIndex !== void 0) {
|
|
257
|
+
handler(playerIndex, rest);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
if (this.transport) {
|
|
261
|
+
this.transport.on(event, wrappedHandler);
|
|
262
|
+
this.registeredHandlers.push({ event, handler: wrappedHandler });
|
|
263
|
+
}
|
|
264
|
+
return () => {
|
|
265
|
+
this.transport?.off(event, wrappedHandler);
|
|
266
|
+
this.registeredHandlers = this.registeredHandlers.filter(
|
|
267
|
+
(h) => h.event !== event || h.handler !== wrappedHandler
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Clean up all resources.
|
|
273
|
+
* Call this when unmounting/destroying the game.
|
|
274
|
+
*/
|
|
275
|
+
destroy() {
|
|
276
|
+
if (this._isDestroyed) return;
|
|
277
|
+
this._isDestroyed = true;
|
|
278
|
+
this._isReady = false;
|
|
279
|
+
for (const { event, handler } of this.registeredHandlers) {
|
|
280
|
+
this.transport?.off(event, handler);
|
|
281
|
+
}
|
|
282
|
+
this.registeredHandlers = [];
|
|
283
|
+
if (this.transport instanceof PostMessageTransport.PostMessageTransport) {
|
|
284
|
+
this.transport.destroy();
|
|
285
|
+
}
|
|
286
|
+
this.transport = null;
|
|
287
|
+
if (this.boundMessageHandler) {
|
|
288
|
+
window.removeEventListener("message", this.boundMessageHandler);
|
|
289
|
+
this.boundMessageHandler = null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
// Private Helpers
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
ensureReady(method) {
|
|
296
|
+
if (!this._isReady || !this.transport) {
|
|
297
|
+
throw new Error(`[SmoreHost] Cannot call ${method}() before host is ready. Wait for onReady callback.`);
|
|
298
|
+
}
|
|
299
|
+
if (this._isDestroyed) {
|
|
300
|
+
throw new Error(`[SmoreHost] Cannot call ${method}() after destroy()`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
exports.SmoreHost = SmoreHost;
|
|
306
|
+
//# sourceMappingURL=SmoreHost.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SmoreHost.cjs","sources":["../../src/SmoreHost.ts"],"sourcesContent":["/**\n * SmoreHost - Unified Host-side class for the S'MORE SDK (AirConsole style)\n *\n * Works in any environment: React, Phaser, Vanilla JS.\n * Automatically detects iframe vs bundled environment.\n *\n * @example Iframe game (auto-detection)\n * ```ts\n * const host = new SmoreHost({\n * onPlayerJoin: (playerIndex) => console.log('Player joined:', playerIndex),\n * listeners: {\n * tap: (playerIndex, data) => handleTap(playerIndex, data),\n * },\n * });\n *\n * // Later\n * host.broadcast('phase-update', { phase: 'playing' });\n * host.gameOver({ scores: { 0: 100, 1: 50 } });\n * ```\n *\n * @example Bundled game (direct socket)\n * ```ts\n * const host = new SmoreHost({\n * socket,\n * roomCode: 'ABCD',\n * players: [...],\n * leaderIndex: 0,\n * listeners: { ... },\n * });\n * ```\n */\n\nimport type { Socket } from 'socket.io-client';\nimport type { Transport, TransportEventHandler } from './transport/types';\nimport { DirectTransport } from './transport/DirectTransport';\nimport { PostMessageTransport } from './transport/PostMessageTransport';\nimport { isSmoreMessage, type SmoreInitMessage, type SmoreUpdateMessage } from './transport/protocol';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PREFIX = 'smore:';\n\nconst SYSTEM_EVENTS = {\n READY: `${SYSTEM_PREFIX}ready`,\n PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,\n PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`,\n GAME_OVER: `${SYSTEM_PREFIX}game-over`,\n} as const;\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): void {\n if (!EVENT_NAME_REGEX.test(event)) {\n throw new Error(\n `[SmoreHost] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Player information exposed to game developers.\n * Uses playerIndex (0, 1, 2, ...) instead of internal sessionId.\n */\nexport interface SmorePlayer {\n /** Player index (0, 1, 2, ...) */\n playerIndex: number;\n /** Player's chosen nickname */\n nickname: string;\n /** Whether player is currently connected */\n connected: boolean;\n /** Player's character appearance (optional) */\n appearance?: {\n skinColor?: string;\n hairColor?: string;\n shirtColor?: string;\n pantsColor?: string;\n };\n}\n\n/**\n * Configuration for SmoreHost constructor.\n */\nexport interface SmoreHostConfig {\n // === Callbacks ===\n\n /** Called when the host is initialized and ready (iframe games only) */\n onReady?: () => void;\n\n /** Called when a player joins the room */\n onPlayerJoin?: (playerIndex: number) => void;\n\n /** Called when a player leaves the room */\n onPlayerLeave?: (playerIndex: number) => void;\n\n /**\n * Event listeners for specific events.\n * Keys are event names (no colons), values are handler functions.\n * Handler receives (playerIndex, data).\n */\n listeners?: Record<string, (playerIndex: number, data: any) => void>;\n\n // === Bundled game options (skip iframe detection) ===\n\n /** Socket.IO socket instance (bundled games only) */\n socket?: Socket;\n\n /** Room code (bundled games only) */\n roomCode?: string;\n\n /** Initial players array (bundled games only) */\n players?: SmorePlayer[];\n\n /** Leader player index (bundled games only) */\n leaderIndex?: number;\n\n // === Iframe game options ===\n\n /** Parent window origin for postMessage validation (iframe games) */\n parentOrigin?: string;\n}\n\n// ---------------------------------------------------------------------------\n// SmoreHost Class\n// ---------------------------------------------------------------------------\n\n/**\n * SmoreHost - Main host-side class for game development.\n *\n * Automatically detects iframe vs bundled environment:\n * - Iframe: Uses PostMessageTransport, waits for smore:init from parent\n * - Bundled: Uses DirectTransport with provided socket\n */\nexport class SmoreHost {\n private transport: Transport | null = null;\n private config: SmoreHostConfig;\n private _players: SmorePlayer[] = [];\n private _roomCode: string = '';\n private _leaderIndex: number = -1;\n private _isReady: boolean = false;\n private _isDestroyed: boolean = false;\n private boundMessageHandler: ((e: MessageEvent) => void) | null = null;\n private registeredHandlers: Array<{ event: string; handler: TransportEventHandler }> = [];\n\n constructor(config: SmoreHostConfig = {}) {\n this.config = config;\n\n // Validate event names in listeners\n if (config.listeners) {\n for (const event of Object.keys(config.listeners)) {\n validateEventName(event);\n }\n }\n\n // Detect environment and initialize\n if (config.socket) {\n // Bundled game mode: use DirectTransport\n this.initBundled(config);\n } else {\n // Iframe game mode: use PostMessageTransport\n this.initIframe(config);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Initialization\n // ---------------------------------------------------------------------------\n\n private initBundled(config: SmoreHostConfig): void {\n if (!config.socket) {\n throw new Error('[SmoreHost] socket is required for bundled games');\n }\n\n this.transport = new DirectTransport(config.socket);\n this._roomCode = config.roomCode || '';\n this._players = config.players || [];\n this._leaderIndex = config.leaderIndex ?? -1;\n\n this.setupEventHandlers();\n\n // Mark as ready immediately for bundled games\n this._isReady = true;\n this.config.onReady?.();\n }\n\n private initIframe(config: SmoreHostConfig): void {\n const parentOrigin = config.parentOrigin || '*';\n\n // Signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n // Listen for init message from parent\n this.boundMessageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const initData = (msg as SmoreInitMessage).payload;\n\n if (initData.side !== 'host') {\n console.error('[SmoreHost] Received init for wrong side:', initData.side);\n return;\n }\n\n // Initialize transport\n this.transport = new PostMessageTransport(parentOrigin);\n this._roomCode = initData.roomCode;\n this._players = this.mapPlayersFromInit(initData.players);\n this._leaderIndex = this.findLeaderIndex(initData.players, initData.leaderId);\n\n this.setupEventHandlers();\n\n this._isReady = true;\n this.config.onReady?.();\n } else if (msg.type === 'smore:update') {\n const updateData = (msg as SmoreUpdateMessage).payload;\n\n if (updateData.players) {\n this._players = this.mapPlayersFromInit(updateData.players);\n }\n if (updateData.leaderId !== undefined) {\n this._leaderIndex = this.findLeaderIndex(this._players as any[], updateData.leaderId);\n }\n }\n };\n\n window.addEventListener('message', this.boundMessageHandler);\n }\n\n private mapPlayersFromInit(players: any[]): SmorePlayer[] {\n return players.map((p, index) => ({\n playerIndex: p.playerIndex ?? index,\n nickname: p.nickname || `Player ${index + 1}`,\n connected: p.connected !== false,\n appearance: p.appearance,\n }));\n }\n\n private findLeaderIndex(players: any[], leaderId: string | null): number {\n if (!leaderId) return -1;\n const idx = players.findIndex((p) => p.sessionId === leaderId);\n return idx >= 0 ? idx : -1;\n }\n\n private setupEventHandlers(): void {\n if (!this.transport) return;\n\n // System events: player join/leave\n this.registerHandler(SYSTEM_EVENTS.PLAYER_JOIN, (data: { player: SmorePlayer }) => {\n const playerIndex = data.player?.playerIndex;\n if (playerIndex !== undefined) {\n this.config.onPlayerJoin?.(playerIndex);\n }\n });\n\n this.registerHandler(SYSTEM_EVENTS.PLAYER_LEAVE, (data: { playerIndex: number }) => {\n if (data.playerIndex !== undefined) {\n this.config.onPlayerLeave?.(data.playerIndex);\n }\n });\n\n // Legacy room events (backward compatibility)\n this.registerHandler('room:player-joined', (data: any) => {\n const playerIndex = data?.player?.playerIndex ?? data?.playerIndex;\n if (playerIndex !== undefined) {\n this.config.onPlayerJoin?.(playerIndex);\n }\n });\n\n this.registerHandler('room:player-left', (data: any) => {\n const playerIndex = data?.playerIndex ?? data?.player?.playerIndex;\n if (playerIndex !== undefined) {\n this.config.onPlayerLeave?.(playerIndex);\n }\n });\n\n // User event listeners\n if (this.config.listeners) {\n for (const [event, handler] of Object.entries(this.config.listeners)) {\n if (!handler) continue;\n\n this.registerHandler(event, (data: { playerIndex: number; [key: string]: any }) => {\n const { playerIndex, ...rest } = data;\n if (playerIndex !== undefined) {\n handler(playerIndex, rest);\n }\n });\n }\n }\n }\n\n private registerHandler(event: string, handler: TransportEventHandler): void {\n if (!this.transport) return;\n this.transport.on(event, handler);\n this.registeredHandlers.push({ event, handler });\n }\n\n // ---------------------------------------------------------------------------\n // Public Properties\n // ---------------------------------------------------------------------------\n\n /**\n * Get all players in the room.\n * Returns a copy to prevent external mutation.\n */\n get players(): SmorePlayer[] {\n return [...this._players];\n }\n\n /**\n * Get the room code.\n */\n get roomCode(): string {\n return this._roomCode;\n }\n\n /**\n * Get the leader's player index (-1 if no leader).\n */\n get leaderIndex(): number {\n return this._leaderIndex;\n }\n\n /**\n * Check if the host is initialized and ready.\n */\n get isReady(): boolean {\n return this._isReady;\n }\n\n // ---------------------------------------------------------------------------\n // Public Methods\n // ---------------------------------------------------------------------------\n\n /**\n * Broadcast an event to all players.\n *\n * @param event - Event name (no colons allowed)\n * @param data - Optional data payload\n *\n * @example\n * ```ts\n * host.broadcast('phase-update', { phase: 'playing' });\n * host.broadcast('timer-tick', { remaining: 30 });\n * ```\n */\n broadcast(event: string, data?: any): void {\n this.ensureReady('broadcast');\n validateEventName(event);\n // Emit user event directly - genericRelay handles Host → Room broadcast\n this.transport!.emit(event, data);\n }\n\n /**\n * Send an event to a specific player.\n *\n * @param playerIndex - Target player index (0, 1, 2, ...)\n * @param event - Event name (no colons allowed)\n * @param data - Optional data payload\n *\n * @example\n * ```ts\n * host.sendToPlayer(0, 'your-turn', { timeLimit: 30 });\n * host.sendToPlayer(1, 'wait', { message: 'Not your turn' });\n * ```\n */\n sendToPlayer(playerIndex: number, event: string, data?: any): void {\n this.ensureReady('sendToPlayer');\n validateEventName(event);\n // Emit user event with targetPlayerIndex - genericRelay handles Host → Player\n this.transport!.emit(event, {\n targetPlayerIndex: playerIndex,\n ...(data && typeof data === 'object' ? data : { data }),\n });\n }\n\n /**\n * Signal game over with results.\n * This will broadcast the game over event to all players.\n *\n * @param results - Game results (scores, winner, etc.)\n *\n * @example\n * ```ts\n * host.gameOver({\n * scores: { 0: 100, 1: 75, 2: 50 },\n * winner: 0,\n * });\n * ```\n */\n gameOver(results?: any): void {\n this.ensureReady('gameOver');\n this.transport!.emit(SYSTEM_EVENTS.GAME_OVER, { results });\n }\n\n /**\n * Add a listener for a specific event after construction.\n *\n * @param event - Event name (no colons allowed)\n * @param handler - Handler function (playerIndex, data) => void\n * @returns Cleanup function to remove the listener\n *\n * @example\n * ```ts\n * const cleanup = host.on('tap', (playerIndex, data) => {\n * console.log(`Player ${playerIndex} tapped`);\n * });\n *\n * // Later\n * cleanup();\n * ```\n */\n on(event: string, handler: (playerIndex: number, data: any) => void): () => void {\n validateEventName(event);\n\n const wrappedHandler = (data: { playerIndex: number; [key: string]: any }) => {\n const { playerIndex, ...rest } = data;\n if (playerIndex !== undefined) {\n handler(playerIndex, rest);\n }\n };\n\n if (this.transport) {\n this.transport.on(event, wrappedHandler);\n this.registeredHandlers.push({ event, handler: wrappedHandler });\n }\n\n return () => {\n this.transport?.off(event, wrappedHandler);\n this.registeredHandlers = this.registeredHandlers.filter(\n (h) => h.event !== event || h.handler !== wrappedHandler\n );\n };\n }\n\n /**\n * Clean up all resources.\n * Call this when unmounting/destroying the game.\n */\n destroy(): void {\n if (this._isDestroyed) return;\n\n this._isDestroyed = true;\n this._isReady = false;\n\n // Remove all registered handlers\n for (const { event, handler } of this.registeredHandlers) {\n this.transport?.off(event, handler);\n }\n this.registeredHandlers = [];\n\n // Destroy transport\n if (this.transport instanceof PostMessageTransport) {\n (this.transport as PostMessageTransport).destroy();\n }\n this.transport = null;\n\n // Remove message listener\n if (this.boundMessageHandler) {\n window.removeEventListener('message', this.boundMessageHandler);\n this.boundMessageHandler = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private Helpers\n // ---------------------------------------------------------------------------\n\n private ensureReady(method: string): void {\n if (!this._isReady || !this.transport) {\n throw new Error(`[SmoreHost] Cannot call ${method}() before host is ready. Wait for onReady callback.`);\n }\n if (this._isDestroyed) {\n throw new Error(`[SmoreHost] Cannot call ${method}() after destroy()`);\n }\n }\n}\n"],"names":["DirectTransport","isSmoreMessage","PostMessageTransport"],"mappings":"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"}
|