@variamosple/variamos-components 0.3.0 → 0.4.1

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/README.md CHANGED
@@ -16,6 +16,8 @@ import { Header, SessionProvider } from "@variamosple/variamos-components";
16
16
 
17
17
  The user menu of the header has the class `variamos-header-options`; its id is generated, so tests and styles should use the class.
18
18
 
19
+ The session lives in the httpOnly `authToken` cookie of the admin service: requests must send it (`credentials: "include"`, or `withCredentials` with axios). Since 0.4.0 the library never keeps the token in `localStorage`, and `SessionProvider` removes the copy that older versions kept there; a front end should not add an `Authorization` header from that storage.
20
+
19
21
  ## Development
20
22
 
21
23
  Requires Node 26 (`.nvmrc`, `.mise.toml`). `npm install` sets up the Git hooks: Biome and cspell on the staged files before a commit, the conventional commit format, and typecheck, lint and tests before a push.
@@ -1,96 +1,101 @@
1
1
  import { jsxs as l, jsx as e } from "react/jsx-runtime";
2
- import { useState as a, useEffect as N } from "react";
3
- import { Modal as o, Form as t, Alert as k, Button as y } from "react-bootstrap";
4
- const G = ({
5
- show: i,
6
- onHide: d,
7
- onSubmit: C,
8
- categories: n,
9
- isSubmitting: c,
10
- errorMessage: s
2
+ import { useState as o, useEffect as x } from "react";
3
+ import { Modal as i, Form as r, Alert as k, Button as B } from "react-bootstrap";
4
+ import { bugFormErrors as F, TITLE_MAX_LENGTH as D } from "./bugFormErrors.js";
5
+ const A = ({
6
+ show: c,
7
+ onHide: p,
8
+ onSubmit: N,
9
+ categories: s,
10
+ isSubmitting: u,
11
+ errorMessage: h
11
12
  }) => {
12
- const [u, h] = a(""), [p, m] = a(""), [g, b] = a(""), [F, f] = a(null), [x, v] = a(!1);
13
- N(() => {
14
- i && (h(""), m(""), b(n[0] || "Other"), f(null), v(!1));
15
- }, [i, n]);
16
- const B = (r) => {
17
- if (r.preventDefault(), r.currentTarget.checkValidity() === !1) {
18
- r.stopPropagation(), v(!0);
13
+ const [n, m] = o(""), [a, b] = o(""), [g, v] = o(""), [I, f] = o(null), [L, C] = o(!1), d = L ? F({ title: n, description: a }) : {};
14
+ x(() => {
15
+ c && (m(""), b(""), v(s[0] || "Other"), f(null), C(!1));
16
+ }, [c, s]);
17
+ const T = (t) => {
18
+ t.preventDefault(), C(!0);
19
+ const y = F({ title: n, description: a });
20
+ if (y.title || y.description) {
21
+ t.stopPropagation();
19
22
  return;
20
23
  }
21
- C({ title: u, description: p, category: g }, F ?? void 0);
24
+ N({ title: n, description: a, category: g }, I ?? void 0);
22
25
  };
23
26
  return /* @__PURE__ */ l(
24
- o,
27
+ i,
25
28
  {
26
- show: i,
27
- onHide: d,
29
+ show: c,
30
+ onHide: p,
28
31
  backdrop: "static",
29
32
  keyboard: !1,
30
33
  centered: !0,
31
34
  children: [
32
- /* @__PURE__ */ e(o.Header, { closeButton: !0, children: /* @__PURE__ */ e(o.Title, { children: "Report a New Bug" }) }),
33
- /* @__PURE__ */ l(t, { noValidate: !0, validated: x, onSubmit: B, children: [
34
- /* @__PURE__ */ l(o.Body, { children: [
35
- s ? /* @__PURE__ */ e(k, { variant: "danger", role: "alert", children: s }) : null,
36
- /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugTitle", children: [
37
- /* @__PURE__ */ e(t.Label, { children: "Title *" }),
35
+ /* @__PURE__ */ e(i.Header, { closeButton: !0, children: /* @__PURE__ */ e(i.Title, { children: "Report a New Bug" }) }),
36
+ /* @__PURE__ */ l(r, { noValidate: !0, onSubmit: T, children: [
37
+ /* @__PURE__ */ l(i.Body, { children: [
38
+ h ? /* @__PURE__ */ e(k, { variant: "danger", role: "alert", children: h }) : null,
39
+ /* @__PURE__ */ l(r.Group, { className: "mb-3", controlId: "bugTitle", children: [
40
+ /* @__PURE__ */ e(r.Label, { children: "Title *" }),
38
41
  /* @__PURE__ */ e(
39
- t.Control,
42
+ r.Control,
40
43
  {
41
44
  required: !0,
42
45
  type: "text",
43
46
  placeholder: "e.g. Build error on auth routes",
44
- value: u,
45
- onChange: (r) => h(r.target.value),
46
- maxLength: 100
47
+ value: n,
48
+ onChange: (t) => m(t.target.value),
49
+ maxLength: D,
50
+ isInvalid: !!d.title
47
51
  }
48
52
  ),
49
- /* @__PURE__ */ e(t.Control.Feedback, { type: "invalid", children: "Title is required (max 100 characters)" })
53
+ /* @__PURE__ */ e(r.Control.Feedback, { type: "invalid", children: d.title })
50
54
  ] }),
51
- /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugDescription", children: [
52
- /* @__PURE__ */ e(t.Label, { children: "Description *" }),
55
+ /* @__PURE__ */ l(r.Group, { className: "mb-3", controlId: "bugDescription", children: [
56
+ /* @__PURE__ */ e(r.Label, { children: "Description *" }),
53
57
  /* @__PURE__ */ e(
54
- t.Control,
58
+ r.Control,
55
59
  {
56
60
  required: !0,
57
61
  as: "textarea",
58
62
  rows: 4,
59
63
  placeholder: "Describe step-by-step how to reproduce the bug...",
60
- value: p,
61
- onChange: (r) => m(r.target.value)
64
+ value: a,
65
+ onChange: (t) => b(t.target.value),
66
+ isInvalid: !!d.description
62
67
  }
63
68
  ),
64
- /* @__PURE__ */ e(t.Control.Feedback, { type: "invalid", children: "Description is required" })
69
+ /* @__PURE__ */ e(r.Control.Feedback, { type: "invalid", children: d.description })
65
70
  ] }),
66
- /* @__PURE__ */ e("div", { className: "row", children: /* @__PURE__ */ e("div", { className: "col-md-12", children: /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugCategory", children: [
67
- /* @__PURE__ */ e(t.Label, { children: "Category *" }),
71
+ /* @__PURE__ */ e("div", { className: "row", children: /* @__PURE__ */ e("div", { className: "col-md-12", children: /* @__PURE__ */ l(r.Group, { className: "mb-3", controlId: "bugCategory", children: [
72
+ /* @__PURE__ */ e(r.Label, { children: "Category *" }),
68
73
  /* @__PURE__ */ e(
69
- t.Select,
74
+ r.Select,
70
75
  {
71
76
  required: !0,
72
77
  value: g,
73
- onChange: (r) => b(r.target.value),
74
- children: n.map((r) => /* @__PURE__ */ e("option", { value: r, children: r }, r))
78
+ onChange: (t) => v(t.target.value),
79
+ children: s.map((t) => /* @__PURE__ */ e("option", { value: t, children: t }, t))
75
80
  }
76
81
  )
77
82
  ] }) }) }),
78
- /* @__PURE__ */ l(t.Group, { className: "mb-3", controlId: "bugFile", children: [
79
- /* @__PURE__ */ e(t.Label, { children: "Attachment (Optional)" }),
83
+ /* @__PURE__ */ l(r.Group, { className: "mb-3", controlId: "bugFile", children: [
84
+ /* @__PURE__ */ e(r.Label, { children: "Attachment (Optional)" }),
80
85
  /* @__PURE__ */ e(
81
- t.Control,
86
+ r.Control,
82
87
  {
83
88
  type: "file",
84
- onChange: (r) => {
85
- r.target.files && r.target.files.length > 0 && f(r.target.files[0]);
89
+ onChange: (t) => {
90
+ t.target.files && t.target.files.length > 0 && f(t.target.files[0]);
86
91
  }
87
92
  }
88
93
  )
89
94
  ] })
90
95
  ] }),
91
- /* @__PURE__ */ l(o.Footer, { children: [
92
- /* @__PURE__ */ e(y, { variant: "secondary", onClick: d, disabled: c, children: "Cancel" }),
93
- /* @__PURE__ */ e(y, { variant: "primary", type: "submit", disabled: c, children: c ? "Creating..." : "Report Bug" })
96
+ /* @__PURE__ */ l(i.Footer, { children: [
97
+ /* @__PURE__ */ e(B, { variant: "secondary", onClick: p, disabled: u, children: "Cancel" }),
98
+ /* @__PURE__ */ e(B, { variant: "primary", type: "submit", disabled: u, children: u ? "Creating..." : "Report Bug" })
94
99
  ] })
95
100
  ] })
96
101
  ]
@@ -98,5 +103,5 @@ const G = ({
98
103
  );
99
104
  };
100
105
  export {
101
- G as BugFormModal
106
+ A as BugFormModal
102
107
  };
@@ -1,10 +1,10 @@
1
- import { jsxs as f, Fragment as T, jsx as r } from "react/jsx-runtime";
2
- import { useId as k, useState as m, useCallback as v, useEffect as C, useMemo as O } from "react";
1
+ import { jsxs as f, Fragment as v, jsx as r } from "react/jsx-runtime";
2
+ import { useId as C, useState as m, useCallback as O, useEffect as T, useMemo as U } from "react";
3
3
  import { NavDropdown as i } from "react-bootstrap";
4
- import { Events as h } from "../../Common/Events.js";
5
- import { isAllowedFor as U } from "../../Common/isAllowedFor.js";
4
+ import { Events as b } from "../../Common/Events.js";
5
+ import { isAllowedFor as j } from "../../Common/isAllowedFor.js";
6
6
  import "../../Context/AnalyticsContext.js";
7
- import { useMenu as j } from "../../Context/MenuContext.js";
7
+ import { useMenu as k } from "../../Context/MenuContext.js";
8
8
  import "../../Context/NotificationContext.js";
9
9
  import { useRouter as x } from "../../Context/RouterContext.js";
10
10
  import { useSession as D } from "../../Context/SessionContext.js";
@@ -20,66 +20,65 @@ const c = {}, y = [
20
20
  "Account/Security",
21
21
  "Other"
22
22
  ], $ = ({
23
- adminApiUrl: b
23
+ adminApiUrl: h
24
24
  }) => {
25
- const { user: o, isAuthenticated: n, logout: S } = D(), { options: d } = j(), { navigate: M } = x(), I = k(), [B, s] = m(!1), [E, p] = m(!1), [A, l] = m(null), w = b || (c == null ? void 0 : c.VITE_ADMIN_API_URL) || "/v1", e = v(() => {
25
+ const { user: t, isAuthenticated: n, logout: M } = D(), { options: d } = k(), { navigate: S } = x(), B = C(), [E, s] = m(!1), [I, p] = m(!1), [A, l] = m(null), w = h || (c == null ? void 0 : c.VITE_ADMIN_API_URL) || "/v1", e = O(() => {
26
26
  l(null), s(!0);
27
27
  }, []);
28
- C(() => (h.subscribe("openReportBugModal", e), () => {
29
- h.unsubscribe(
28
+ T(() => (b.subscribe("openReportBugModal", e), () => {
29
+ b.unsubscribe(
30
30
  "openReportBugModal",
31
31
  e
32
32
  );
33
33
  }), [e]);
34
- const R = async (t, u) => {
34
+ const R = async (o, u) => {
35
35
  p(!0), l(null);
36
- const g = await N(t, u, {
36
+ const g = await N(o, u, {
37
37
  adminApiUrl: w,
38
- reporterEmail: o == null ? void 0 : o.email,
39
- authToken: localStorage.getItem("authToken")
38
+ reporterEmail: t == null ? void 0 : t.email
40
39
  });
41
40
  p(!1), g ? l(g) : s(!1);
42
- }, a = O(
43
- () => (d ?? []).filter((t) => U(t, o)),
44
- [d, o]
41
+ }, a = U(
42
+ () => (d ?? []).filter((o) => j(o, t)),
43
+ [d, t]
45
44
  );
46
- return a.length === 0 && !n ? null : /* @__PURE__ */ f(T, { children: [
45
+ return a.length === 0 && !n ? null : /* @__PURE__ */ f(v, { children: [
47
46
  /* @__PURE__ */ f(
48
47
  i,
49
48
  {
50
- title: (o == null ? void 0 : o.name) || "Options",
51
- id: I,
49
+ title: (t == null ? void 0 : t.name) || "Options",
50
+ id: B,
52
51
  className: "variamos-header-options",
53
52
  children: [
54
- a.map((t) => /* @__PURE__ */ r(
53
+ a.map((o) => /* @__PURE__ */ r(
55
54
  i.Item,
56
55
  {
57
56
  onClick: () => {
58
- if (t.location.endsWith("#report-bug")) {
57
+ if (o.location.endsWith("#report-bug")) {
59
58
  e();
60
59
  return;
61
60
  }
62
- M(t.location, {
63
- target: F(t.target)
61
+ S(o.location, {
62
+ target: F(o.target)
64
63
  });
65
64
  },
66
- children: t.title
65
+ children: o.title
67
66
  },
68
- t.title
67
+ o.title
69
68
  )),
70
69
  n && a.length > 0 ? /* @__PURE__ */ r(i.Divider, {}) : null,
71
- n ? /* @__PURE__ */ r(i.Item, { onClick: S, children: "Logout" }) : null
70
+ n ? /* @__PURE__ */ r(i.Item, { onClick: M, children: "Logout" }) : null
72
71
  ]
73
72
  }
74
73
  ),
75
74
  /* @__PURE__ */ r(
76
75
  L,
77
76
  {
78
- show: B,
77
+ show: E,
79
78
  onHide: () => s(!1),
80
- onSubmit: (t, u) => void R(t, u),
79
+ onSubmit: (o, u) => void R(o, u),
81
80
  categories: y,
82
- isSubmitting: E,
81
+ isSubmitting: I,
83
82
  errorMessage: A
84
83
  }
85
84
  )
@@ -0,0 +1,11 @@
1
+ export interface BugFormFields {
2
+ title: string;
3
+ description: string;
4
+ }
5
+ export declare const TITLE_MAX_LENGTH = 100;
6
+ /**
7
+ * The message of each invalid field, or nothing for a valid one. The form
8
+ * shows them only after a submission attempt, and writes no message text
9
+ * otherwise, so that no style of the host application can show it early.
10
+ */
11
+ export declare function bugFormErrors({ title, description, }: BugFormFields): Partial<Record<keyof BugFormFields, string>>;
@@ -0,0 +1,12 @@
1
+ const t = 100;
2
+ function T({
3
+ title: i,
4
+ description: e
5
+ }) {
6
+ const r = {};
7
+ return (i.trim() === "" || i.length > 100) && (r.title = "Title is required (max 100 characters)"), e.trim() === "" && (r.description = "Description is required"), r;
8
+ }
9
+ export {
10
+ t as TITLE_MAX_LENGTH,
11
+ T as bugFormErrors
12
+ };
@@ -6,10 +6,9 @@ export interface BugReport {
6
6
  export interface BugReportSender {
7
7
  adminApiUrl: string;
8
8
  reporterEmail?: string;
9
- authToken?: string | null;
10
9
  fetchImpl?: typeof fetch;
11
10
  }
12
11
  /** The bug route, next to the versioned API: ".../v1" and ".../" give "/bugs". */
13
12
  export declare const bugReportUrl: (adminApiUrl: string) => string;
14
13
  /** Sends the report; resolves to null once sent, or to the message to show. */
15
- export declare function submitBugReport(report: BugReport, file: File | undefined, { adminApiUrl, reporterEmail, authToken, fetchImpl }: BugReportSender): Promise<string | null>;
14
+ export declare function submitBugReport(report: BugReport, file: File | undefined, { adminApiUrl, reporterEmail, fetchImpl }: BugReportSender): Promise<string | null>;
@@ -1,23 +1,22 @@
1
- import { fieldOf as p } from "../../Common/fieldOf.js";
2
- const c = "Failed to submit bug. Please try again.", u = (t) => `${t.replace(/\/v1$/, "").replace(/\/$/, "")}/bugs`;
3
- async function f(t, a, { adminApiUrl: d, reporterEmail: n, authToken: i, fetchImpl: l = fetch }) {
1
+ import { fieldOf as s } from "../../Common/fieldOf.js";
2
+ const p = "Failed to submit bug. Please try again.", l = (t) => `${t.replace(/\/v1$/, "").replace(/\/$/, "")}/bugs`;
3
+ async function m(t, n, { adminApiUrl: c, reporterEmail: a, fetchImpl: d = fetch }) {
4
4
  const e = new FormData();
5
- e.append("title", t.title), e.append("description", t.description), e.append("priority", "medium"), e.append("category", t.category), n && e.append("reporterEmail", n), a && e.append("file", a);
5
+ e.append("title", t.title), e.append("description", t.description), e.append("priority", "medium"), e.append("category", t.category), a && e.append("reporterEmail", a), n && e.append("file", n);
6
6
  try {
7
- const r = await l(u(d), {
7
+ const r = await d(l(c), {
8
8
  method: "POST",
9
- headers: i ? { Authorization: `Bearer ${i}` } : {},
10
9
  body: e,
11
10
  credentials: "include"
12
- }), s = await r.json();
13
- if (r.ok && !p(s, "errorCode")) return null;
14
- const o = p(s, "message");
15
- return typeof o == "string" && o !== "" ? o : c;
11
+ }), i = await r.json();
12
+ if (r.ok && !s(i, "errorCode")) return null;
13
+ const o = s(i, "message");
14
+ return typeof o == "string" && o !== "" ? o : p;
16
15
  } catch (r) {
17
- return console.error(r), c;
16
+ return console.error(r), p;
18
17
  }
19
18
  }
20
19
  export {
21
- u as bugReportUrl,
22
- f as submitBugReport
20
+ l as bugReportUrl,
21
+ m as submitBugReport
23
22
  };
@@ -1,6 +1,6 @@
1
1
  import { jsx as ve } from "react/jsx-runtime";
2
- import { createContext as Ee, useMemo as P, useEffect as ke, useContext as Te } from "react";
3
- import { createAccessTokenProvider as Ae, accessTokenUrl as Oe } from "./notificationAccessToken.js";
2
+ import { createContext as Ee, useMemo as P, useEffect as ke, useContext as Ae } from "react";
3
+ import { createAccessTokenProvider as Te, accessTokenUrl as Oe } from "./notificationAccessToken.js";
4
4
  import { createNotificationApi as Re, notificationItemOf as Ce } from "./notificationApi.js";
5
5
  import { useSession as Se } from "./SessionContext.js";
6
6
  import { useNotificationList as Be, useNotificationChanges as Ne, useNotificationPreferences as xe } from "./useNotificationState.js";
@@ -116,10 +116,10 @@ function Ue() {
116
116
  });
117
117
  }
118
118
  let I;
119
- function T(n) {
119
+ function A(n) {
120
120
  return n.reduce((e, t) => e + t.length, 0);
121
121
  }
122
- function A(n, e) {
122
+ function T(n, e) {
123
123
  if (n[0].length === e)
124
124
  return n.shift();
125
125
  const t = new Uint8Array(e);
@@ -136,28 +136,28 @@ function Ve(n, e) {
136
136
  transform(o, c) {
137
137
  for (t.push(o); ; ) {
138
138
  if (s === 0) {
139
- if (T(t) < 1)
139
+ if (A(t) < 1)
140
140
  break;
141
- const h = A(t, 1);
141
+ const h = T(t, 1);
142
142
  r = (h[0] & 128) === 128, i = h[0] & 127, i < 126 ? s = 3 : i === 126 ? s = 1 : s = 2;
143
143
  } else if (s === 1) {
144
- if (T(t) < 2)
144
+ if (A(t) < 2)
145
145
  break;
146
- const h = A(t, 2);
146
+ const h = T(t, 2);
147
147
  i = new DataView(h.buffer, h.byteOffset, h.length).getUint16(0), s = 3;
148
148
  } else if (s === 2) {
149
- if (T(t) < 8)
149
+ if (A(t) < 8)
150
150
  break;
151
- const h = A(t, 8), l = new DataView(h.buffer, h.byteOffset, h.length), f = l.getUint32(0);
151
+ const h = T(t, 8), l = new DataView(h.buffer, h.byteOffset, h.length), f = l.getUint32(0);
152
152
  if (f > Math.pow(2, 21) - 1) {
153
153
  c.enqueue(F);
154
154
  break;
155
155
  }
156
156
  i = f * Math.pow(2, 32) + l.getUint32(4), s = 3;
157
157
  } else {
158
- if (T(t) < i)
158
+ if (A(t) < i)
159
159
  break;
160
- const h = A(t, i);
160
+ const h = T(t, i);
161
161
  c.enqueue(W(r ? h : I.decode(h), e)), s = 0;
162
162
  }
163
163
  if (i === 0 || i > n) {
@@ -1422,7 +1422,7 @@ const kt = Number.isInteger || function(n) {
1422
1422
  function re(n) {
1423
1423
  return Object.prototype.toString.call(n) === "[object Object]";
1424
1424
  }
1425
- const Tt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1425
+ const At = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1426
1426
  __proto__: null,
1427
1427
  Decoder: z,
1428
1428
  Encoder: vt,
@@ -1435,7 +1435,7 @@ function y(n, e, t) {
1435
1435
  n.off(e, t);
1436
1436
  };
1437
1437
  }
1438
- const At = Object.freeze({
1438
+ const Tt = Object.freeze({
1439
1439
  connect: 1,
1440
1440
  connect_error: 1,
1441
1441
  disconnect: 1,
@@ -1560,7 +1560,7 @@ class _e extends u {
1560
1560
  */
1561
1561
  emit(e, ...t) {
1562
1562
  var s, i, r;
1563
- if (At.hasOwnProperty(e))
1563
+ if (Tt.hasOwnProperty(e))
1564
1564
  throw new Error('"' + e.toString() + '" is a reserved event name');
1565
1565
  if (t.unshift(e), this._opts.retries && !this.flags.fromQueue && !this.flags.volatile)
1566
1566
  return this._addToQueue(t), this;
@@ -2067,7 +2067,7 @@ class H extends u {
2067
2067
  max: this.reconnectionDelayMax(),
2068
2068
  jitter: this.randomizationFactor()
2069
2069
  }), this.timeout(t.timeout == null ? 2e4 : t.timeout), this._readyState = "closed", this.uri = e;
2070
- const i = t.parser || Tt;
2070
+ const i = t.parser || At;
2071
2071
  this.encoder = new i.Encoder(), this.decoder = new i.Decoder(), this._autoConnect = t.autoConnect !== !1, this._autoConnect && this.open();
2072
2072
  }
2073
2073
  reconnection(e) {
@@ -2313,10 +2313,10 @@ Object.assign(B, {
2313
2313
  const we = Ee(
2314
2314
  void 0
2315
2315
  ), Ot = (n) => async () => {
2316
- const t = await (n == null ? void 0 : n.get()) ?? localStorage.getItem("authToken");
2316
+ const e = await (n == null ? void 0 : n.get());
2317
2317
  return {
2318
2318
  "Content-Type": "application/json",
2319
- ...t ? { Authorization: `Bearer ${t}` } : {}
2319
+ ...e ? { Authorization: `Bearer ${e}` } : {}
2320
2320
  };
2321
2321
  }, Pt = ({
2322
2322
  apiUrl: n,
@@ -2325,7 +2325,7 @@ const we = Ee(
2325
2325
  }) => {
2326
2326
  var Z;
2327
2327
  const s = (Z = Se().user) == null ? void 0 : Z.id, i = P(
2328
- () => e ? Ae({ url: Oe(e) }) : null,
2328
+ () => e ? Te({ url: Oe(e) }) : null,
2329
2329
  [e]
2330
2330
  ), r = P(
2331
2331
  () => s ? Re({
@@ -2389,7 +2389,7 @@ const we = Ee(
2389
2389
  );
2390
2390
  return /* @__PURE__ */ ve(we.Provider, { value: be, children: t });
2391
2391
  }, qt = () => {
2392
- const n = Te(we);
2392
+ const n = Ae(we);
2393
2393
  if (n === void 0)
2394
2394
  throw new Error(
2395
2395
  "useNotifications must be used within a NotificationProvider"
@@ -1,90 +1,91 @@
1
- import { jsx as A } from "react/jsx-runtime";
2
- import { createContext as N, useState as v, useEffect as g, useContext as R } from "react";
3
- import { Events as G } from "../Common/Events.js";
4
- import { usePageVisibility as L } from "../Hooks/usePageVisibility.js";
5
- const S = N(void 0), I = "guestId", s = (t) => {
6
- t && G.publish("variamosNavigate", t);
7
- }, _ = (t) => {
8
- t && localStorage.setItem("authToken", t);
1
+ import { jsx as G } from "react/jsx-runtime";
2
+ import { createContext as L, useState as g, useEffect as d, useContext as T } from "react";
3
+ import { Events as _ } from "../Common/Events.js";
4
+ import { usePageVisibility as j } from "../Hooks/usePageVisibility.js";
5
+ import { forgetStoredSessionToken as s } from "./storedSessionToken.js";
6
+ const I = L(void 0), w = "guestId", E = (r) => {
7
+ r && _.publish("variamosNavigate", r);
9
8
  };
10
- function f(t, i) {
11
- var a, c, d;
12
- return t != null && t.errorCode ? (i(null), !1) : (_((a = t == null ? void 0 : t.data) == null ? void 0 : a.authToken), i(((c = t == null ? void 0 : t.data) == null ? void 0 : c.user) ?? null), s((d = t == null ? void 0 : t.data) == null ? void 0 : d.redirect), !0);
9
+ function f(r, i) {
10
+ var a, c;
11
+ return r != null && r.errorCode ? (i(null), !1) : (i(((a = r == null ? void 0 : r.data) == null ? void 0 : a.user) ?? null), E((c = r == null ? void 0 : r.data) == null ? void 0 : c.redirect), !0);
13
12
  }
14
- const w = "Sign in error.", U = ({
15
- loginUrl: t,
13
+ const S = "Sign in error.", V = ({
14
+ loginUrl: r,
16
15
  getSessionInfo: i,
17
16
  requestSignUp: a,
18
17
  requestSignIn: c,
19
- requestSignInAsGuest: d,
20
- requestLogout: E,
21
- children: y
18
+ requestSignInAsGuest: l,
19
+ requestLogout: y,
20
+ children: C
22
21
  }) => {
23
- const [l, e] = v(null), [C, o] = v(!0), u = l !== null, h = L();
24
- g(() => {
25
- o(!0), i().then((n) => f(n, e)).catch(() => {
22
+ const [h, e] = g(null), [x, o] = g(!0), u = h !== null, m = j();
23
+ d(() => {
24
+ s();
25
+ }, []), d(() => {
26
+ o(!0), i().then((t) => f(t, e)).catch(() => {
26
27
  e(null);
27
28
  }).finally(() => o(!1));
28
- }, [i]), g(() => {
29
- if (!(u && h)) return;
30
- const n = () => {
31
- i().then((k) => f(k, e)).catch(() => {
29
+ }, [i]), d(() => {
30
+ if (!(u && m)) return;
31
+ const t = () => {
32
+ i().then((R) => f(R, e)).catch(() => {
32
33
  e(null);
33
34
  });
34
35
  };
35
- n();
36
- const r = setInterval(n, 6e4);
37
- return () => clearInterval(r);
38
- }, [u, h, i]);
39
- const m = (n) => i().then((r) => (f(r, e) || n.withError(
40
- r.errorCode ?? 500,
41
- r.message ?? w
42
- ), n)).catch(() => (e(null), n.withError(500, w))), x = (n) => {
36
+ t();
37
+ const n = setInterval(t, 6e4);
38
+ return () => clearInterval(n);
39
+ }, [u, m, i]);
40
+ const v = (t) => i().then((n) => (f(n, e) || t.withError(
41
+ n.errorCode ?? 500,
42
+ n.message ?? S
43
+ ), t)).catch(() => (e(null), t.withError(500, S))), P = (t) => {
43
44
  if (!a)
44
45
  throw new Error("requestSignUp is not defined.");
45
- return o(!0), a(n).finally(() => o(!1));
46
- }, P = (n) => {
46
+ return o(!0), a(t).finally(() => o(!1));
47
+ }, b = (t) => {
47
48
  if (!c)
48
49
  throw new Error("requestSignIn is not defined.");
49
- return o(!0), c(n).then(
50
- (r) => r != null && r.errorCode ? r : m(r)
50
+ return o(!0), c(t).then(
51
+ (n) => n != null && n.errorCode ? n : v(n)
51
52
  ).finally(() => o(!1));
52
- }, T = () => {
53
- if (!d)
53
+ }, A = () => {
54
+ if (!l)
54
55
  throw new Error("requestSignInAsGuest is not defined.");
55
- return o(!0), d(localStorage.getItem(I)).then((n) => {
56
- var r;
57
- return n != null && n.errorCode ? n : (localStorage.setItem(I, ((r = n == null ? void 0 : n.data) == null ? void 0 : r.id) || ""), m(n));
56
+ return o(!0), l(localStorage.getItem(w)).then((t) => {
57
+ var n;
58
+ return t != null && t.errorCode ? t : (localStorage.setItem(w, ((n = t == null ? void 0 : t.data) == null ? void 0 : n.id) || ""), v(t));
58
59
  }).finally(() => o(!1));
59
- }, b = () => {
60
- E().then(() => {
61
- localStorage.removeItem("authToken"), s(t), setTimeout(() => {
60
+ }, N = () => {
61
+ y().then(() => {
62
+ s(), E(r), setTimeout(() => {
62
63
  e(null);
63
64
  }, 200);
64
65
  });
65
66
  };
66
- return /* @__PURE__ */ A(
67
- S.Provider,
67
+ return /* @__PURE__ */ G(
68
+ I.Provider,
68
69
  {
69
70
  value: {
70
- user: l,
71
+ user: h,
71
72
  isAuthenticated: u,
72
- logout: b,
73
- signUp: x,
74
- signIn: P,
75
- signInAsGuest: T,
76
- isLoading: C
73
+ logout: N,
74
+ signUp: P,
75
+ signIn: b,
76
+ signInAsGuest: A,
77
+ isLoading: x
77
78
  },
78
- children: y
79
+ children: C
79
80
  }
80
81
  );
81
- }, V = () => {
82
- const t = R(S);
83
- if (t === void 0)
82
+ }, z = () => {
83
+ const r = T(I);
84
+ if (r === void 0)
84
85
  throw new Error("useSession must be used within a SessionProvider");
85
- return t;
86
+ return r;
86
87
  };
87
88
  export {
88
- U as SessionProvider,
89
- V as useSession
89
+ V as SessionProvider,
90
+ z as useSession
90
91
  };
@@ -10,8 +10,6 @@ export interface NotificationAccessToken {
10
10
  export declare function accessTokenUrl(adminApiUrl: string): string;
11
11
  export interface AccessTokenProviderOptions {
12
12
  url: string;
13
- /** Session token of the platform, sent to the admin service. */
14
- getSessionToken?: () => string | null;
15
13
  fetchImpl?: typeof fetch;
16
14
  now?: () => number;
17
15
  /** A token is renewed this long before it expires. */
@@ -26,4 +24,4 @@ export interface AccessTokenProvider {
26
24
  * Keeps one token in memory, renews it shortly before it expires, and
27
25
  * shares a single request between concurrent callers.
28
26
  */
29
- export declare function createAccessTokenProvider({ url, getSessionToken, fetchImpl, now, refreshMarginMs, }: AccessTokenProviderOptions): AccessTokenProvider;
27
+ export declare function createAccessTokenProvider({ url, fetchImpl, now, refreshMarginMs, }: AccessTokenProviderOptions): AccessTokenProvider;
@@ -1,31 +1,27 @@
1
- import { fieldOf as o } from "../Common/fieldOf.js";
2
- function g(t) {
1
+ import { fieldOf as s } from "../Common/fieldOf.js";
2
+ function d(t) {
3
3
  return `${t.replace(/\/+$/, "").replace(/\/v1$/, "")}/v1/notifications/access-token`;
4
4
  }
5
- function h({
5
+ function k({
6
6
  url: t,
7
- getSessionToken: s = () => localStorage.getItem("authToken"),
8
- fetchImpl: u = (...e) => fetch(...e),
9
- now: f = Date.now,
10
- refreshMarginMs: p = 6e4
7
+ fetchImpl: o = (...e) => fetch(...e),
8
+ now: i = Date.now,
9
+ refreshMarginMs: u = 6e4
11
10
  }) {
12
11
  let e = null, n = null;
13
- const k = async () => {
12
+ const f = async () => {
14
13
  try {
15
- const a = s(), c = await u(t, {
16
- credentials: "include",
17
- headers: a ? { Authorization: `Bearer ${a}` } : {}
18
- });
14
+ const c = await o(t, { credentials: "include" });
19
15
  if (!c.ok) return null;
20
- const l = o(await c.json(), "data"), r = o(l, "token"), i = Date.parse(String(o(l, "expiresAt")));
21
- return typeof r != "string" || Number.isNaN(i) ? null : (e = { token: r, expiresAt: i }, r);
16
+ const l = s(await c.json(), "data"), r = s(l, "token"), a = Date.parse(String(s(l, "expiresAt")));
17
+ return typeof r != "string" || Number.isNaN(a) ? null : (e = { token: r, expiresAt: a }, r);
22
18
  } catch {
23
19
  return null;
24
20
  }
25
21
  };
26
22
  return {
27
23
  get() {
28
- return e && f() < e.expiresAt - p ? Promise.resolve(e.token) : (n ?? (n = k().finally(() => {
24
+ return e && i() < e.expiresAt - u ? Promise.resolve(e.token) : (n ?? (n = f().finally(() => {
29
25
  n = null;
30
26
  })), n);
31
27
  },
@@ -35,6 +31,6 @@ function h({
35
31
  };
36
32
  }
37
33
  export {
38
- g as accessTokenUrl,
39
- h as createAccessTokenProvider
34
+ d as accessTokenUrl,
35
+ k as createAccessTokenProvider
40
36
  };
@@ -0,0 +1,8 @@
1
+ /** Key under which versions before 0.4.0 kept the session token. */
2
+ export declare const STORED_TOKEN_KEY = "authToken";
3
+ /**
4
+ * Removes the session token that versions before 0.4.0 kept in the browser
5
+ * storage, where any script of the page can read it. The session lives in
6
+ * the httpOnly cookie of the admin service alone (EV-117).
7
+ */
8
+ export declare function forgetStoredSessionToken(storage?: Pick<Storage, "removeItem"> | undefined): void;
@@ -0,0 +1,11 @@
1
+ const e = "authToken";
2
+ function n(o = globalThis.localStorage) {
3
+ try {
4
+ o == null || o.removeItem(e);
5
+ } catch {
6
+ }
7
+ }
8
+ export {
9
+ e as STORED_TOKEN_KEY,
10
+ n as forgetStoredSessionToken
11
+ };
@@ -1,6 +1,5 @@
1
1
  import { SessionUser } from './SessionUser';
2
2
  export interface SessionInfoResponse {
3
3
  user: SessionUser;
4
- authToken?: string;
5
4
  redirect?: string;
6
5
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@variamosple/variamos-components",
3
3
  "private": false,
4
- "version": "0.3.0",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
7
7
  "types": "dist/main.d.ts",