@beta-gamer/angular 0.1.2 → 0.1.4

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,34 +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";
167
- this.showAfkWarning = true;
179
+ this.onLeave = new EventEmitter();
168
180
  this.svc = inject3(BetaGamerService);
169
- }
170
- get embedUrl() {
171
- return this.showAfkWarning ? `${this.serverUrl}/embed/chess` : `${this.serverUrl}/embed/chess?afkWarning=0`;
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
+ };
172
188
  }
173
189
  ngAfterViewInit() {
190
+ window.addEventListener("message", this.messageHandler);
174
191
  const iframe = this.iframeRef?.nativeElement;
175
192
  if (!iframe) return;
176
193
  iframe.addEventListener("load", () => {
177
194
  iframe.contentWindow?.postMessage({ type: "bg:init", token: this.svc.token }, "*");
178
195
  });
179
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
+ }
180
207
  };
181
208
  __decorateClass([
182
209
  Input3()
183
- ], ChessBoardComponent.prototype, "serverUrl", 2);
210
+ ], EmbedBoardBase.prototype, "serverUrl", 2);
184
211
  __decorateClass([
185
- Input3()
186
- ], ChessBoardComponent.prototype, "showAfkWarning", 2);
212
+ Output()
213
+ ], EmbedBoardBase.prototype, "onLeave", 2);
187
214
  __decorateClass([
188
215
  ViewChild("iframe")
189
- ], 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);
190
256
  ChessBoardComponent = __decorateClass([
191
257
  Component3({
192
258
  selector: "bg-chess-board",
@@ -198,19 +264,25 @@ ChessBoardComponent = __decorateClass([
198
264
  `
199
265
  })
200
266
  ], ChessBoardComponent);
267
+
268
+ // src/components/chess/ChessMoveHistoryComponent.ts
269
+ import { Component as Component4 } from "@angular/core";
201
270
  var ChessMoveHistoryComponent = class {
202
271
  };
203
272
  ChessMoveHistoryComponent = __decorateClass([
204
- Component3({
273
+ Component4({
205
274
  selector: "bg-chess-move-history",
206
275
  standalone: true,
207
276
  template: `<div data-role="move-history"></div>`
208
277
  })
209
278
  ], ChessMoveHistoryComponent);
279
+
280
+ // src/components/chess/ChessCapturedPiecesComponent.ts
281
+ import { Component as Component5 } from "@angular/core";
210
282
  var ChessCapturedPiecesComponent = class {
211
283
  };
212
284
  ChessCapturedPiecesComponent = __decorateClass([
213
- Component3({
285
+ Component5({
214
286
  selector: "bg-chess-captured-pieces",
215
287
  standalone: true,
216
288
  template: `<div data-role="captured-pieces"></div>`
@@ -218,35 +290,33 @@ ChessCapturedPiecesComponent = __decorateClass([
218
290
  ], ChessCapturedPiecesComponent);
219
291
 
220
292
  // src/components/checkers/index.ts
221
- import { Component as Component4, Input as Input4, ViewChild as ViewChild2, inject as inject4 } from "@angular/core";
222
- var CheckersBoardComponent = class {
293
+ import { Component as Component6, Input as Input5 } from "@angular/core";
294
+ var CheckersBoardComponent = class extends EmbedBoardBase {
223
295
  constructor() {
224
- this.serverUrl = "https://api.beta-gamer.com";
296
+ super(...arguments);
225
297
  this.showAfkWarning = true;
226
- this.svc = inject4(BetaGamerService);
298
+ this.showLastMove = true;
299
+ this.orientation = "auto";
227
300
  }
228
301
  get embedUrl() {
229
- return this.showAfkWarning ? `${this.serverUrl}/embed/checkers` : `${this.serverUrl}/embed/checkers?afkWarning=0`;
230
- }
231
- ngAfterViewInit() {
232
- const iframe = this.iframeRef?.nativeElement;
233
- if (!iframe) return;
234
- iframe.addEventListener("load", () => {
235
- 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
236
306
  });
237
307
  }
238
308
  };
239
309
  __decorateClass([
240
- Input4()
241
- ], CheckersBoardComponent.prototype, "serverUrl", 2);
242
- __decorateClass([
243
- Input4()
310
+ Input5()
244
311
  ], CheckersBoardComponent.prototype, "showAfkWarning", 2);
245
312
  __decorateClass([
246
- ViewChild2("iframe")
247
- ], CheckersBoardComponent.prototype, "iframeRef", 2);
313
+ Input5()
314
+ ], CheckersBoardComponent.prototype, "showLastMove", 2);
315
+ __decorateClass([
316
+ Input5()
317
+ ], CheckersBoardComponent.prototype, "orientation", 2);
248
318
  CheckersBoardComponent = __decorateClass([
249
- Component4({
319
+ Component6({
250
320
  selector: "bg-checkers-board",
251
321
  standalone: true,
252
322
  template: `
@@ -257,36 +327,24 @@ CheckersBoardComponent = __decorateClass([
257
327
  })
258
328
  ], CheckersBoardComponent);
259
329
 
260
- // src/components/connect4/index.ts
261
- import { Component as Component5, Input as Input5, ViewChild as ViewChild3, inject as inject5 } from "@angular/core";
262
- 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 {
263
333
  constructor() {
264
- this.serverUrl = "https://api.beta-gamer.com";
334
+ super(...arguments);
265
335
  this.showAfkWarning = true;
266
- this.svc = inject5(BetaGamerService);
267
336
  }
268
337
  get embedUrl() {
269
- return this.showAfkWarning ? `${this.serverUrl}/embed/connect4` : `${this.serverUrl}/embed/connect4?afkWarning=0`;
270
- }
271
- ngAfterViewInit() {
272
- const iframe = this.iframeRef?.nativeElement;
273
- if (!iframe) return;
274
- iframe.addEventListener("load", () => {
275
- 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
276
340
  });
277
341
  }
278
342
  };
279
343
  __decorateClass([
280
- Input5()
281
- ], Connect4BoardComponent.prototype, "serverUrl", 2);
282
- __decorateClass([
283
- Input5()
344
+ Input6()
284
345
  ], Connect4BoardComponent.prototype, "showAfkWarning", 2);
285
- __decorateClass([
286
- ViewChild3("iframe")
287
- ], Connect4BoardComponent.prototype, "iframeRef", 2);
288
346
  Connect4BoardComponent = __decorateClass([
289
- Component5({
347
+ Component7({
290
348
  selector: "bg-connect4-board",
291
349
  standalone: true,
292
350
  template: `
@@ -296,9 +354,12 @@ Connect4BoardComponent = __decorateClass([
296
354
  `
297
355
  })
298
356
  ], Connect4BoardComponent);
357
+
358
+ // src/components/connect4/Connect4ScoreComponent.ts
359
+ import { Component as Component8, inject as inject4 } from "@angular/core";
299
360
  var Connect4ScoreComponent = class {
300
361
  constructor() {
301
- this.svc = inject5(BetaGamerService);
362
+ this.svc = inject4(BetaGamerService);
302
363
  }
303
364
  get scores() {
304
365
  const s = this.svc.gameState()?.scores ?? {};
@@ -306,7 +367,7 @@ var Connect4ScoreComponent = class {
306
367
  }
307
368
  };
308
369
  Connect4ScoreComponent = __decorateClass([
309
- Component5({
370
+ Component8({
310
371
  selector: "bg-connect4-score",
311
372
  standalone: true,
312
373
  template: `
@@ -320,35 +381,23 @@ Connect4ScoreComponent = __decorateClass([
320
381
  ], Connect4ScoreComponent);
321
382
 
322
383
  // src/components/tictactoe/index.ts
323
- import { Component as Component6, Input as Input6, ViewChild as ViewChild4, inject as inject6 } from "@angular/core";
324
- var TictactoeBoardComponent = class {
384
+ import { Component as Component9, Input as Input7 } from "@angular/core";
385
+ var TictactoeBoardComponent = class extends EmbedBoardBase {
325
386
  constructor() {
326
- this.serverUrl = "https://api.beta-gamer.com";
387
+ super(...arguments);
327
388
  this.showAfkWarning = true;
328
- this.svc = inject6(BetaGamerService);
329
389
  }
330
390
  get embedUrl() {
331
- return this.showAfkWarning ? `${this.serverUrl}/embed/tictactoe` : `${this.serverUrl}/embed/tictactoe?afkWarning=0`;
332
- }
333
- ngAfterViewInit() {
334
- const iframe = this.iframeRef?.nativeElement;
335
- if (!iframe) return;
336
- iframe.addEventListener("load", () => {
337
- 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
338
393
  });
339
394
  }
340
395
  };
341
396
  __decorateClass([
342
- Input6()
343
- ], TictactoeBoardComponent.prototype, "serverUrl", 2);
344
- __decorateClass([
345
- Input6()
397
+ Input7()
346
398
  ], TictactoeBoardComponent.prototype, "showAfkWarning", 2);
347
- __decorateClass([
348
- ViewChild4("iframe")
349
- ], TictactoeBoardComponent.prototype, "iframeRef", 2);
350
399
  TictactoeBoardComponent = __decorateClass([
351
- Component6({
400
+ Component9({
352
401
  selector: "bg-tictactoe-board",
353
402
  standalone: true,
354
403
  template: `
@@ -359,12 +408,12 @@ TictactoeBoardComponent = __decorateClass([
359
408
  })
360
409
  ], TictactoeBoardComponent);
361
410
 
362
- // src/components/subway-runner/index.ts
363
- 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";
364
413
  var SubwayRunnerGameComponent = class {
365
414
  constructor() {
366
415
  this.serverUrl = "https://api.beta-gamer.com";
367
- this.svc = inject7(BetaGamerService);
416
+ this.svc = inject5(BetaGamerService);
368
417
  }
369
418
  get embedUrl() {
370
419
  return `${this.serverUrl}/embed/subway-runner`;
@@ -378,13 +427,13 @@ var SubwayRunnerGameComponent = class {
378
427
  }
379
428
  };
380
429
  __decorateClass([
381
- Input7()
430
+ Input8()
382
431
  ], SubwayRunnerGameComponent.prototype, "serverUrl", 2);
383
432
  __decorateClass([
384
- ViewChild5("iframe")
433
+ ViewChild2("iframe")
385
434
  ], SubwayRunnerGameComponent.prototype, "iframeRef", 2);
386
435
  SubwayRunnerGameComponent = __decorateClass([
387
- Component7({
436
+ Component10({
388
437
  selector: "bg-subway-runner",
389
438
  standalone: true,
390
439
  template: `
@@ -394,21 +443,27 @@ SubwayRunnerGameComponent = __decorateClass([
394
443
  `
395
444
  })
396
445
  ], SubwayRunnerGameComponent);
446
+
447
+ // src/components/subway-runner/SubwayRunnerScoreComponent.ts
448
+ import { Component as Component11, inject as inject6 } from "@angular/core";
397
449
  var SubwayRunnerScoreComponent = class {
398
450
  constructor() {
399
- this.svc = inject7(BetaGamerService);
451
+ this.svc = inject6(BetaGamerService);
400
452
  }
401
453
  get score() {
402
454
  return this.svc.gameState()?.score ?? 0;
403
455
  }
404
456
  };
405
457
  SubwayRunnerScoreComponent = __decorateClass([
406
- Component7({
458
+ Component11({
407
459
  selector: "bg-subway-runner-score",
408
460
  standalone: true,
409
461
  template: `<div data-role="score"><span data-role="score-value">{{ score }}</span></div>`
410
462
  })
411
463
  ], SubwayRunnerScoreComponent);
464
+
465
+ // src/components/subway-runner/SubwayRunnerLivesComponent.ts
466
+ import { Component as Component12, inject as inject7 } from "@angular/core";
412
467
  var SubwayRunnerLivesComponent = class {
413
468
  constructor() {
414
469
  this.svc = inject7(BetaGamerService);
@@ -418,7 +473,7 @@ var SubwayRunnerLivesComponent = class {
418
473
  }
419
474
  };
420
475
  SubwayRunnerLivesComponent = __decorateClass([
421
- Component7({
476
+ Component12({
422
477
  selector: "bg-subway-runner-lives",
423
478
  standalone: true,
424
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.2",
3
+ "version": "0.1.4",
4
4
  "description": "Angular SDK for Beta Gamer GaaS — composable game components",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",