@mideind/netskrafl-react 1.0.1 → 1.0.2

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/esm/index.js CHANGED
@@ -1,6 +1,98 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import React, { useEffect } from 'react';
3
3
 
4
+ // Key for storing auth settings in sessionStorage
5
+ const AUTH_SETTINGS_KEY = "netskrafl_auth_settings";
6
+ // Save authentication settings to sessionStorage
7
+ const saveAuthSettings = (settings) => {
8
+ if (!settings) {
9
+ clearAuthSettings();
10
+ return;
11
+ }
12
+ try {
13
+ // Filter to only include properties defined in PersistedAuthSettings interface
14
+ const filteredSettings = {
15
+ userEmail: settings.userEmail, // Required field
16
+ };
17
+ // Only add optional fields if they are defined
18
+ if (settings.userId !== undefined)
19
+ filteredSettings.userId = settings.userId;
20
+ if (settings.userNick !== undefined)
21
+ filteredSettings.userNick = settings.userNick;
22
+ if (settings.firebaseAPIKey !== undefined)
23
+ filteredSettings.firebaseAPIKey = settings.firebaseAPIKey;
24
+ if (settings.beginner !== undefined)
25
+ filteredSettings.beginner = settings.beginner;
26
+ if (settings.fairPlay !== undefined)
27
+ filteredSettings.fairPlay = settings.fairPlay;
28
+ if (settings.ready !== undefined)
29
+ filteredSettings.ready = settings.ready;
30
+ if (settings.readyTimed !== undefined)
31
+ filteredSettings.readyTimed = settings.readyTimed;
32
+ // Only save if we have actual settings to persist
33
+ if (Object.keys(filteredSettings).length > 1) {
34
+ sessionStorage.setItem(AUTH_SETTINGS_KEY, JSON.stringify(filteredSettings));
35
+ }
36
+ else {
37
+ clearAuthSettings();
38
+ }
39
+ }
40
+ catch (error) {
41
+ // SessionStorage might be unavailable or full
42
+ console.warn("Could not save auth settings to sessionStorage:", error);
43
+ }
44
+ };
45
+ // Retrieve authentication settings from sessionStorage
46
+ const loadAuthSettings = () => {
47
+ try {
48
+ const stored = sessionStorage.getItem(AUTH_SETTINGS_KEY);
49
+ if (stored) {
50
+ return JSON.parse(stored);
51
+ }
52
+ }
53
+ catch (error) {
54
+ // SessionStorage might be unavailable or data might be corrupted
55
+ console.warn("Could not load auth settings from sessionStorage:", error);
56
+ }
57
+ return null;
58
+ };
59
+ // Clear authentication settings from sessionStorage
60
+ const clearAuthSettings = () => {
61
+ try {
62
+ sessionStorage.removeItem(AUTH_SETTINGS_KEY);
63
+ }
64
+ catch (error) {
65
+ console.warn("Could not clear auth settings from sessionStorage:", error);
66
+ }
67
+ };
68
+ // Apply persisted settings to a GlobalState object
69
+ const applyPersistedSettings = (state) => {
70
+ var _a, _b, _c, _d;
71
+ const persisted = loadAuthSettings();
72
+ if (!persisted) {
73
+ return state;
74
+ }
75
+ // CRITICAL SECURITY CHECK: Only apply persisted settings if they belong to the current user
76
+ // This prevents data leakage between different users in the same browser session
77
+ if (persisted.userEmail !== state.userEmail) {
78
+ // Different user detected - clear the old user's settings
79
+ clearAuthSettings();
80
+ return state;
81
+ }
82
+ // Apply persisted settings, but don't override values explicitly passed in props
83
+ return {
84
+ ...state,
85
+ // Only apply persisted values if current values are defaults
86
+ userId: state.userId || persisted.userId || state.userId,
87
+ userNick: state.userNick || persisted.userNick || state.userNick,
88
+ firebaseAPIKey: state.firebaseAPIKey || persisted.firebaseAPIKey || state.firebaseAPIKey,
89
+ beginner: (_a = persisted.beginner) !== null && _a !== void 0 ? _a : state.beginner,
90
+ fairPlay: (_b = persisted.fairPlay) !== null && _b !== void 0 ? _b : state.fairPlay,
91
+ ready: (_c = persisted.ready) !== null && _c !== void 0 ? _c : state.ready,
92
+ readyTimed: (_d = persisted.readyTimed) !== null && _d !== void 0 ? _d : state.readyTimed,
93
+ };
94
+ };
95
+
4
96
  const DEFAULT_STATE = {
5
97
  projectId: "netskrafl",
6
98
  firebaseAPIKey: "",
@@ -21,12 +113,12 @@ const DEFAULT_STATE = {
21
113
  loginUrl: "",
22
114
  loginMethod: "",
23
115
  newUser: false,
24
- beginner: false,
25
- fairPlay: true,
116
+ beginner: true,
117
+ fairPlay: false,
26
118
  plan: "", // Not a friend
27
119
  hasPaid: false,
28
- ready: false,
29
- readyTimed: false,
120
+ ready: true,
121
+ readyTimed: true,
30
122
  uiFullscreen: true,
31
123
  uiLandscape: false,
32
124
  runningLocal: false,
@@ -50,7 +142,9 @@ const makeGlobalState = (overrides) => {
50
142
  ...DEFAULT_STATE,
51
143
  ...overrides,
52
144
  };
53
- return { ...state, ...makeServerUrls(state.serverUrl, state.movesUrl) };
145
+ const stateWithUrls = { ...state, ...makeServerUrls(state.serverUrl, state.movesUrl) };
146
+ // Apply any persisted authentication settings from sessionStorage
147
+ return applyPersistedSettings(stateWithUrls);
54
148
  };
55
149
 
56
150
  function getDefaultExportFromCjs (x) {
@@ -27403,6 +27497,7 @@ class AuthenticationError extends Error {
27403
27497
  }
27404
27498
  // Internal function to ensure authentication
27405
27499
  const ensureAuthenticated = async (state) => {
27500
+ var _a, _b, _c, _d, _e, _f, _g, _h;
27406
27501
  // If login is already in progress, wait for it to complete
27407
27502
  if (authPromise) {
27408
27503
  await authPromise;
@@ -27415,11 +27510,37 @@ const ensureAuthenticated = async (state) => {
27415
27510
  if (result.status === "expired") {
27416
27511
  // Token has expired, notify the React component if callback is set
27417
27512
  state.tokenExpired && state.tokenExpired();
27513
+ // Clear any persisted settings since they're no longer valid
27514
+ clearAuthSettings();
27418
27515
  throw new Error("Token expired");
27419
27516
  }
27420
27517
  else if (result.status !== "success") {
27518
+ // Clear any persisted settings on auth failure
27519
+ clearAuthSettings();
27421
27520
  throw new Error(`Authentication failed: ${result.message || result.status}`);
27422
27521
  }
27522
+ // Update the user's ID to the internal one used by the backend and Firebase
27523
+ state.userId = result.user_id || state.userId;
27524
+ // Update the user's nickname
27525
+ state.userNick = result.nickname || state.userNick;
27526
+ // Use the server's Firebase API key, if provided
27527
+ state.firebaseAPIKey = result.firebase_api_key || state.firebaseAPIKey;
27528
+ // Load state flags and preferences
27529
+ state.beginner = (_b = (_a = result.prefs) === null || _a === void 0 ? void 0 : _a.beginner) !== null && _b !== void 0 ? _b : true;
27530
+ state.fairPlay = (_d = (_c = result.prefs) === null || _c === void 0 ? void 0 : _c.fairplay) !== null && _d !== void 0 ? _d : false;
27531
+ state.ready = (_f = (_e = result.prefs) === null || _e === void 0 ? void 0 : _e.ready) !== null && _f !== void 0 ? _f : true;
27532
+ state.readyTimed = (_h = (_g = result.prefs) === null || _g === void 0 ? void 0 : _g.ready_timed) !== null && _h !== void 0 ? _h : true;
27533
+ // Save the authentication settings to sessionStorage for persistence
27534
+ saveAuthSettings({
27535
+ userEmail: state.userEmail, // CRITICAL: Include email to validate ownership
27536
+ userId: state.userId,
27537
+ userNick: state.userNick,
27538
+ firebaseAPIKey: state.firebaseAPIKey,
27539
+ beginner: state.beginner,
27540
+ fairPlay: state.fairPlay,
27541
+ ready: state.ready,
27542
+ readyTimed: state.readyTimed,
27543
+ });
27423
27544
  // Success: Log in to Firebase with the token passed from the server
27424
27545
  await loginFirebase(state, result.firebase_token);
27425
27546
  }
@@ -35124,6 +35245,7 @@ class Model {
35124
35245
  state.userNick = user.nickname;
35125
35246
  state.beginner = user.beginner;
35126
35247
  state.fairPlay = user.fairplay;
35248
+ saveAuthSettings(state);
35127
35249
  }
35128
35250
  // Note that state.plan is updated via a Firebase notification
35129
35251
  // Give the game instance a chance to update its state
@@ -35158,41 +35280,22 @@ class Model {
35158
35280
  return false;
35159
35281
  }
35160
35282
  handleUserMessage(json, firstAttach) {
35161
- var _a;
35162
35283
  // Handle an incoming Firebase user message, i.e. a message
35163
35284
  // on the /user/[userid] path
35164
- if (firstAttach || !this.state)
35285
+ if (firstAttach || !this.state || !json)
35165
35286
  return;
35166
35287
  let redraw = false;
35167
- if (json.friend !== undefined) {
35168
- // Potential change of user friendship status
35169
- const newFriend = json.friend ? true : false;
35170
- if (this.user && this.user.friend != newFriend) {
35171
- this.user.friend = newFriend;
35172
- redraw = true;
35173
- }
35174
- }
35175
- if (json.plan !== undefined) {
35288
+ if (typeof json.plan === "string") {
35176
35289
  // Potential change of user subscription plan
35177
- if (this.state.plan != json.plan) {
35290
+ if (this.state.plan !== json.plan) {
35178
35291
  this.state.plan = json.plan;
35179
35292
  redraw = true;
35180
35293
  }
35181
- if (this.user && !this.user.friend && this.state.plan == "friend") {
35182
- // plan == "friend" implies that user.friend should be true
35183
- this.user.friend = true;
35184
- redraw = true;
35185
- }
35186
- if (this.state.plan == "" && ((_a = this.user) === null || _a === void 0 ? void 0 : _a.friend)) {
35187
- // Conversely, an empty plan string means that the user is not a friend
35188
- this.user.friend = false;
35189
- redraw = true;
35190
- }
35191
35294
  }
35192
35295
  if (json.hasPaid !== undefined) {
35193
35296
  // Potential change of payment status
35194
- const newHasPaid = (this.state.plan != "" && json.hasPaid) ? true : false;
35195
- if (this.state.hasPaid != newHasPaid) {
35297
+ const newHasPaid = (this.state.plan !== "" && json.hasPaid) ? true : false;
35298
+ if (this.state.hasPaid !== newHasPaid) {
35196
35299
  this.state.hasPaid = newHasPaid;
35197
35300
  redraw = true;
35198
35301
  }
@@ -35696,6 +35799,8 @@ class Actions {
35696
35799
  url: "/setuserpref",
35697
35800
  body: pref
35698
35801
  }); // No result required or expected
35802
+ // Update the persisted settings in sessionStorage
35803
+ saveAuthSettings(this.model.state);
35699
35804
  }
35700
35805
  catch (e) {
35701
35806
  // A future TODO might be to signal an error in the UI
@@ -35992,6 +36097,10 @@ const RiddleScore = {
35992
36097
  else {
35993
36098
  classes.push(".hot");
35994
36099
  }
36100
+ // Add celebration class if the player achieved the best possible score
36101
+ if (score >= riddle.bestPossibleScore) {
36102
+ classes.push(".celebrate");
36103
+ }
35995
36104
  }
35996
36105
  return m("div" + classes.join(""), m("span.gata-dagsins-legend", displayText));
35997
36106
  }
@@ -36333,6 +36442,84 @@ const GataDagsinsRightSide = {
36333
36442
  }
36334
36443
  };
36335
36444
 
36445
+ /*
36446
+
36447
+ gatadagsins-help.ts
36448
+
36449
+ Help dialog for Gáta Dagsins
36450
+
36451
+ Copyright (C) 2025 Miðeind ehf.
36452
+ Author: Vilhjálmur Þorsteinsson
36453
+
36454
+ The Creative Commons Attribution-NonCommercial 4.0
36455
+ International Public License (CC-BY-NC 4.0) applies to this software.
36456
+ For further information, see https://github.com/mideind/Netskrafl
36457
+
36458
+ */
36459
+ const GataDagsinsHelp = {
36460
+ view: (vnode) => {
36461
+ const closeHelp = vnode.attrs.onClose;
36462
+ return m(".modal-dialog.gatadagsins-help", m(".modal-content", [
36463
+ // Header with close button
36464
+ m(".modal-header", [
36465
+ m("h2", "Um Gátu dagsins"),
36466
+ m("button.close", {
36467
+ onclick: closeHelp,
36468
+ "aria-label": "Loka"
36469
+ }, m("span", { "aria-hidden": "true" }, "×"))
36470
+ ]),
36471
+ // Body with help content
36472
+ m(".modal-body", [
36473
+ m("p", "Gáta dagsins er dagleg krossgátuþraut, svipuð skrafli, þar sem þú reynir að finna " +
36474
+ "stigahæsta orðið sem hægt er að mynda með gefnum stöfum."),
36475
+ m("h3", "Hvernig á að spila"),
36476
+ m("ul", [
36477
+ m("li", "Þú færð borð með allmörgum stöfum sem þegar hafa verið lagðir."),
36478
+ m("li", "Neðst á skjánum eru stafaflísar sem þú getur notað til að mynda orð."),
36479
+ m("li", "Dragðu flísar á borðið til að mynda orð, annaðhvort lárétt eða lóðrétt."),
36480
+ m("li", "Orðin verða að tengjast við stafi sem fyrir eru á borðinu."),
36481
+ m("li", "Þú sérð jafnóðum hvort lögnin á borðinu er gild og hversu mörg stig hún gefur."),
36482
+ m("li", "Þú getur prófað eins mörg orð og þú vilt - besta skorið þitt er vistað."),
36483
+ ]),
36484
+ m("h3", "Stigagjöf"),
36485
+ m("p", "Þú færð stig fyrir hvern staf í orðinu, auk bónusstiga fyrir lengri orð:"),
36486
+ m("ul", [
36487
+ m("li", "Hver stafur gefur 1-10 stig eftir gildi hans"),
36488
+ m("li", "Orð sem nota allar 7 stafaflísarnar gefa 50 stiga bónus"),
36489
+ m("li", "Sumir reitir á borðinu tvöfalda eða þrefalda stafagildið"),
36490
+ m("li", "Sumir reitir tvöfalda eða þrefalda heildarorðagildið"),
36491
+ ]),
36492
+ m("h3", "Hitamælir"),
36493
+ m("p", "Hitamælirinn hægra megin (eða efst á farsímum) sýnir:"),
36494
+ m("ul", [
36495
+ m("li", m("strong", "Besta mögulega skor:"), " Hæstu stig sem hægt er að ná á þessu borði."),
36496
+ m("li", m("strong", "Besta skor dagsins:"), " Hæstu stig sem einhver leikmaður hefur náð í dag."),
36497
+ m("li", m("strong", "Þín bestu orð:"), " Orðin sem þú hefur lagt og stigin fyrir þau."),
36498
+ m("li", "Þú getur smellt á orð á hitamælinum til að fá þá lögn aftur á borðið."),
36499
+ ]),
36500
+ m("h3", "Ábendingar"),
36501
+ m("ul", [
36502
+ m("li", "Reyndu að nota dýra stafi (eins og X, Ý, Þ) á tvöföldunar- eða þreföldunarreitum."),
36503
+ m("li", "Lengri orð gefa mun fleiri stig vegna bónussins."),
36504
+ m("li", "Þú getur dregið allar flísar til baka með bláa endurkalls-hnappnum."),
36505
+ m("li", "Ný gáta birtist á hverjum nýjum degi - klukkan 00:00!"),
36506
+ ]),
36507
+ m("h3", "Um leikinn"),
36508
+ m("p", [
36509
+ "Gáta dagsins er systkini ",
36510
+ m("a", { href: "https://netskrafl.is", target: "_blank" }, "Netskrafls"),
36511
+ ", hins sívinsæla íslenska krossgátuleiks á netinu. ",
36512
+ "Leikurinn er þróaður af Miðeind ehf."
36513
+ ]),
36514
+ ]),
36515
+ // Footer with close button
36516
+ m(".modal-footer", m("button.btn.btn-primary", {
36517
+ onclick: closeHelp
36518
+ }, "Loka"))
36519
+ ]));
36520
+ }
36521
+ };
36522
+
36336
36523
  /*
36337
36524
 
36338
36525
  GataDagsins.ts
@@ -36401,47 +36588,65 @@ const currentMoveState = (riddle) => {
36401
36588
  }
36402
36589
  return { selectedMoves, bestMove };
36403
36590
  };
36404
- const GataDagsins$1 = {
36591
+ const GataDagsins$1 = () => {
36405
36592
  // A view of the Gáta Dagsins page
36406
- oninit: (vnode) => {
36407
- const { model, actions } = vnode.attrs.view;
36408
- const { riddle } = model;
36409
- if (!riddle) {
36410
- const { date, locale } = vnode.attrs;
36411
- // Initialize a fresh riddle object if it doesn't exist
36412
- actions.fetchRiddle(date, locale);
36593
+ let showHelp = false;
36594
+ return {
36595
+ oninit: (vnode) => {
36596
+ const { model, actions } = vnode.attrs.view;
36597
+ const { riddle } = model;
36598
+ if (!riddle) {
36599
+ const { date, locale } = vnode.attrs;
36600
+ // Initialize a fresh riddle object if it doesn't exist
36601
+ actions.fetchRiddle(date, locale);
36602
+ }
36603
+ // Initialize help dialog state
36604
+ showHelp = false;
36605
+ },
36606
+ view: (vnode) => {
36607
+ var _a;
36608
+ const { view } = vnode.attrs;
36609
+ const { model } = view;
36610
+ const { riddle } = model;
36611
+ const { selectedMoves, bestMove } = (riddle
36612
+ ? currentMoveState(riddle)
36613
+ : { selectedMoves: [], bestMove: undefined });
36614
+ const toggleHelp = () => {
36615
+ showHelp = !showHelp;
36616
+ m.redraw();
36617
+ };
36618
+ return m("div.drop-target", {
36619
+ id: "gatadagsins-background",
36620
+ }, [
36621
+ // The main content area
36622
+ riddle ? m(".gatadagsins-container", [
36623
+ // Main display area with flex layout
36624
+ m(".gatadagsins-main", [
36625
+ // Board and rack component (left side)
36626
+ m(GataDagsinsBoardAndRack, { view }),
36627
+ // Right-side component with scores and comparisons
36628
+ m(GataDagsinsRightSide, { view, selectedMoves, bestMove }),
36629
+ // Blank dialog
36630
+ riddle.askingForBlank
36631
+ ? m(BlankDialog, { game: riddle })
36632
+ : "",
36633
+ ])
36634
+ ]) : "",
36635
+ // The left margin elements: back button and info/help button
36636
+ // These elements appear after the main container for proper z-order
36637
+ // m(LeftLogo), // Currently no need for the logo for Gáta Dagsins
36638
+ // Show the Beginner component if the user is a beginner
36639
+ ((_a = model.state) === null || _a === void 0 ? void 0 : _a.beginner) ? m(Beginner, { view }) : "",
36640
+ // Custom Info button for GataDagsins that shows help dialog
36641
+ m(".info", { title: ts("Upplýsingar og hjálp") }, m("a.iconlink", { href: "#", onclick: (e) => { e.preventDefault(); toggleHelp(); } }, glyph("info-sign"))),
36642
+ // Help dialog and backdrop
36643
+ showHelp ? [
36644
+ m(".modal-backdrop", { onclick: (e) => { e.preventDefault(); } }),
36645
+ m(GataDagsinsHelp, { onClose: toggleHelp })
36646
+ ] : "",
36647
+ ]);
36413
36648
  }
36414
- },
36415
- view: (vnode) => {
36416
- const { view } = vnode.attrs;
36417
- const { model } = view;
36418
- const { riddle } = model;
36419
- const { selectedMoves, bestMove } = (riddle
36420
- ? currentMoveState(riddle)
36421
- : { selectedMoves: [], bestMove: undefined });
36422
- return m("div.drop-target", {
36423
- id: "gatadagsins-background",
36424
- }, [
36425
- // The main content area
36426
- riddle ? m(".gatadagsins-container", [
36427
- // Main display area with flex layout
36428
- m(".gatadagsins-main", [
36429
- // Board and rack component (left side)
36430
- m(GataDagsinsBoardAndRack, { view }),
36431
- // Right-side component with scores and comparisons
36432
- m(GataDagsinsRightSide, { view, selectedMoves, bestMove }),
36433
- // Blank dialog
36434
- riddle.askingForBlank
36435
- ? m(BlankDialog, { game: riddle })
36436
- : "",
36437
- ])
36438
- ]) : "",
36439
- // The left margin elements: back button and info/help button
36440
- // These elements appear after the main container for proper z-order
36441
- m(LeftLogo),
36442
- m(Info),
36443
- ]);
36444
- }
36649
+ };
36445
36650
  };
36446
36651
 
36447
36652
  /*