@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.js CHANGED
@@ -48,9 +48,8 @@ module.exports = __toCommonJS(index_exports);
48
48
  var import_core = require("@angular/core");
49
49
  var import_socket = require("socket.io-client");
50
50
  function decodeToken(token) {
51
- if (!token || typeof token !== "string") {
51
+ if (!token || typeof token !== "string")
52
52
  throw new Error("@beta-gamer/angular: token is required and must be a string");
53
- }
54
53
  const parts = token.split(".");
55
54
  if (parts.length < 2) throw new Error("@beta-gamer/angular: invalid session token format");
56
55
  const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
@@ -61,20 +60,33 @@ var BetaGamerService = class {
61
60
  constructor() {
62
61
  this._socket = null;
63
62
  this._token = "";
63
+ /** Decoded session token payload — null until `init()` is called */
64
64
  this.session = (0, import_core.signal)(null);
65
+ /** Live game state updated by server events */
65
66
  this.gameState = (0, import_core.signal)({ status: "pending", players: [] });
66
67
  }
67
68
  get token() {
68
69
  return this._token;
69
70
  }
71
+ /** The active socket.io connection — available after `init()` with `connectSocket=true` */
70
72
  get socket() {
71
73
  return this._socket;
72
74
  }
73
- init(token, serverUrl = "https://api.beta-gamer.com", socketPath = "/socket.io") {
75
+ /**
76
+ * @description Initialises the service with a session token.
77
+ * Decodes the token, sets up reactive state, and optionally connects the socket.
78
+ *
79
+ * @param token - JWT session token from your backend
80
+ * @param serverUrl - Base URL of the Beta Gamer server
81
+ * @param socketPath - socket.io path (default `/socket.io`)
82
+ * @param connectSocket - Set false to skip socket connection (e.g. for SSR or testing)
83
+ */
84
+ init(token, serverUrl = "https://api.beta-gamer.com", socketPath = "/socket.io", connectSocket = true) {
74
85
  this._token = token;
75
86
  const payload = decodeToken(token);
76
87
  this.session.set(payload);
77
88
  this.gameState.set({ status: "pending", players: payload.players });
89
+ if (!connectSocket) return;
78
90
  const s = (0, import_socket.io)(`${serverUrl}/${payload.game}`, {
79
91
  auth: { token },
80
92
  path: socketPath,
@@ -149,7 +161,7 @@ var TimerComponent = class {
149
161
  constructor() {
150
162
  this.player = "self";
151
163
  this.initialSeconds = 600;
152
- this.seconds = this.initialSeconds;
164
+ this.seconds = 600;
153
165
  this.interval = null;
154
166
  this.svc = (0, import_core3.inject)(BetaGamerService);
155
167
  }
@@ -160,20 +172,17 @@ var TimerComponent = class {
160
172
  }
161
173
  ngOnInit() {
162
174
  this.seconds = this.initialSeconds;
163
- const socket = this.svc["_socket"];
164
- if (socket) {
165
- socket.on("game:clock", (clocks) => {
166
- if (clocks[this.player] !== void 0) this.seconds = clocks[this.player];
167
- });
168
- }
175
+ this.svc.socket?.on("game:clock", (clocks) => {
176
+ if (clocks[this.player] !== void 0) this.seconds = clocks[this.player];
177
+ });
169
178
  this.interval = setInterval(() => {
170
- if (this.svc.gameState().status === "active") {
179
+ if (this.svc.gameState().status === "active")
171
180
  this.seconds = Math.max(0, this.seconds - 1);
172
- }
173
181
  }, 1e3);
174
182
  }
175
183
  ngOnDestroy() {
176
184
  if (this.interval) clearInterval(this.interval);
185
+ this.svc.socket?.off("game:clock");
177
186
  }
178
187
  };
179
188
  __decorateClass([
@@ -194,32 +203,93 @@ TimerComponent = __decorateClass([
194
203
  })
195
204
  ], TimerComponent);
196
205
 
197
- // src/components/chess/index.ts
206
+ // src/components/chess/ChessBoardComponent.ts
207
+ var import_core5 = require("@angular/core");
208
+
209
+ // src/components/shared/EmbedBoardBase.ts
198
210
  var import_core4 = require("@angular/core");
199
- var ChessBoardComponent = class {
211
+ var EmbedBoardBase = class {
200
212
  constructor() {
201
213
  this.serverUrl = "https://api.beta-gamer.com";
214
+ this.onLeave = new import_core4.EventEmitter();
202
215
  this.svc = (0, import_core4.inject)(BetaGamerService);
203
- }
204
- get embedUrl() {
205
- return `${this.serverUrl}/embed/chess`;
216
+ this.messageHandler = (e) => {
217
+ try {
218
+ const data = typeof e.data === "string" ? JSON.parse(e.data) : e.data;
219
+ if (data?.type === "bg:leave") this.onLeave.emit();
220
+ } catch {
221
+ }
222
+ };
206
223
  }
207
224
  ngAfterViewInit() {
225
+ window.addEventListener("message", this.messageHandler);
208
226
  const iframe = this.iframeRef?.nativeElement;
209
227
  if (!iframe) return;
210
228
  iframe.addEventListener("load", () => {
211
229
  iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
212
230
  });
213
231
  }
232
+ ngOnDestroy() {
233
+ window.removeEventListener("message", this.messageHandler);
234
+ }
235
+ /** Builds a query string from a params object, omitting undefined values */
236
+ buildQuery(params) {
237
+ const p = new URLSearchParams();
238
+ for (const [k, v] of Object.entries(params)) if (v !== void 0) p.set(k, v);
239
+ const qs = p.toString();
240
+ return qs ? "?" + qs : "";
241
+ }
214
242
  };
215
243
  __decorateClass([
216
244
  (0, import_core4.Input)()
217
- ], ChessBoardComponent.prototype, "serverUrl", 2);
245
+ ], EmbedBoardBase.prototype, "serverUrl", 2);
246
+ __decorateClass([
247
+ (0, import_core4.Output)()
248
+ ], EmbedBoardBase.prototype, "onLeave", 2);
218
249
  __decorateClass([
219
250
  (0, import_core4.ViewChild)("iframe")
220
- ], ChessBoardComponent.prototype, "iframeRef", 2);
251
+ ], EmbedBoardBase.prototype, "iframeRef", 2);
252
+ EmbedBoardBase = __decorateClass([
253
+ (0, import_core4.Directive)()
254
+ ], EmbedBoardBase);
255
+
256
+ // src/components/chess/ChessBoardComponent.ts
257
+ var ChessBoardComponent = class extends EmbedBoardBase {
258
+ constructor() {
259
+ super(...arguments);
260
+ this.showAfkWarning = true;
261
+ this.showCoords = true;
262
+ this.showLastMove = true;
263
+ this.showLegalMoves = true;
264
+ this.orientation = "auto";
265
+ }
266
+ get embedUrl() {
267
+ return this.serverUrl + "/embed/chess" + this.buildQuery({
268
+ afkWarning: !this.showAfkWarning ? "0" : void 0,
269
+ showCoords: !this.showCoords ? "0" : void 0,
270
+ showLastMove: !this.showLastMove ? "0" : void 0,
271
+ showLegalMoves: !this.showLegalMoves ? "0" : void 0,
272
+ orientation: this.orientation !== "auto" ? this.orientation : void 0
273
+ });
274
+ }
275
+ };
276
+ __decorateClass([
277
+ (0, import_core5.Input)()
278
+ ], ChessBoardComponent.prototype, "showAfkWarning", 2);
279
+ __decorateClass([
280
+ (0, import_core5.Input)()
281
+ ], ChessBoardComponent.prototype, "showCoords", 2);
282
+ __decorateClass([
283
+ (0, import_core5.Input)()
284
+ ], ChessBoardComponent.prototype, "showLastMove", 2);
285
+ __decorateClass([
286
+ (0, import_core5.Input)()
287
+ ], ChessBoardComponent.prototype, "showLegalMoves", 2);
288
+ __decorateClass([
289
+ (0, import_core5.Input)()
290
+ ], ChessBoardComponent.prototype, "orientation", 2);
221
291
  ChessBoardComponent = __decorateClass([
222
- (0, import_core4.Component)({
292
+ (0, import_core5.Component)({
223
293
  selector: "bg-chess-board",
224
294
  standalone: true,
225
295
  template: `
@@ -229,19 +299,25 @@ ChessBoardComponent = __decorateClass([
229
299
  `
230
300
  })
231
301
  ], ChessBoardComponent);
302
+
303
+ // src/components/chess/ChessMoveHistoryComponent.ts
304
+ var import_core6 = require("@angular/core");
232
305
  var ChessMoveHistoryComponent = class {
233
306
  };
234
307
  ChessMoveHistoryComponent = __decorateClass([
235
- (0, import_core4.Component)({
308
+ (0, import_core6.Component)({
236
309
  selector: "bg-chess-move-history",
237
310
  standalone: true,
238
311
  template: `<div data-role="move-history"></div>`
239
312
  })
240
313
  ], ChessMoveHistoryComponent);
314
+
315
+ // src/components/chess/ChessCapturedPiecesComponent.ts
316
+ var import_core7 = require("@angular/core");
241
317
  var ChessCapturedPiecesComponent = class {
242
318
  };
243
319
  ChessCapturedPiecesComponent = __decorateClass([
244
- (0, import_core4.Component)({
320
+ (0, import_core7.Component)({
245
321
  selector: "bg-chess-captured-pieces",
246
322
  standalone: true,
247
323
  template: `<div data-role="captured-pieces"></div>`
@@ -249,31 +325,33 @@ ChessCapturedPiecesComponent = __decorateClass([
249
325
  ], ChessCapturedPiecesComponent);
250
326
 
251
327
  // src/components/checkers/index.ts
252
- var import_core5 = require("@angular/core");
253
- var CheckersBoardComponent = class {
328
+ var import_core8 = require("@angular/core");
329
+ var CheckersBoardComponent = class extends EmbedBoardBase {
254
330
  constructor() {
255
- this.serverUrl = "https://api.beta-gamer.com";
256
- this.svc = (0, import_core5.inject)(BetaGamerService);
331
+ super(...arguments);
332
+ this.showAfkWarning = true;
333
+ this.showLastMove = true;
334
+ this.orientation = "auto";
257
335
  }
258
336
  get embedUrl() {
259
- return `${this.serverUrl}/embed/checkers`;
260
- }
261
- ngAfterViewInit() {
262
- const iframe = this.iframeRef?.nativeElement;
263
- if (!iframe) return;
264
- iframe.addEventListener("load", () => {
265
- iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
337
+ return this.serverUrl + "/embed/checkers" + this.buildQuery({
338
+ afkWarning: !this.showAfkWarning ? "0" : void 0,
339
+ showLastMove: !this.showLastMove ? "0" : void 0,
340
+ orientation: this.orientation !== "auto" ? this.orientation : void 0
266
341
  });
267
342
  }
268
343
  };
269
344
  __decorateClass([
270
- (0, import_core5.Input)()
271
- ], CheckersBoardComponent.prototype, "serverUrl", 2);
345
+ (0, import_core8.Input)()
346
+ ], CheckersBoardComponent.prototype, "showAfkWarning", 2);
347
+ __decorateClass([
348
+ (0, import_core8.Input)()
349
+ ], CheckersBoardComponent.prototype, "showLastMove", 2);
272
350
  __decorateClass([
273
- (0, import_core5.ViewChild)("iframe")
274
- ], CheckersBoardComponent.prototype, "iframeRef", 2);
351
+ (0, import_core8.Input)()
352
+ ], CheckersBoardComponent.prototype, "orientation", 2);
275
353
  CheckersBoardComponent = __decorateClass([
276
- (0, import_core5.Component)({
354
+ (0, import_core8.Component)({
277
355
  selector: "bg-checkers-board",
278
356
  standalone: true,
279
357
  template: `
@@ -284,32 +362,24 @@ CheckersBoardComponent = __decorateClass([
284
362
  })
285
363
  ], CheckersBoardComponent);
286
364
 
287
- // src/components/connect4/index.ts
288
- var import_core6 = require("@angular/core");
289
- var Connect4BoardComponent = class {
365
+ // src/components/connect4/Connect4BoardComponent.ts
366
+ var import_core9 = require("@angular/core");
367
+ var Connect4BoardComponent = class extends EmbedBoardBase {
290
368
  constructor() {
291
- this.serverUrl = "https://api.beta-gamer.com";
292
- this.svc = (0, import_core6.inject)(BetaGamerService);
369
+ super(...arguments);
370
+ this.showAfkWarning = true;
293
371
  }
294
372
  get embedUrl() {
295
- return `${this.serverUrl}/embed/connect4`;
296
- }
297
- ngAfterViewInit() {
298
- const iframe = this.iframeRef?.nativeElement;
299
- if (!iframe) return;
300
- iframe.addEventListener("load", () => {
301
- iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
373
+ return this.serverUrl + "/embed/connect4" + this.buildQuery({
374
+ afkWarning: !this.showAfkWarning ? "0" : void 0
302
375
  });
303
376
  }
304
377
  };
305
378
  __decorateClass([
306
- (0, import_core6.Input)()
307
- ], Connect4BoardComponent.prototype, "serverUrl", 2);
308
- __decorateClass([
309
- (0, import_core6.ViewChild)("iframe")
310
- ], Connect4BoardComponent.prototype, "iframeRef", 2);
379
+ (0, import_core9.Input)()
380
+ ], Connect4BoardComponent.prototype, "showAfkWarning", 2);
311
381
  Connect4BoardComponent = __decorateClass([
312
- (0, import_core6.Component)({
382
+ (0, import_core9.Component)({
313
383
  selector: "bg-connect4-board",
314
384
  standalone: true,
315
385
  template: `
@@ -319,9 +389,12 @@ Connect4BoardComponent = __decorateClass([
319
389
  `
320
390
  })
321
391
  ], Connect4BoardComponent);
392
+
393
+ // src/components/connect4/Connect4ScoreComponent.ts
394
+ var import_core10 = require("@angular/core");
322
395
  var Connect4ScoreComponent = class {
323
396
  constructor() {
324
- this.svc = (0, import_core6.inject)(BetaGamerService);
397
+ this.svc = (0, import_core10.inject)(BetaGamerService);
325
398
  }
326
399
  get scores() {
327
400
  const s = this.svc.gameState()?.scores ?? {};
@@ -329,7 +402,7 @@ var Connect4ScoreComponent = class {
329
402
  }
330
403
  };
331
404
  Connect4ScoreComponent = __decorateClass([
332
- (0, import_core6.Component)({
405
+ (0, import_core10.Component)({
333
406
  selector: "bg-connect4-score",
334
407
  standalone: true,
335
408
  template: `
@@ -343,31 +416,23 @@ Connect4ScoreComponent = __decorateClass([
343
416
  ], Connect4ScoreComponent);
344
417
 
345
418
  // src/components/tictactoe/index.ts
346
- var import_core7 = require("@angular/core");
347
- var TictactoeBoardComponent = class {
419
+ var import_core11 = require("@angular/core");
420
+ var TictactoeBoardComponent = class extends EmbedBoardBase {
348
421
  constructor() {
349
- this.serverUrl = "https://api.beta-gamer.com";
350
- this.svc = (0, import_core7.inject)(BetaGamerService);
422
+ super(...arguments);
423
+ this.showAfkWarning = true;
351
424
  }
352
425
  get embedUrl() {
353
- return `${this.serverUrl}/embed/tictactoe`;
354
- }
355
- ngAfterViewInit() {
356
- const iframe = this.iframeRef?.nativeElement;
357
- if (!iframe) return;
358
- iframe.addEventListener("load", () => {
359
- iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
426
+ return this.serverUrl + "/embed/tictactoe" + this.buildQuery({
427
+ afkWarning: !this.showAfkWarning ? "0" : void 0
360
428
  });
361
429
  }
362
430
  };
363
431
  __decorateClass([
364
- (0, import_core7.Input)()
365
- ], TictactoeBoardComponent.prototype, "serverUrl", 2);
366
- __decorateClass([
367
- (0, import_core7.ViewChild)("iframe")
368
- ], TictactoeBoardComponent.prototype, "iframeRef", 2);
432
+ (0, import_core11.Input)()
433
+ ], TictactoeBoardComponent.prototype, "showAfkWarning", 2);
369
434
  TictactoeBoardComponent = __decorateClass([
370
- (0, import_core7.Component)({
435
+ (0, import_core11.Component)({
371
436
  selector: "bg-tictactoe-board",
372
437
  standalone: true,
373
438
  template: `
@@ -378,12 +443,12 @@ TictactoeBoardComponent = __decorateClass([
378
443
  })
379
444
  ], TictactoeBoardComponent);
380
445
 
381
- // src/components/subway-runner/index.ts
382
- var import_core8 = require("@angular/core");
446
+ // src/components/subway-runner/SubwayRunnerGameComponent.ts
447
+ var import_core12 = require("@angular/core");
383
448
  var SubwayRunnerGameComponent = class {
384
449
  constructor() {
385
450
  this.serverUrl = "https://api.beta-gamer.com";
386
- this.svc = (0, import_core8.inject)(BetaGamerService);
451
+ this.svc = (0, import_core12.inject)(BetaGamerService);
387
452
  }
388
453
  get embedUrl() {
389
454
  return `${this.serverUrl}/embed/subway-runner`;
@@ -397,13 +462,13 @@ var SubwayRunnerGameComponent = class {
397
462
  }
398
463
  };
399
464
  __decorateClass([
400
- (0, import_core8.Input)()
465
+ (0, import_core12.Input)()
401
466
  ], SubwayRunnerGameComponent.prototype, "serverUrl", 2);
402
467
  __decorateClass([
403
- (0, import_core8.ViewChild)("iframe")
468
+ (0, import_core12.ViewChild)("iframe")
404
469
  ], SubwayRunnerGameComponent.prototype, "iframeRef", 2);
405
470
  SubwayRunnerGameComponent = __decorateClass([
406
- (0, import_core8.Component)({
471
+ (0, import_core12.Component)({
407
472
  selector: "bg-subway-runner",
408
473
  standalone: true,
409
474
  template: `
@@ -413,31 +478,37 @@ SubwayRunnerGameComponent = __decorateClass([
413
478
  `
414
479
  })
415
480
  ], SubwayRunnerGameComponent);
481
+
482
+ // src/components/subway-runner/SubwayRunnerScoreComponent.ts
483
+ var import_core13 = require("@angular/core");
416
484
  var SubwayRunnerScoreComponent = class {
417
485
  constructor() {
418
- this.svc = (0, import_core8.inject)(BetaGamerService);
486
+ this.svc = (0, import_core13.inject)(BetaGamerService);
419
487
  }
420
488
  get score() {
421
489
  return this.svc.gameState()?.score ?? 0;
422
490
  }
423
491
  };
424
492
  SubwayRunnerScoreComponent = __decorateClass([
425
- (0, import_core8.Component)({
493
+ (0, import_core13.Component)({
426
494
  selector: "bg-subway-runner-score",
427
495
  standalone: true,
428
496
  template: `<div data-role="score"><span data-role="score-value">{{ score }}</span></div>`
429
497
  })
430
498
  ], SubwayRunnerScoreComponent);
499
+
500
+ // src/components/subway-runner/SubwayRunnerLivesComponent.ts
501
+ var import_core14 = require("@angular/core");
431
502
  var SubwayRunnerLivesComponent = class {
432
503
  constructor() {
433
- this.svc = (0, import_core8.inject)(BetaGamerService);
504
+ this.svc = (0, import_core14.inject)(BetaGamerService);
434
505
  }
435
506
  get lives() {
436
507
  return this.svc.gameState()?.lives ?? 0;
437
508
  }
438
509
  };
439
510
  SubwayRunnerLivesComponent = __decorateClass([
440
- (0, import_core8.Component)({
511
+ (0, import_core14.Component)({
441
512
  selector: "bg-subway-runner-lives",
442
513
  standalone: true,
443
514
  template: `<div data-role="lives"><span data-role="lives-value">{{ lives }}</span></div>`