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