backpack-viewer 0.7.14 → 0.7.15
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/app/assets/index-Cab62Pxr.css +1 -0
- package/dist/app/assets/index-DpElI3pz.js +6 -0
- package/dist/app/index.html +2 -2
- package/dist/extensions/share/src/index.js +143 -138
- package/dist/server-api-routes.js +19 -0
- package/dist/sidebar.js +18 -2
- package/dist/style.css +16 -0
- package/package.json +1 -1
- package/dist/app/assets/index-BbX2AsyK.css +0 -1
- package/dist/app/assets/index-CSbPTbYZ.js +0 -6
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
const
|
|
2
|
-
let
|
|
3
|
-
const
|
|
1
|
+
const R = "https://app.backpackontology.com";
|
|
2
|
+
let g = R, L = `${g}/.well-known/oauth-authorization-server`;
|
|
3
|
+
const $ = new Uint8Array([66, 80, 65, 75]), I = 1;
|
|
4
4
|
let m = null;
|
|
5
|
-
async function
|
|
5
|
+
async function q(t) {
|
|
6
6
|
const e = await t.settings.get("relay_url");
|
|
7
|
-
e && (
|
|
7
|
+
e && (g = e, L = `${g}/.well-known/oauth-authorization-server`), t.registerTaskbarIcon({
|
|
8
8
|
label: "Share",
|
|
9
9
|
iconText: "↗",
|
|
10
10
|
position: "bottom-center",
|
|
11
|
-
onClick: () =>
|
|
11
|
+
onClick: () => B(t)
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function B(t) {
|
|
15
15
|
if (m && m.isVisible()) {
|
|
16
16
|
m.setVisible(!1);
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
const e = t.getGraphName();
|
|
20
20
|
if (!e) return;
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const s = document.createElement("div");
|
|
22
|
+
s.className = "share-panel-body", m ? (m.element.replaceChildren(), m.element.appendChild(s), m.setTitle(`Share "${e}"`), m.setVisible(!0), m.bringToFront()) : (s.textContent = "Loading...", m = t.mountPanel(s, {
|
|
23
23
|
title: `Share "${e}"`,
|
|
24
24
|
defaultPosition: { left: Math.max(100, (window.innerWidth - 380) / 2), top: Math.max(80, (window.innerHeight - 400) / 2) },
|
|
25
25
|
persistKey: "share-v2",
|
|
@@ -27,122 +27,122 @@ function $(t) {
|
|
|
27
27
|
onClose: () => {
|
|
28
28
|
m = null;
|
|
29
29
|
}
|
|
30
|
-
})), C(t,
|
|
30
|
+
})), C(t, s);
|
|
31
31
|
}
|
|
32
32
|
async function C(t, e) {
|
|
33
|
-
const
|
|
34
|
-
e.replaceChildren(),
|
|
33
|
+
const s = await t.settings.get("relay_token");
|
|
34
|
+
e.replaceChildren(), s ? V(t, e, s) : O(t, e);
|
|
35
35
|
}
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
36
|
+
function O(t, e) {
|
|
37
|
+
const s = document.createElement("div");
|
|
38
|
+
s.className = "share-upsell";
|
|
39
39
|
const n = document.createElement("h4");
|
|
40
|
-
n.textContent = "Share this graph with anyone",
|
|
41
|
-
const
|
|
42
|
-
|
|
40
|
+
n.textContent = "Share this graph with anyone", s.appendChild(n);
|
|
41
|
+
const r = document.createElement("p");
|
|
42
|
+
r.textContent = "Encrypt your graph and get a shareable link. Recipients open it in their browser — no install needed.", s.appendChild(r);
|
|
43
43
|
const o = document.createElement("button");
|
|
44
|
-
o.className = "share-cta-btn", o.textContent = "Sign in to share", o.addEventListener("click", () =>
|
|
45
|
-
const
|
|
46
|
-
|
|
44
|
+
o.className = "share-cta-btn", o.textContent = "Sign in to share", o.addEventListener("click", () => G(t, e)), s.appendChild(o);
|
|
45
|
+
const a = document.createElement("button");
|
|
46
|
+
a.className = "share-token-link", a.textContent = "Or paste an API token", a.addEventListener("click", () => D(t, e)), s.appendChild(a);
|
|
47
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.",
|
|
48
|
+
c.className = "share-trust", c.textContent = "Free account. Your graph is encrypted before upload — we can’t read it.", s.appendChild(c), e.replaceChildren(s);
|
|
49
49
|
}
|
|
50
|
-
function
|
|
51
|
-
const
|
|
52
|
-
|
|
50
|
+
function D(t, e) {
|
|
51
|
+
const s = document.createElement("div");
|
|
52
|
+
s.className = "share-token-input";
|
|
53
53
|
const n = document.createElement("p");
|
|
54
|
-
n.textContent = "Paste your API token from your account settings:",
|
|
55
|
-
const
|
|
56
|
-
|
|
54
|
+
n.textContent = "Paste your API token from your account settings:", s.appendChild(n);
|
|
55
|
+
const r = document.createElement("input");
|
|
56
|
+
r.type = "password", r.placeholder = "Token", r.className = "share-input", s.appendChild(r);
|
|
57
57
|
const o = document.createElement("div");
|
|
58
58
|
o.className = "share-btn-row";
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
const i =
|
|
59
|
+
const a = document.createElement("button");
|
|
60
|
+
a.className = "share-btn-primary", a.textContent = "Save", a.addEventListener("click", async () => {
|
|
61
|
+
const i = r.value.trim();
|
|
62
62
|
i && (await t.settings.set("relay_token", i), C(t, e));
|
|
63
|
-
}), o.appendChild(
|
|
63
|
+
}), o.appendChild(a);
|
|
64
64
|
const c = document.createElement("button");
|
|
65
|
-
c.className = "share-btn-secondary", c.textContent = "Back", c.addEventListener("click", () => C(t, e)), o.appendChild(c),
|
|
65
|
+
c.className = "share-btn-secondary", c.textContent = "Back", c.addEventListener("click", () => C(t, e)), o.appendChild(c), s.appendChild(o), e.replaceChildren(s);
|
|
66
66
|
}
|
|
67
|
-
async function
|
|
67
|
+
async function F(t) {
|
|
68
68
|
try {
|
|
69
|
-
const e = await fetch(`${
|
|
69
|
+
const e = await fetch(`${g}/api/graphs`, {
|
|
70
70
|
headers: { Authorization: `Bearer ${t}` }
|
|
71
71
|
});
|
|
72
72
|
if (e.status === 401) return { graphs: [], error: "unauthorized" };
|
|
73
73
|
if (!e.ok) return { graphs: [], error: `status ${e.status}` };
|
|
74
|
-
const
|
|
75
|
-
return { graphs: Array.isArray(
|
|
74
|
+
const s = await e.json();
|
|
75
|
+
return { graphs: Array.isArray(s) ? s : [] };
|
|
76
76
|
} catch (e) {
|
|
77
77
|
return { graphs: [], error: e.message };
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
async function
|
|
81
|
-
const n = t.getGraphName(),
|
|
82
|
-
|
|
83
|
-
const o = await
|
|
80
|
+
async function V(t, e, s) {
|
|
81
|
+
const n = t.getGraphName(), r = document.createElement("div");
|
|
82
|
+
r.className = "share-loading", r.textContent = "Checking account…", e.replaceChildren(r);
|
|
83
|
+
const o = await F(s);
|
|
84
84
|
if (o.error === "unauthorized") {
|
|
85
85
|
await t.settings.remove("relay_token"), C(t, e);
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
e.replaceChildren();
|
|
89
|
-
const
|
|
90
|
-
|
|
89
|
+
const a = o.graphs.find((c) => c.name === n);
|
|
90
|
+
a ? z(t, e, s, a) : Y(t, e, s);
|
|
91
91
|
}
|
|
92
|
-
function
|
|
93
|
-
const
|
|
94
|
-
|
|
92
|
+
function z(t, e, s, n) {
|
|
93
|
+
const r = document.createElement("div");
|
|
94
|
+
r.className = "share-synced";
|
|
95
95
|
const o = document.createElement("h4");
|
|
96
|
-
if (o.textContent = "Synced to your account",
|
|
96
|
+
if (o.textContent = "Synced to your account", r.appendChild(o), n.syncedAt) {
|
|
97
97
|
const d = document.createElement("p");
|
|
98
|
-
d.className = "share-note", d.textContent = `Last synced: ${new Date(n.syncedAt).toLocaleString()}`,
|
|
98
|
+
d.className = "share-note", d.textContent = `Last synced: ${new Date(n.syncedAt).toLocaleString()}`, r.appendChild(d);
|
|
99
99
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
100
|
+
const a = document.createElement("p");
|
|
101
|
+
a.className = "share-note", a.textContent = n.encrypted ? "Encrypted — server cannot read your data." : "Public — stored unencrypted.", r.appendChild(a);
|
|
102
102
|
const c = document.createElement("button");
|
|
103
103
|
c.className = "share-cta-btn", c.textContent = "Update & Share", c.addEventListener("click", async () => {
|
|
104
104
|
c.disabled = !0, c.textContent = n.encrypted ? "Encrypting…" : "Syncing…";
|
|
105
105
|
try {
|
|
106
|
-
await
|
|
106
|
+
await A(t, e, s, n.encrypted);
|
|
107
107
|
} catch (d) {
|
|
108
|
-
c.disabled = !1, c.textContent = "Update & Share",
|
|
108
|
+
c.disabled = !1, c.textContent = "Update & Share", N(r), E(r, d.message);
|
|
109
109
|
}
|
|
110
|
-
}),
|
|
110
|
+
}), r.appendChild(c);
|
|
111
111
|
const i = document.createElement("a");
|
|
112
|
-
i.href =
|
|
112
|
+
i.href = g, i.target = "_blank", i.rel = "noopener", i.className = "share-token-link", i.textContent = "Open dashboard", r.appendChild(i), x(t, e, r);
|
|
113
113
|
}
|
|
114
|
-
function
|
|
114
|
+
function Y(t, e, s) {
|
|
115
115
|
const n = document.createElement("div");
|
|
116
116
|
n.className = "share-form";
|
|
117
|
-
const
|
|
118
|
-
|
|
117
|
+
const r = document.createElement("p");
|
|
118
|
+
r.className = "share-description", r.textContent = "Your graph will be encrypted and synced to your Backpack account. You’ll get a shareable link.", n.appendChild(r);
|
|
119
119
|
const o = document.createElement("button");
|
|
120
120
|
o.className = "share-cta-btn", o.textContent = "Sync & Share", o.addEventListener("click", async () => {
|
|
121
121
|
o.disabled = !0, o.textContent = "Encrypting…";
|
|
122
122
|
try {
|
|
123
|
-
await
|
|
123
|
+
await A(t, e, s, !0);
|
|
124
124
|
} catch (c) {
|
|
125
125
|
o.disabled = !1, o.textContent = "Sync & Share";
|
|
126
126
|
const i = c.message;
|
|
127
127
|
if (i.includes("encrypted") && i.includes("limit")) {
|
|
128
|
-
|
|
128
|
+
j(t, e, s);
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
|
-
|
|
131
|
+
N(n), E(n, i);
|
|
132
132
|
}
|
|
133
133
|
}), n.appendChild(o);
|
|
134
|
-
const
|
|
135
|
-
|
|
134
|
+
const a = document.createElement("p");
|
|
135
|
+
a.className = "share-trust", a.textContent = "Your first encrypted graph is free. Data is encrypted before upload — we can’t read it.", n.appendChild(a), x(t, e, n);
|
|
136
136
|
}
|
|
137
|
-
function
|
|
137
|
+
function j(t, e, s) {
|
|
138
138
|
const n = document.createElement("div");
|
|
139
139
|
n.className = "share-quota";
|
|
140
|
-
const
|
|
141
|
-
|
|
140
|
+
const r = document.createElement("h4");
|
|
141
|
+
r.textContent = "Encrypted limit reached", n.appendChild(r);
|
|
142
142
|
const o = document.createElement("p");
|
|
143
143
|
o.className = "share-description", o.textContent = "Your free account includes one encrypted graph, which is already in use.", n.appendChild(o);
|
|
144
|
-
const
|
|
145
|
-
|
|
144
|
+
const a = document.createElement("a");
|
|
145
|
+
a.href = `${g}/settings`, a.target = "_blank", a.rel = "noopener", a.className = "share-cta-btn share-btn-link", a.textContent = "Upgrade for unlimited encryption", n.appendChild(a);
|
|
146
146
|
const c = document.createElement("div");
|
|
147
147
|
c.className = "share-divider", n.appendChild(c);
|
|
148
148
|
const i = document.createElement("p");
|
|
@@ -161,70 +161,73 @@ function z(t, e, a) {
|
|
|
161
161
|
}), h.addEventListener("click", async () => {
|
|
162
162
|
h.disabled = !0, h.textContent = "Syncing…";
|
|
163
163
|
try {
|
|
164
|
-
await
|
|
165
|
-
} catch (
|
|
166
|
-
h.disabled = !1, h.textContent = "Share as public graph",
|
|
164
|
+
await A(t, e, s, !1, "public");
|
|
165
|
+
} catch (f) {
|
|
166
|
+
h.disabled = !1, h.textContent = "Share as public graph", N(n), E(n, f.message);
|
|
167
167
|
}
|
|
168
|
-
}), n.appendChild(h),
|
|
168
|
+
}), n.appendChild(h), x(t, e, n);
|
|
169
169
|
}
|
|
170
|
-
function
|
|
170
|
+
function x(t, e, s) {
|
|
171
171
|
const n = document.createElement("div");
|
|
172
172
|
n.className = "share-footer";
|
|
173
|
-
const
|
|
174
|
-
|
|
173
|
+
const r = document.createElement("button");
|
|
174
|
+
r.className = "share-token-link", r.textContent = "Sign out", r.addEventListener("click", async () => {
|
|
175
175
|
await t.settings.remove("relay_token"), C(t, e);
|
|
176
|
-
}), n.appendChild(
|
|
176
|
+
}), n.appendChild(r), s.appendChild(n), e.replaceChildren(s);
|
|
177
177
|
}
|
|
178
|
-
function
|
|
178
|
+
function N(t) {
|
|
179
179
|
for (const e of t.querySelectorAll(".share-error")) e.remove();
|
|
180
180
|
}
|
|
181
|
-
function
|
|
182
|
-
const
|
|
183
|
-
|
|
181
|
+
function E(t, e) {
|
|
182
|
+
const s = document.createElement("p");
|
|
183
|
+
s.className = "share-error", s.textContent = e, t.appendChild(s);
|
|
184
184
|
}
|
|
185
|
-
async function
|
|
185
|
+
async function G(t, e) {
|
|
186
186
|
try {
|
|
187
|
-
const n = await (await fetch(
|
|
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",
|
|
187
|
+
const n = await (await fetch(L)).json(), o = await (await fetch(n.registration_endpoint, { method: "POST" })).json(), a = M(), c = await J(a), 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", a), sessionStorage.setItem("share_oauth_redirect_uri", i);
|
|
189
189
|
const u = window.open(l.toString(), "backpack-share-auth", "width=500,height=700"), p = async (h) => {
|
|
190
|
-
var
|
|
191
|
-
if (((
|
|
190
|
+
var w;
|
|
191
|
+
if (((w = h.data) == null ? void 0 : w.type) !== "backpack-oauth-callback") return;
|
|
192
192
|
window.removeEventListener("message", p);
|
|
193
|
-
const { code:
|
|
194
|
-
if (
|
|
195
|
-
|
|
193
|
+
const { code: f, returnedState: _ } = h.data;
|
|
194
|
+
if (_ === d) {
|
|
195
|
+
H();
|
|
196
196
|
try {
|
|
197
|
-
const
|
|
197
|
+
const y = await (await fetch(n.token_endpoint, {
|
|
198
198
|
method: "POST",
|
|
199
199
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
200
|
-
body: new URLSearchParams({ grant_type: "authorization_code", code:
|
|
200
|
+
body: new URLSearchParams({ grant_type: "authorization_code", code: f, redirect_uri: i, client_id: o.client_id, code_verifier: a }).toString()
|
|
201
201
|
})).json();
|
|
202
|
-
if (!
|
|
203
|
-
|
|
202
|
+
if (!y.access_token) {
|
|
203
|
+
E(e, "Token exchange failed: no access token returned.");
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
|
-
await t.settings.set("relay_token",
|
|
207
|
-
} catch (
|
|
208
|
-
|
|
206
|
+
await t.settings.set("relay_token", y.access_token), C(t, e);
|
|
207
|
+
} catch (S) {
|
|
208
|
+
E(e, `Token exchange failed: ${S.message}`);
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
};
|
|
212
212
|
window.addEventListener("message", p), (!u || u.closed) && (window.location.href = l.toString());
|
|
213
|
-
} catch (
|
|
214
|
-
|
|
213
|
+
} catch (s) {
|
|
214
|
+
E(e, `Auth failed: ${s.message}`);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
-
async function
|
|
218
|
-
const o = t.getGraph(),
|
|
219
|
-
if (!o || !
|
|
217
|
+
async function A(t, e, s, n, r = "private") {
|
|
218
|
+
const o = t.getGraph(), a = t.getGraphName();
|
|
219
|
+
if (!o || !a) throw new Error("No graph loaded");
|
|
220
220
|
const c = new TextEncoder().encode(JSON.stringify(o));
|
|
221
221
|
let i, d, l = "";
|
|
222
222
|
if (n) {
|
|
223
|
-
const y = await import("../index-B8_hkT8R.js"),
|
|
224
|
-
|
|
223
|
+
const y = await import("../index-B8_hkT8R.js"), b = await t.settings.get("keys") || {};
|
|
224
|
+
let k = b[a];
|
|
225
|
+
k || (k = await y.generateX25519Identity(), b[a] = k, await t.settings.set("keys", b));
|
|
226
|
+
const P = await y.identityToRecipient(k), v = new y.Encrypter();
|
|
227
|
+
v.addRecipient(P), i = await v.encrypt(c), d = "age-v1", l = btoa(k).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
225
228
|
} else
|
|
226
229
|
i = c, d = "plaintext";
|
|
227
|
-
const u = await
|
|
230
|
+
const u = await K(a, i, d, o), p = `${g}/api/graphs/${encodeURIComponent(a)}/sync${r === "public" ? "?visibility=public" : ""}`, h = await T(s, p, {
|
|
228
231
|
method: "PUT",
|
|
229
232
|
headers: { "Content-Type": "application/octet-stream" },
|
|
230
233
|
body: u
|
|
@@ -234,35 +237,37 @@ async function x(t, e, a, n, s = "private") {
|
|
|
234
237
|
throw await t.settings.remove("relay_token"), C(t, e), new Error("Session expired. Please sign in again.");
|
|
235
238
|
let y = `Sync failed (${h.status})`;
|
|
236
239
|
try {
|
|
237
|
-
const
|
|
238
|
-
|
|
240
|
+
const b = await h.json();
|
|
241
|
+
b.error && (y = b.error);
|
|
239
242
|
} catch {
|
|
240
243
|
}
|
|
241
244
|
throw new Error(y);
|
|
242
245
|
}
|
|
243
|
-
const
|
|
246
|
+
const f = await t.settings.get("synced") || {};
|
|
247
|
+
f[a] || (f[a] = !0, await t.settings.set("synced", f));
|
|
248
|
+
const _ = await T(s, `${g}/api/graphs/${encodeURIComponent(a)}/share`, {
|
|
244
249
|
method: "POST"
|
|
245
250
|
});
|
|
246
|
-
if (!
|
|
247
|
-
|
|
251
|
+
if (!_.ok) {
|
|
252
|
+
U(e, null, n, void 0);
|
|
248
253
|
return;
|
|
249
254
|
}
|
|
250
|
-
const
|
|
251
|
-
|
|
255
|
+
const w = await _.json(), S = l ? `${w.url}#k=${l}` : w.url;
|
|
256
|
+
U(e, S, n, w.expires_at);
|
|
252
257
|
}
|
|
253
|
-
async function
|
|
254
|
-
const n = new Headers(
|
|
255
|
-
return n.set("Authorization", `Bearer ${t}`), fetch(e, { ...
|
|
258
|
+
async function T(t, e, s = {}) {
|
|
259
|
+
const n = new Headers(s.headers);
|
|
260
|
+
return n.set("Authorization", `Bearer ${t}`), fetch(e, { ...s, headers: n });
|
|
256
261
|
}
|
|
257
|
-
async function
|
|
258
|
-
const
|
|
262
|
+
async function K(t, e, s, n) {
|
|
263
|
+
const r = new Uint8Array(e).buffer, o = await crypto.subtle.digest("SHA-256", r), a = "sha256:" + Array.from(new Uint8Array(o)).map((h) => h.toString(16).padStart(2, "0")).join(""), c = /* @__PURE__ */ new Set();
|
|
259
264
|
for (const h of n.nodes) c.add(h.type);
|
|
260
265
|
const i = JSON.stringify({
|
|
261
|
-
format:
|
|
266
|
+
format: s,
|
|
262
267
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
263
268
|
backpack_name: t,
|
|
264
269
|
graph_count: 1,
|
|
265
|
-
checksum:
|
|
270
|
+
checksum: a,
|
|
266
271
|
node_count: n.nodes.length,
|
|
267
272
|
edge_count: n.edges.length,
|
|
268
273
|
node_types: Array.from(c)
|
|
@@ -270,17 +275,17 @@ async function j(t, e, a, n) {
|
|
|
270
275
|
new DataView(l).setUint32(0, d.length, !1);
|
|
271
276
|
const u = new Uint8Array(9 + d.length + e.length);
|
|
272
277
|
let p = 0;
|
|
273
|
-
return u.set(
|
|
278
|
+
return u.set($, p), p += 4, u[p] = I, p += 1, u.set(new Uint8Array(l), p), p += 4, u.set(d, p), p += d.length, u.set(e, p), u;
|
|
274
279
|
}
|
|
275
|
-
function
|
|
276
|
-
const
|
|
277
|
-
|
|
280
|
+
function U(t, e, s, n) {
|
|
281
|
+
const r = document.createElement("div");
|
|
282
|
+
r.className = "share-success";
|
|
278
283
|
const o = document.createElement("h4");
|
|
279
|
-
if (o.textContent = e ? "Synced & shared!" : "Synced!",
|
|
280
|
-
const
|
|
281
|
-
|
|
284
|
+
if (o.textContent = e ? "Synced & shared!" : "Synced!", r.appendChild(o), e) {
|
|
285
|
+
const a = document.createElement("div");
|
|
286
|
+
a.className = "share-link-row";
|
|
282
287
|
const c = document.createElement("input");
|
|
283
|
-
c.type = "text", c.readOnly = !0, c.value = e, c.className = "share-link-input",
|
|
288
|
+
c.type = "text", c.readOnly = !0, c.value = e, c.className = "share-link-input", a.appendChild(c);
|
|
284
289
|
const i = document.createElement("button");
|
|
285
290
|
i.className = "share-btn-primary", i.textContent = "Copy", i.addEventListener("click", () => {
|
|
286
291
|
navigator.clipboard.writeText(e).then(() => {
|
|
@@ -292,32 +297,32 @@ function A(t, e, a, n) {
|
|
|
292
297
|
i.textContent = "Copy";
|
|
293
298
|
}, 2e3);
|
|
294
299
|
});
|
|
295
|
-
}),
|
|
300
|
+
}), a.appendChild(i), r.appendChild(a);
|
|
296
301
|
}
|
|
297
|
-
if (
|
|
298
|
-
const
|
|
299
|
-
|
|
302
|
+
if (s) {
|
|
303
|
+
const a = document.createElement("p");
|
|
304
|
+
a.className = "share-note", a.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.", r.appendChild(a);
|
|
300
305
|
} else {
|
|
301
|
-
const
|
|
302
|
-
|
|
306
|
+
const a = document.createElement("p");
|
|
307
|
+
a.className = "share-note", a.textContent = "This graph is stored unencrypted. Anyone with the link can view it.", r.appendChild(a);
|
|
303
308
|
}
|
|
304
309
|
if (n) {
|
|
305
|
-
const
|
|
306
|
-
|
|
310
|
+
const a = document.createElement("p");
|
|
311
|
+
a.className = "share-note", a.textContent = `Expires: ${new Date(n).toLocaleDateString()}`, r.appendChild(a);
|
|
307
312
|
}
|
|
308
|
-
t.replaceChildren(
|
|
313
|
+
t.replaceChildren(r);
|
|
309
314
|
}
|
|
310
|
-
function
|
|
315
|
+
function H() {
|
|
311
316
|
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");
|
|
312
317
|
}
|
|
313
|
-
function
|
|
318
|
+
function M() {
|
|
314
319
|
const t = new Uint8Array(32);
|
|
315
320
|
return crypto.getRandomValues(t), btoa(String.fromCharCode(...t)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
316
321
|
}
|
|
317
|
-
async function
|
|
318
|
-
const e = new TextEncoder().encode(t),
|
|
322
|
+
async function J(t) {
|
|
323
|
+
const e = new TextEncoder().encode(t), s = new Uint8Array(e).buffer, n = await crypto.subtle.digest("SHA-256", s);
|
|
319
324
|
return btoa(String.fromCharCode(...new Uint8Array(n))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
320
325
|
}
|
|
321
326
|
export {
|
|
322
|
-
|
|
327
|
+
q as activate
|
|
323
328
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { listBackpacks, getActiveBackpack, setActiveBackpack, registerBackpack, unregisterBackpack, } from "backpack-ontology";
|
|
2
|
+
import { readExtensionSettings } from "./server-extensions.js";
|
|
2
3
|
// --- Small HTTP helpers (used only inside this module) ---
|
|
3
4
|
function readBody(req) {
|
|
4
5
|
return new Promise((resolve, reject) => {
|
|
@@ -375,6 +376,24 @@ export async function handleApiRequest(req, res, ctx) {
|
|
|
375
376
|
}
|
|
376
377
|
return true;
|
|
377
378
|
}
|
|
379
|
+
// --- /api/sync-status ---
|
|
380
|
+
if (url === "/api/sync-status" && method === "GET") {
|
|
381
|
+
try {
|
|
382
|
+
const settings = await readExtensionSettings("share");
|
|
383
|
+
const syncedMap = settings.synced;
|
|
384
|
+
const synced = [];
|
|
385
|
+
if (syncedMap && typeof syncedMap === "object" && !Array.isArray(syncedMap)) {
|
|
386
|
+
for (const name of Object.keys(syncedMap)) {
|
|
387
|
+
synced.push(name);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
sendJson(res, 200, { synced });
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
sendJson(res, 200, { synced: [] });
|
|
394
|
+
}
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
378
397
|
// --- /api/ontologies/* ---
|
|
379
398
|
if (url === "/api/ontologies" && method === "GET") {
|
|
380
399
|
try {
|
package/dist/sidebar.js
CHANGED
|
@@ -254,6 +254,10 @@ export function initSidebar(container, onSelectOrCallbacks) {
|
|
|
254
254
|
const lockBatchPromise = fetch("/api/locks")
|
|
255
255
|
.then((r) => r.json())
|
|
256
256
|
.catch(() => ({}));
|
|
257
|
+
const syncStatusPromise = fetch("/api/sync-status")
|
|
258
|
+
.then((r) => r.json())
|
|
259
|
+
.then((d) => new Set(d.synced))
|
|
260
|
+
.catch(() => new Set());
|
|
257
261
|
items = summaries.map((s) => {
|
|
258
262
|
const li = document.createElement("li");
|
|
259
263
|
li.className = "ontology-item";
|
|
@@ -273,8 +277,6 @@ export function initSidebar(container, onSelectOrCallbacks) {
|
|
|
273
277
|
lockBadge.className = "sidebar-lock-badge";
|
|
274
278
|
lockBadge.dataset.graph = s.name;
|
|
275
279
|
lockBatchPromise.then((locks) => {
|
|
276
|
-
// Bail if this badge has been detached from the DOM (sidebar
|
|
277
|
-
// re-rendered before the batch resolved)
|
|
278
280
|
if (!lockBadge.isConnected)
|
|
279
281
|
return;
|
|
280
282
|
const lock = locks[s.name];
|
|
@@ -284,9 +286,23 @@ export function initSidebar(container, onSelectOrCallbacks) {
|
|
|
284
286
|
lockBadge.classList.add("active");
|
|
285
287
|
}
|
|
286
288
|
});
|
|
289
|
+
// Sync badge — shows a cloud icon for graphs that have been synced
|
|
290
|
+
const syncBadge = document.createElement("span");
|
|
291
|
+
syncBadge.className = "sidebar-sync-badge";
|
|
292
|
+
syncBadge.dataset.graph = s.name;
|
|
293
|
+
syncStatusPromise.then((syncedSet) => {
|
|
294
|
+
if (!syncBadge.isConnected)
|
|
295
|
+
return;
|
|
296
|
+
if (syncedSet.has(s.name)) {
|
|
297
|
+
syncBadge.textContent = "synced";
|
|
298
|
+
syncBadge.title = "This graph has been synced";
|
|
299
|
+
syncBadge.classList.add("active");
|
|
300
|
+
}
|
|
301
|
+
});
|
|
287
302
|
li.appendChild(nameSpan);
|
|
288
303
|
li.appendChild(statsSpan);
|
|
289
304
|
li.appendChild(lockBadge);
|
|
305
|
+
li.appendChild(syncBadge);
|
|
290
306
|
li.appendChild(branchSpan);
|
|
291
307
|
if (cbs.onRename) {
|
|
292
308
|
const editBtn = document.createElement("button");
|
package/dist/style.css
CHANGED
|
@@ -506,6 +506,22 @@ body {
|
|
|
506
506
|
color: #c08c00;
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
+
.sidebar-sync-badge {
|
|
510
|
+
font-size: 10px;
|
|
511
|
+
color: #5a9fd4;
|
|
512
|
+
display: none;
|
|
513
|
+
margin-top: 2px;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.sidebar-sync-badge.active {
|
|
517
|
+
display: block;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.sidebar-sync-badge.active::before {
|
|
521
|
+
content: "☁ ";
|
|
522
|
+
color: #5a9fd4;
|
|
523
|
+
}
|
|
524
|
+
|
|
509
525
|
.branch-picker {
|
|
510
526
|
background: var(--bg-surface);
|
|
511
527
|
border: 1px solid var(--border);
|
package/package.json
CHANGED