@kossabos/patchwork-image-boardgameio 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 kossabos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,334 @@
1
+ import { Game, StorageAPI, State, LogEntry, Server, CredentialedActionShape, ChatMessage } from 'boardgame.io';
2
+
3
+ /**
4
+ * @kossabos/patchwork-image-boardgameio
5
+ *
6
+ * Setup function for the Boardgame.io image.
7
+ * Uses Tailwind Play CDN for runtime CSS generation.
8
+ */
9
+ interface SetupOptions {
10
+ darkMode?: boolean | 'system';
11
+ cssRuntime?: boolean;
12
+ multiplayer?: boolean;
13
+ }
14
+ declare global {
15
+ interface Window {
16
+ tailwind?: {
17
+ config?: Record<string, unknown>;
18
+ };
19
+ }
20
+ }
21
+ declare function setup(container: HTMLElement, options?: SetupOptions): Promise<void>;
22
+ declare function cleanup(container: HTMLElement): void;
23
+ /**
24
+ * Ensures PeerJS is loaded (can be called directly by mount if needed)
25
+ */
26
+ declare function ensurePeerJS(): Promise<void>;
27
+
28
+ /**
29
+ * Settings Context for boardgame.io widgets
30
+ *
31
+ * Provides game settings to board components via React context.
32
+ * Uses window.React at runtime (preloaded by the compiler).
33
+ */
34
+ type GameSettings = Record<string, unknown>;
35
+ interface SettingsProviderProps {
36
+ settings: GameSettings;
37
+ children: unknown;
38
+ }
39
+ declare function SettingsProvider({ settings, children, }: SettingsProviderProps): unknown;
40
+ /**
41
+ * Hook to access game settings from within a board component.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * function MyBoard({ G, ctx, moves }: BoardProps) {
46
+ * const settings = useSettings<{ difficulty: string }>();
47
+ * // ...
48
+ * }
49
+ * ```
50
+ */
51
+ declare function useSettings<T extends GameSettings = GameSettings>(): T;
52
+
53
+ /**
54
+ * Mount helper for boardgame.io widgets
55
+ *
56
+ * Handles mounting automatically when widgets export `game` and `app` (or default).
57
+ * The image detects these exports and creates the mount - widgets don't need to
58
+ * reference image internals.
59
+ *
60
+ * Uses window.React at runtime (preloaded by the compiler).
61
+ */
62
+
63
+ /** Multiplayer configuration passed via inputs */
64
+ interface MultiplayerInput {
65
+ matchID: string;
66
+ playerID: string;
67
+ credentials?: string;
68
+ isHost?: boolean;
69
+ }
70
+ interface GameMountOptions {
71
+ /** Number of players for the game */
72
+ numPlayers?: number;
73
+ /** Default player ID */
74
+ defaultPlayerID?: string;
75
+ }
76
+ interface BoardgameGame {
77
+ name?: string;
78
+ minPlayers?: number;
79
+ maxPlayers?: number;
80
+ setup: (context: unknown) => unknown;
81
+ ai?: {
82
+ enumerate: (...args: unknown[]) => unknown;
83
+ };
84
+ }
85
+ /** Manifest structure for player count */
86
+ interface WidgetManifest {
87
+ players?: {
88
+ min?: number;
89
+ max?: number;
90
+ };
91
+ [key: string]: unknown;
92
+ }
93
+ type BoardComponent = (props: any) => unknown;
94
+ declare global {
95
+ interface Window {
96
+ BoardgameReact?: {
97
+ Client: (opts: {
98
+ game: BoardgameGame;
99
+ board: BoardComponent;
100
+ numPlayers?: number;
101
+ multiplayer?: unknown;
102
+ ai?: unknown;
103
+ }) => any;
104
+ };
105
+ BoardgameAI?: {
106
+ RandomBot?: () => unknown;
107
+ };
108
+ BoardgameMultiplayer?: {
109
+ Local?: (opts?: {
110
+ bots?: Record<string, unknown>;
111
+ }) => unknown;
112
+ };
113
+ React?: any;
114
+ ReactDOM?: {
115
+ createRoot: (el: HTMLElement) => {
116
+ render: (el: unknown) => void;
117
+ unmount: () => void;
118
+ };
119
+ };
120
+ }
121
+ }
122
+ /**
123
+ * Creates a mount function for a boardgame.io widget.
124
+ *
125
+ * @param game - The boardgame.io game definition
126
+ * @param Board - The React board component
127
+ * @param options - Mount options (numPlayers, defaultPlayerID)
128
+ * @returns A mount function compatible with the patchwork runtime
129
+ *
130
+ * @example
131
+ * ```tsx
132
+ * export const game = { name: 'my-game', setup: () => ({}) };
133
+ * export default function MyBoard({ G, ctx, moves }) { ... }
134
+ * export const mount = createGameMount(game, MyBoard, { numPlayers: 2 });
135
+ * ```
136
+ */
137
+ declare function createGameMount(game: BoardgameGame, Board: BoardComponent, options?: GameMountOptions): (container: HTMLElement, inputs?: GameSettings) => () => void;
138
+ /**
139
+ * Widget module structure expected by the boardgameio image.
140
+ * Widgets should export `game` and either `app` or `default`.
141
+ */
142
+ interface BoardgameWidgetModule {
143
+ game: BoardgameGame;
144
+ app?: BoardComponent;
145
+ default?: BoardComponent;
146
+ }
147
+ /**
148
+ * Creates a mount function from a widget module's exports.
149
+ * Called automatically by the runtime when it detects a boardgameio widget.
150
+ *
151
+ * @param module - The widget module with game and app/default exports
152
+ * @param manifest - The widget manifest (for player count settings)
153
+ * @returns A mount function or null if not a valid boardgameio widget
154
+ */
155
+ declare function createMountFromExports(module: BoardgameWidgetModule, manifest?: WidgetManifest): ((container: HTMLElement, inputs?: GameSettings) => () => void) | null;
156
+ /**
157
+ * Injects the mount helpers onto the window for runtime access.
158
+ * Called during image setup.
159
+ */
160
+ declare function injectMountHelper(): void;
161
+ /**
162
+ * Image mount function - handles mounting widgets for the boardgameio image.
163
+ *
164
+ * Supports three patterns:
165
+ * 1. BoardGame.io widgets: exports `game` and `app`/`default`
166
+ * 2. Custom mount: exports a `mount` function
167
+ * 3. React component: exports `default` as a React component
168
+ *
169
+ * @param module - The widget module exports
170
+ * @param container - The DOM element to mount into
171
+ * @param inputs - Props/settings to pass to the widget
172
+ * @returns A cleanup function to unmount, or void
173
+ */
174
+ declare function mount(module: Record<string, unknown>, container: HTMLElement, inputs: Record<string, unknown>): Promise<void | (() => void)>;
175
+
176
+ interface MultiplayerConfig {
177
+ isMultiplayer: boolean;
178
+ isHost?: boolean;
179
+ appId: string;
180
+ matchID?: string;
181
+ botCount?: number;
182
+ }
183
+ declare function getMultiplayer(config: MultiplayerConfig, game?: Game): unknown;
184
+ declare function generateMatchID(): string;
185
+
186
+ declare class P2PDB {
187
+ private initialState;
188
+ private state;
189
+ private log;
190
+ private metadata;
191
+ connect(): void;
192
+ createMatch(matchID: string, opts: StorageAPI.CreateMatchOpts): void;
193
+ setState(matchID: string, state: State, deltalog?: LogEntry[]): void;
194
+ setMetadata(matchID: string, metadata: Server.MatchData): void;
195
+ fetch<O extends StorageAPI.FetchOpts>(matchID: string): StorageAPI.FetchResult<O>;
196
+ wipe(matchID: string): void;
197
+ listMatches(): string[];
198
+ }
199
+
200
+ interface ClientMetadata {
201
+ playerID: string | null;
202
+ credentials?: string;
203
+ message?: string;
204
+ }
205
+ interface Client {
206
+ metadata: ClientMetadata;
207
+ send: (data: unknown) => void;
208
+ }
209
+ type ClientAction = {
210
+ type: 'sync';
211
+ } | {
212
+ type: 'update';
213
+ args: [string, State, CredentialedActionShape.Any];
214
+ } | {
215
+ type: 'chat';
216
+ args: [string, ChatMessage, string | undefined];
217
+ };
218
+
219
+ declare function generateCredentials(): string;
220
+ declare function generateKeyPair(): {
221
+ publicKey: string;
222
+ privateKey: string;
223
+ };
224
+
225
+ declare class P2PHost {
226
+ private clients;
227
+ private hostClient;
228
+ private matchID;
229
+ private db;
230
+ private game;
231
+ private numPlayers;
232
+ private state;
233
+ constructor({ game, numPlayers, matchID, }: {
234
+ game: Game;
235
+ numPlayers?: number;
236
+ matchID: string;
237
+ });
238
+ private createInitialState;
239
+ registerClient(client: Client): void;
240
+ registerHostClient(client: Client): void;
241
+ unregisterClient(client: Client): void;
242
+ processAction(client: Client, data: ClientAction): void;
243
+ private syncClient;
244
+ private filterStateForPlayer;
245
+ private handleUpdate;
246
+ private broadcastState;
247
+ private broadcastChat;
248
+ }
249
+
250
+ declare global {
251
+ interface Window {
252
+ Peer?: new (id?: string, options?: object) => PeerInstance;
253
+ __peerConfig?: {
254
+ host?: string;
255
+ port?: number;
256
+ path?: string;
257
+ secure?: boolean;
258
+ iceServers?: RTCIceServer[];
259
+ };
260
+ }
261
+ }
262
+ interface DataConnection {
263
+ on(event: 'data' | 'open' | 'close' | 'error', handler: (data?: unknown) => void): void;
264
+ send(data: unknown): void;
265
+ close(): void;
266
+ }
267
+ interface PeerInstance {
268
+ on(event: 'open' | 'connection' | 'error' | 'close', handler: (data?: unknown) => void): void;
269
+ connect(peerId: string, options?: {
270
+ reliable?: boolean;
271
+ serialization?: string;
272
+ }): DataConnection;
273
+ destroy(): void;
274
+ id: string;
275
+ }
276
+ type TransportData = {
277
+ type: 'sync' | 'update' | 'chat';
278
+ args: unknown[];
279
+ };
280
+ interface P2PTransportOpts {
281
+ isHost?: boolean;
282
+ peerOptions?: object;
283
+ onError?: (error: Error) => void;
284
+ }
285
+ interface TransportConfig {
286
+ gameName: string;
287
+ playerID: string | null;
288
+ matchID: string;
289
+ credentials?: string;
290
+ numPlayers: number;
291
+ game: Game;
292
+ transportDataCallback: (data: TransportData) => void;
293
+ }
294
+ declare class P2PTransport {
295
+ private peer;
296
+ private connection;
297
+ private host;
298
+ private isHost;
299
+ private game;
300
+ private gameName;
301
+ private playerID;
302
+ private matchID;
303
+ private credentials?;
304
+ private numPlayers;
305
+ private privateKey?;
306
+ private transportDataCallback;
307
+ private peerOptions?;
308
+ private onError?;
309
+ private connected;
310
+ private connectionStatusCallbacks;
311
+ constructor(config: TransportConfig, opts?: P2PTransportOpts);
312
+ private get hostID();
313
+ private setCredentials;
314
+ private get metadata();
315
+ connect(): void;
316
+ private retryCount;
317
+ private maxRetries;
318
+ private connectToHost;
319
+ private onConnect;
320
+ private notifyConnectionStatus;
321
+ private notifyClient;
322
+ disconnect(): void;
323
+ requestSync(): void;
324
+ sendAction(state: State, action: CredentialedActionShape.Any): void;
325
+ sendChatMessage(matchID: string, chatMessage: ChatMessage): void;
326
+ updateMatchID(id: string): void;
327
+ updatePlayerID(id: string): void;
328
+ updateCredentials(credentials?: string): void;
329
+ isConnected(): boolean;
330
+ subscribeToConnectionStatus(callback: (connected: boolean) => void): () => void;
331
+ }
332
+ declare function createP2PTransport(opts?: P2PTransportOpts): (config: TransportConfig) => P2PTransport;
333
+
334
+ export { type BoardgameGame, type BoardgameWidgetModule, type GameMountOptions, type GameSettings, type MultiplayerConfig, type MultiplayerInput, P2PDB, P2PHost, P2PTransport, type P2PTransportOpts, SettingsProvider, type SettingsProviderProps, type SetupOptions, type TransportConfig, type WidgetManifest, cleanup, createGameMount, createMountFromExports, createP2PTransport, ensurePeerJS, generateCredentials, generateKeyPair, generateMatchID, getMultiplayer, injectMountHelper, mount, setup, useSettings };