@edge-sky/dsh-oauth-adapter 0.1.1-rc.10

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/lib/client.js ADDED
@@ -0,0 +1,683 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@edge-sky/dsh-oauth-adapter",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/client/OAuthAccountsSection.tsx
10
+ /** OAuth account settings page. */
11
+ function safeHttpUrl(value) {
12
+ if (value === void 0) return void 0;
13
+ try {
14
+ const url = new URL(value);
15
+ return url.protocol === "http:" || url.protocol === "https:" ? url.href : void 0;
16
+ } catch {
17
+ return;
18
+ }
19
+ }
20
+ function AuthorizationLink(props) {
21
+ const host = new URL(props.url).host;
22
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
23
+ className: "dsh-oauth-link",
24
+ href: props.url,
25
+ target: "_blank",
26
+ rel: "noreferrer noopener",
27
+ children: [
28
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
29
+ className: "dsh-oauth-link-label",
30
+ children: props.t("openLink")
31
+ }),
32
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33
+ className: "dsh-oauth-link-host",
34
+ children: host
35
+ }),
36
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
37
+ className: "dsh-oauth-link-icon",
38
+ "aria-label": props.t("externalLink"),
39
+ children: "↗"
40
+ })
41
+ ]
42
+ });
43
+ }
44
+ function PromptForm(props) {
45
+ const { prompt } = props.active;
46
+ const [value, setValue] = (0, react.useState)(prompt.kind === "select" ? prompt.options[0]?.id ?? "" : "");
47
+ const canSubmit = value.length > 0 || prompt.kind === "text" && prompt.allowEmpty === true;
48
+ const submit = () => {
49
+ if (!canSubmit) return;
50
+ props.controller.respond(props.attemptId, props.active.id, value);
51
+ setValue("");
52
+ };
53
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
54
+ className: "dsh-oauth-prompt",
55
+ children: [
56
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: prompt.kind === "text" && prompt.allowEmpty === true ? props.t("githubDomainPrompt") : prompt.message }),
57
+ prompt.kind === "text" && prompt.allowEmpty === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
58
+ className: "dsh-oauth-muted",
59
+ children: props.t("githubDomainHint")
60
+ }),
61
+ prompt.kind === "select" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
62
+ className: "dsh-oauth-select",
63
+ value,
64
+ onChange: (event) => {
65
+ setValue(event.target.value);
66
+ },
67
+ children: prompt.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
68
+ value: option.id,
69
+ children: option.label
70
+ }, option.id))
71
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
72
+ className: "dsh-oauth-input",
73
+ type: prompt.kind === "secret" ? "password" : "text",
74
+ value,
75
+ placeholder: prompt.placeholder,
76
+ autoComplete: "off",
77
+ onChange: (event) => {
78
+ setValue(event.target.value);
79
+ },
80
+ onKeyDown: (event) => {
81
+ if (event.key === "Enter") submit();
82
+ }
83
+ }),
84
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
85
+ className: "dsh-oauth-button",
86
+ "data-variant": "primary",
87
+ type: "button",
88
+ disabled: !canSubmit,
89
+ onClick: submit,
90
+ children: props.t("submit")
91
+ })
92
+ ]
93
+ });
94
+ }
95
+ function ErrorPanel(props) {
96
+ const collapsible = props.detail !== void 0 && (props.detail.includes("\n") || props.detail.length > 240);
97
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
98
+ className: "dsh-oauth-error",
99
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: props.message }), props.detail !== void 0 && (collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
100
+ className: "dsh-oauth-error-details",
101
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: props.t("technicalDetails") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: props.detail })]
102
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: props.detail }))]
103
+ });
104
+ }
105
+ function AttemptPanel(props) {
106
+ const [copied, setCopied] = (0, react.useState)(false);
107
+ const url = safeHttpUrl(props.attempt.notice?.url);
108
+ const code = props.attempt.notice?.code;
109
+ const status = props.attempt.phase === "authorized" ? props.t("authorized") : props.attempt.phase === "cancelled" ? props.t("cancelled") : props.attempt.phase === "failed" ? props.t("failed") : props.attempt.phase === "starting" ? props.t("starting") : props.attempt.prompt !== void 0 ? props.t("awaitingAction") : props.t("waitingProvider");
110
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
111
+ className: "dsh-oauth-flow",
112
+ children: [
113
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: status }),
114
+ props.attempt.notice !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: props.attempt.notice.message }),
115
+ url !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuthorizationLink, {
116
+ url,
117
+ t: props.t
118
+ }),
119
+ code !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
120
+ className: "dsh-oauth-code",
121
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: code }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
122
+ className: "dsh-oauth-button",
123
+ type: "button",
124
+ onClick: () => {
125
+ navigator.clipboard.writeText(code).then(() => {
126
+ setCopied(true);
127
+ });
128
+ },
129
+ children: copied ? props.t("copied") : props.t("copy")
130
+ })]
131
+ }),
132
+ props.attempt.id !== void 0 && props.attempt.prompt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PromptForm, {
133
+ attemptId: props.attempt.id,
134
+ active: props.attempt.prompt,
135
+ controller: props.controller,
136
+ t: props.t
137
+ }, props.attempt.prompt.id),
138
+ props.attempt.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorPanel, {
139
+ message: props.attempt.error,
140
+ ...props.attempt.errorDetail === void 0 ? {} : { detail: props.attempt.errorDetail },
141
+ t: props.t
142
+ }),
143
+ props.attempt.id !== void 0 && (props.attempt.phase === "running" || props.attempt.phase === "starting") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
144
+ className: "dsh-oauth-button",
145
+ type: "button",
146
+ onClick: () => {
147
+ props.controller.cancel(props.attempt.id);
148
+ },
149
+ children: props.t("cancel")
150
+ })
151
+ ]
152
+ });
153
+ }
154
+ /** Render the standalone OAuth Accounts settings section. */
155
+ function OAuthAccountsSection(props) {
156
+ const controller = props.controller;
157
+ const t = props.t;
158
+ if (controller === void 0 || t === void 0) return null;
159
+ const snapshot = (0, react.useSyncExternalStore)(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
160
+ const accountByProvider = new Map(snapshot.accounts.map((account) => [account.provider, account]));
161
+ const providers = ["openai-codex", "github-copilot"];
162
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
163
+ className: "dsh-oauth-section",
164
+ children: [
165
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
166
+ className: "dsh-oauth-title",
167
+ children: t("title")
168
+ }),
169
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
170
+ className: "dsh-oauth-intro",
171
+ children: t("intro")
172
+ }),
173
+ snapshot.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorPanel, {
174
+ message: snapshot.error,
175
+ ...snapshot.errorDetail === void 0 ? {} : { detail: snapshot.errorDetail },
176
+ t
177
+ }),
178
+ snapshot.connection !== "open" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
179
+ className: "dsh-oauth-button",
180
+ "data-variant": "secondary",
181
+ type: "button",
182
+ onClick: () => {
183
+ controller.retry();
184
+ },
185
+ children: t("retry")
186
+ }),
187
+ snapshot.accounts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
188
+ className: "dsh-oauth-muted",
189
+ children: t("loading")
190
+ }),
191
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
192
+ className: "dsh-oauth-list",
193
+ children: providers.map((provider) => {
194
+ const account = accountByProvider.get(provider);
195
+ const attempt = snapshot.attempt?.provider === provider ? snapshot.attempt : void 0;
196
+ const busy = account?.inFlight === true || attempt?.phase === "running" || attempt?.phase === "starting";
197
+ const state = busy ? "authorizing" : account?.configured === true ? account.modelsEnabled ? "ready" : "warning" : "disconnected";
198
+ const stateText = busy ? t("authorizing") : account?.configured === true ? account.modelsEnabled ? t("ready") : t("connectedNoModels") : t("disconnected");
199
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
200
+ className: "dsh-oauth-card",
201
+ children: [
202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
203
+ className: "dsh-oauth-head",
204
+ children: [
205
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
206
+ className: "dsh-oauth-name",
207
+ children: account?.label ?? provider
208
+ }),
209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
210
+ className: "dsh-oauth-state",
211
+ "data-state": state,
212
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsh-oauth-dot" }), stateText]
213
+ }),
214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
215
+ className: "dsh-oauth-actions",
216
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
217
+ className: "dsh-oauth-button",
218
+ "data-variant": "primary",
219
+ type: "button",
220
+ disabled: snapshot.connection !== "open" || busy || account?.available !== true,
221
+ onClick: () => {
222
+ controller.begin(provider);
223
+ },
224
+ children: account?.configured === true ? t("reconnect") : t("connect")
225
+ }), account?.configured === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
226
+ className: "dsh-oauth-button",
227
+ "data-variant": "danger",
228
+ type: "button",
229
+ disabled: snapshot.connection !== "open" || busy || account.writable !== true,
230
+ onClick: () => {
231
+ controller.forget(provider);
232
+ },
233
+ children: t("forget")
234
+ })]
235
+ })
236
+ ]
237
+ }),
238
+ account !== void 0 && !account.available && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
239
+ className: "dsh-oauth-muted",
240
+ children: t("unavailable")
241
+ }),
242
+ attempt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttemptPanel, {
243
+ attempt,
244
+ controller,
245
+ t
246
+ })
247
+ ]
248
+ }, provider);
249
+ })
250
+ })
251
+ ]
252
+ });
253
+ }
254
+ //#endregion
255
+ //#region src/client/locales.ts
256
+ /** English copy for the OAuth accounts page. */
257
+ const en = {
258
+ nav: "OAuth Accounts",
259
+ title: "OAuth Accounts",
260
+ intro: "Connect subscription-backed providers. Their models appear in separate OAuth groups, apart from API-key providers.",
261
+ ready: "Connected · Models ready",
262
+ connectedNoModels: "Connected · Model route unavailable",
263
+ disconnected: "Not connected",
264
+ authorizing: "Sign-in in progress",
265
+ unavailable: "OAuth is unavailable in this DSH installation.",
266
+ connect: "Connect",
267
+ reconnect: "Reconnect",
268
+ cancel: "Cancel",
269
+ forget: "Sign out",
270
+ retry: "Retry connection",
271
+ starting: "Preparing a secure sign-in session…",
272
+ awaitingAction: "Action required to continue sign-in.",
273
+ waitingProvider: "Waiting for the provider to complete sign-in…",
274
+ authorized: "Sign-in completed.",
275
+ cancelled: "Sign-in was cancelled.",
276
+ failed: "Sign-in failed.",
277
+ technicalDetails: "Technical details",
278
+ submit: "Continue",
279
+ copy: "Copy code",
280
+ copied: "Copied",
281
+ openLink: "Open authorization page",
282
+ externalLink: "External provider page",
283
+ githubDomainPrompt: "GitHub Enterprise domain",
284
+ githubDomainHint: "Leave blank to use github.com.",
285
+ loading: "Loading account status…",
286
+ hostDisconnected: "The OAuth connection to DSH was interrupted."
287
+ };
288
+ /** Simplified Chinese copy for the OAuth accounts page. */
289
+ const zh = {
290
+ nav: "OAuth 账户",
291
+ title: "OAuth 账户",
292
+ intro: "连接订阅型模型服务。OAuth 模型会以独立分组显示,与 API 密钥提供商分开。",
293
+ ready: "已连接 · 模型可用",
294
+ connectedNoModels: "已连接 · 模型路由不可用",
295
+ disconnected: "未连接",
296
+ authorizing: "正在登录",
297
+ unavailable: "当前 DSH 安装未提供此 OAuth 流程。",
298
+ connect: "连接",
299
+ reconnect: "重新连接",
300
+ cancel: "取消",
301
+ forget: "退出登录",
302
+ retry: "重新连接 DSH",
303
+ starting: "正在准备安全登录会话…",
304
+ awaitingAction: "需要完成操作以继续登录。",
305
+ waitingProvider: "正在等待提供商完成登录…",
306
+ authorized: "登录已完成。",
307
+ cancelled: "登录已取消。",
308
+ failed: "登录失败。",
309
+ technicalDetails: "技术详情",
310
+ submit: "继续",
311
+ copy: "复制代码",
312
+ copied: "已复制",
313
+ openLink: "打开授权页面",
314
+ externalLink: "外部提供商页面",
315
+ githubDomainPrompt: "GitHub Enterprise 域名",
316
+ githubDomainHint: "留空将默认使用 github.com。",
317
+ loading: "正在读取账户状态…",
318
+ hostDisconnected: "与 DSH 的 OAuth 连接已中断。"
319
+ };
320
+ //#endregion
321
+ //#region src/protocol.ts
322
+ /** Shared browser/Host wire vocabulary for the OAuth account surface. */
323
+ const OAUTH_SOCKET_PATH = "/_edge-sky/dsh-oauth";
324
+ const OAUTH_SOCKET_PROTOCOL = "dsh-oauth-v1";
325
+ //#endregion
326
+ //#region src/client/store.ts
327
+ /** Browser state machine for the plugin-owned OAuth WebSocket. */
328
+ function socketUrl() {
329
+ return `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}${OAUTH_SOCKET_PATH}`;
330
+ }
331
+ function isServerMessage(value) {
332
+ return typeof value === "object" && value !== null && typeof value.type === "string";
333
+ }
334
+ /** Remove interaction-only content once an authorization attempt settles. */
335
+ function settleAttemptView(attempt, status) {
336
+ const next = {
337
+ ...attempt,
338
+ phase: status
339
+ };
340
+ delete next.prompt;
341
+ delete next.notice;
342
+ return next;
343
+ }
344
+ /** Hide a successful attempt after the account snapshot confirms its model route. */
345
+ function reconcileAttemptView(attempt, accounts) {
346
+ if (attempt?.phase !== "authorized" || attempt.error !== void 0) return attempt;
347
+ const account = accounts.find((candidate) => candidate.provider === attempt.provider);
348
+ return account?.configured === true && account.modelsEnabled ? void 0 : attempt;
349
+ }
350
+ /** App-lifetime controller; reconnects without persisting provider messages or answers. */
351
+ var OAuthAccountsController = class {
352
+ snapshot = {
353
+ connection: "connecting",
354
+ accounts: []
355
+ };
356
+ listeners = /* @__PURE__ */ new Set();
357
+ socket;
358
+ reconnectTimer;
359
+ reconnectDelay = 500;
360
+ disposed = false;
361
+ constructor() {
362
+ this.connect();
363
+ }
364
+ subscribe = (listener) => {
365
+ this.listeners.add(listener);
366
+ return () => {
367
+ this.listeners.delete(listener);
368
+ };
369
+ };
370
+ getSnapshot = () => this.snapshot;
371
+ begin(provider) {
372
+ if (this.snapshot.attempt?.phase === "starting" || this.snapshot.attempt?.phase === "running") return;
373
+ const { error: _error, errorDetail: _errorDetail, ...current } = this.snapshot;
374
+ this.update({
375
+ ...current,
376
+ attempt: {
377
+ provider,
378
+ phase: "starting"
379
+ }
380
+ });
381
+ this.send({
382
+ type: "begin",
383
+ requestId: crypto.randomUUID(),
384
+ provider
385
+ });
386
+ }
387
+ respond(attemptId, promptId, value) {
388
+ this.send({
389
+ type: "respond",
390
+ requestId: crypto.randomUUID(),
391
+ attemptId,
392
+ promptId,
393
+ value
394
+ });
395
+ const attempt = this.snapshot.attempt;
396
+ if (attempt?.id === attemptId && attempt.prompt?.id === promptId) {
397
+ const next = { ...attempt };
398
+ delete next.prompt;
399
+ this.update({
400
+ ...this.snapshot,
401
+ attempt: next
402
+ });
403
+ }
404
+ }
405
+ cancel(attemptId) {
406
+ this.send({
407
+ type: "cancel",
408
+ requestId: crypto.randomUUID(),
409
+ attemptId
410
+ });
411
+ }
412
+ forget(provider) {
413
+ this.send({
414
+ type: "forget",
415
+ requestId: crypto.randomUUID(),
416
+ provider
417
+ });
418
+ }
419
+ retry() {
420
+ if (this.socket?.readyState === WebSocket.OPEN || this.socket?.readyState === WebSocket.CONNECTING) return;
421
+ if (this.reconnectTimer !== void 0) window.clearTimeout(this.reconnectTimer);
422
+ this.reconnectTimer = void 0;
423
+ this.connect();
424
+ }
425
+ dispose() {
426
+ this.disposed = true;
427
+ if (this.reconnectTimer !== void 0) window.clearTimeout(this.reconnectTimer);
428
+ this.reconnectTimer = void 0;
429
+ this.socket?.close(1e3, "client plugin disposed");
430
+ this.socket = void 0;
431
+ this.listeners.clear();
432
+ }
433
+ connect() {
434
+ if (this.disposed) return;
435
+ this.update({
436
+ ...this.snapshot,
437
+ connection: this.socket === void 0 ? "connecting" : "reconnecting"
438
+ });
439
+ const socket = new WebSocket(socketUrl(), OAUTH_SOCKET_PROTOCOL);
440
+ this.socket = socket;
441
+ socket.addEventListener("open", () => {
442
+ if (this.socket !== socket) return;
443
+ this.reconnectDelay = 500;
444
+ const { error: _error, errorDetail: _errorDetail, ...current } = this.snapshot;
445
+ this.update({
446
+ ...current,
447
+ connection: "open"
448
+ });
449
+ this.send({
450
+ type: "refresh",
451
+ requestId: crypto.randomUUID()
452
+ });
453
+ });
454
+ socket.addEventListener("message", (event) => {
455
+ if (this.socket !== socket || typeof event.data !== "string") return;
456
+ let value;
457
+ try {
458
+ value = JSON.parse(event.data);
459
+ } catch {
460
+ const next = {
461
+ ...this.snapshot,
462
+ error: "DSH returned an invalid OAuth message."
463
+ };
464
+ delete next.errorDetail;
465
+ this.update(next);
466
+ return;
467
+ }
468
+ if (isServerMessage(value)) this.receive(value);
469
+ });
470
+ socket.addEventListener("close", () => {
471
+ if (this.socket !== socket) return;
472
+ this.socket = void 0;
473
+ if (this.disposed) {
474
+ this.update({
475
+ ...this.snapshot,
476
+ connection: "closed"
477
+ });
478
+ return;
479
+ }
480
+ const attempt = this.snapshot.attempt;
481
+ const interrupted = {
482
+ ...this.snapshot,
483
+ connection: "reconnecting",
484
+ error: "The OAuth connection to DSH was interrupted.",
485
+ ...attempt === void 0 || attempt.phase === "authorized" || attempt.phase === "cancelled" ? {} : { attempt: {
486
+ ...attempt,
487
+ phase: "failed",
488
+ error: "The OAuth connection to DSH was interrupted."
489
+ } }
490
+ };
491
+ delete interrupted.errorDetail;
492
+ if (interrupted.attempt?.error === "The OAuth connection to DSH was interrupted.") delete interrupted.attempt.errorDetail;
493
+ this.update(interrupted);
494
+ const delay = this.reconnectDelay;
495
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 5e3);
496
+ this.reconnectTimer = window.setTimeout(() => {
497
+ this.reconnectTimer = void 0;
498
+ this.connect();
499
+ }, delay);
500
+ });
501
+ socket.addEventListener("error", () => {});
502
+ }
503
+ send(command) {
504
+ if (this.socket?.readyState !== WebSocket.OPEN) {
505
+ const next = {
506
+ ...this.snapshot,
507
+ error: "The OAuth connection to DSH is not ready."
508
+ };
509
+ delete next.errorDetail;
510
+ this.update(next);
511
+ return;
512
+ }
513
+ this.socket.send(JSON.stringify(command));
514
+ }
515
+ receive(message) {
516
+ switch (message.type) {
517
+ case "snapshot":
518
+ const accounts = [...message.accounts];
519
+ const reconciled = reconcileAttemptView(this.snapshot.attempt, accounts);
520
+ const nextSnapshot = {
521
+ ...this.snapshot,
522
+ accounts
523
+ };
524
+ if (reconciled === void 0) delete nextSnapshot.attempt;
525
+ else nextSnapshot.attempt = reconciled;
526
+ this.update(nextSnapshot);
527
+ return;
528
+ case "started":
529
+ const { error: _error, errorDetail: _errorDetail, ...current } = this.snapshot;
530
+ this.update({
531
+ ...current,
532
+ attempt: {
533
+ id: message.attemptId,
534
+ provider: message.provider,
535
+ phase: "running"
536
+ }
537
+ });
538
+ return;
539
+ case "notice": {
540
+ const attempt = this.snapshot.attempt;
541
+ if (attempt?.id !== message.attemptId) return;
542
+ this.update({
543
+ ...this.snapshot,
544
+ attempt: {
545
+ ...attempt,
546
+ notice: {
547
+ message: message.message,
548
+ ...message.url === void 0 ? {} : { url: message.url },
549
+ ...message.code === void 0 ? {} : { code: message.code }
550
+ }
551
+ }
552
+ });
553
+ return;
554
+ }
555
+ case "prompt": {
556
+ const attempt = this.snapshot.attempt;
557
+ if (attempt?.id !== message.attemptId) return;
558
+ this.update({
559
+ ...this.snapshot,
560
+ attempt: {
561
+ ...attempt,
562
+ prompt: {
563
+ id: message.promptId,
564
+ prompt: message.prompt
565
+ }
566
+ }
567
+ });
568
+ return;
569
+ }
570
+ case "prompt-withdrawn": {
571
+ const attempt = this.snapshot.attempt;
572
+ if (attempt?.id !== message.attemptId || attempt.prompt?.id !== message.promptId) return;
573
+ const next = { ...attempt };
574
+ delete next.prompt;
575
+ this.update({
576
+ ...this.snapshot,
577
+ attempt: next
578
+ });
579
+ return;
580
+ }
581
+ case "settled": {
582
+ const attempt = this.snapshot.attempt;
583
+ if (attempt?.id !== message.attemptId) return;
584
+ this.update({
585
+ ...this.snapshot,
586
+ attempt: settleAttemptView(attempt, message.status)
587
+ });
588
+ return;
589
+ }
590
+ case "error": {
591
+ const attempt = this.snapshot.attempt;
592
+ if (message.attemptId !== void 0 && attempt?.id === message.attemptId) {
593
+ const nextAttempt = {
594
+ ...attempt,
595
+ error: message.message
596
+ };
597
+ if (message.detail === void 0) delete nextAttempt.errorDetail;
598
+ else nextAttempt.errorDetail = message.detail;
599
+ this.update({
600
+ ...this.snapshot,
601
+ attempt: nextAttempt
602
+ });
603
+ } else {
604
+ const next = {
605
+ ...this.snapshot,
606
+ error: message.message
607
+ };
608
+ if (message.detail === void 0) delete next.errorDetail;
609
+ else next.errorDetail = message.detail;
610
+ this.update(next);
611
+ }
612
+ return;
613
+ }
614
+ case "ack": return;
615
+ }
616
+ }
617
+ update(snapshot) {
618
+ this.snapshot = snapshot;
619
+ for (const listener of this.listeners) listener();
620
+ }
621
+ };
622
+ //#endregion
623
+ //#region src/client/styles.ts
624
+ /** Plugin-owned styles, injected and removed with the browser Cordis fiber. */
625
+ const styles = `
626
+ .dsh-oauth-section{display:flex;flex-direction:column;gap:12px;max-width:720px;color:var(--dsw-alias-label-primary)}
627
+ .dsh-oauth-title{margin:0;font-size:16px;line-height:24px;font-weight:500}
628
+ .dsh-oauth-intro,.dsh-oauth-muted{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
629
+ .dsh-oauth-error{display:flex;flex-direction:column;gap:6px;margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.dsh-oauth-error p{margin:0}.dsh-oauth-error pre{box-sizing:border-box;max-width:100%;max-height:320px;margin:0;padding:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;overflow:auto;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:11px/17px ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.dsh-oauth-error-details summary{cursor:pointer;color:var(--dsw-alias-label-secondary);user-select:none}.dsh-oauth-error-details[open] summary{margin-bottom:6px}
630
+ .dsh-oauth-list{display:flex;flex-direction:column;gap:8px;margin:4px 0 0;padding:0;list-style:none}
631
+ .dsh-oauth-card{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}
632
+ .dsh-oauth-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dsh-oauth-name{font-size:14px;font-weight:500}.dsh-oauth-state{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);font-size:12px;color:var(--dsw-alias-label-secondary)}
633
+ .dsh-oauth-dot{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary)}.dsh-oauth-state[data-state=ready] .dsh-oauth-dot{background:var(--dsw-alias-state-success-primary)}.dsh-oauth-state[data-state=warning] .dsh-oauth-dot{background:var(--dsw-alias-state-error-primary)}.dsh-oauth-state[data-state=authorizing] .dsh-oauth-dot{background:var(--dsw-alias-button-info-fill)}
634
+ .dsh-oauth-actions{display:flex;gap:6px;margin-left:auto}.dsh-oauth-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:30px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:15px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}.dsh-oauth-button[data-variant=primary]{border-color:transparent;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.dsh-oauth-button[data-variant=danger]{border-color:transparent;color:var(--dsw-alias-state-error-primary)}.dsh-oauth-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-button[data-variant=primary]:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover);color:var(--dsw-alias-label-primary-foreground)}.dsh-oauth-button[data-variant=danger]:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.dsh-oauth-button:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.dsh-oauth-button:disabled{cursor:not-allowed;opacity:.4}
635
+ .dsh-oauth-flow{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l3);font-size:13px;line-height:20px}.dsh-oauth-link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);text-decoration:none}.dsh-oauth-link:hover{background:var(--dsw-alias-interactive-bg-hover-solid)}.dsh-oauth-link-label{font-weight:500}.dsh-oauth-link-host{max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsh-oauth-link-icon{color:var(--dsw-alias-brand-text)}.dsh-oauth-code{display:flex;align-items:center;gap:8px}.dsh-oauth-code code{padding:5px 8px;border-radius:6px;background:var(--dsw-alias-bg-module-platform);font-size:14px;letter-spacing:.08em}
636
+ .dsh-oauth-prompt{display:flex;flex-direction:column;gap:7px}.dsh-oauth-input,.dsh-oauth-select{box-sizing:border-box;width:100%;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}
637
+ `;
638
+ //#endregion
639
+ //#region src/client/index.ts
640
+ const NS = "settings.oauth-accounts";
641
+ /** Required browser services for settings placement and localized copy. */
642
+ const inject = ["slots", "locale"];
643
+ /** Register the OAuth account page and its app-lifetime connection controller. */
644
+ function apply(ctx) {
645
+ ctx.effect(() => ctx.locale.register(NS, {
646
+ zh,
647
+ en
648
+ }), "dsh-oauth-adapter: dictionaries");
649
+ ctx.effect(() => {
650
+ const style = document.createElement("style");
651
+ style.dataset.plugin = "@edge-sky/dsh-oauth-adapter";
652
+ style.textContent = styles;
653
+ document.head.append(style);
654
+ return () => {
655
+ style.remove();
656
+ };
657
+ }, "dsh-oauth-adapter: styles");
658
+ const controller = new OAuthAccountsController();
659
+ ctx.effect(() => () => {
660
+ controller.dispose();
661
+ }, "dsh-oauth-adapter: controller");
662
+ const t = ctx.locale.bind(NS);
663
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
664
+ name: "settings.section",
665
+ id: "oauth-accounts",
666
+ order: 15,
667
+ label: () => t("nav"),
668
+ inject: () => ({
669
+ controller,
670
+ t
671
+ })
672
+ }, OAuthAccountsSection));
673
+ }
674
+ //#endregion
675
+ exports.OAuthAccountsController = OAuthAccountsController;
676
+ exports.OAuthAccountsSection = OAuthAccountsSection;
677
+ exports.apply = apply;
678
+ exports.inject = inject;
679
+ return module.exports;
680
+ }
681
+ });
682
+
683
+ //# sourceMappingURL=client.js.map