web-comments 1.0.0 → 1.0.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.
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  CommentsError,
3
3
  createComments,
4
4
  newId
5
- } from "./chunks/chunk-RRSB326L.js";
5
+ } from "./chunks/chunk-HN5V6BS6.js";
6
6
  import "./chunks/chunk-WSFCRVEQ.js";
7
7
  export {
8
8
  CommentsError,
@@ -0,0 +1 @@
1
+ export declare const icon = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuODg2IDQuMThBMTEuOTgyIDExLjk4MiAwIDAgMSAxMS45OSAwQzE4LjYyNCAwIDI0IDUuMzc2IDI0IDEyLjAwOWMwIDMuNjQtMS42MiA2LjkwMy00LjE4IDkuMTA1TDIuODg3IDQuMThaTTEuODE3IDUuNjI2bDE2LjU1NiAxNi41NTZjLS41MjQuMzMtMS4wNzUuNjItMS42NS44NjZMLjk1MSA3LjI3N2MuMjQ3LS41NzUuNTM3LTEuMTI2Ljg2Ni0xLjY1Wk0uMzIyIDkuMTYzbDE0LjUxNSAxNC41MTVjLS43MS4xNzItMS40NDMuMjgyLTIuMTk1LjMyMkwwIDExLjM1OGExMiAxMiAwIDAgMSAuMzIyLTIuMTk1Wm0tLjE3IDQuODYyIDkuODIzIDkuODI0YTEyLjAyIDEyLjAyIDAgMCAxLTkuODI0LTkuODI0WiIvPjwvc3ZnPg==";
@@ -7,7 +7,9 @@ export interface LinearOptions {
7
7
  * - `{ accessToken }`: an OAuth token.
8
8
  * - `{ clientId, clientSecret }`: an OAuth app with client credentials
9
9
  * enabled. Issues and comments are the app's, shown under each
10
- * commenter's name and picture.
10
+ * commenter's name and picture, and people can connect their own Linear
11
+ * account to it (its callback URL: `{basePath}/linear/callback`), after
12
+ * which what they do reaches Linear as themselves.
11
13
  */
12
14
  auth: {
13
15
  apiKey: string;
@@ -38,9 +40,10 @@ export interface LinearOptions {
38
40
  /**
39
41
  * Every thread becomes a Linear issue, with its screenshots, and stays in
40
42
  * step with it: replies go both ways, and resolving one resolves the other.
41
- * Options can depend on the environment; returning null leaves Linear out
42
- * of that one (say, until its secrets are set).
43
+ * People are matched across by email, or connect their Linear account in
44
+ * the widget's Linear panel. Options can depend on the environment;
45
+ * returning null leaves Linear out of that one (say, until its secrets are set).
43
46
  *
44
- * connectors: [linear((env) => ({ auth: { apiKey: env.LINEAR_API_KEY }, team: "ENG", webhookSecret: env.LINEAR_WEBHOOK_SECRET }))]
47
+ * connectors: [linear((env) => ({ auth: { clientId: env.LINEAR_CLIENT_ID, clientSecret: env.LINEAR_CLIENT_SECRET }, team: "ENG", webhookSecret: env.LINEAR_WEBHOOK_SECRET }))]
45
48
  */
46
49
  export declare function linear<Env = unknown>(options: LinearOptions | ((env: Env) => LinearOptions | null)): Connector<Env>;
@@ -0,0 +1 @@
1
+ export declare const icon = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIxLjIxIDAgMjEuNTggMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEzLjg1IDBhNC4xNiA0LjE2IDAgMCAwLTIuOTUgMS4yMTdMMS40NTYgMTAuNjZhLjgzNS44MzUgMCAwIDAgMCAxLjE4LjgzNS44MzUgMCAwIDAgMS4xOCAwbDkuNDQyLTkuNDQyYTIuNDkgMi40OSAwIDAgMSAzLjU0MSAwIDIuNDkgMi40OSAwIDAgMSAwIDMuNTQxTDguNTkgMTIuOTdsLS4xLjFhLjgzNS44MzUgMCAwIDAgMCAxLjE4LjgzNS44MzUgMCAwIDAgMS4xOCAwbC4xLS4wOTggNy4wMy03LjAzNGEyLjQ5IDIuNDkgMCAwIDEgMy41NDIgMGwuMDQ5LjA1YTIuNDkgMi40OSAwIDAgMSAwIDMuNTRsLTguNTQgOC41NGExLjk2IDEuOTYgMCAwIDAgMCAyLjc1NWwxLjc1MyAxLjc1M2EuODM1LjgzNSAwIDAgMCAxLjE4IDAgLjgzNS44MzUgMCAwIDAgMC0xLjE4bC0xLjc1My0xLjc1M2EuMjY2LjI2NiAwIDAgMSAwLS4zOTRsOC41NC04LjU0YTQuMTg1IDQuMTg1IDAgMCAwIDAtNS45bC0uMDUtLjA1YTQuMTYgNC4xNiAwIDAgMC0yLjk1LTEuMjE4Yy0uMiAwLS40MDEuMDItLjYuMDQ4YTQuMTcgNC4xNyAwIDAgMC0xLjE3LTMuNTUyQTQuMTYgNC4xNiAwIDAgMCAxMy44NSAwbTAgMy4zMzNhLjg0Ljg0IDAgMCAwLS41OS4yNDVMNi4yNzUgMTAuNTZhNC4xODYgNC4xODYgMCAwIDAgMCA1LjkwMiA0LjE4NiA0LjE4NiAwIDAgMCA1LjkwMiAwTDE5LjE2IDkuNDhhLjgzNS44MzUgMCAwIDAgMC0xLjE4LjgzNS44MzUgMCAwIDAtMS4xOCAwbC02Ljk4NSA2Ljk4NGEyLjQ5IDIuNDkgMCAwIDEtMy41NCAwIDIuNDkgMi40OSAwIDAgMSAwLTMuNTRsNi45ODMtNi45ODVhLjgzNS44MzUgMCAwIDAgMC0xLjE4Ljg0Ljg0IDAgMCAwLS41OS0uMjQ1Ii8+PC9zdmc+";
@@ -8,6 +8,8 @@ export interface OAuthOptions {
8
8
  }
9
9
  /** What a token stands for. */
10
10
  export interface Grant {
11
+ /** One approval of one client by one person; revoking it ends every token it issued. */
12
+ grantId: string;
11
13
  identity: Identity;
12
14
  clientId: string;
13
15
  clientName: string;
@@ -45,6 +47,17 @@ export declare function authorizationServerMetadata(base: string, oidc: boolean)
45
47
  export declare function jwks(): Response;
46
48
  /** The grant behind an access token, or null if it isn't a live one. */
47
49
  export declare function grantOf(storage: ConnectorStorage, token: string): Promise<Grant | null>;
50
+ interface GrantRecord {
51
+ clientId: string;
52
+ clientName: string;
53
+ createdAt: string;
54
+ }
55
+ /** The agents a person has approved and not revoked, newest first. */
56
+ export declare function grantsOf(storage: ConnectorStorage, identityId: string): Promise<(GrantRecord & {
57
+ grantId: string;
58
+ })[]>;
59
+ export declare function revokeGrant(storage: ConnectorStorage, identityId: string, grantId: string): Promise<void>;
48
60
  export declare function looksLikeOurToken(token: string): boolean;
49
61
  /** Answers the OAuth paths under the connector's base; null for anything else. */
50
62
  export declare function handleOAuth(request: Request, path: string, context: ConnectorRequestContext, options: OAuthOptions): Promise<Response | null>;
63
+ export {};
@@ -1,4 +1,4 @@
1
- import type { Comment, Identity, ScreenshotKind, Thread, ThreadStatus } from "../shared/types";
1
+ import type { Comment, ConnectorPanel, Identity, ScreenshotKind, Thread, ThreadStatus } from "../shared/types";
2
2
  import type { CommentsEvent } from "./events";
3
3
  import type { ThreadLink } from "./store";
4
4
  /**
@@ -29,10 +29,24 @@ export interface Connector<Env = unknown> {
29
29
  * sends them (see `matches()`).
30
30
  */
31
31
  rootPaths?(basePath: string): string[];
32
+ /**
33
+ * What this connector shows in the widget: a button on the toolbar (above
34
+ * the person's avatar) that opens a panel of its own. Built from the
35
+ * blocks below, so a connector needs no front-end code. Null hides it
36
+ * (say, for someone it has nothing for).
37
+ */
38
+ panel?(who: Identity, context: ConnectorContext<Env>): Promise<ConnectorPanel | null>;
39
+ /**
40
+ * A button in the panel was pressed (`{ type: "button", action }`). Runs
41
+ * signed in and checked like the API's own requests.
42
+ */
43
+ action?(action: string, who: Identity, input: Record<string, unknown>, context: ConnectorContext<Env>): Promise<void>;
32
44
  }
33
45
  export interface ConnectorContext<Env = unknown> {
34
46
  env: Env;
35
47
  site: string;
48
+ /** The origin the request came to, e.g. https://app.example.com. */
49
+ origin: string;
36
50
  /** Where the API is mounted, e.g. "/__comments". */
37
51
  basePath: string;
38
52
  /**
@@ -73,6 +87,10 @@ export interface ConnectorRequestContext<Env = unknown> extends ConnectorContext
73
87
  * `authorize`), or null: e.g. the person approving an OAuth client.
74
88
  */
75
89
  identify(request: Request): Promise<Identity | null>;
90
+ /** Who a request is from by the auth plugins alone, whether or not `authorize` lets them in. */
91
+ signedIn(request: Request): Promise<Identity | null>;
92
+ /** Whether the API's `authorize` lets this person in now (for credentials kept since, like tokens). */
93
+ allowed(who: Identity, request: Request): Promise<boolean>;
76
94
  /** Where the auth plugins send someone to sign in, if they know. */
77
95
  loginUrl(request: Request): string | undefined;
78
96
  }
@@ -11,6 +11,8 @@ export declare class CommentsError extends Error {
11
11
  /** One request's view of the comments: its site, where things are kept, and where changes go. */
12
12
  export interface Scope {
13
13
  site: string;
14
+ /** The origin the request came to, e.g. https://app.example.com. */
15
+ origin: string;
14
16
  store: CommentsStore;
15
17
  assets: AssetStore | null;
16
18
  announce(event: CommentsEvent): void;
@@ -100,6 +100,61 @@ export interface ThreadLinkSummary {
100
100
  /** How to show it, e.g. "ENG-123". */
101
101
  title: string | null;
102
102
  }
103
+ /** A connector's panel in the widget. */
104
+ export interface ConnectorPanel {
105
+ title: string;
106
+ /** An image URL (https: or data:), shown at 20px on the toolbar and in the panel's header. */
107
+ icon: string;
108
+ /** Puts a dot on the toolbar button: something here wants the person's attention. */
109
+ attention?: boolean;
110
+ blocks: PanelBlock[];
111
+ }
112
+ export type PanelBlock = {
113
+ type: "text";
114
+ text: string;
115
+ } | {
116
+ type: "status";
117
+ label: string;
118
+ /** positive: all set; notice: worth acting on; neutral: just so. */
119
+ tone: "positive" | "notice" | "neutral";
120
+ description?: string;
121
+ } | {
122
+ type: "code";
123
+ label: string;
124
+ value: string;
125
+ } | {
126
+ /**
127
+ * Leaves the page for `href` (e.g. an OAuth page). With `returnTo`,
128
+ * the current address goes along as `?return=`.
129
+ */
130
+ type: "link";
131
+ label: string;
132
+ href: string;
133
+ returnTo?: boolean;
134
+ variant?: "accent" | "secondary";
135
+ } | {
136
+ /** Calls the connector's `action`, then shows the panel again. */
137
+ type: "button";
138
+ label: string;
139
+ action: string;
140
+ input?: Record<string, unknown>;
141
+ variant?: "accent" | "secondary" | "negative";
142
+ /** Asks first, with this question. */
143
+ confirm?: string;
144
+ } | {
145
+ type: "list";
146
+ label: string;
147
+ empty: string;
148
+ items: {
149
+ title: string;
150
+ description?: string;
151
+ button?: {
152
+ label: string;
153
+ action: string;
154
+ input?: Record<string, unknown>;
155
+ };
156
+ }[];
157
+ };
103
158
  export interface ApiError {
104
159
  error: {
105
160
  code: string;
@@ -1,4 +1,4 @@
1
- import type { Anchor, Identity, Thread, ThreadContext, ThreadStatus } from "../shared/types";
1
+ import type { Anchor, ConnectorPanel, Identity, Thread, ThreadContext, ThreadStatus } from "../shared/types";
2
2
  import type { Shots } from "./capture";
3
3
  export interface ApiOptions {
4
4
  endpoint: string;
@@ -17,10 +17,19 @@ export declare class ApiError extends Error {
17
17
  readonly status: number;
18
18
  constructor(message: string, status: number);
19
19
  }
20
+ /** A connector's panel, as /connectors sends it. */
21
+ export type ConnectorPanelView = ConnectorPanel & {
22
+ connector: string;
23
+ };
20
24
  export declare function createApi(options: ApiOptions): {
21
25
  /** A path the API returned (a screenshot), as a URL the browser can load: it's relative to the API's host. */
22
26
  resolve: (path: string) => string;
23
27
  me: () => Promise<Identity>;
28
+ /** The connectors' panels for this person; none from a server that has no connectors. */
29
+ connectors: () => Promise<ConnectorPanelView[]>;
30
+ connectorAction: (connector: string, action: string, input?: Record<string, unknown>) => Promise<{
31
+ ok: true;
32
+ }>;
24
33
  threads: (page: string) => Promise<Thread[]>;
25
34
  thread: (threadId: string) => Promise<Thread>;
26
35
  /** Opens a thread, sending its screenshots along in the same request when there are any. */
@@ -0,0 +1,13 @@
1
+ import type { Api, ConnectorPanelView } from "./api";
2
+ /** A connector's mark, monochrome, in the colour of the text around it. */
3
+ export declare function ConnectorIcon({ src, size }: {
4
+ src: string;
5
+ size?: number;
6
+ }): import("react").JSX.Element;
7
+ /** One connector's panel. Actions run, then the panel is asked for again. */
8
+ export declare function ConnectorPanel({ panel, api, onClose, onChanged, }: {
9
+ panel: ConnectorPanelView;
10
+ api: Api;
11
+ onClose: () => void;
12
+ onChanged: () => Promise<void>;
13
+ }): import("react").JSX.Element;