@comms-id/website-logo 0.0.0-stage → 0.3.2

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.
Files changed (47) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +43 -2
  3. package/dist/browser.d.ts +5 -0
  4. package/dist/browser.js +6 -0
  5. package/dist/controller.d.ts +13 -0
  6. package/dist/controller.js +9 -0
  7. package/dist/descriptor.d.ts +11 -0
  8. package/dist/descriptor.js +15 -0
  9. package/dist/fixtures.d.ts +5 -0
  10. package/dist/fixtures.js +10 -0
  11. package/dist/generated/client.d.ts +8 -0
  12. package/dist/generated/client.js +6 -0
  13. package/dist/generated/fixtures.d.ts +2 -0
  14. package/dist/generated/fixtures.js +27 -0
  15. package/dist/generated/operations.d.ts +6 -0
  16. package/dist/generated/operations.js +214 -0
  17. package/dist/generated/types.d.ts +29 -0
  18. package/dist/generated/types.js +2 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.js +4 -0
  21. package/dist/local/profile.d.ts +12 -0
  22. package/dist/local/profile.js +77 -0
  23. package/dist/runtime/browser-core.d.ts +23 -0
  24. package/dist/runtime/browser-core.js +41 -0
  25. package/dist/runtime/environment.d.ts +5 -0
  26. package/dist/runtime/environment.js +30 -0
  27. package/dist/runtime/errors.d.ts +29 -0
  28. package/dist/runtime/errors.js +40 -0
  29. package/dist/runtime/fixture-core.d.ts +7 -0
  30. package/dist/runtime/fixture-core.js +26 -0
  31. package/dist/runtime/http.d.ts +34 -0
  32. package/dist/runtime/http.js +172 -0
  33. package/dist/runtime/image-controller.d.ts +7 -0
  34. package/dist/runtime/image-controller.js +184 -0
  35. package/dist/runtime/image-types.d.ts +112 -0
  36. package/dist/runtime/image-types.js +8 -0
  37. package/dist/runtime/public-core.d.ts +12 -0
  38. package/dist/runtime/public-core.js +16 -0
  39. package/dist/runtime/server-core.d.ts +26 -0
  40. package/dist/runtime/server-core.js +39 -0
  41. package/dist/runtime/sign.d.ts +24 -0
  42. package/dist/runtime/sign.js +43 -0
  43. package/dist/runtime/validate.d.ts +57 -0
  44. package/dist/runtime/validate.js +175 -0
  45. package/dist/server.d.ts +9 -0
  46. package/dist/server.js +8 -0
  47. package/package.json +63 -3
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Copyright (c) 2026 COMMS.ID PTY LTD. Proprietary software; not open source.
2
+
3
+ Subject to the Comms.ID policy agreement, you may install, copy, bundle, run and
4
+ deliver this client or component within your applications, adapt the supplied
5
+ component source, and use the designated local functions and fixtures offline.
6
+ Hosted operations are provided by the Comms.ID service.
7
+
8
+ You may keep replies for your own customers and transactions, and your registered
9
+ application may relay requests for its own users.
10
+
11
+ You may not republish these packages as a standalone offering, resell access, offer
12
+ a substitute lookup service, or harvest replies to reconstruct or redistribute a
13
+ source dataset.
14
+
15
+ Preserve this notice and any applicable third-party notices.
package/README.md CHANGED
@@ -1,3 +1,44 @@
1
- # Temporary Holding Version
1
+ # @comms-id/website-logo
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed client for the Comms.ID Website logo API (`https://api.comms.id/logo/v1`): a company's logo, resolved from its website address.
4
+
5
+ `src/` is generated from the Website logo contract (`packages/website-logo-service/openapi/website-logo.v1.json`) by `@comms-id/forge-transformers`. Do not edit it: change the contract and run `pnpm --filter @comms-id/forge-transformers generate`.
6
+
7
+ ```ts
8
+ import { createWebsiteLogoClient } from "@comms-id/website-logo/server";
9
+
10
+ const client = createWebsiteLogoClient({
11
+ clientId: process.env.COMMS_ID_CLIENT_ID!,
12
+ privateJwk: JSON.parse(process.env.COMMS_ID_PRIVATE_JWK!),
13
+ });
14
+ const reply = await client.resolve({"hostname": "example.com", "mode": "direct-favicon"});
15
+ ```
16
+
17
+ ## Operations
18
+
19
+ - `resolve`: Resolve a website logo using the selected source or named composition. A genuine miss is a 200 reply with status missing; source failures remain distinct.
20
+
21
+ Every operation is a read, safe to repeat, and is retried after a retryable failure. A reply that is not a match is one of the outcomes of the reply convention: an error with `outcomeOf(error)` of `no-match`, `source-unavailable` or `failed`; an outage is never a "no match".
22
+
23
+ ## Test it first
24
+
25
+ `createWebsiteLogoFixtureClient()` from `@comms-id/website-logo/fixtures` answers from the contract's own examples with no network and says so (`fixture: true`). To call the TEST service (synthetic replies, your development app's key), pass `environment: "test"`; going live changes only the environment and the key.
26
+
27
+ `COMMS_ID_PRIVATE_JWK` is an ML-DSA-87 private JWK (`kty` `AKP`, with `priv` and `pub`). The server entry needs Node.js 24.7 or later, or a Cloudflare Worker with the compatibility flag `webcrypto_modern_algorithms`.
28
+
29
+ Entries: `.` (types and errors, safe in a browser), `./server` (signs each call), `./browser` (your relay route; never signs), `./fixtures` (LOCAL FIXTURES, no network), `./descriptor` (what `@comms-id/relay` may forward).
30
+
31
+ `LICENSE` is the consumer client licence.
32
+
33
+ ## The image
34
+
35
+ `@comms-id/website-logo/controller` is the headless controller of a Website logo image: it asks the service about a site and decides what to show, and it refuses any image outside the Comms.ID hosts. It has no framework and no DOM. `@comms-id/website-logo-react` (a hook and a component) and `<comms-id-website-logo>` (a web element) run on it, and so can your own UI.
36
+
37
+ ```ts
38
+ import { createWebsiteLogoController } from "@comms-id/website-logo/controller";
39
+
40
+ const controller = createWebsiteLogoController({ client, site: "example.com" });
41
+ controller.subscribe(() => render(controller.getState())); // state.url is the image to load, or the letter state.monogram
42
+ ```
43
+
44
+ `src/local/profile.ts` is the only product-specific part: it reads the site, prepares the call and takes the Comms.ID image from the reply. Everything else is generated.
@@ -0,0 +1,5 @@
1
+ import { type WebsiteLogoClient } from "./generated/client.js";
2
+ import { type BrowserOptions } from "./runtime/browser-core.js";
3
+ export type { Environment } from "./runtime/environment.js";
4
+ export type { BrowserOptions, CapabilityOptions, RelayOptions } from "./runtime/browser-core.js";
5
+ export declare const createWebsiteLogoBrowserClient: (options: BrowserOptions) => WebsiteLogoClient;
@@ -0,0 +1,6 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ // Browser entry: it never signs and holds no secret. Use the consumer's relay route, or a
3
+ // short-lived capability that the caller supplies.
4
+ import { buildClient } from "./generated/client.js";
5
+ import { createBrowserTransport } from "./runtime/browser-core.js";
6
+ export const createWebsiteLogoBrowserClient = (options) => buildClient(createBrowserTransport(options));
@@ -0,0 +1,13 @@
1
+ import type { WebsiteLogoClient } from "./generated/client.js";
2
+ import { type ProductRecord, profile } from "./local/profile.js";
3
+ import type { ImageController, ImageControllerOptions, ImageState } from "./runtime/image-types.js";
4
+ export { initialImageState, isAllowedImage } from "./runtime/image-controller.js";
5
+ export type { FallbackReason, ImageError, ImageProfile, ImageReading, ImageState, ImageStatus, Resolved, Run, } from "./runtime/image-types.js";
6
+ export type { WebsiteLogoClient } from "./generated/client.js";
7
+ export { profile };
8
+ export type { ProductRecord as WebsiteLogoRecord };
9
+ export type WebsiteLogoController = ImageController<ProductRecord>;
10
+ export type WebsiteLogoControllerOptions = Omit<ImageControllerOptions<WebsiteLogoClient, ProductRecord>, "profile">;
11
+ export type WebsiteLogoState = ImageState<ProductRecord>;
12
+ /** Creates the controller. Nothing is called until a site is set. */
13
+ export declare const createWebsiteLogoController: (options: WebsiteLogoControllerOptions) => WebsiteLogoController;
@@ -0,0 +1,9 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ // Controller entry: the headless controller of the Comms.ID Website logo image and the product's profile.
3
+ // No framework, no DOM. The React package and the web element run on it.
4
+ import { profile } from "./local/profile.js";
5
+ import { createImageController } from "./runtime/image-controller.js";
6
+ export { initialImageState, isAllowedImage } from "./runtime/image-controller.js";
7
+ export { profile };
8
+ /** Creates the controller. Nothing is called until a site is set. */
9
+ export const createWebsiteLogoController = (options) => createImageController({ ...options, profile });
@@ -0,0 +1,11 @@
1
+ export declare const websiteLogoProduct: {
2
+ readonly product: "website-logo";
3
+ readonly audience: "website-logo";
4
+ readonly scope: "website-logo";
5
+ readonly maxBodyBytes: 16384;
6
+ readonly operations: readonly [{
7
+ readonly name: "resolve";
8
+ readonly method: "POST";
9
+ readonly path: "/website-logo/v1/resolve";
10
+ }];
11
+ };
@@ -0,0 +1,15 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ // The product descriptor that @comms-id/relay reads: which operations may be forwarded.
3
+ export const websiteLogoProduct = {
4
+ product: "website-logo",
5
+ audience: "website-logo",
6
+ scope: "website-logo",
7
+ maxBodyBytes: 16_384,
8
+ operations: [
9
+ {
10
+ "name": "resolve",
11
+ "method": "POST",
12
+ "path": "/website-logo/v1/resolve"
13
+ }
14
+ ],
15
+ };
@@ -0,0 +1,5 @@
1
+ import { type WebsiteLogoClient } from "./generated/client.js";
2
+ export { FIXTURE_HEADER, FIXTURE_NOTICE } from "./runtime/fixture-core.js";
3
+ export declare const createWebsiteLogoFixtureClient: () => WebsiteLogoClient & {
4
+ readonly fixture: true;
5
+ };
@@ -0,0 +1,10 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ // LOCAL FIXTURES: no network. The client runs the same request and reply checks as a real call.
3
+ import { buildClient } from "./generated/client.js";
4
+ import { fixtures } from "./generated/fixtures.js";
5
+ import { createFixtureTransport } from "./runtime/fixture-core.js";
6
+ export { FIXTURE_HEADER, FIXTURE_NOTICE } from "./runtime/fixture-core.js";
7
+ export const createWebsiteLogoFixtureClient = () => ({
8
+ ...buildClient(createFixtureTransport(fixtures)),
9
+ fixture: true,
10
+ });
@@ -0,0 +1,8 @@
1
+ import { type CallOptions, type TransportConfig } from "../runtime/http.js";
2
+ import type { ResolveRequest, ResolveResponse } from "./types.js";
3
+ /** The client of Comms.ID Website logo: one method for each operation. */
4
+ export interface WebsiteLogoClient {
5
+ /** Resolve a website logo using the selected source or named composition. A genuine miss is a 200 reply with status missing; source failures remain distinct. */
6
+ readonly resolve: (input: ResolveRequest, options?: CallOptions) => Promise<ResolveResponse>;
7
+ }
8
+ export declare const buildClient: (transport: TransportConfig) => WebsiteLogoClient;
@@ -0,0 +1,6 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ import { call } from "../runtime/http.js";
3
+ import { operations } from "./operations.js";
4
+ export const buildClient = (transport) => ({
5
+ resolve: (input, options) => call(transport, operations.resolve, input, options),
6
+ });
@@ -0,0 +1,2 @@
1
+ /** LOCAL FIXTURES: one canned reply for each operation path. Fake data. */
2
+ export declare const fixtures: Readonly<Record<string, unknown>>;
@@ -0,0 +1,27 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ // Every reply is the example in the contract.
3
+ /** LOCAL FIXTURES: one canned reply for each operation path. Fake data. */
4
+ export const fixtures = {
5
+ "/website-logo/v1/resolve": {
6
+ "mode": "direct-favicon",
7
+ "status": "found",
8
+ "resolution": {
9
+ "resolved": true,
10
+ "logoUrl": "https://example.com/favicon.ico",
11
+ "source": "direct-favicon",
12
+ "hostname": "example.com",
13
+ "canonicalUrl": "https://example.com",
14
+ "redirectHostname": null,
15
+ "iframeBlocked": null,
16
+ "cached": false,
17
+ "imageUrl": "https://api.comms.id/website-logo/v1/image/ZGlyZWN0LWZhdmljb258ZXhhbXBsZS5jb218MTc5MTYzMzYwMA.c2lnbmF0dXJlLWV4YW1wbGUtc2lnbmF0dXJlLWV4YW1wbGUtMDA"
18
+ },
19
+ "outcomes": [
20
+ {
21
+ "source": "direct-favicon",
22
+ "outcome": "hit",
23
+ "code": null
24
+ }
25
+ ]
26
+ }
27
+ };
@@ -0,0 +1,6 @@
1
+ import type { Operation } from "../runtime/http.js";
2
+ export type OperationName = "resolve";
3
+ /** The operations of Comms.ID Website logo, with the schemas the client checks. */
4
+ export declare const operations: Readonly<Record<OperationName, Operation>>;
5
+ /** The error codes the contract documents for each operation. */
6
+ export declare const errorCodes: Readonly<Record<OperationName, readonly string[]>>;
@@ -0,0 +1,214 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ /** The operations of Comms.ID Website logo, with the schemas the client checks. */
3
+ export const operations = {
4
+ resolve: {
5
+ name: "resolve",
6
+ method: "POST",
7
+ path: "/website-logo/v1/resolve",
8
+ safeToRetry: true,
9
+ request: {
10
+ "t": "obj",
11
+ "props": {
12
+ "hostname": {
13
+ "t": "str",
14
+ "max": 253
15
+ },
16
+ "url": {
17
+ "t": "str",
18
+ "max": 2048
19
+ },
20
+ "fresh": {
21
+ "t": "bool"
22
+ },
23
+ "mode": {
24
+ "t": "str",
25
+ "values": [
26
+ "composed",
27
+ "microlink",
28
+ "logo-dev",
29
+ "direct-favicon"
30
+ ]
31
+ }
32
+ },
33
+ "req": [],
34
+ "extra": false
35
+ },
36
+ response: {
37
+ "t": "obj",
38
+ "props": {
39
+ "mode": {
40
+ "t": "str",
41
+ "values": [
42
+ "composed",
43
+ "microlink",
44
+ "logo-dev",
45
+ "direct-favicon"
46
+ ]
47
+ },
48
+ "status": {
49
+ "t": "str",
50
+ "values": [
51
+ "found",
52
+ "missing"
53
+ ]
54
+ },
55
+ "resolution": {
56
+ "t": "obj",
57
+ "props": {
58
+ "resolved": {
59
+ "t": "bool"
60
+ },
61
+ "logoUrl": {
62
+ "t": "any_of",
63
+ "of": [
64
+ {
65
+ "t": "str"
66
+ },
67
+ {
68
+ "t": "null"
69
+ }
70
+ ]
71
+ },
72
+ "source": {
73
+ "t": "any_of",
74
+ "of": [
75
+ {
76
+ "t": "str",
77
+ "values": [
78
+ "microlink",
79
+ "logo.dev",
80
+ "direct-favicon"
81
+ ]
82
+ },
83
+ {
84
+ "t": "null"
85
+ }
86
+ ]
87
+ },
88
+ "hostname": {
89
+ "t": "str"
90
+ },
91
+ "canonicalUrl": {
92
+ "t": "any_of",
93
+ "of": [
94
+ {
95
+ "t": "str"
96
+ },
97
+ {
98
+ "t": "null"
99
+ }
100
+ ]
101
+ },
102
+ "redirectHostname": {
103
+ "t": "any_of",
104
+ "of": [
105
+ {
106
+ "t": "str"
107
+ },
108
+ {
109
+ "t": "null"
110
+ }
111
+ ]
112
+ },
113
+ "iframeBlocked": {
114
+ "t": "any_of",
115
+ "of": [
116
+ {
117
+ "t": "bool"
118
+ },
119
+ {
120
+ "t": "null"
121
+ }
122
+ ]
123
+ },
124
+ "cached": {
125
+ "t": "bool"
126
+ },
127
+ "imageUrl": {
128
+ "t": "any_of",
129
+ "of": [
130
+ {
131
+ "t": "str"
132
+ },
133
+ {
134
+ "t": "null"
135
+ }
136
+ ]
137
+ }
138
+ },
139
+ "req": [
140
+ "resolved",
141
+ "logoUrl",
142
+ "source",
143
+ "hostname",
144
+ "canonicalUrl",
145
+ "redirectHostname",
146
+ "iframeBlocked",
147
+ "cached",
148
+ "imageUrl"
149
+ ],
150
+ "extra": false
151
+ },
152
+ "outcomes": {
153
+ "t": "arr",
154
+ "of": {
155
+ "t": "obj",
156
+ "props": {
157
+ "source": {
158
+ "t": "str",
159
+ "values": [
160
+ "microlink",
161
+ "logo.dev",
162
+ "direct-favicon"
163
+ ]
164
+ },
165
+ "outcome": {
166
+ "t": "str",
167
+ "values": [
168
+ "hit",
169
+ "miss",
170
+ "error",
171
+ "timeout",
172
+ "skipped"
173
+ ]
174
+ },
175
+ "code": {
176
+ "t": "any_of",
177
+ "of": [
178
+ {
179
+ "t": "str",
180
+ "values": [
181
+ "UPSTREAM_ERROR",
182
+ "TIMEOUT",
183
+ "MISSING_CREDENTIALS"
184
+ ]
185
+ },
186
+ {
187
+ "t": "null"
188
+ }
189
+ ]
190
+ }
191
+ },
192
+ "req": [
193
+ "source",
194
+ "outcome",
195
+ "code"
196
+ ],
197
+ "extra": false
198
+ }
199
+ }
200
+ },
201
+ "req": [
202
+ "mode",
203
+ "status",
204
+ "resolution",
205
+ "outcomes"
206
+ ],
207
+ "extra": false
208
+ },
209
+ },
210
+ };
211
+ /** The error codes the contract documents for each operation. */
212
+ export const errorCodes = {
213
+ resolve: ["INVALID_INPUT", "UNAUTHORIZED", "ORIGIN_NOT_ALLOWED", "NOT_FOUND", "METHOD_NOT_ALLOWED", "LIMIT_REACHED", "FAIR_USE_CEILING", "CANCELLED", "INTERNAL_ERROR", "CONFIGURATION_ERROR", "UNAVAILABLE_MODE", "RESIDENCY_UNAVAILABLE", "HTTP_503"],
214
+ };
@@ -0,0 +1,29 @@
1
+ /** The body of resolve. */
2
+ export type ResolveRequest = {
3
+ readonly hostname?: string;
4
+ readonly url?: string;
5
+ readonly fresh?: boolean;
6
+ readonly mode?: "composed" | "microlink" | "logo-dev" | "direct-favicon";
7
+ };
8
+ /** The reply of resolve. */
9
+ export type ResolveResponse = {
10
+ readonly mode: "composed" | "microlink" | "logo-dev" | "direct-favicon";
11
+ readonly status: "found" | "missing";
12
+ readonly resolution: {
13
+ readonly resolved: boolean;
14
+ readonly logoUrl: string | null;
15
+ readonly source: "microlink" | "logo.dev" | "direct-favicon" | null;
16
+ readonly hostname: string;
17
+ readonly canonicalUrl: string | null;
18
+ readonly redirectHostname: string | null;
19
+ readonly iframeBlocked: boolean | null;
20
+ readonly cached: boolean;
21
+ /** The image served by Comms.ID (https://api.comms.id/website-logo/v1/image/<id>), valid for seven days; null when nothing resolved. Show this one, not logoUrl. */
22
+ readonly imageUrl: string | null;
23
+ };
24
+ readonly outcomes: ReadonlyArray<{
25
+ readonly source: "microlink" | "logo.dev" | "direct-favicon";
26
+ readonly outcome: "hit" | "miss" | "error" | "timeout" | "skipped";
27
+ readonly code: "UPSTREAM_ERROR" | "TIMEOUT" | "MISSING_CREDENTIALS" | null;
28
+ }>;
29
+ };
@@ -0,0 +1,2 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ export {};
@@ -0,0 +1,7 @@
1
+ export type { WebsiteLogoClient } from "./generated/client.js";
2
+ export { errorCodes, operations } from "./generated/operations.js";
3
+ export type { OperationName } from "./generated/operations.js";
4
+ export type { ResolveRequest, ResolveResponse } from "./generated/types.js";
5
+ export { CommsIdError, isCommsIdError, outcomeOf } from "./runtime/errors.js";
6
+ export type { Outcome } from "./runtime/errors.js";
7
+ export type { CallOptions } from "./runtime/http.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // Generated by @comms-id/forge-transformers from the service's OpenAPI contract. Do not edit: change the contract and regenerate.
2
+ // Browser-safe entry: types, errors and the operation table. No signing code is imported here.
3
+ export { errorCodes, operations } from "./generated/operations.js";
4
+ export { CommsIdError, isCommsIdError, outcomeOf } from "./runtime/errors.js";
@@ -0,0 +1,12 @@
1
+ import type { WebsiteLogoClient } from "../generated/client.js";
2
+ import type { ResolveRequest, ResolveResponse } from "../generated/types.js";
3
+ import type { ImageProfile } from "../runtime/image-types.js";
4
+ /** The reply of the lookup: the resolution of the site, with its outcomes. */
5
+ export type ProductRecord = ResolveResponse;
6
+ /** The hosts an image may be loaded from: the live and the TEST API. */
7
+ export declare const IMAGE_ORIGINS: readonly ["https://api.comms.id", "https://api-test.comms.id"];
8
+ /** The hostname of a site given as a hostname or a URL, or undefined when it is neither. */
9
+ export declare const hostnameOf: (site: string) => string | undefined;
10
+ /** The request for a site, or undefined when the site is not valid. */
11
+ export declare const requestOf: (site: string) => ResolveRequest | undefined;
12
+ export declare const profile: ImageProfile<WebsiteLogoClient, ProductRecord>;
@@ -0,0 +1,77 @@
1
+ // The Website logo image's profile: the only logo-specific code of the image. It reads the site the
2
+ // page gave (a hostname such as example.com, or a URL), prepares the call, and takes from the reply the
3
+ // one image the browser may load: the Comms.ID copy named in `resolution.imageUrl`, never the
4
+ // upstream `logoUrl`. The controller, the React package and the web element are generated and know
5
+ // nothing else.
6
+ //
7
+ // The browser contacts only Comms.ID. The controller refuses any image outside `allowedOrigins`, so a
8
+ // reply that names another host is shown as the letter, and a visitor's address is never sent to a
9
+ // third party.
10
+ /** The hosts an image may be loaded from: the live and the TEST API. */
11
+ export const IMAGE_ORIGINS = ["https://api.comms.id", "https://api-test.comms.id"];
12
+ const MAX_HOSTNAME = 253;
13
+ const MAX_URL = 2048;
14
+ const SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
15
+ const WWW = /^www\./;
16
+ const HOSTNAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
17
+ const FIRST_LETTER = /[a-z0-9]/i;
18
+ const TRAILING_DOT = /\.$/;
19
+ /** The hostname of a site given as a hostname or a URL, or undefined when it is neither. */
20
+ export const hostnameOf = (site) => {
21
+ const text = site.trim();
22
+ if (text === "") {
23
+ return;
24
+ }
25
+ if (SCHEME.test(text)) {
26
+ try {
27
+ const url = new URL(text);
28
+ return (url.protocol === "https:" || url.protocol === "http:") && url.hostname !== ""
29
+ ? url.hostname.toLowerCase()
30
+ : undefined;
31
+ }
32
+ catch {
33
+ return;
34
+ }
35
+ }
36
+ const host = text.toLowerCase().replace(TRAILING_DOT, "");
37
+ return host.length <= MAX_HOSTNAME && HOSTNAME.test(host) ? host : undefined;
38
+ };
39
+ /** The request for a site, or undefined when the site is not valid. */
40
+ export const requestOf = (site) => {
41
+ const text = site.trim();
42
+ if (hostnameOf(text) === undefined) {
43
+ return;
44
+ }
45
+ if (SCHEME.test(text)) {
46
+ return text.length <= MAX_URL ? { url: text } : undefined;
47
+ }
48
+ return { hostname: text.toLowerCase().replace(TRAILING_DOT, "") };
49
+ };
50
+ export const profile = {
51
+ allowedOrigins: IMAGE_ORIGINS,
52
+ service: "Logo lookup",
53
+ label: (site) => {
54
+ const host = hostnameOf(site);
55
+ return host === undefined ? "Logo" : `${host.replace(WWW, "")} logo`;
56
+ },
57
+ monogram: (site) => {
58
+ const host = hostnameOf(site)?.replace(WWW, "");
59
+ return (host === undefined ? "" : (FIRST_LETTER.exec(host)?.[0] ?? "")).toUpperCase();
60
+ },
61
+ read: (site) => {
62
+ if (site.trim() === "") {
63
+ return { kind: "idle" };
64
+ }
65
+ const request = requestOf(site);
66
+ if (request === undefined) {
67
+ return { kind: "invalid", message: "Enter a website address such as example.com." };
68
+ }
69
+ return {
70
+ kind: "resolve",
71
+ run: async (client, options) => {
72
+ const record = await client.resolve(request, options);
73
+ return { record, url: record.resolution.imageUrl ?? undefined };
74
+ },
75
+ };
76
+ },
77
+ };
@@ -0,0 +1,23 @@
1
+ import { type Environment } from "./environment.js";
2
+ import type { TransportConfig } from "./http.js";
3
+ interface Common {
4
+ readonly deadlineMs?: number;
5
+ readonly fetch?: typeof fetch;
6
+ readonly maxRetries?: number;
7
+ }
8
+ /** Calls the consumer's own server route, which signs for the app (the relay). */
9
+ export interface RelayOptions extends Common {
10
+ /** For example "/api/comms-id/address". The operation name is appended. */
11
+ readonly relayUrl: string;
12
+ }
13
+ /** Calls the hosted service with a short-lived capability that the caller supplies. */
14
+ export interface CapabilityOptions extends Common {
15
+ /** Another host. It wins over `environment`. */
16
+ readonly baseUrl?: string;
17
+ /** "live" (the default) or "test". */
18
+ readonly environment?: Environment;
19
+ readonly getCapability: () => Promise<string>;
20
+ }
21
+ export type BrowserOptions = RelayOptions | CapabilityOptions;
22
+ export declare const createBrowserTransport: (options: BrowserOptions) => TransportConfig;
23
+ export {};
@@ -0,0 +1,41 @@
1
+ // Runtime template: copied into every generated client package as src/runtime/browser-core.ts.
2
+ // Browser entry: it never signs and never holds a secret. It either calls the consumer's own
3
+ // relay route, or sends a short-lived capability that the caller obtains by other means.
4
+ import { resolveBaseUrl } from "./environment.js";
5
+ import { CommsIdError } from "./errors.js";
6
+ const TRAILING_SLASHES = /\/+$/;
7
+ const passThrough = (options) => ({
8
+ ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
9
+ ...(options.deadlineMs === undefined ? {} : { deadlineMs: options.deadlineMs }),
10
+ ...(options.maxRetries === undefined ? {} : { maxRetries: options.maxRetries }),
11
+ });
12
+ /** A browser client with neither a relay nor a capability source has no identified caller. */
13
+ const requireCaller = (options) => {
14
+ const relay = options.relayUrl;
15
+ const capability = options.getCapability;
16
+ if ((typeof relay !== "string" || relay === "") && typeof capability !== "function") {
17
+ throw new CommsIdError({
18
+ code: "NOT_CONFIGURED",
19
+ message: "Set relayUrl (your server route) or getCapability: the browser holds no credential of its own.",
20
+ status: 0,
21
+ retryable: false,
22
+ });
23
+ }
24
+ };
25
+ export const createBrowserTransport = (options) => {
26
+ requireCaller(options);
27
+ if ("relayUrl" in options) {
28
+ const relay = options.relayUrl.replace(TRAILING_SLASHES, "");
29
+ return {
30
+ urlFor: (operation) => `${relay}/${operation.name}`,
31
+ headers: () => Promise.resolve({}),
32
+ ...passThrough(options),
33
+ };
34
+ }
35
+ const base = resolveBaseUrl(options.environment, options.baseUrl).replace(TRAILING_SLASHES, "");
36
+ return {
37
+ urlFor: (operation) => `${base}${operation.path}`,
38
+ headers: async () => ({ authorization: `Bearer ${await options.getCapability()}` }),
39
+ ...passThrough(options),
40
+ };
41
+ };
@@ -0,0 +1,5 @@
1
+ export type Environment = "live" | "test";
2
+ export declare const BASE_URLS: Readonly<Record<Environment, string>>;
3
+ export declare const isEnvironment: (value: unknown) => value is Environment;
4
+ /** The base URL: an explicit one wins, then the environment (live by default). */
5
+ export declare const resolveBaseUrl: (environment: unknown, baseUrl: string | undefined) => string;