@canmi/web 2026.1005.4 → 2026.1005.7

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.
@@ -8,7 +8,10 @@ import { join } from "node:path";
8
8
  const VERSIONED = [
9
9
  "@codemirror/view",
10
10
  "@videojs/core",
11
- "d3-hierarchy"
11
+ "d3-array",
12
+ "d3-hierarchy",
13
+ "d3-scale",
14
+ "d3-shape"
12
15
  ];
13
16
  /** The globals a package used on every page is disclosed as, given its installed version. */
14
17
  const GLOBALS = {
@@ -0,0 +1,10 @@
1
+ import { MiddlewareHandler } from "hono";
2
+ //#region disclose/src/hono.d.ts
3
+ /**
4
+ * `X-Powered-By: Hono` on every answer, which is all Wappalyzer's Hono fingerprint reads -- and on
5
+ * a page's own response alone, so an API answered in a browser is named too. Hono's own
6
+ * `poweredBy` sets the header in place, which throws on an answer passed through from a `fetch`,
7
+ * whose headers are immutable; that one is copied first. See spec/web/disclose.md.
8
+ */
9
+ export declare function poweredBy(): MiddlewareHandler;
10
+ //#endregion
@@ -0,0 +1,25 @@
1
+ //#region disclose/src/hono.ts
2
+ /**
3
+ * `X-Powered-By: Hono` on every answer, which is all Wappalyzer's Hono fingerprint reads -- and on
4
+ * a page's own response alone, so an API answered in a browser is named too. Hono's own
5
+ * `poweredBy` sets the header in place, which throws on an answer passed through from a `fetch`,
6
+ * whose headers are immutable; that one is copied first. See spec/web/disclose.md.
7
+ */
8
+ function poweredBy() {
9
+ return async (c, next) => {
10
+ await next();
11
+ try {
12
+ c.res.headers.set("X-Powered-By", "Hono");
13
+ } catch {
14
+ const headers = new Headers(c.res.headers);
15
+ headers.set("X-Powered-By", "Hono");
16
+ c.res = new Response(c.res.body, {
17
+ status: c.res.status,
18
+ statusText: c.res.statusText,
19
+ headers
20
+ });
21
+ }
22
+ };
23
+ }
24
+ //#endregion
25
+ export { poweredBy };
@@ -19,6 +19,12 @@ export interface Disclosure {
19
19
  * is nothing to say. See spec/web/disclose.md.
20
20
  */
21
21
  export declare function disclosureHead(disclosure: Disclosure | undefined): string;
22
+ /**
23
+ * D3, from the component that draws with it: `d3.version`, which no D3 module sets. The value names
24
+ * the module, which Wappalyzer's version check refuses, so D3 shows without the module's own major
25
+ * read as D3's. Nothing when the app's build found no D3 module. See spec/web/disclose.md.
26
+ */
27
+ export declare function discloseD3(disclosure: Disclosure | undefined): void;
22
28
  /** Sets `globals` on `window`, from an app's root layout; nothing on the server. */
23
29
  export declare function discloseGlobals(disclosure: Disclosure | undefined): void;
24
30
  /**
@@ -14,6 +14,23 @@ function disclosureHead(disclosure) {
14
14
  }
15
15
  return parts.join("");
16
16
  }
17
+ /** The D3 modules an app may draw with, any one of which is D3 to a fingerprint. */
18
+ const D3_MODULES = [
19
+ "d3-hierarchy",
20
+ "d3-scale",
21
+ "d3-shape",
22
+ "d3-array"
23
+ ];
24
+ /**
25
+ * D3, from the component that draws with it: `d3.version`, which no D3 module sets. The value names
26
+ * the module, which Wappalyzer's version check refuses, so D3 shows without the module's own major
27
+ * read as D3's. Nothing when the app's build found no D3 module. See spec/web/disclose.md.
28
+ */
29
+ function discloseD3(disclosure) {
30
+ const versions = disclosure?.versions ?? {};
31
+ const name = D3_MODULES.find((module) => versions[module]);
32
+ if (name) disclose({ "d3.version": `${name}@${versions[name]}` });
33
+ }
17
34
  /** Sets `globals` on `window`, from an app's root layout; nothing on the server. */
18
35
  function discloseGlobals(disclosure) {
19
36
  if (disclosure) disclose(disclosure.globals);
@@ -41,4 +58,4 @@ function disclose(entries) {
41
58
  }
42
59
  }
43
60
  //#endregion
44
- export { disclose, discloseGlobals, disclosureHead };
61
+ export { disclose, discloseD3, discloseGlobals, disclosureHead };
@@ -0,0 +1,10 @@
1
+ //#region sentry/src/feedback.d.ts
2
+ /**
3
+ * Show the dialog, registering the integration the first time.
4
+ *
5
+ * Answers whether it opened: not without a client, which an app without a DSN never makes, and
6
+ * not when the dialog itself fails. A caller offers another way to write when it did not. In
7
+ * development it opens, and what is sent is dropped.
8
+ */
9
+ export declare function openReport(): Promise<boolean>;
10
+ //#endregion
@@ -0,0 +1,36 @@
1
+ import { feedbackIntegration, getClient, getFeedback } from "@sentry/sveltekit";
2
+ //#region sentry/src/feedback.ts
3
+ /**
4
+ * Sentry's feedback dialog, in a module nothing imports until a reader asks for it.
5
+ *
6
+ * Its widget is 24KB gzipped, so it is a module of its own and an app reaches it with
7
+ * `await import('@canmi/web/sentry/feedback')` from the control that opens it, never at the top
8
+ * of a file: that keeps it out of every page's entry, and out of the server, where
9
+ * `@sentry/sveltekit` has no `getFeedback`. Named imports, because a namespace import forces every
10
+ * export live and the bundler could no longer split this off. See spec/web/sentry.md.
11
+ */
12
+ let form;
13
+ /**
14
+ * Show the dialog, registering the integration the first time.
15
+ *
16
+ * Answers whether it opened: not without a client, which an app without a DSN never makes, and
17
+ * not when the dialog itself fails. A caller offers another way to write when it did not. In
18
+ * development it opens, and what is sent is dropped.
19
+ */
20
+ async function openReport() {
21
+ try {
22
+ const client = getClient();
23
+ if (!client) return false;
24
+ if (!getFeedback()) client.addIntegration(feedbackIntegration({ autoInject: false }));
25
+ const feedback = getFeedback();
26
+ if (!feedback) return false;
27
+ form ??= await feedback.createForm();
28
+ form.appendToDom();
29
+ form.open();
30
+ return true;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+ //#endregion
36
+ export { openReport };
@@ -0,0 +1,10 @@
1
+ //#region sentry/src/report.d.ts
2
+ /**
3
+ * What a "report a problem" control calls: Sentry's dialog when it opens, and the reader's mail
4
+ * client at `mailto` when it does not -- no client, a dialog that fails, or the dialog's own chunk
5
+ * not loading, as for a tab opened before a deploy. Imports nothing of Sentry itself, so a page
6
+ * may import this at the top and on the server; the dialog is fetched on the press. See
7
+ * spec/web/sentry.md.
8
+ */
9
+ export declare function report(mailto: string): Promise<void>;
10
+ //#endregion
@@ -0,0 +1,17 @@
1
+ //#region sentry/src/report.ts
2
+ /**
3
+ * What a "report a problem" control calls: Sentry's dialog when it opens, and the reader's mail
4
+ * client at `mailto` when it does not -- no client, a dialog that fails, or the dialog's own chunk
5
+ * not loading, as for a tab opened before a deploy. Imports nothing of Sentry itself, so a page
6
+ * may import this at the top and on the server; the dialog is fetched on the press. See
7
+ * spec/web/sentry.md.
8
+ */
9
+ async function report(mailto) {
10
+ try {
11
+ const { openReport } = await import("./feedback.js");
12
+ if (await openReport()) return;
13
+ } catch {}
14
+ window.location.href = mailto;
15
+ }
16
+ //#endregion
17
+ export { report };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmi/web",
3
- "version": "2026.1005.4",
3
+ "version": "2026.1005.7",
4
4
  "description": "SvelteKit compat, referer and Sentry.",
5
5
  "keywords": [
6
6
  "browserslist",
@@ -40,6 +40,14 @@
40
40
  "types": "./dist/sentry/src/client.d.ts",
41
41
  "default": "./dist/sentry/src/client.js"
42
42
  },
43
+ "./sentry/feedback": {
44
+ "types": "./dist/sentry/src/feedback.d.ts",
45
+ "default": "./dist/sentry/src/feedback.js"
46
+ },
47
+ "./sentry/report": {
48
+ "types": "./dist/sentry/src/report.d.ts",
49
+ "default": "./dist/sentry/src/report.js"
50
+ },
43
51
  "./sentry/server": {
44
52
  "types": "./dist/sentry/src/server.d.ts",
45
53
  "default": "./dist/sentry/src/server.js"
@@ -48,6 +56,10 @@
48
56
  "types": "./dist/disclose/src/build.d.ts",
49
57
  "default": "./dist/disclose/src/build.js"
50
58
  },
59
+ "./disclose/hono": {
60
+ "types": "./dist/disclose/src/hono.d.ts",
61
+ "default": "./dist/disclose/src/hono.js"
62
+ },
51
63
  "./disclose": {
52
64
  "types": "./dist/disclose/src/index.d.ts",
53
65
  "default": "./dist/disclose/src/index.js"
@@ -59,11 +71,13 @@
59
71
  },
60
72
  "devDependencies": {
61
73
  "@sentry/sveltekit": "^11.4.0",
62
- "@sveltejs/kit": "^3.0.0"
74
+ "@sveltejs/kit": "^3.0.0",
75
+ "hono": "^4.13.12"
63
76
  },
64
77
  "peerDependencies": {
65
78
  "@sentry/sveltekit": "^11.4.0",
66
- "@sveltejs/kit": "^3.0.0"
79
+ "@sveltejs/kit": "^3.0.0",
80
+ "hono": "^4.13.12"
67
81
  },
68
82
  "peerDependenciesMeta": {
69
83
  "@sentry/sveltekit": {
@@ -71,6 +85,9 @@
71
85
  },
72
86
  "@sveltejs/kit": {
73
87
  "optional": true
88
+ },
89
+ "hono": {
90
+ "optional": true
74
91
  }
75
92
  },
76
93
  "scripts": {