backpack-viewer 0.7.7 → 0.7.11

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.
@@ -1,200 +1,323 @@
1
- const E = "https://app.backpackontology.com", x = `${E}/.well-known/oauth-authorization-server`, _ = new Uint8Array([66, 80, 65, 75]), S = 1;
1
+ const L = "https://app.backpackontology.com";
2
+ let f = L, T = `${f}/.well-known/oauth-authorization-server`;
3
+ const P = new Uint8Array([66, 80, 65, 75]), R = 1;
2
4
  let m = null;
3
- function I(e) {
4
- e.registerTaskbarIcon({
5
+ async function M(t) {
6
+ const e = await t.settings.get("relay_url");
7
+ e && (f = e, T = `${f}/.well-known/oauth-authorization-server`), t.registerTaskbarIcon({
5
8
  label: "Share",
6
9
  iconText: "↗",
7
10
  position: "bottom-center",
8
- onClick: () => N(e)
11
+ onClick: () => $(t)
9
12
  });
10
13
  }
11
- function N(e) {
14
+ function $(t) {
12
15
  if (m && m.isVisible()) {
13
16
  m.setVisible(!1);
14
17
  return;
15
18
  }
16
- const t = e.getGraphName();
17
- if (!t) return;
18
- const n = document.createElement("div");
19
- n.className = "share-panel-body", m ? (m.element.replaceChildren(), m.element.appendChild(n), m.setTitle(`Share "${t}"`), m.setVisible(!0), m.bringToFront()) : (n.textContent = "Loading...", m = e.mountPanel(n, {
20
- title: `Share "${t}"`,
19
+ const e = t.getGraphName();
20
+ if (!e) return;
21
+ const a = document.createElement("div");
22
+ a.className = "share-panel-body", m ? (m.element.replaceChildren(), m.element.appendChild(a), m.setTitle(`Share "${e}"`), m.setVisible(!0), m.bringToFront()) : (a.textContent = "Loading...", m = t.mountPanel(a, {
23
+ title: `Share "${e}"`,
21
24
  defaultPosition: { left: Math.max(100, (window.innerWidth - 380) / 2), top: Math.max(80, (window.innerHeight - 400) / 2) },
22
25
  persistKey: "share-v2",
23
26
  showFullscreenButton: !1,
24
27
  onClose: () => {
25
28
  m = null;
26
29
  }
27
- })), w(e, n);
30
+ })), C(t, a);
31
+ }
32
+ async function C(t, e) {
33
+ const a = await t.settings.get("relay_token");
34
+ e.replaceChildren(), a ? D(t, e, a) : I(t, e);
35
+ }
36
+ function I(t, e) {
37
+ const a = document.createElement("div");
38
+ a.className = "share-upsell";
39
+ const n = document.createElement("h4");
40
+ n.textContent = "Share this graph with anyone", a.appendChild(n);
41
+ const s = document.createElement("p");
42
+ s.textContent = "Encrypt your graph and get a shareable link. Recipients open it in their browser — no install needed.", a.appendChild(s);
43
+ const o = document.createElement("button");
44
+ o.className = "share-cta-btn", o.textContent = "Sign in to share", o.addEventListener("click", () => Y(t, e)), a.appendChild(o);
45
+ const r = document.createElement("button");
46
+ r.className = "share-token-link", r.textContent = "Or paste an API token", r.addEventListener("click", () => B(t, e)), a.appendChild(r);
47
+ const c = document.createElement("p");
48
+ c.className = "share-trust", c.textContent = "Free account. Your graph is encrypted before upload — we can’t read it.", a.appendChild(c), e.replaceChildren(a);
49
+ }
50
+ function B(t, e) {
51
+ const a = document.createElement("div");
52
+ a.className = "share-token-input";
53
+ const n = document.createElement("p");
54
+ n.textContent = "Paste your API token from your account settings:", a.appendChild(n);
55
+ const s = document.createElement("input");
56
+ s.type = "password", s.placeholder = "Token", s.className = "share-input", a.appendChild(s);
57
+ const o = document.createElement("div");
58
+ o.className = "share-btn-row";
59
+ const r = document.createElement("button");
60
+ r.className = "share-btn-primary", r.textContent = "Save", r.addEventListener("click", async () => {
61
+ const i = s.value.trim();
62
+ i && (await t.settings.set("relay_token", i), C(t, e));
63
+ }), o.appendChild(r);
64
+ const c = document.createElement("button");
65
+ c.className = "share-btn-secondary", c.textContent = "Back", c.addEventListener("click", () => C(t, e)), o.appendChild(c), a.appendChild(o), e.replaceChildren(a);
66
+ }
67
+ async function O(t) {
68
+ try {
69
+ const e = await fetch(`${f}/api/graphs`, {
70
+ headers: { Authorization: `Bearer ${t}` }
71
+ });
72
+ if (e.status === 401) return { graphs: [], error: "unauthorized" };
73
+ if (!e.ok) return { graphs: [], error: `status ${e.status}` };
74
+ const a = await e.json();
75
+ return { graphs: Array.isArray(a) ? a : [] };
76
+ } catch (e) {
77
+ return { graphs: [], error: e.message };
78
+ }
28
79
  }
29
- async function w(e, t) {
30
- const n = await e.settings.get("relay_token");
31
- t.replaceChildren(), n ? T(e, t, n) : A(e, t);
80
+ async function D(t, e, a) {
81
+ const n = t.getGraphName(), s = document.createElement("div");
82
+ s.className = "share-loading", s.textContent = "Checking account…", e.replaceChildren(s);
83
+ const o = await O(a);
84
+ if (o.error === "unauthorized") {
85
+ await t.settings.remove("relay_token"), C(t, e);
86
+ return;
87
+ }
88
+ e.replaceChildren();
89
+ const r = o.graphs.find((c) => c.name === n);
90
+ r ? F(t, e, a, r) : V(t, e, a);
91
+ }
92
+ function F(t, e, a, n) {
93
+ const s = document.createElement("div");
94
+ s.className = "share-synced";
95
+ const o = document.createElement("h4");
96
+ if (o.textContent = "Synced to your account", s.appendChild(o), n.syncedAt) {
97
+ const d = document.createElement("p");
98
+ d.className = "share-note", d.textContent = `Last synced: ${new Date(n.syncedAt).toLocaleString()}`, s.appendChild(d);
99
+ }
100
+ const r = document.createElement("p");
101
+ r.className = "share-note", r.textContent = n.encrypted ? "Encrypted — server cannot read your data." : "Public — stored unencrypted.", s.appendChild(r);
102
+ const c = document.createElement("button");
103
+ c.className = "share-cta-btn", c.textContent = "Update & Share", c.addEventListener("click", async () => {
104
+ c.disabled = !0, c.textContent = n.encrypted ? "Encrypting…" : "Syncing…";
105
+ try {
106
+ await x(t, e, a, n.encrypted);
107
+ } catch (d) {
108
+ c.disabled = !1, c.textContent = "Update & Share", S(s), b(s, d.message);
109
+ }
110
+ }), s.appendChild(c);
111
+ const i = document.createElement("a");
112
+ i.href = f, i.target = "_blank", i.rel = "noopener", i.className = "share-token-link", i.textContent = "Open dashboard", s.appendChild(i), _(t, e, s);
32
113
  }
33
- function A(e, t) {
114
+ function V(t, e, a) {
34
115
  const n = document.createElement("div");
35
- n.className = "share-upsell", n.innerHTML = `
36
- <h4>Share this graph with anyone</h4>
37
- <p>Encrypt your graph and get a shareable link. Recipients open it in their browser no install needed. Your data stays encrypted on our servers.</p>
38
- `;
39
- const a = document.createElement("button");
40
- a.className = "share-cta-btn", a.textContent = "Sign in to share", a.addEventListener("click", () => v(e, t)), n.appendChild(a);
116
+ n.className = "share-form";
117
+ const s = document.createElement("p");
118
+ s.className = "share-description", s.textContent = "Your graph will be encrypted and synced to your Backpack account. You’ll get a shareable link.", n.appendChild(s);
41
119
  const o = document.createElement("button");
42
- o.className = "share-token-link", o.textContent = "Or paste an API token", o.addEventListener("click", () => P(e, t)), n.appendChild(o);
120
+ o.className = "share-cta-btn", o.textContent = "Sync & Share", o.addEventListener("click", async () => {
121
+ o.disabled = !0, o.textContent = "Encrypting…";
122
+ try {
123
+ await x(t, e, a, !0);
124
+ } catch (c) {
125
+ o.disabled = !1, o.textContent = "Sync & Share";
126
+ const i = c.message;
127
+ if (i.includes("encrypted") && i.includes("limit")) {
128
+ z(t, e, a);
129
+ return;
130
+ }
131
+ S(n), b(n, i);
132
+ }
133
+ }), n.appendChild(o);
134
+ const r = document.createElement("p");
135
+ r.className = "share-trust", r.textContent = "Your first encrypted graph is free. Data is encrypted before upload — we can’t read it.", n.appendChild(r), _(t, e, n);
136
+ }
137
+ function z(t, e, a) {
138
+ const n = document.createElement("div");
139
+ n.className = "share-quota";
140
+ const s = document.createElement("h4");
141
+ s.textContent = "Encrypted limit reached", n.appendChild(s);
142
+ const o = document.createElement("p");
143
+ o.className = "share-description", o.textContent = "Your free account includes one encrypted graph, which is already in use.", n.appendChild(o);
144
+ const r = document.createElement("a");
145
+ r.href = `${f}/settings`, r.target = "_blank", r.rel = "noopener", r.className = "share-cta-btn share-btn-link", r.textContent = "Upgrade for unlimited encryption", n.appendChild(r);
146
+ const c = document.createElement("div");
147
+ c.className = "share-divider", n.appendChild(c);
43
148
  const i = document.createElement("p");
44
- i.className = "share-trust", i.textContent = "Free account. Your graph is encrypted before upload — we can't read it.", n.appendChild(i), t.replaceChildren(n);
149
+ i.className = "share-description", i.textContent = "Or share as a public graph:", n.appendChild(i);
150
+ const d = document.createElement("p");
151
+ d.className = "share-warning", d.textContent = "Your graph data will be stored unencrypted and visible to the server and anyone with the link.", n.appendChild(d);
152
+ const l = document.createElement("label");
153
+ l.className = "share-toggle-row";
154
+ const u = document.createElement("input");
155
+ u.type = "checkbox", l.appendChild(u);
156
+ const p = document.createElement("span");
157
+ p.textContent = "I understand this graph will not be encrypted", l.appendChild(p), n.appendChild(l);
158
+ const h = document.createElement("button");
159
+ h.className = "share-btn-secondary", h.textContent = "Share as public graph", h.disabled = !0, u.addEventListener("change", () => {
160
+ h.disabled = !u.checked;
161
+ }), h.addEventListener("click", async () => {
162
+ h.disabled = !0, h.textContent = "Syncing…";
163
+ try {
164
+ await x(t, e, a, !1, "public");
165
+ } catch (w) {
166
+ h.disabled = !1, h.textContent = "Share as public graph", S(n), b(n, w.message);
167
+ }
168
+ }), n.appendChild(h), _(t, e, n);
45
169
  }
46
- function P(e, t) {
170
+ function _(t, e, a) {
47
171
  const n = document.createElement("div");
48
- n.className = "share-token-input";
172
+ n.className = "share-footer";
173
+ const s = document.createElement("button");
174
+ s.className = "share-token-link", s.textContent = "Sign out", s.addEventListener("click", async () => {
175
+ await t.settings.remove("relay_token"), C(t, e);
176
+ }), n.appendChild(s), a.appendChild(n), e.replaceChildren(a);
177
+ }
178
+ function S(t) {
179
+ for (const e of t.querySelectorAll(".share-error")) e.remove();
180
+ }
181
+ function b(t, e) {
49
182
  const a = document.createElement("p");
50
- a.textContent = "Paste your API token from Backpack App settings:", n.appendChild(a);
51
- const o = document.createElement("input");
52
- o.type = "password", o.placeholder = "Token", o.className = "share-input", n.appendChild(o);
53
- const i = document.createElement("div");
54
- i.className = "share-btn-row";
55
- const d = document.createElement("button");
56
- d.className = "share-btn-primary", d.textContent = "Save", d.addEventListener("click", async () => {
57
- const c = o.value.trim();
58
- c && (await e.settings.set("relay_token", c), w(e, t));
59
- }), i.appendChild(d);
60
- const l = document.createElement("button");
61
- l.className = "share-btn-secondary", l.textContent = "Back", l.addEventListener("click", () => w(e, t)), i.appendChild(l), n.appendChild(i), t.replaceChildren(n);
62
- }
63
- async function v(e, t) {
183
+ a.className = "share-error", a.textContent = e, t.appendChild(a);
184
+ }
185
+ async function Y(t, e) {
64
186
  try {
65
- const a = await (await fetch(x)).json(), i = await (await fetch(a.registration_endpoint, { method: "POST" })).json(), d = B(), l = await $(d), c = window.location.origin + "/oauth/callback", r = crypto.randomUUID(), s = new URL(a.authorization_endpoint);
66
- s.searchParams.set("client_id", i.client_id), s.searchParams.set("redirect_uri", c), s.searchParams.set("response_type", "code"), s.searchParams.set("code_challenge", l), s.searchParams.set("code_challenge_method", "S256"), s.searchParams.set("state", r), s.searchParams.has("scope") || s.searchParams.set("scope", "openid email profile offline_access"), sessionStorage.setItem("share_oauth_state", r);
67
- const h = window.open(s.toString(), "backpack-share-auth", "width=500,height=700"), g = async (p) => {
68
- var f;
69
- if (((f = p.data) == null ? void 0 : f.type) !== "backpack-oauth-callback") return;
70
- window.removeEventListener("message", g);
71
- const { code: u, returnedState: k } = p.data;
72
- if (k !== r) return;
73
- const C = await (await fetch(a.token_endpoint, {
74
- method: "POST",
75
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
76
- body: new URLSearchParams({ grant_type: "authorization_code", code: u, redirect_uri: c, client_id: i.client_id, code_verifier: d }).toString()
77
- })).json();
78
- await e.settings.set("relay_token", C.access_token), w(e, t);
187
+ const n = await (await fetch(T)).json(), o = await (await fetch(n.registration_endpoint, { method: "POST" })).json(), r = K(), c = await H(r), i = window.location.origin + "/oauth/callback", d = crypto.randomUUID(), l = new URL(n.authorization_endpoint);
188
+ l.searchParams.set("client_id", o.client_id), l.searchParams.set("redirect_uri", i), l.searchParams.set("response_type", "code"), l.searchParams.set("code_challenge", c), l.searchParams.set("code_challenge_method", "S256"), l.searchParams.set("state", d), l.searchParams.has("scope") || l.searchParams.set("scope", "openid email profile offline_access"), sessionStorage.setItem("share_oauth_state", d), sessionStorage.setItem("share_oauth_token_endpoint", n.token_endpoint), sessionStorage.setItem("share_oauth_client_id", o.client_id), sessionStorage.setItem("share_oauth_code_verifier", r), sessionStorage.setItem("share_oauth_redirect_uri", i);
189
+ const u = window.open(l.toString(), "backpack-share-auth", "width=500,height=700"), p = async (h) => {
190
+ var k;
191
+ if (((k = h.data) == null ? void 0 : k.type) !== "backpack-oauth-callback") return;
192
+ window.removeEventListener("message", p);
193
+ const { code: w, returnedState: E } = h.data;
194
+ if (E === d) {
195
+ G();
196
+ try {
197
+ const g = await (await fetch(n.token_endpoint, {
198
+ method: "POST",
199
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
200
+ body: new URLSearchParams({ grant_type: "authorization_code", code: w, redirect_uri: i, client_id: o.client_id, code_verifier: r }).toString()
201
+ })).json();
202
+ if (!g.access_token) {
203
+ b(e, "Token exchange failed: no access token returned.");
204
+ return;
205
+ }
206
+ await t.settings.set("relay_token", g.access_token), C(t, e);
207
+ } catch (y) {
208
+ b(e, `Token exchange failed: ${y.message}`);
209
+ }
210
+ }
79
211
  };
80
- if (window.addEventListener("message", g), !h || h.closed) {
81
- const p = document.createElement("p");
82
- p.className = "share-error", p.textContent = "Popup blocked. ";
83
- const u = document.createElement("a");
84
- u.href = s.toString(), u.target = "_blank", u.textContent = "Click here to sign in", p.appendChild(u), t.appendChild(p);
85
- }
86
- } catch (n) {
87
- const a = document.createElement("p");
88
- a.className = "share-error", a.textContent = `Auth failed: ${n.message}`, t.appendChild(a);
212
+ window.addEventListener("message", p), (!u || u.closed) && (window.location.href = l.toString());
213
+ } catch (a) {
214
+ b(e, `Auth failed: ${a.message}`);
89
215
  }
90
216
  }
91
- function T(e, t, n) {
92
- const a = document.createElement("div");
93
- a.className = "share-form";
94
- const o = document.createElement("label");
95
- o.className = "share-toggle-row";
96
- const i = document.createElement("input");
97
- i.type = "checkbox", i.checked = !0, o.appendChild(i);
98
- const d = document.createElement("span");
99
- d.textContent = "Encrypt (recommended)", o.appendChild(d), a.appendChild(o);
100
- const l = document.createElement("div");
101
- l.className = "share-pass-row";
102
- const c = document.createElement("input");
103
- c.type = "password", c.placeholder = "Passphrase (optional)", c.className = "share-input", l.appendChild(c), a.appendChild(l);
104
- const r = document.createElement("button");
105
- r.className = "share-btn-primary", r.textContent = "Share", r.addEventListener("click", async () => {
106
- r.disabled = !0, r.textContent = "Encrypting...";
217
+ async function x(t, e, a, n, s = "private") {
218
+ const o = t.getGraph(), r = t.getGraphName();
219
+ if (!o || !r) throw new Error("No graph loaded");
220
+ const c = new TextEncoder().encode(JSON.stringify(o));
221
+ let i, d, l = "";
222
+ if (n) {
223
+ const y = await import("../index-B8_hkT8R.js"), g = await y.generateX25519Identity(), U = await y.identityToRecipient(g), N = new y.Encrypter();
224
+ N.addRecipient(U), i = await N.encrypt(c), d = "age-v1", l = btoa(g).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
225
+ } else
226
+ i = c, d = "plaintext";
227
+ const u = await j(r, i, d, o), p = `${f}/api/graphs/${encodeURIComponent(r)}/sync${s === "public" ? "?visibility=public" : ""}`, h = await v(a, p, {
228
+ method: "PUT",
229
+ headers: { "Content-Type": "application/octet-stream" },
230
+ body: u
231
+ });
232
+ if (!h.ok) {
233
+ if (h.status === 401)
234
+ throw await t.settings.remove("relay_token"), C(t, e), new Error("Session expired. Please sign in again.");
235
+ let y = `Sync failed (${h.status})`;
107
236
  try {
108
- await L(e, t, n, i.checked, c.value.trim());
109
- } catch (g) {
110
- r.disabled = !1, r.textContent = "Share";
111
- const p = document.createElement("p");
112
- p.className = "share-error", p.textContent = g.message, a.appendChild(p);
237
+ const g = await h.json();
238
+ g.error && (y = g.error);
239
+ } catch {
113
240
  }
114
- }), a.appendChild(r);
115
- const s = document.createElement("p");
116
- s.className = "share-note", s.textContent = "Recipients open the link in their browser. No install needed.", a.appendChild(s);
117
- const h = document.createElement("button");
118
- h.className = "share-token-link", h.textContent = "Sign out", h.addEventListener("click", async () => {
119
- await e.settings.remove("relay_token"), w(e, t);
120
- }), a.appendChild(h), t.replaceChildren(a);
121
- }
122
- async function L(e, t, n, a, o) {
123
- const i = e.getGraph(), d = e.getGraphName();
124
- if (!i || !d) throw new Error("No graph loaded");
125
- const l = new TextEncoder().encode(JSON.stringify(i));
126
- let c, r, s = "";
127
- if (a) {
128
- const y = await import("../index-B-LFGJqd.js"), C = await y.generateX25519Identity(), f = await y.identityToRecipient(C), b = new y.Encrypter();
129
- b.addRecipient(f), c = await b.encrypt(l), r = "age-v1", s = btoa(C).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
130
- } else
131
- c = l, r = "plaintext";
132
- const h = await U(d, c, r), g = {
133
- "Content-Type": "application/octet-stream",
134
- Authorization: `Bearer ${n}`
135
- };
136
- o && (g["X-Passphrase"] = o);
137
- const p = await fetch(`${E}/v1/share`, {
138
- method: "POST",
139
- headers: g,
140
- body: h
241
+ throw new Error(y);
242
+ }
243
+ const w = await v(a, `${f}/api/graphs/${encodeURIComponent(r)}/share`, {
244
+ method: "POST"
141
245
  });
142
- if (!p.ok) {
143
- const y = await p.text().catch(() => "");
144
- throw p.status === 401 ? (await e.settings.remove("relay_token"), w(e, t), new Error("Session expired. Please sign in again.")) : new Error(`Upload failed: ${y}`);
246
+ if (!w.ok) {
247
+ A(e, null, n, void 0);
248
+ return;
145
249
  }
146
- const u = await p.json(), k = s ? `${u.url}#k=${s}` : u.url;
147
- R(t, k, a, u.expires_at);
148
- }
149
- async function U(e, t, n) {
150
- const a = new ArrayBuffer(t.byteLength);
151
- new Uint8Array(a).set(t);
152
- const o = await crypto.subtle.digest("SHA-256", a), i = "sha256:" + Array.from(new Uint8Array(o)).map((h) => h.toString(16).padStart(2, "0")).join(""), d = JSON.stringify({
153
- format: n,
250
+ const E = await w.json(), k = l ? `${E.url}#k=${l}` : E.url;
251
+ A(e, k, n, E.expires_at);
252
+ }
253
+ async function v(t, e, a = {}) {
254
+ const n = new Headers(a.headers);
255
+ return n.set("Authorization", `Bearer ${t}`), fetch(e, { ...a, headers: n });
256
+ }
257
+ async function j(t, e, a, n) {
258
+ const s = new Uint8Array(e).buffer, o = await crypto.subtle.digest("SHA-256", s), r = "sha256:" + Array.from(new Uint8Array(o)).map((h) => h.toString(16).padStart(2, "0")).join(""), c = /* @__PURE__ */ new Set();
259
+ for (const h of n.nodes) c.add(h.type);
260
+ const i = JSON.stringify({
261
+ format: a,
154
262
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
155
- backpack_name: e,
263
+ backpack_name: t,
156
264
  graph_count: 1,
157
- checksum: i
158
- }), l = new TextEncoder().encode(d), c = new ArrayBuffer(4);
159
- new DataView(c).setUint32(0, l.length, !1);
160
- const r = new Uint8Array(9 + l.length + t.length);
161
- let s = 0;
162
- return r.set(_, s), s += 4, r[s] = S, s += 1, r.set(new Uint8Array(c), s), s += 4, r.set(l, s), s += l.length, r.set(t, s), r;
163
- }
164
- function R(e, t, n, a) {
165
- const o = document.createElement("div");
166
- o.className = "share-success";
167
- const i = document.createElement("h4");
168
- i.textContent = "Shared!", o.appendChild(i);
169
- const d = document.createElement("div");
170
- d.className = "share-link-row";
171
- const l = document.createElement("input");
172
- l.type = "text", l.readOnly = !0, l.value = t, l.className = "share-link-input", d.appendChild(l);
173
- const c = document.createElement("button");
174
- if (c.className = "share-btn-primary", c.textContent = "Copy", c.addEventListener("click", () => {
175
- navigator.clipboard.writeText(t), c.textContent = "Copied!", setTimeout(() => {
176
- c.textContent = "Copy";
177
- }, 2e3);
178
- }), d.appendChild(c), o.appendChild(d), n) {
179
- const r = document.createElement("p");
180
- r.className = "share-note", r.textContent = "The decryption key is in the link. Anyone with the full link can view. The server cannot read your data.", o.appendChild(r);
265
+ checksum: r,
266
+ node_count: n.nodes.length,
267
+ edge_count: n.edges.length,
268
+ node_types: Array.from(c)
269
+ }), d = new TextEncoder().encode(i), l = new ArrayBuffer(4);
270
+ new DataView(l).setUint32(0, d.length, !1);
271
+ const u = new Uint8Array(9 + d.length + e.length);
272
+ let p = 0;
273
+ return u.set(P, p), p += 4, u[p] = R, p += 1, u.set(new Uint8Array(l), p), p += 4, u.set(d, p), p += d.length, u.set(e, p), u;
274
+ }
275
+ function A(t, e, a, n) {
276
+ const s = document.createElement("div");
277
+ s.className = "share-success";
278
+ const o = document.createElement("h4");
279
+ if (o.textContent = e ? "Synced & shared!" : "Synced!", s.appendChild(o), e) {
280
+ const r = document.createElement("div");
281
+ r.className = "share-link-row";
282
+ const c = document.createElement("input");
283
+ c.type = "text", c.readOnly = !0, c.value = e, c.className = "share-link-input", r.appendChild(c);
284
+ const i = document.createElement("button");
285
+ i.className = "share-btn-primary", i.textContent = "Copy", i.addEventListener("click", () => {
286
+ navigator.clipboard.writeText(e).then(() => {
287
+ i.textContent = "Copied!", setTimeout(() => {
288
+ i.textContent = "Copy";
289
+ }, 2e3);
290
+ }).catch(() => {
291
+ i.textContent = "Failed", setTimeout(() => {
292
+ i.textContent = "Copy";
293
+ }, 2e3);
294
+ });
295
+ }), r.appendChild(i), s.appendChild(r);
181
296
  }
182
297
  if (a) {
183
298
  const r = document.createElement("p");
184
- r.className = "share-note", r.textContent = `Expires: ${new Date(a).toLocaleDateString()}`, o.appendChild(r);
299
+ r.className = "share-note", r.textContent = "The decryption key is embedded in the link. Share the complete link — if the #k= part is removed, recipients won’t be able to decrypt. The server cannot read your data.", s.appendChild(r);
300
+ } else {
301
+ const r = document.createElement("p");
302
+ r.className = "share-note", r.textContent = "This graph is stored unencrypted. Anyone with the link can view it.", s.appendChild(r);
185
303
  }
186
- e.replaceChildren(o);
304
+ if (n) {
305
+ const r = document.createElement("p");
306
+ r.className = "share-note", r.textContent = `Expires: ${new Date(n).toLocaleDateString()}`, s.appendChild(r);
307
+ }
308
+ t.replaceChildren(s);
309
+ }
310
+ function G() {
311
+ sessionStorage.removeItem("share_oauth_state"), sessionStorage.removeItem("share_oauth_token_endpoint"), sessionStorage.removeItem("share_oauth_client_id"), sessionStorage.removeItem("share_oauth_code_verifier"), sessionStorage.removeItem("share_oauth_redirect_uri");
187
312
  }
188
- function B() {
189
- const e = new Uint8Array(32);
190
- return crypto.getRandomValues(e), btoa(String.fromCharCode(...e)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
313
+ function K() {
314
+ const t = new Uint8Array(32);
315
+ return crypto.getRandomValues(t), btoa(String.fromCharCode(...t)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
191
316
  }
192
- async function $(e) {
193
- const t = new TextEncoder().encode(e), n = new ArrayBuffer(t.byteLength);
194
- new Uint8Array(n).set(t);
195
- const a = await crypto.subtle.digest("SHA-256", n);
196
- return btoa(String.fromCharCode(...new Uint8Array(a))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
317
+ async function H(t) {
318
+ const e = new TextEncoder().encode(t), a = new Uint8Array(e).buffer, n = await crypto.subtle.digest("SHA-256", a);
319
+ return btoa(String.fromCharCode(...new Uint8Array(n))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
197
320
  }
198
321
  export {
199
- I as activate
322
+ M as activate
200
323
  };
@@ -5,12 +5,16 @@
5
5
  min-width: 300px;
6
6
  }
7
7
 
8
- .share-upsell h4 {
8
+ .share-upsell h4,
9
+ .share-synced h4,
10
+ .share-quota h4,
11
+ .share-success h4 {
9
12
  margin: 0 0 8px;
10
13
  font-size: 15px;
11
14
  }
12
15
 
13
- .share-upsell p {
16
+ .share-upsell p,
17
+ .share-description {
14
18
  margin: 0 0 12px;
15
19
  color: var(--text-muted);
16
20
  line-height: 1.5;
@@ -28,12 +32,20 @@
28
32
  font-weight: 600;
29
33
  cursor: pointer;
30
34
  margin-bottom: 8px;
35
+ text-align: center;
36
+ text-decoration: none;
37
+ box-sizing: border-box;
31
38
  }
32
39
 
33
40
  .share-cta-btn:hover {
34
41
  opacity: 0.9;
35
42
  }
36
43
 
44
+ .share-cta-btn:disabled {
45
+ opacity: 0.5;
46
+ cursor: default;
47
+ }
48
+
37
49
  .share-token-link {
38
50
  background: none;
39
51
  border: none;
@@ -68,6 +80,10 @@
68
80
  margin-top: 8px;
69
81
  }
70
82
 
83
+ .share-btn-row-stack {
84
+ flex-direction: column;
85
+ }
86
+
71
87
  .share-btn-primary {
72
88
  padding: 8px 16px;
73
89
  background: var(--accent);
@@ -91,6 +107,18 @@
91
107
  border-radius: 4px;
92
108
  font-size: 13px;
93
109
  cursor: pointer;
110
+ text-align: center;
111
+ text-decoration: none;
112
+ }
113
+
114
+ .share-btn-secondary:disabled {
115
+ opacity: 0.5;
116
+ cursor: default;
117
+ }
118
+
119
+ .share-btn-link {
120
+ display: block;
121
+ text-decoration: none;
94
122
  }
95
123
 
96
124
  .share-toggle-row {
@@ -101,10 +129,6 @@
101
129
  cursor: pointer;
102
130
  }
103
131
 
104
- .share-pass-row {
105
- margin-bottom: 12px;
106
- }
107
-
108
132
  .share-form {
109
133
  display: flex;
110
134
  flex-direction: column;
@@ -123,8 +147,14 @@
123
147
  margin-top: 8px;
124
148
  }
125
149
 
150
+ .share-warning {
151
+ color: var(--warning, #d69e2e);
152
+ font-size: 12px;
153
+ margin: 0 0 8px;
154
+ line-height: 1.4;
155
+ }
156
+
126
157
  .share-success h4 {
127
- margin: 0 0 12px;
128
158
  color: var(--text);
129
159
  }
130
160
 
@@ -149,3 +179,25 @@
149
179
  margin: 0 0 8px;
150
180
  color: var(--text-muted);
151
181
  }
182
+
183
+ .share-loading {
184
+ color: var(--text-muted);
185
+ font-size: 13px;
186
+ padding: 8px 0;
187
+ }
188
+
189
+ .share-footer {
190
+ margin-top: 16px;
191
+ padding-top: 8px;
192
+ border-top: 1px solid var(--border);
193
+ }
194
+
195
+ .share-divider {
196
+ height: 1px;
197
+ background: var(--border);
198
+ margin: 16px 0;
199
+ }
200
+
201
+ .share-hidden {
202
+ display: none;
203
+ }