@beta-gamer/angular 0.1.1 → 0.1.3

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.mjs CHANGED
@@ -13,9 +13,8 @@ var __decorateClass = (decorators, target, key, kind) => {
13
13
  import { Injectable, signal } from "@angular/core";
14
14
  import { io } from "socket.io-client";
15
15
  function decodeToken(token) {
16
- if (!token || typeof token !== "string") {
16
+ if (!token || typeof token !== "string")
17
17
  throw new Error("@beta-gamer/angular: token is required and must be a string");
18
- }
19
18
  const parts = token.split(".");
20
19
  if (parts.length < 2) throw new Error("@beta-gamer/angular: invalid session token format");
21
20
  const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
@@ -26,20 +25,33 @@ var BetaGamerService = class {
26
25
  constructor() {
27
26
  this._socket = null;
28
27
  this._token = "";
28
+ /** Decoded session token payload — null until `init()` is called */
29
29
  this.session = signal(null);
30
+ /** Live game state updated by server events */
30
31
  this.gameState = signal({ status: "pending", players: [] });
31
32
  }
32
33
  get token() {
33
34
  return this._token;
34
35
  }
36
+ /** The active socket.io connection — available after `init()` with `connectSocket=true` */
35
37
  get socket() {
36
38
  return this._socket;
37
39
  }
38
- init(token, serverUrl = "https://api.beta-gamer.com", socketPath = "/socket.io") {
40
+ /**
41
+ * @description Initialises the service with a session token.
42
+ * Decodes the token, sets up reactive state, and optionally connects the socket.
43
+ *
44
+ * @param token - JWT session token from your backend
45
+ * @param serverUrl - Base URL of the Beta Gamer server
46
+ * @param socketPath - socket.io path (default `/socket.io`)
47
+ * @param connectSocket - Set false to skip socket connection (e.g. for SSR or testing)
48
+ */
49
+ init(token, serverUrl = "https://api.beta-gamer.com", socketPath = "/socket.io", connectSocket = true) {
39
50
  this._token = token;
40
51
  const payload = decodeToken(token);
41
52
  this.session.set(payload);
42
53
  this.gameState.set({ status: "pending", players: payload.players });
54
+ if (!connectSocket) return;
43
55
  const s = io(`${serverUrl}/${payload.game}`, {
44
56
  auth: { token },
45
57
  path: socketPath,
@@ -114,7 +126,7 @@ var TimerComponent = class {
114
126
  constructor() {
115
127
  this.player = "self";
116
128
  this.initialSeconds = 600;
117
- this.seconds = this.initialSeconds;
129
+ this.seconds = 600;
118
130
  this.interval = null;
119
131
  this.svc = inject2(BetaGamerService);
120
132
  }
@@ -125,20 +137,17 @@ var TimerComponent = class {
125
137
  }
126
138
  ngOnInit() {
127
139
  this.seconds = this.initialSeconds;
128
- const socket = this.svc["_socket"];
129
- if (socket) {
130
- socket.on("game:clock", (clocks) => {
131
- if (clocks[this.player] !== void 0) this.seconds = clocks[this.player];
132
- });
133
- }
140
+ this.svc.socket?.on("game:clock", (clocks) => {
141
+ if (clocks[this.player] !== void 0) this.seconds = clocks[this.player];
142
+ });
134
143
  this.interval = setInterval(() => {
135
- if (this.svc.gameState().status === "active") {
144
+ if (this.svc.gameState().status === "active")
136
145
  this.seconds = Math.max(0, this.seconds - 1);
137
- }
138
146
  }, 1e3);
139
147
  }
140
148
  ngOnDestroy() {
141
149
  if (this.interval) clearInterval(this.interval);
150
+ this.svc.socket?.off("game:clock");
142
151
  }
143
152
  };
144
153
  __decorateClass([
@@ -159,30 +168,91 @@ TimerComponent = __decorateClass([
159
168
  })
160
169
  ], TimerComponent);
161
170
 
162
- // src/components/chess/index.ts
163
- import { Component as Component3, Input as Input3, ViewChild, inject as inject3 } from "@angular/core";
164
- var ChessBoardComponent = class {
171
+ // src/components/chess/ChessBoardComponent.ts
172
+ import { Component as Component3, Input as Input4 } from "@angular/core";
173
+
174
+ // src/components/shared/EmbedBoardBase.ts
175
+ import { Directive, Input as Input3, Output, EventEmitter, ViewChild, inject as inject3 } from "@angular/core";
176
+ var EmbedBoardBase = class {
165
177
  constructor() {
166
178
  this.serverUrl = "https://api.beta-gamer.com";
179
+ this.onLeave = new EventEmitter();
167
180
  this.svc = inject3(BetaGamerService);
168
- }
169
- get embedUrl() {
170
- return `${this.serverUrl}/embed/chess`;
181
+ this.messageHandler = (e) => {
182
+ try {
183
+ const data = typeof e.data === "string" ? JSON.parse(e.data) : e.data;
184
+ if (data?.type === "bg:leave") this.onLeave.emit();
185
+ } catch {
186
+ }
187
+ };
171
188
  }
172
189
  ngAfterViewInit() {
190
+ window.addEventListener("message", this.messageHandler);
173
191
  const iframe = this.iframeRef?.nativeElement;
174
192
  if (!iframe) return;
175
193
  iframe.addEventListener("load", () => {
176
194
  iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
177
195
  });
178
196
  }
197
+ ngOnDestroy() {
198
+ window.removeEventListener("message", this.messageHandler);
199
+ }
200
+ /** Builds a query string from a params object, omitting undefined values */
201
+ buildQuery(params) {
202
+ const p = new URLSearchParams();
203
+ for (const [k, v] of Object.entries(params)) if (v !== void 0) p.set(k, v);
204
+ const qs = p.toString();
205
+ return qs ? "?" + qs : "";
206
+ }
179
207
  };
180
208
  __decorateClass([
181
209
  Input3()
182
- ], ChessBoardComponent.prototype, "serverUrl", 2);
210
+ ], EmbedBoardBase.prototype, "serverUrl", 2);
211
+ __decorateClass([
212
+ Output()
213
+ ], EmbedBoardBase.prototype, "onLeave", 2);
183
214
  __decorateClass([
184
215
  ViewChild("iframe")
185
- ], ChessBoardComponent.prototype, "iframeRef", 2);
216
+ ], EmbedBoardBase.prototype, "iframeRef", 2);
217
+ EmbedBoardBase = __decorateClass([
218
+ Directive()
219
+ ], EmbedBoardBase);
220
+
221
+ // src/components/chess/ChessBoardComponent.ts
222
+ var ChessBoardComponent = class extends EmbedBoardBase {
223
+ constructor() {
224
+ super(...arguments);
225
+ this.showAfkWarning = true;
226
+ this.showCoords = true;
227
+ this.showLastMove = true;
228
+ this.showLegalMoves = true;
229
+ this.orientation = "auto";
230
+ }
231
+ get embedUrl() {
232
+ return this.serverUrl + "/embed/chess" + this.buildQuery({
233
+ afkWarning: !this.showAfkWarning ? "0" : void 0,
234
+ showCoords: !this.showCoords ? "0" : void 0,
235
+ showLastMove: !this.showLastMove ? "0" : void 0,
236
+ showLegalMoves: !this.showLegalMoves ? "0" : void 0,
237
+ orientation: this.orientation !== "auto" ? this.orientation : void 0
238
+ });
239
+ }
240
+ };
241
+ __decorateClass([
242
+ Input4()
243
+ ], ChessBoardComponent.prototype, "showAfkWarning", 2);
244
+ __decorateClass([
245
+ Input4()
246
+ ], ChessBoardComponent.prototype, "showCoords", 2);
247
+ __decorateClass([
248
+ Input4()
249
+ ], ChessBoardComponent.prototype, "showLastMove", 2);
250
+ __decorateClass([
251
+ Input4()
252
+ ], ChessBoardComponent.prototype, "showLegalMoves", 2);
253
+ __decorateClass([
254
+ Input4()
255
+ ], ChessBoardComponent.prototype, "orientation", 2);
186
256
  ChessBoardComponent = __decorateClass([
187
257
  Component3({
188
258
  selector: "bg-chess-board",
@@ -194,19 +264,25 @@ ChessBoardComponent = __decorateClass([
194
264
  `
195
265
  })
196
266
  ], ChessBoardComponent);
267
+
268
+ // src/components/chess/ChessMoveHistoryComponent.ts
269
+ import { Component as Component4 } from "@angular/core";
197
270
  var ChessMoveHistoryComponent = class {
198
271
  };
199
272
  ChessMoveHistoryComponent = __decorateClass([
200
- Component3({
273
+ Component4({
201
274
  selector: "bg-chess-move-history",
202
275
  standalone: true,
203
276
  template: `<div data-role="move-history"></div>`
204
277
  })
205
278
  ], ChessMoveHistoryComponent);
279
+
280
+ // src/components/chess/ChessCapturedPiecesComponent.ts
281
+ import { Component as Component5 } from "@angular/core";
206
282
  var ChessCapturedPiecesComponent = class {
207
283
  };
208
284
  ChessCapturedPiecesComponent = __decorateClass([
209
- Component3({
285
+ Component5({
210
286
  selector: "bg-chess-captured-pieces",
211
287
  standalone: true,
212
288
  template: `<div data-role="captured-pieces"></div>`
@@ -214,31 +290,33 @@ ChessCapturedPiecesComponent = __decorateClass([
214
290
  ], ChessCapturedPiecesComponent);
215
291
 
216
292
  // src/components/checkers/index.ts
217
- import { Component as Component4, Input as Input4, ViewChild as ViewChild2, inject as inject4 } from "@angular/core";
218
- var CheckersBoardComponent = class {
293
+ import { Component as Component6, Input as Input5 } from "@angular/core";
294
+ var CheckersBoardComponent = class extends EmbedBoardBase {
219
295
  constructor() {
220
- this.serverUrl = "https://api.beta-gamer.com";
221
- this.svc = inject4(BetaGamerService);
296
+ super(...arguments);
297
+ this.showAfkWarning = true;
298
+ this.showLastMove = true;
299
+ this.orientation = "auto";
222
300
  }
223
301
  get embedUrl() {
224
- return `${this.serverUrl}/embed/checkers`;
225
- }
226
- ngAfterViewInit() {
227
- const iframe = this.iframeRef?.nativeElement;
228
- if (!iframe) return;
229
- iframe.addEventListener("load", () => {
230
- iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
302
+ return this.serverUrl + "/embed/checkers" + this.buildQuery({
303
+ afkWarning: !this.showAfkWarning ? "0" : void 0,
304
+ showLastMove: !this.showLastMove ? "0" : void 0,
305
+ orientation: this.orientation !== "auto" ? this.orientation : void 0
231
306
  });
232
307
  }
233
308
  };
234
309
  __decorateClass([
235
- Input4()
236
- ], CheckersBoardComponent.prototype, "serverUrl", 2);
310
+ Input5()
311
+ ], CheckersBoardComponent.prototype, "showAfkWarning", 2);
237
312
  __decorateClass([
238
- ViewChild2("iframe")
239
- ], CheckersBoardComponent.prototype, "iframeRef", 2);
313
+ Input5()
314
+ ], CheckersBoardComponent.prototype, "showLastMove", 2);
315
+ __decorateClass([
316
+ Input5()
317
+ ], CheckersBoardComponent.prototype, "orientation", 2);
240
318
  CheckersBoardComponent = __decorateClass([
241
- Component4({
319
+ Component6({
242
320
  selector: "bg-checkers-board",
243
321
  standalone: true,
244
322
  template: `
@@ -249,32 +327,24 @@ CheckersBoardComponent = __decorateClass([
249
327
  })
250
328
  ], CheckersBoardComponent);
251
329
 
252
- // src/components/connect4/index.ts
253
- import { Component as Component5, Input as Input5, ViewChild as ViewChild3, inject as inject5 } from "@angular/core";
254
- var Connect4BoardComponent = class {
330
+ // src/components/connect4/Connect4BoardComponent.ts
331
+ import { Component as Component7, Input as Input6 } from "@angular/core";
332
+ var Connect4BoardComponent = class extends EmbedBoardBase {
255
333
  constructor() {
256
- this.serverUrl = "https://api.beta-gamer.com";
257
- this.svc = inject5(BetaGamerService);
334
+ super(...arguments);
335
+ this.showAfkWarning = true;
258
336
  }
259
337
  get embedUrl() {
260
- return `${this.serverUrl}/embed/connect4`;
261
- }
262
- ngAfterViewInit() {
263
- const iframe = this.iframeRef?.nativeElement;
264
- if (!iframe) return;
265
- iframe.addEventListener("load", () => {
266
- iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
338
+ return this.serverUrl + "/embed/connect4" + this.buildQuery({
339
+ afkWarning: !this.showAfkWarning ? "0" : void 0
267
340
  });
268
341
  }
269
342
  };
270
343
  __decorateClass([
271
- Input5()
272
- ], Connect4BoardComponent.prototype, "serverUrl", 2);
273
- __decorateClass([
274
- ViewChild3("iframe")
275
- ], Connect4BoardComponent.prototype, "iframeRef", 2);
344
+ Input6()
345
+ ], Connect4BoardComponent.prototype, "showAfkWarning", 2);
276
346
  Connect4BoardComponent = __decorateClass([
277
- Component5({
347
+ Component7({
278
348
  selector: "bg-connect4-board",
279
349
  standalone: true,
280
350
  template: `
@@ -284,9 +354,12 @@ Connect4BoardComponent = __decorateClass([
284
354
  `
285
355
  })
286
356
  ], Connect4BoardComponent);
357
+
358
+ // src/components/connect4/Connect4ScoreComponent.ts
359
+ import { Component as Component8, inject as inject4 } from "@angular/core";
287
360
  var Connect4ScoreComponent = class {
288
361
  constructor() {
289
- this.svc = inject5(BetaGamerService);
362
+ this.svc = inject4(BetaGamerService);
290
363
  }
291
364
  get scores() {
292
365
  const s = this.svc.gameState()?.scores ?? {};
@@ -294,7 +367,7 @@ var Connect4ScoreComponent = class {
294
367
  }
295
368
  };
296
369
  Connect4ScoreComponent = __decorateClass([
297
- Component5({
370
+ Component8({
298
371
  selector: "bg-connect4-score",
299
372
  standalone: true,
300
373
  template: `
@@ -308,31 +381,23 @@ Connect4ScoreComponent = __decorateClass([
308
381
  ], Connect4ScoreComponent);
309
382
 
310
383
  // src/components/tictactoe/index.ts
311
- import { Component as Component6, Input as Input6, ViewChild as ViewChild4, inject as inject6 } from "@angular/core";
312
- var TictactoeBoardComponent = class {
384
+ import { Component as Component9, Input as Input7 } from "@angular/core";
385
+ var TictactoeBoardComponent = class extends EmbedBoardBase {
313
386
  constructor() {
314
- this.serverUrl = "https://api.beta-gamer.com";
315
- this.svc = inject6(BetaGamerService);
387
+ super(...arguments);
388
+ this.showAfkWarning = true;
316
389
  }
317
390
  get embedUrl() {
318
- return `${this.serverUrl}/embed/tictactoe`;
319
- }
320
- ngAfterViewInit() {
321
- const iframe = this.iframeRef?.nativeElement;
322
- if (!iframe) return;
323
- iframe.addEventListener("load", () => {
324
- iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
391
+ return this.serverUrl + "/embed/tictactoe" + this.buildQuery({
392
+ afkWarning: !this.showAfkWarning ? "0" : void 0
325
393
  });
326
394
  }
327
395
  };
328
396
  __decorateClass([
329
- Input6()
330
- ], TictactoeBoardComponent.prototype, "serverUrl", 2);
331
- __decorateClass([
332
- ViewChild4("iframe")
333
- ], TictactoeBoardComponent.prototype, "iframeRef", 2);
397
+ Input7()
398
+ ], TictactoeBoardComponent.prototype, "showAfkWarning", 2);
334
399
  TictactoeBoardComponent = __decorateClass([
335
- Component6({
400
+ Component9({
336
401
  selector: "bg-tictactoe-board",
337
402
  standalone: true,
338
403
  template: `
@@ -343,12 +408,12 @@ TictactoeBoardComponent = __decorateClass([
343
408
  })
344
409
  ], TictactoeBoardComponent);
345
410
 
346
- // src/components/subway-runner/index.ts
347
- import { Component as Component7, Input as Input7, ViewChild as ViewChild5, inject as inject7 } from "@angular/core";
411
+ // src/components/subway-runner/SubwayRunnerGameComponent.ts
412
+ import { Component as Component10, Input as Input8, ViewChild as ViewChild2, inject as inject5 } from "@angular/core";
348
413
  var SubwayRunnerGameComponent = class {
349
414
  constructor() {
350
415
  this.serverUrl = "https://api.beta-gamer.com";
351
- this.svc = inject7(BetaGamerService);
416
+ this.svc = inject5(BetaGamerService);
352
417
  }
353
418
  get embedUrl() {
354
419
  return `${this.serverUrl}/embed/subway-runner`;
@@ -362,13 +427,13 @@ var SubwayRunnerGameComponent = class {
362
427
  }
363
428
  };
364
429
  __decorateClass([
365
- Input7()
430
+ Input8()
366
431
  ], SubwayRunnerGameComponent.prototype, "serverUrl", 2);
367
432
  __decorateClass([
368
- ViewChild5("iframe")
433
+ ViewChild2("iframe")
369
434
  ], SubwayRunnerGameComponent.prototype, "iframeRef", 2);
370
435
  SubwayRunnerGameComponent = __decorateClass([
371
- Component7({
436
+ Component10({
372
437
  selector: "bg-subway-runner",
373
438
  standalone: true,
374
439
  template: `
@@ -378,21 +443,27 @@ SubwayRunnerGameComponent = __decorateClass([
378
443
  `
379
444
  })
380
445
  ], SubwayRunnerGameComponent);
446
+
447
+ // src/components/subway-runner/SubwayRunnerScoreComponent.ts
448
+ import { Component as Component11, inject as inject6 } from "@angular/core";
381
449
  var SubwayRunnerScoreComponent = class {
382
450
  constructor() {
383
- this.svc = inject7(BetaGamerService);
451
+ this.svc = inject6(BetaGamerService);
384
452
  }
385
453
  get score() {
386
454
  return this.svc.gameState()?.score ?? 0;
387
455
  }
388
456
  };
389
457
  SubwayRunnerScoreComponent = __decorateClass([
390
- Component7({
458
+ Component11({
391
459
  selector: "bg-subway-runner-score",
392
460
  standalone: true,
393
461
  template: `<div data-role="score"><span data-role="score-value">{{ score }}</span></div>`
394
462
  })
395
463
  ], SubwayRunnerScoreComponent);
464
+
465
+ // src/components/subway-runner/SubwayRunnerLivesComponent.ts
466
+ import { Component as Component12, inject as inject7 } from "@angular/core";
396
467
  var SubwayRunnerLivesComponent = class {
397
468
  constructor() {
398
469
  this.svc = inject7(BetaGamerService);
@@ -402,7 +473,7 @@ var SubwayRunnerLivesComponent = class {
402
473
  }
403
474
  };
404
475
  SubwayRunnerLivesComponent = __decorateClass([
405
- Component7({
476
+ Component12({
406
477
  selector: "bg-subway-runner-lives",
407
478
  standalone: true,
408
479
  template: `<div data-role="lives"><span data-role="lives-value">{{ lives }}</span></div>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beta-gamer/angular",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Angular SDK for Beta Gamer GaaS — composable game components",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",