@zackbart/connecta 0.9.1 → 0.10.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/CHANGELOG.md +128 -0
- package/README.md +49 -92
- package/dist/access-tokens.d.ts +32 -0
- package/dist/access-tokens.d.ts.map +1 -0
- package/dist/access-tokens.js +225 -0
- package/dist/access-tokens.js.map +1 -0
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +1 -4
- package/dist/catalog-service.js.map +1 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +3 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +79 -18
- package/dist/execute.js.map +1 -1
- package/dist/executor-result.d.ts.map +1 -1
- package/dist/executor-result.js +37 -6
- package/dist/executor-result.js.map +1 -1
- package/dist/executors/quickjs-protocol.d.ts +12 -0
- package/dist/executors/quickjs-protocol.d.ts.map +1 -1
- package/dist/executors/quickjs-protocol.js +14 -0
- package/dist/executors/quickjs-protocol.js.map +1 -1
- package/dist/executors/quickjs-runtime.d.ts.map +1 -1
- package/dist/executors/quickjs-runtime.js +6 -3
- package/dist/executors/quickjs-runtime.js.map +1 -1
- package/dist/executors/quickjs.d.ts.map +1 -1
- package/dist/executors/quickjs.js +10 -4
- package/dist/executors/quickjs.js.map +1 -1
- package/dist/index.d.ts +27 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +1 -4
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +26 -5
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +84 -40
- package/dist/meta-tools.js.map +1 -1
- package/dist/operator-ui/generated.d.ts +2 -2
- package/dist/operator-ui/generated.d.ts.map +1 -1
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/generated.js.map +1 -1
- package/dist/operator-ui/model.d.ts +2 -0
- package/dist/operator-ui/model.d.ts.map +1 -1
- package/dist/operator-ui/model.js.map +1 -1
- package/dist/routes/access-tokens.d.ts +7 -0
- package/dist/routes/access-tokens.d.ts.map +1 -0
- package/dist/routes/access-tokens.js +84 -0
- package/dist/routes/access-tokens.js.map +1 -0
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +8 -2
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/shared.d.ts +11 -2
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/routes/shared.js.map +1 -1
- package/dist/routes/ui.d.ts.map +1 -1
- package/dist/routes/ui.js +9 -1
- package/dist/routes/ui.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -0
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +15 -3
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +63 -10
- package/dist/skills.js.map +1 -1
- package/dist/storage/file.d.ts.map +1 -1
- package/dist/storage/file.js +5 -0
- package/dist/storage/file.js.map +1 -1
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +8 -0
- package/dist/storage/memory.js.map +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +4 -4
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +44 -1
- package/dist/ui.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +2 -2
- package/src/access-tokens.ts +289 -0
- package/src/catalog-service.ts +1 -5
- package/src/errors.ts +28 -0
- package/src/execute.ts +123 -48
- package/src/executor-result.ts +50 -6
- package/src/executors/quickjs-protocol.ts +19 -0
- package/src/executors/quickjs-runtime.ts +6 -2
- package/src/executors/quickjs.ts +10 -3
- package/src/index.ts +85 -5
- package/src/invocation.ts +1 -5
- package/src/meta-tools.ts +116 -53
- package/src/operator-ui/browser.css +63 -0
- package/src/operator-ui/browser.ts +288 -2
- package/src/operator-ui/generated.ts +2 -2
- package/src/operator-ui/model.ts +6 -0
- package/src/routes/access-tokens.ts +115 -0
- package/src/routes/mcp.ts +8 -2
- package/src/routes/shared.ts +11 -1
- package/src/routes/ui.ts +9 -0
- package/src/server.ts +5 -0
- package/src/skills.ts +79 -9
- package/src/storage/file.ts +5 -0
- package/src/storage/memory.ts +8 -0
- package/src/types.ts +20 -0
- package/src/ui.ts +50 -1
- package/src/version.ts +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AccessTokenManagementCapability,
|
|
2
3
|
filterUiConnectors,
|
|
3
4
|
type CredentialManagementCapability,
|
|
4
5
|
type UiData,
|
|
5
6
|
} from "./model.js";
|
|
6
7
|
|
|
7
|
-
type OperatorPage = "connections" | "credentials" | "activity";
|
|
8
|
+
type OperatorPage = "connections" | "credentials" | "tokens" | "activity";
|
|
8
9
|
|
|
9
10
|
interface BrowserAuth {
|
|
10
11
|
kind: string;
|
|
@@ -69,6 +70,17 @@ interface UiActionResponse {
|
|
|
69
70
|
ok?: boolean;
|
|
70
71
|
message?: string;
|
|
71
72
|
error?: string;
|
|
73
|
+
token?: string;
|
|
74
|
+
accessToken?: UiAccessToken;
|
|
75
|
+
accessTokens?: UiAccessToken[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface UiAccessToken {
|
|
79
|
+
id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
tokenPrefix: string;
|
|
82
|
+
createdAt: string;
|
|
83
|
+
revokedAt?: string;
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
interface OperatorElement extends HTMLElement {
|
|
@@ -99,12 +111,15 @@ const $ = (id: string): OperatorElement => {
|
|
|
99
111
|
const PAGE_META = {
|
|
100
112
|
connections: { path: "/", label: "Connections" },
|
|
101
113
|
credentials: { path: "/credentials", label: "Credentials" },
|
|
114
|
+
tokens: { path: "/tokens", label: "Access tokens" },
|
|
102
115
|
activity: { path: "/activity", label: "Activity" },
|
|
103
116
|
} satisfies Record<OperatorPage, { path: string; label: string }>;
|
|
104
117
|
let DATA: UiData | null = null;
|
|
105
118
|
let ACTIVITY: UiActivityEvent[] = [];
|
|
106
119
|
let ACTIVITY_CURSOR: string | null = null;
|
|
107
120
|
let ACTIVITY_LOADED = false;
|
|
121
|
+
let ACCESS_TOKENS: UiAccessToken[] = [];
|
|
122
|
+
let ACCESS_TOKENS_LOADED = false;
|
|
108
123
|
let CURRENT_PAGE: OperatorPage = INITIAL_PAGE;
|
|
109
124
|
let SESSION_GENERATION = 0;
|
|
110
125
|
let ACTIVITY_GENERATION = 0;
|
|
@@ -194,9 +209,20 @@ function clearIdentityState(): void {
|
|
|
194
209
|
$("credentialUnavailable").textContent = "";
|
|
195
210
|
$("credentialUnavailable").classList.add("hidden");
|
|
196
211
|
$("credentialList").classList.add("hidden");
|
|
212
|
+
ACCESS_TOKENS = [];
|
|
213
|
+
ACCESS_TOKENS_LOADED = false;
|
|
214
|
+
$("tokenList").innerHTML = "";
|
|
215
|
+
$("tokenList").setAttribute("aria-busy", "false");
|
|
216
|
+
$("tokenNotice").textContent = "";
|
|
217
|
+
clearCreatedAccessToken();
|
|
218
|
+
$("tokenName").value = "";
|
|
219
|
+
$("tokenUnavailable").textContent = "";
|
|
220
|
+
$("tokenUnavailable").classList.add("hidden");
|
|
221
|
+
$("tokenAvailable").classList.add("hidden");
|
|
197
222
|
$("activityUnavailable").classList.add("hidden");
|
|
198
223
|
$("activityAvailable").classList.add("hidden");
|
|
199
224
|
$("credentialsNav").classList.add("hidden");
|
|
225
|
+
$("tokensNav").classList.add("hidden");
|
|
200
226
|
$("activityNav").classList.add("hidden");
|
|
201
227
|
$("serverInfo").textContent = PRODUCT_OPERATOR_LABEL;
|
|
202
228
|
}
|
|
@@ -221,6 +247,7 @@ function showGate(msg: string): void {
|
|
|
221
247
|
|
|
222
248
|
function pageForPath(path: string): OperatorPage {
|
|
223
249
|
if (path === "/credentials") return "credentials";
|
|
250
|
+
if (path === "/tokens") return "tokens";
|
|
224
251
|
if (path === "/activity") return "activity";
|
|
225
252
|
return "connections";
|
|
226
253
|
}
|
|
@@ -237,16 +264,43 @@ function credentialUnavailableCopy(
|
|
|
237
264
|
return "Credential management requires an eligible Clerk operator. Bearer-authenticated sessions can inspect connections but cannot manage stored credentials.";
|
|
238
265
|
}
|
|
239
266
|
|
|
267
|
+
function accessTokenUnavailableCopy(
|
|
268
|
+
capability?: AccessTokenManagementCapability,
|
|
269
|
+
): string {
|
|
270
|
+
if (capability === "not_configured") {
|
|
271
|
+
return "Access tokens are not configured for this deployment. Add accessTokens to the deployment configuration to enable them.";
|
|
272
|
+
}
|
|
273
|
+
return "Access token management requires an eligible Clerk operator. A Bearer token can connect to MCP, but it cannot create or revoke other tokens.";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function setTokenNotice(message: string, error = false): void {
|
|
277
|
+
$("tokenNotice").textContent = message;
|
|
278
|
+
$("tokenNotice").setAttribute("role", error ? "alert" : "status");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function clearCreatedAccessToken(): void {
|
|
282
|
+
$("createdToken").textContent = "";
|
|
283
|
+
$("tokenReveal").classList.add("hidden");
|
|
284
|
+
$("tokenCreateForm").classList.remove("hidden");
|
|
285
|
+
}
|
|
286
|
+
|
|
240
287
|
function updateCapabilities(): void {
|
|
241
288
|
const credentialsAvailable =
|
|
242
289
|
DATA?.credentialManagement === "available";
|
|
243
290
|
$("credentialsNav").classList.toggle("hidden", !credentialsAvailable);
|
|
291
|
+
const tokensAvailable = DATA?.accessTokenManagement === "available";
|
|
292
|
+
$("tokensNav").classList.toggle("hidden", !tokensAvailable);
|
|
244
293
|
$("activityNav").classList.toggle("hidden", !DATA?.activityEnabled);
|
|
245
294
|
$("credentialUnavailable").classList.toggle("hidden", credentialsAvailable);
|
|
246
295
|
$("credentialList").classList.toggle("hidden", !credentialsAvailable);
|
|
247
296
|
$("credentialUnavailable").textContent = credentialsAvailable
|
|
248
297
|
? ""
|
|
249
298
|
: credentialUnavailableCopy(DATA?.credentialManagement);
|
|
299
|
+
$("tokenUnavailable").classList.toggle("hidden", Boolean(tokensAvailable));
|
|
300
|
+
$("tokenAvailable").classList.toggle("hidden", !tokensAvailable);
|
|
301
|
+
$("tokenUnavailable").textContent = tokensAvailable
|
|
302
|
+
? ""
|
|
303
|
+
: accessTokenUnavailableCopy(DATA?.accessTokenManagement);
|
|
250
304
|
$("activityUnavailable").classList.toggle("hidden", Boolean(DATA?.activityEnabled));
|
|
251
305
|
$("activityAvailable").classList.toggle("hidden", !DATA?.activityEnabled);
|
|
252
306
|
}
|
|
@@ -269,6 +323,13 @@ function activatePage(
|
|
|
269
323
|
updateCapabilities();
|
|
270
324
|
if (next === "connections") renderConnections();
|
|
271
325
|
if (next === "credentials") renderCredentials();
|
|
326
|
+
if (
|
|
327
|
+
next === "tokens" &&
|
|
328
|
+
DATA.accessTokenManagement === "available" &&
|
|
329
|
+
!ACCESS_TOKENS_LOADED
|
|
330
|
+
) {
|
|
331
|
+
void loadAccessTokens();
|
|
332
|
+
}
|
|
272
333
|
if (next === "activity" && DATA.activityEnabled && !ACTIVITY_LOADED) {
|
|
273
334
|
loadActivity(true);
|
|
274
335
|
}
|
|
@@ -277,6 +338,7 @@ function activatePage(
|
|
|
277
338
|
$("credentialList").innerHTML = "";
|
|
278
339
|
setNotice("");
|
|
279
340
|
}
|
|
341
|
+
if (next !== "tokens") clearCreatedAccessToken();
|
|
280
342
|
// Focus what is actually on screen. While gated the page views are hidden, so
|
|
281
343
|
// focusing their heading is a silent no-op that drops focus to <body> and
|
|
282
344
|
// restarts the next Tab from the top of the document — the gate's own h1 is
|
|
@@ -569,7 +631,7 @@ function renderConnections(): void {
|
|
|
569
631
|
|
|
570
632
|
async function operatorRequest(
|
|
571
633
|
path: string,
|
|
572
|
-
method: "DELETE" | "POST" | "PUT",
|
|
634
|
+
method: "DELETE" | "GET" | "POST" | "PUT",
|
|
573
635
|
generation: number,
|
|
574
636
|
body?: object,
|
|
575
637
|
): Promise<UiActionResponse | null> {
|
|
@@ -597,6 +659,226 @@ async function operatorRequest(
|
|
|
597
659
|
return payload;
|
|
598
660
|
}
|
|
599
661
|
|
|
662
|
+
function renderAccessTokens(): void {
|
|
663
|
+
const list = $("tokenList");
|
|
664
|
+
list.innerHTML = "";
|
|
665
|
+
if (!ACCESS_TOKENS.length) {
|
|
666
|
+
list.innerHTML =
|
|
667
|
+
'<p class="empty">No access tokens yet. Name the first MCP client above.</p>';
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
for (const token of ACCESS_TOKENS) {
|
|
671
|
+
const revoked = Boolean(token.revokedAt);
|
|
672
|
+
const item = document.createElement("section");
|
|
673
|
+
item.className = "token-card" + (revoked ? " revoked" : "");
|
|
674
|
+
item.setAttribute("aria-labelledby", "access-token-" + token.id);
|
|
675
|
+
item.innerHTML =
|
|
676
|
+
'<div class="token-card-head"><div><h2 id="access-token-' +
|
|
677
|
+
esc(token.id) + '">' + esc(token.name) +
|
|
678
|
+
'</h2><p class="mono">' + esc(token.tokenPrefix) +
|
|
679
|
+
'…</p></div><div class="cap">' +
|
|
680
|
+
(revoked
|
|
681
|
+
? "Revoked " + esc(formatDate(token.revokedAt!))
|
|
682
|
+
: "Created " + esc(formatDate(token.createdAt))) +
|
|
683
|
+
"</div></div>" +
|
|
684
|
+
'<div class="credential-actions">' +
|
|
685
|
+
'<button class="linklike" type="button" data-token-action="rename" data-token-id="' +
|
|
686
|
+
esc(token.id) + '">Rename</button>' +
|
|
687
|
+
(revoked
|
|
688
|
+
? ""
|
|
689
|
+
: '<button class="linklike danger" type="button" data-token-action="revoke" data-token-id="' +
|
|
690
|
+
esc(token.id) + '">Revoke</button>') +
|
|
691
|
+
"</div>" +
|
|
692
|
+
'<form class="credential-form hidden" data-token-form="' +
|
|
693
|
+
esc(token.id) + '"><label class="visually-hidden" for="token-name-' +
|
|
694
|
+
esc(token.id) + '">Token name</label><input id="token-name-' +
|
|
695
|
+
esc(token.id) + '" type="text" maxlength="80" value="' +
|
|
696
|
+
esc(token.name) + '" autocomplete="off">' +
|
|
697
|
+
'<button class="linklike" type="submit" data-token-action="save-name" data-token-id="' +
|
|
698
|
+
esc(token.id) + '">Save name</button>' +
|
|
699
|
+
'<button class="linklike" type="button" data-token-action="cancel-name" data-token-id="' +
|
|
700
|
+
esc(token.id) + '">Cancel</button></form>';
|
|
701
|
+
list.appendChild(item);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function loadAccessTokens(): Promise<void> {
|
|
706
|
+
const generation = SESSION_GENERATION;
|
|
707
|
+
$("tokenList").setAttribute("aria-busy", "true");
|
|
708
|
+
setTokenNotice("Loading access tokens…");
|
|
709
|
+
try {
|
|
710
|
+
const payload = await operatorRequest(
|
|
711
|
+
"/ui/access-tokens",
|
|
712
|
+
"GET",
|
|
713
|
+
generation,
|
|
714
|
+
);
|
|
715
|
+
if (generation !== SESSION_GENERATION) return;
|
|
716
|
+
ACCESS_TOKENS = payload?.accessTokens || [];
|
|
717
|
+
ACCESS_TOKENS_LOADED = true;
|
|
718
|
+
setTokenNotice("");
|
|
719
|
+
renderAccessTokens();
|
|
720
|
+
} catch (error) {
|
|
721
|
+
if (generation !== SESSION_GENERATION) return;
|
|
722
|
+
setTokenNotice(errorMessage(
|
|
723
|
+
error,
|
|
724
|
+
"Access tokens could not be loaded.",
|
|
725
|
+
), true);
|
|
726
|
+
} finally {
|
|
727
|
+
if (generation === SESSION_GENERATION) {
|
|
728
|
+
$("tokenList").setAttribute("aria-busy", "false");
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
$("tokenCreateForm").onsubmit = async (event) => {
|
|
734
|
+
event.preventDefault();
|
|
735
|
+
const name = $("tokenName").value.trim();
|
|
736
|
+
if (!name) {
|
|
737
|
+
setTokenNotice("Name the MCP client before creating a token.", true);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const generation = SESSION_GENERATION;
|
|
741
|
+
$("createToken").disabled = true;
|
|
742
|
+
setTokenNotice("");
|
|
743
|
+
try {
|
|
744
|
+
const payload = await operatorRequest(
|
|
745
|
+
"/ui/access-tokens",
|
|
746
|
+
"POST",
|
|
747
|
+
generation,
|
|
748
|
+
{ name },
|
|
749
|
+
);
|
|
750
|
+
if (generation !== SESSION_GENERATION) return;
|
|
751
|
+
if (!payload?.token || !payload.accessToken) {
|
|
752
|
+
throw new Error("The created token was not returned.");
|
|
753
|
+
}
|
|
754
|
+
ACCESS_TOKENS = [
|
|
755
|
+
payload.accessToken,
|
|
756
|
+
...ACCESS_TOKENS.filter((token) => token.id !== payload.accessToken!.id),
|
|
757
|
+
];
|
|
758
|
+
ACCESS_TOKENS_LOADED = true;
|
|
759
|
+
$("tokenName").value = "";
|
|
760
|
+
$("createdToken").textContent = payload.token;
|
|
761
|
+
$("tokenCreateForm").classList.add("hidden");
|
|
762
|
+
$("tokenReveal").classList.remove("hidden");
|
|
763
|
+
setTokenNotice("Access token created.");
|
|
764
|
+
renderAccessTokens();
|
|
765
|
+
$("tokenRevealHeading").focus();
|
|
766
|
+
} catch (error) {
|
|
767
|
+
if (generation !== SESSION_GENERATION) return;
|
|
768
|
+
setTokenNotice(errorMessage(
|
|
769
|
+
error,
|
|
770
|
+
"Access token could not be created.",
|
|
771
|
+
), true);
|
|
772
|
+
$("tokenNotice").focus();
|
|
773
|
+
} finally {
|
|
774
|
+
if (generation === SESSION_GENERATION) $("createToken").disabled = false;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
$("copyCreatedToken").onclick = async () => {
|
|
779
|
+
const button = $("copyCreatedToken");
|
|
780
|
+
const token = $("createdToken").textContent || "";
|
|
781
|
+
try {
|
|
782
|
+
await navigator.clipboard.writeText(token);
|
|
783
|
+
button.textContent = "Copied";
|
|
784
|
+
} catch {
|
|
785
|
+
button.textContent = "Copy failed";
|
|
786
|
+
}
|
|
787
|
+
window.setTimeout(() => { button.textContent = "Copy token"; }, 1600);
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
$("dismissCreatedToken").onclick = () => {
|
|
791
|
+
clearCreatedAccessToken();
|
|
792
|
+
$("tokenName").focus();
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
$("tokenList").onclick = async (event) => {
|
|
796
|
+
const button = closestElement<HTMLButtonElement>(
|
|
797
|
+
event.target,
|
|
798
|
+
"[data-token-action]",
|
|
799
|
+
);
|
|
800
|
+
if (!button) return;
|
|
801
|
+
const id = button.dataset.tokenId;
|
|
802
|
+
const action = button.dataset.tokenAction;
|
|
803
|
+
if (!id || !action) return;
|
|
804
|
+
const form = document.querySelector<HTMLFormElement>(
|
|
805
|
+
'[data-token-form="' + CSS.escape(id) + '"]',
|
|
806
|
+
);
|
|
807
|
+
if (!form) return;
|
|
808
|
+
if (action === "rename") {
|
|
809
|
+
form.classList.remove("hidden");
|
|
810
|
+
form.querySelector("input")?.focus();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (action === "cancel-name") {
|
|
814
|
+
const current = ACCESS_TOKENS.find((token) => token.id === id);
|
|
815
|
+
const input = form.querySelector<HTMLInputElement>("input");
|
|
816
|
+
if (input && current) input.value = current.name;
|
|
817
|
+
form.classList.add("hidden");
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (action !== "revoke") return;
|
|
821
|
+
const named = ACCESS_TOKENS.find((token) => token.id === id);
|
|
822
|
+
if (!window.confirm(
|
|
823
|
+
"Revoke " + (named?.name || "this access token") +
|
|
824
|
+
"? Its MCP client will immediately lose access."
|
|
825
|
+
)) return;
|
|
826
|
+
const generation = SESSION_GENERATION;
|
|
827
|
+
try {
|
|
828
|
+
const payload = await operatorRequest(
|
|
829
|
+
"/ui/access-tokens/" + encodeURIComponent(id),
|
|
830
|
+
"DELETE",
|
|
831
|
+
generation,
|
|
832
|
+
);
|
|
833
|
+
if (generation !== SESSION_GENERATION) return;
|
|
834
|
+
if (payload?.accessToken) {
|
|
835
|
+
ACCESS_TOKENS = ACCESS_TOKENS.map((token) =>
|
|
836
|
+
token.id === id ? payload.accessToken! : token);
|
|
837
|
+
}
|
|
838
|
+
setTokenNotice("Access token revoked.");
|
|
839
|
+
renderAccessTokens();
|
|
840
|
+
$("tokenNotice").focus();
|
|
841
|
+
} catch (error) {
|
|
842
|
+
if (generation !== SESSION_GENERATION) return;
|
|
843
|
+
setTokenNotice(errorMessage(
|
|
844
|
+
error,
|
|
845
|
+
"Access token could not be revoked.",
|
|
846
|
+
), true);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
$("tokenList").onsubmit = async (event) => {
|
|
851
|
+
event.preventDefault();
|
|
852
|
+
const form = closestElement<HTMLFormElement>(event.target, "[data-token-form]");
|
|
853
|
+
if (!form) return;
|
|
854
|
+
const id = form.dataset.tokenForm;
|
|
855
|
+
const name = form.querySelector<HTMLInputElement>("input")?.value.trim();
|
|
856
|
+
if (!id || !name) return;
|
|
857
|
+
const generation = SESSION_GENERATION;
|
|
858
|
+
try {
|
|
859
|
+
const payload = await operatorRequest(
|
|
860
|
+
"/ui/access-tokens/" + encodeURIComponent(id),
|
|
861
|
+
"PUT",
|
|
862
|
+
generation,
|
|
863
|
+
{ name },
|
|
864
|
+
);
|
|
865
|
+
if (generation !== SESSION_GENERATION) return;
|
|
866
|
+
if (payload?.accessToken) {
|
|
867
|
+
ACCESS_TOKENS = ACCESS_TOKENS.map((token) =>
|
|
868
|
+
token.id === id ? payload.accessToken! : token);
|
|
869
|
+
}
|
|
870
|
+
setTokenNotice("Access token renamed.");
|
|
871
|
+
renderAccessTokens();
|
|
872
|
+
$("tokenNotice").focus();
|
|
873
|
+
} catch (error) {
|
|
874
|
+
if (generation !== SESSION_GENERATION) return;
|
|
875
|
+
setTokenNotice(errorMessage(
|
|
876
|
+
error,
|
|
877
|
+
"Access token could not be renamed.",
|
|
878
|
+
), true);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
600
882
|
$("list").onclick = async (event) => {
|
|
601
883
|
const button = closestElement<HTMLButtonElement>(
|
|
602
884
|
event.target,
|
|
@@ -931,6 +1213,7 @@ document.addEventListener("click", (event) => {
|
|
|
931
1213
|
if (
|
|
932
1214
|
page !== "connections" &&
|
|
933
1215
|
page !== "credentials" &&
|
|
1216
|
+
page !== "tokens" &&
|
|
934
1217
|
page !== "activity"
|
|
935
1218
|
) return;
|
|
936
1219
|
event.preventDefault();
|
|
@@ -939,6 +1222,9 @@ document.addEventListener("click", (event) => {
|
|
|
939
1222
|
window.addEventListener("popstate", () => {
|
|
940
1223
|
activatePage(pageForPath(window.location.pathname), { focus: true });
|
|
941
1224
|
});
|
|
1225
|
+
// A document restored from the back-forward cache must not restore a
|
|
1226
|
+
// one-time secret that was visible when the operator left the page.
|
|
1227
|
+
window.addEventListener("pagehide", clearCreatedAccessToken);
|
|
942
1228
|
|
|
943
1229
|
async function init(): Promise<void> {
|
|
944
1230
|
if (AUTH.kind === "clerk") {
|