@devframes/hub-ui-onboard 1.2.4 → 1.2.5

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.
@@ -119,10 +119,14 @@ function p(e) {
119
119
  U(e), e.state === "installing" && setTimeout(() => void W(), n);
120
120
  }
121
121
  async function G() {
122
+ if (P.disabled = !0, I.textContent = h.confirming, !(await i("install", { method: "POST" })).ok) {
123
+ await W(), N.hidden = !1, N.textContent = h.refused;
124
+ return;
125
+ }
122
126
  U({
123
127
  ...e,
124
128
  state: "installing"
125
- }), await i("install", { method: "POST" }), await W();
129
+ }), await W();
126
130
  }
127
131
  async function K() {
128
132
  (await i("disable", { method: "POST" })).ok && t.remove();
package/dist/index.d.mts CHANGED
@@ -19,6 +19,10 @@ interface OnboardingMessages {
19
19
  /** Short sentence under the heading. */
20
20
  description: string;
21
21
  install: string;
22
+ /** Install button label while `onConfirm` waits for the developer. */
23
+ confirming: string;
24
+ /** Shown when `onConfirm` refused the install. */
25
+ refused: string;
22
26
  /** Hides the button for this browser tab (`sessionStorage`). */
23
27
  hide: string;
24
28
  /** Writes the state file; the host stops injecting the button. */
@@ -30,6 +34,15 @@ interface OnboardingMessages {
30
34
  }
31
35
  /** A web-standard request handler the hub exposes once installed. */
32
36
  type OnboardingHandler = (request: Request) => Response | Promise<Response>;
37
+ /** What `onConfirm` receives when the panel asks to install. */
38
+ interface OnboardingConfirmation {
39
+ /** The packages Install adds, as given in `packages`. */
40
+ packages: string[];
41
+ /** The exact command Install runs. */
42
+ command: string[];
43
+ /** The panel's request, for example to read its `Origin` or `User-Agent`. */
44
+ request: Request;
45
+ }
33
46
  interface CreateOnboardingOptions {
34
47
  /** Packages to install when the user clicks Install. Package specs as the package manager accepts them. */
35
48
  packages: string[];
@@ -50,6 +63,14 @@ interface CreateOnboardingOptions {
50
63
  * user to restart the dev server.
51
64
  */
52
65
  onInstalled?: () => void | OnboardingHandler | Promise<void | OnboardingHandler>;
66
+ /**
67
+ * Ask the developer to allow an install that the panel requested; the
68
+ * dev server can be reachable from other devices. Return `true` to run
69
+ * it. Only one call is open at a time: clicks meanwhile share its answer.
70
+ * Default: a `y/N` question in the terminal, or a refusal when stdin is
71
+ * not a TTY.
72
+ */
73
+ onConfirm?: (confirmation: OnboardingConfirmation) => boolean | Promise<boolean>;
53
74
  }
54
75
  type OnboardingState = 'idle' | 'installing' | 'installed' | 'ready' | 'error' | 'disabled';
55
76
  /** The `GET <base>__onboard/status` body. */
@@ -90,4 +111,4 @@ export declare function defaultMessages(productName: string): OnboardingMessages
90
111
  */
91
112
  export declare function createOnboarding(options: CreateOnboardingOptions): Onboarding;
92
113
  //#endregion
93
- export type { CreateOnboardingOptions, Onboarding, OnboardingBranding, OnboardingHandler, OnboardingLogo, OnboardingMessages, OnboardingState, OnboardingStatus };
114
+ export type { CreateOnboardingOptions, Onboarding, OnboardingBranding, OnboardingConfirmation, OnboardingHandler, OnboardingLogo, OnboardingMessages, OnboardingState, OnboardingStatus };
package/dist/index.mjs CHANGED
@@ -38,6 +38,14 @@ const diagnostics = defineDiagnostics({
38
38
  DF9004: {
39
39
  why: (p) => `The onboarding state file ${p.file} could not be read or written.`,
40
40
  fix: "Make sure that `stateDir` is writable, or point it at another directory."
41
+ },
42
+ DF9005: {
43
+ why: (p) => `A browser asked to run \`${p.command}\`. Type y and press Enter to allow it.`,
44
+ fix: "If you did not click Install, press Enter to refuse. Another device can reach this dev server, so bind it to localhost."
45
+ },
46
+ DF9006: {
47
+ why: (p) => `A browser asked to run \`${p.command}\`, but no interactive terminal can confirm it. The install was refused.`,
48
+ fix: "Run the command yourself, or pass an `onConfirm` function to `createOnboarding()`."
41
49
  }
42
50
  }
43
51
  });
@@ -94,6 +102,26 @@ function packageName(spec) {
94
102
  return /^((?:@[\w.~-]+\/)?[\w.~-]+)(?:@[^/]*)?$/.exec(spec)?.[1];
95
103
  }
96
104
  //#endregion
105
+ //#region src/terminal-confirm.ts
106
+ /**
107
+ * The default `onConfirm`: a `y/N` question in the terminal that runs the dev
108
+ * server. Without an interactive terminal nobody can answer, so it refuses.
109
+ */
110
+ function confirmInTerminal(command, input = process.stdin) {
111
+ if (!input.isTTY) {
112
+ diagnostics.DF9006({ command: command.join(" ") });
113
+ return Promise.resolve(false);
114
+ }
115
+ diagnostics.DF9005({ command: command.join(" ") });
116
+ const flowing = input.readableFlowing;
117
+ return new Promise((resolve) => {
118
+ input.once("data", (chunk) => {
119
+ if (!flowing) input.pause();
120
+ resolve(/^y(?:es)?$/i.test(String(chunk).trim()));
121
+ });
122
+ });
123
+ }
124
+ //#endregion
97
125
  //#region src/index.ts
98
126
  const STATE_FILE = "hub-ui-onboard.json";
99
127
  /** `devframes` → `/devframes/`, same rule as `normalizeHubBase` in `@devframes/hub`. */
@@ -115,6 +143,8 @@ function defaultMessages(productName) {
115
143
  title: productName,
116
144
  description: `${productName} enables development features in your project. Install it with:`,
117
145
  install: `Install ${productName}`,
146
+ confirming: "Confirm in your terminal...",
147
+ refused: "The dev server did not allow the install. Check its terminal, or run the command above yourself.",
118
148
  hide: "Hide in this session",
119
149
  disable: "Disable entirely",
120
150
  installing: "Installing...",
@@ -258,11 +288,29 @@ function createOnboarding(options) {
258
288
  state = "disabled";
259
289
  return new Response(null, { status: 204 });
260
290
  }
291
+ const commandLine = async () => {
292
+ const resolved = await getCommand();
293
+ return [resolved.command, ...resolved.args];
294
+ };
295
+ const onConfirm = options.onConfirm ?? (({ command }) => confirmInTerminal(command));
296
+ let asking;
297
+ async function startInstall(request) {
298
+ if (state !== "idle" && state !== "error") return new Response(null, { status: 202 });
299
+ asking ??= commandLine().then((command) => onConfirm({
300
+ packages: plan.packages,
301
+ command,
302
+ request
303
+ })).finally(() => {
304
+ asking = void 0;
305
+ });
306
+ if (!await asking) return new Response(null, { status: 403 });
307
+ if (state === "idle" || state === "error") install();
308
+ return new Response(null, { status: 202 });
309
+ }
261
310
  async function status() {
262
- const command = await getCommand();
263
311
  return {
264
312
  state,
265
- command: [command.command, ...command.args],
313
+ command: await commandLine(),
266
314
  branding,
267
315
  messages,
268
316
  error
@@ -281,9 +329,7 @@ function createOnboarding(options) {
281
329
  if (route === "GET __onboard/status") return json(await status());
282
330
  if (route === "POST __onboard/install" || route === "POST __onboard/disable") {
283
331
  if (!isSameOrigin(request)) return new Response(null, { status: 403 });
284
- if (route.endsWith("disable")) return disable();
285
- if (state === "idle" || state === "error") install();
286
- return new Response(null, { status: 202 });
332
+ return route.endsWith("disable") ? disable() : startInstall(request);
287
333
  }
288
334
  return new Response(null, { status: 404 });
289
335
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devframes/hub-ui-onboard",
3
3
  "type": "module",
4
- "version": "1.2.4",
4
+ "version": "1.2.5",
5
5
  "description": "A tiny stand-in for the devframe hub UI: a floating button that installs it on demand.",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",