@picked/app-kit 0.0.117-next.2 → 0.0.117

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.
@@ -0,0 +1,145 @@
1
+ import type { AdminApiRequestOptions, TadaDocumentNode } from '@picked/admin-api-client';
2
+ import { type InstallationRef } from '@picked/app-protocol';
3
+ /** How to read a label's `value`. */
4
+ export type AppLabelType = 'text' | 'number' | 'boolean' | 'json';
5
+ /** A label the app keeps for an installation, as Picked's `Label` type describes it. */
6
+ export interface AppLabel {
7
+ namespace: string;
8
+ key: string;
9
+ type: AppLabelType;
10
+ /** The value, as a string to read through `type`; a secret label's value included. */
11
+ value: string;
12
+ /** Whether the value is kept in Vault. */
13
+ secret: boolean;
14
+ /** When the label expires, or null. */
15
+ expiresAt: string | null;
16
+ updatedAt: string;
17
+ }
18
+ /**
19
+ * A label to set, as Picked's `LabelInput` takes it. A namespace and a key are each 1-64
20
+ * lowercase letters, digits, underscores or hyphens; a value holds at most 64 KiB, and an
21
+ * installation holds at most 100 labels.
22
+ */
23
+ export interface AppLabelInput {
24
+ namespace: string;
25
+ key: string;
26
+ type: AppLabelType;
27
+ /** The value, as a string that parses as `type`. */
28
+ value?: string;
29
+ /** What to add to a `number` label, in place of `value`. */
30
+ increment?: number;
31
+ /** Seconds until the label expires, from 1 to 31536000. */
32
+ expiresIn?: number;
33
+ /** Writes only when no live label has this namespace and key. */
34
+ onlyIfAbsent?: boolean;
35
+ /** Keeps the value in Vault: a `text` or `json` value, with no `increment` or `onlyIfAbsent`. */
36
+ secret?: boolean;
37
+ }
38
+ /** A label to remove, as Picked's `LabelIdentifierInput` takes it. */
39
+ export interface AppLabelIdentifier {
40
+ namespace: string;
41
+ key: string;
42
+ /** Removes the label only while it holds this value. */
43
+ ifValue?: string;
44
+ }
45
+ /** A refused label write, as Picked's `LabelUserError` describes it. */
46
+ export interface AppLabelUserError {
47
+ field: string[];
48
+ message: string;
49
+ code: string;
50
+ }
51
+ /** A label write Picked refused, carrying its user errors. */
52
+ export declare class LabelWriteError extends Error {
53
+ readonly userErrors: readonly AppLabelUserError[];
54
+ /**
55
+ * Builds the error from Picked's user errors.
56
+ *
57
+ * @param {readonly AppLabelUserError[]} userErrors - Why Picked refused the write.
58
+ * @example new LabelWriteError([{ field: ['labels', '0', 'key'], message: 'Key is invalid.', code: 'KEY_INVALID' }])
59
+ */
60
+ constructor(userErrors: readonly AppLabelUserError[]);
61
+ }
62
+ /** The labels an app keeps for one installation in Picked, which the installation owns. */
63
+ export interface InstallationLabels {
64
+ /** Every live label, secret values included. */
65
+ list: () => Promise<AppLabel[]>;
66
+ /** One live label, or undefined when none has the namespace and key. */
67
+ get: (namespace: string, key: string) => Promise<AppLabel | undefined>;
68
+ /**
69
+ * Creates or replaces labels, as `labelsSet` does, throwing {@link LabelWriteError} when
70
+ * Picked refuses one. `skipped` holds the live labels an `onlyIfAbsent` label left in place.
71
+ */
72
+ set: (labels: readonly AppLabelInput[], options?: {
73
+ atomic?: boolean;
74
+ }) => Promise<{
75
+ labels: AppLabel[];
76
+ skipped: AppLabel[];
77
+ }>;
78
+ /** Removes labels, as `labelsDelete` does, returning the ids removed. */
79
+ delete: (labels: readonly AppLabelIdentifier[]) => Promise<string[]>;
80
+ }
81
+ /** Calls Picked as one installation. */
82
+ export interface InstallationClient {
83
+ /** The installation's labels. */
84
+ labels: InstallationLabels;
85
+ /**
86
+ * A short-lived token carrying the installation's scopes: the one Picked sent with the
87
+ * request being answered, or one minted with an app assertion, which Picked grants an app with
88
+ * offline access, or one answering a dashboard user's session. Renewed before it expires.
89
+ */
90
+ token: () => Promise<string>;
91
+ /**
92
+ * Runs a typed document with the installation's token and returns its `data`. Build the
93
+ * document with `graphql` from `@picked/admin-api-client`.
94
+ *
95
+ * @throws {Error} An `AdminApiResponseError` when Picked answers with GraphQL errors, and a
96
+ * plain `Error` on a network failure, a timeout, or a response without GraphQL errors.
97
+ */
98
+ request: <Result, Variables extends Record<string, unknown>>(document: TadaDocumentNode<Result, Variables>, variables: Variables, options?: AdminApiRequestOptions) => Promise<Result>;
99
+ }
100
+ /** Where and as whom an installation client calls Picked. */
101
+ export interface InstallationClientOptions {
102
+ /** The Picked API origin, as the request from Picked names it in `apiUrl`. */
103
+ apiUrl: string;
104
+ /** The installation. */
105
+ installationId: string;
106
+ /** The app's Ed25519 private key, as the base64 of its PKCS#8 DER or a PKCS#8 PEM. */
107
+ privateKey: string;
108
+ /** A token Picked sent with the request being answered, used until it nears expiry. */
109
+ token?: string;
110
+ /** When `token` expires. */
111
+ tokenExpiresAt?: string;
112
+ /**
113
+ * The session token of the dashboard user a page request comes from, which lets an app
114
+ * without offline access mint a token while it answers that user.
115
+ */
116
+ sessionToken?: string;
117
+ /** The fetch to send with; the global one by default. */
118
+ fetch?: typeof fetch;
119
+ }
120
+ /**
121
+ * Builds a client that calls Picked as one installation.
122
+ *
123
+ * @param {InstallationClientOptions} options - Where and as whom.
124
+ * @returns {InstallationClient} The client.
125
+ * @example createInstallationClient({ ...event, privateKey })
126
+ */
127
+ export declare function createInstallationClient(options: InstallationClientOptions): InstallationClient;
128
+ /**
129
+ * Builds the client for the installation a request names: with the token Picked sent, and for a
130
+ * page request, the session token the frame forwarded.
131
+ *
132
+ * @param {Pick<InstallationRef, 'apiUrl' | 'installationId' | 'token' | 'tokenExpiresAt'>} ref - The installation, as the request names it.
133
+ * @param {object} options - As whom and how.
134
+ * @param {string} options.privateKey - The app's private key.
135
+ * @param {string | null} [options.sessionToken] - The session token of a page request.
136
+ * @param {typeof fetch} [options.fetch] - The fetch to send with.
137
+ * @returns {InstallationClient} The client.
138
+ * @example installationClientFor(event, { privateKey: environment.privateKey })
139
+ */
140
+ export declare function installationClientFor(ref: Pick<InstallationRef, 'apiUrl' | 'installationId' | 'token' | 'tokenExpiresAt'>, options: {
141
+ privateKey: string;
142
+ sessionToken?: string | null;
143
+ fetch?: typeof fetch;
144
+ }): InstallationClient;
145
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @file The handshake between an app's iframe and the dashboard hosting it.
3
+ *
4
+ * The frame posts `picked:ready`; the dashboard answers `picked:context` with where the
5
+ * frame renders and a session token the frame forwards to its own server. The frame can
6
+ * post `picked:resize` to set its height. Each `picked:ready` gets a context with a new session
7
+ * token, which `keepConnected` uses to renew the token before it expires.
8
+ */
9
+ import type { AppSlotLocation } from '@picked/app-protocol';
10
+ /** What the dashboard tells a frame. */
11
+ export interface FrameContext {
12
+ installationId: string;
13
+ organizationId: string;
14
+ organizationSlug: string;
15
+ projectId: string;
16
+ /** The slot the frame renders in. */
17
+ location: AppSlotLocation;
18
+ /** The installation's pinned app version. */
19
+ appVersion: number;
20
+ /** A short-lived token asserting the user and installation; verify it with `readSession`. */
21
+ sessionToken: string;
22
+ /** The dashboard's colour scheme. */
23
+ theme: 'light' | 'dark';
24
+ }
25
+ /** A message the frame posts to the dashboard. */
26
+ export type FrameToHostMessage = {
27
+ type: 'picked:ready';
28
+ } | {
29
+ type: 'picked:resize';
30
+ height: number;
31
+ };
32
+ /** A message the dashboard posts to the frame. */
33
+ export interface HostToFrameMessage {
34
+ type: 'picked:context';
35
+ context: FrameContext;
36
+ }
37
+ /**
38
+ * Connects to the dashboard and resolves with the frame's context. Call once on load. It
39
+ * works only in a page the Picked dashboard frames: it rejects in a top-level window, and
40
+ * accepts the context only from the frame's parent window on the dashboard's origin.
41
+ *
42
+ * @param {object} [options] - Connection options.
43
+ * @param {string} [options.dashboardOrigin] - The dashboard origin to accept and post to; the referrer's when absent.
44
+ * @returns {Promise<FrameContext>} Where the frame renders and who is looking.
45
+ * @example const context = await connect()
46
+ */
47
+ export declare function connect(options?: {
48
+ dashboardOrigin?: string;
49
+ }): Promise<FrameContext>;
50
+ /** A connection to the dashboard that renews its session token before the token expires. */
51
+ export interface FrameConnection {
52
+ /**
53
+ * Returns the newest context the dashboard sent. Read it for each request and each
54
+ * `authorizeInPopup`, so they carry a session token that has not expired.
55
+ */
56
+ context: () => FrameContext;
57
+ /** Stops renewing the session token. */
58
+ stop: () => void;
59
+ }
60
+ /**
61
+ * Connects to the dashboard as `connect` does, then asks the dashboard for a new context two
62
+ * minutes before each session token expires and at most four minutes after the last one. It
63
+ * checks the clock every 15 seconds and when the frame regains focus or visibility, and asks
64
+ * again 15 seconds after a renewal the dashboard leaves unanswered for 10 seconds. Call once
65
+ * on load, in place of `connect`.
66
+ *
67
+ * @param {object} [options] - Connection options.
68
+ * @param {string} [options.dashboardOrigin] - The dashboard origin to accept and post to; the referrer's when absent.
69
+ * @returns {Promise<FrameConnection>} The connection, once the dashboard sends the first context.
70
+ * @example const connection = await keepConnected()
71
+ */
72
+ export declare function keepConnected(options?: {
73
+ dashboardOrigin?: string;
74
+ }): Promise<FrameConnection>;
75
+ /**
76
+ * Sets the frame's height in the dashboard.
77
+ *
78
+ * @param {number} height - The height in pixels.
79
+ * @returns {void}
80
+ * @example resize(document.body.scrollHeight)
81
+ */
82
+ export declare function resize(height: number): void;
83
+ /**
84
+ * Keeps the frame's height matched to its content.
85
+ *
86
+ * @returns {() => void} Stops observing.
87
+ * @example const stop = autoResize()
88
+ */
89
+ export declare function autoResize(): () => void;
90
+ /** How an authorization the frame opened in a popup ended. */
91
+ export interface AuthorizationResult {
92
+ ok: boolean;
93
+ message: string;
94
+ }
95
+ /** What the frame starts an authorization with. */
96
+ export interface AuthorizationRequest {
97
+ /** The session token from the frame's context, which the popup's start request carries. */
98
+ sessionToken: string;
99
+ /** What the app's `/oauth/start` hands its `authorize` as `params`, such as a data centre. */
100
+ params?: unknown;
101
+ /**
102
+ * Reads whether the app now holds the connection, answering the message to show when it
103
+ * does and null when it does not. Once the popup reads as closed without reporting back,
104
+ * the frame asks it every second for thirty seconds and settles on the first message.
105
+ */
106
+ connected?: () => Promise<string | null>;
107
+ }
108
+ /**
109
+ * Runs an OAuth authorization in a popup. Call it from a click handler: the popup opens at
110
+ * once on the app's `/oauth/start`, which asks for the session, and this frame answers that
111
+ * popup alone with the session token and params. It accepts messages only from that popup, on
112
+ * the app's own origin. The start binds the authorization to the browser with a cookie and
113
+ * goes on to the provider. The promise settles when the app's
114
+ * callback page reports back, or once the popup reads as closed and this frame has focus. A
115
+ * popup a provider's `Cross-Origin-Opener-Policy` separates from this frame reads as closed
116
+ * while the user may still be connecting in it, so the frame waits for the user to come back,
117
+ * then asks `connected` until the app holds the connection the popup saves.
118
+ *
119
+ * @param {AuthorizationRequest} request - The session token and the start's params.
120
+ * @returns {Promise<AuthorizationResult>} How the authorization ended.
121
+ * @example button.onclick = async () => show(await authorizeInPopup({ sessionToken: context.sessionToken }))
122
+ */
123
+ export declare function authorizeInPopup(request: AuthorizationRequest): Promise<AuthorizationResult>;
124
+ //# sourceMappingURL=frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../src/frame.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAE3D,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,qCAAqC;IACrC,QAAQ,EAAE,eAAe,CAAA;IACzB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAA;IAClB,6FAA6F;IAC7F,YAAY,EAAE,MAAM,CAAA;IACpB,qCAAqC;IACrC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAA;CACxB;AAED,kDAAkD;AAClD,MAAM,MAAM,kBAAkB,GAC5B;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtE,kDAAkD;AAClD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,YAAY,CAAA;CACtB;AAqFD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAKzF;AAsED,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,EAAE,MAAM,YAAY,CAAA;IAC3B,wCAAwC;IACxC,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAA;CAAO,GACzC,OAAO,CAAC,eAAe,CAAC,CAoC1B;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,IAAI,CAKvC;AAED,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,mDAAmD;AACnD,MAAM,WAAW,oBAAoB;IACnC,2FAA2F;IAC3F,YAAY,EAAE,MAAM,CAAA;IACpB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CACzC;AAoCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA6D5F"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file `@picked/app-kit`: the app manifest and the protocol Picked speaks to apps, re-exported
3
+ * from `@picked/app-protocol`.
4
+ *
5
+ * `@picked/app-kit/server` builds an app's endpoint, `@picked/app-kit/node` serves it, and
6
+ * `@picked/app-kit/frame` runs inside a dashboard slot's iframe.
7
+ */
8
+ export * from '@picked/app-protocol';
9
+ export type { FrameContext, FrameToHostMessage, HostToFrameMessage } from './frame.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,sBAAsB,CAAA;AACpC,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @file The dashboard link that opens the installation a label names, for an app to send a
3
+ * person from its own site back to the right project in Picked.
4
+ */
5
+ /**
6
+ * Tells whether a value has the shape of an app ID.
7
+ *
8
+ * @param {string} value - The value.
9
+ * @returns {boolean} Whether it is `app_` and lowercase letters and digits.
10
+ * @example isAppId('app_k3v9x2m1q8zt') // true
11
+ */
12
+ export declare function isAppId(value: string): boolean;
13
+ /** What a link to an installation names. */
14
+ export interface InstallationLink {
15
+ /** The dashboard's origin, such as `https://app.picked.app`. A path on it is ignored. */
16
+ dashboardOrigin: string;
17
+ /**
18
+ * The app's ID on that dashboard's Picked, such as `app_k3v9x2m1q8zt`, which
19
+ * `picked app deploy` prints. Each Picked environment gives the app its own ID.
20
+ */
21
+ appId: string;
22
+ /** The namespace of a plain label the app keeps on the installation. */
23
+ namespace: string;
24
+ /** The label's key within its namespace. */
25
+ key: string;
26
+ /** The value the installation's label holds. */
27
+ value: string;
28
+ }
29
+ /**
30
+ * Builds the dashboard URL that opens the installation of an app holding a plain label with a
31
+ * value: `/apps/<app ID>/open?label=<namespace>.<key>&value=<value>`. The dashboard opens the
32
+ * one installation the signed-in person can see, or offers a choice of several. A secret label
33
+ * never matches.
34
+ *
35
+ * @param {InstallationLink} link - The dashboard, app, label and value.
36
+ * @returns {string} The URL.
37
+ * @throws {TypeError} When the app ID, namespace or key is not one Picked holds, or the origin is not a URL.
38
+ * @example installationLinkUrl({ dashboardOrigin: 'https://app.picked.app', appId: 'app_k3v9x2m1q8zt', namespace: 'vercel', key: 'configuration-id', value: 'icfg_123' })
39
+ */
40
+ export declare function installationLinkUrl(link: InstallationLink): string;
41
+ //# sourceMappingURL=link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/link.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9C;AAKD,4CAA4C;AAC5C,MAAM,WAAW,gBAAgB;IAC/B,yFAAyF;IACzF,eAAe,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAA;IACjB,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAA;IACX,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAUlE"}
@@ -0,0 +1,19 @@
1
+ import type { Server } from 'node:http';
2
+ /**
3
+ * Serves a handler on a port, logging each request's method, path and status.
4
+ *
5
+ * @param {(request: Request) => Promise<Response>} handler - The handler.
6
+ * @param {number} port - The port.
7
+ * @returns {Server} The listening server.
8
+ * @example serve(createAppHandler(options), 4100)
9
+ */
10
+ export declare function serve(handler: (request: Request) => Promise<Response>, port: number): Server;
11
+ /**
12
+ * Answers with the browser build of `@picked/app-kit/frame`, for an app's pages to import as
13
+ * `/app-kit/frame.js`.
14
+ *
15
+ * @returns {Promise<Response>} The script.
16
+ * @example if (path === '/app-kit/frame.js') return frameScript()
17
+ */
18
+ export declare function frameScript(): Promise<Response>;
19
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/node.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAmB,MAAM,EAAkB,MAAM,WAAW,CAAA;AA0DxE;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAgB5F;AAGD;;;;;;GAMG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC,CAGrD"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @file OAuth authorization-code helpers for apps that connect an installation to a third
3
+ * party. The popup starts at the app's `/oauth/start`, which takes the dashboard session from
4
+ * the frame that opened it, sets a cookie holding a nonce, and sends the browser to the
5
+ * provider with a sealed `state` carrying the installation and that nonce. The provider's
6
+ * callback completes only in the browser whose cookie holds the state's nonce, and its page
7
+ * tells the frame how it went.
8
+ */
9
+ import { type PublicKeySet, type SessionClaims } from '@picked/app-protocol';
10
+ /** The message the callback page posts to the frame that opened it. */
11
+ export declare const OAUTH_RESULT_MESSAGE = "picked-app:oauth-result";
12
+ /** The message the start page posts to its opener once it can take the session. */
13
+ export declare const OAUTH_START_READY_MESSAGE = "picked-app:oauth-start-ready";
14
+ /** The message the opener answers the start page with: the session token and the start's params. */
15
+ export declare const OAUTH_START_MESSAGE = "picked-app:oauth-start";
16
+ /** The path an app serves the popup's start page (`GET`) and its start request (`POST`) at. */
17
+ export declare const OAUTH_START_PATH = "/oauth/start";
18
+ /** What the `state` parameter carries through the provider. */
19
+ export interface OAuthState {
20
+ /** The installation being connected. */
21
+ installationId: string;
22
+ /** The Picked API the installation calls back. */
23
+ apiUrl: string;
24
+ /** Which connection this is, for an app with more than one provider or data centre. */
25
+ connection: string;
26
+ /**
27
+ * The session token of the dashboard user who started the connection, with which the
28
+ * callback mints a token to save it.
29
+ */
30
+ sessionToken: string;
31
+ /** Expiry, in unix seconds. */
32
+ exp: number;
33
+ /** The value of the cookie the start set in the browser that began the authorization. */
34
+ nonce: string;
35
+ }
36
+ /**
37
+ * Renders the page a provider's callback answers with. It posts the result to the frame that
38
+ * opened the popup, on the app's own origin, and closes. A popup whose opener is gone, as a
39
+ * provider's `Cross-Origin-Opener-Policy` can leave it, stays open showing the result.
40
+ *
41
+ * @param {object} result - How the authorization went.
42
+ * @param {boolean} result.ok - Whether the connection was saved.
43
+ * @param {string} result.message - What to tell the user.
44
+ * @returns {string} The page's HTML.
45
+ * @example oauthCallbackPage({ ok: true, message: 'Connected' })
46
+ */
47
+ export declare function oauthCallbackPage(result: {
48
+ ok: boolean;
49
+ message: string;
50
+ }): string;
51
+ /**
52
+ * Renders the page the popup opens at. It tells the frame that opened it that it is ready,
53
+ * takes the session token and params that frame answers with, from that frame alone, and
54
+ * posts them to its own path. That answer sets the binding cookie and names the provider's
55
+ * URL, which the page goes to; a refusal goes to the frame as the result.
56
+ *
57
+ * @returns {string} The page's HTML.
58
+ * @example oauthStartPage()
59
+ */
60
+ export declare function oauthStartPage(): string;
61
+ /** An authorization the popup asked to start. */
62
+ export interface AuthorizationStart {
63
+ /** The verified session of the dashboard user starting it. */
64
+ session: SessionClaims;
65
+ /** The raw session token, which an installation client passes on as `sessionToken`. */
66
+ sessionToken: string;
67
+ /** What the app's page passed `authorizeInPopup` as `params`, or null. */
68
+ params: unknown;
69
+ /**
70
+ * Seals the `state` for a connection of the session's installation, carrying the session
71
+ * token and the nonce of the cookie this start sets.
72
+ */
73
+ state: (connection: string) => Promise<string>;
74
+ }
75
+ /** What `startAuthorization` is given. */
76
+ export interface StartAuthorizationOptions {
77
+ /** The app's private key, from which the state's key derives. */
78
+ privateKey: string;
79
+ /** Picked's public keys, which sign session tokens. */
80
+ pickedKeys: PublicKeySet;
81
+ /**
82
+ * Builds the provider's authorization URL with a `state` from `start.state`, or answers a
83
+ * refusal, which goes back to the frame and sets no cookie.
84
+ */
85
+ authorize: (start: AuthorizationStart) => Promise<string | Response>;
86
+ }
87
+ /**
88
+ * Answers the app's `/oauth/start`. `GET` serves the popup's page. `POST`, which that page
89
+ * sends with the session token as `Authorization: Bearer <token>` and the params as its JSON
90
+ * body, verifies the session, sets the binding cookie to a fresh nonce, and answers
91
+ * `{ url }`: the provider's URL `authorize` builds.
92
+ *
93
+ * @param {Request} request - The request to `/oauth/start`.
94
+ * @param {StartAuthorizationOptions} options - The app's keys and its provider's URL.
95
+ * @returns {Promise<Response>} The page, the provider's URL, or a refusal.
96
+ * @example if (pathname === OAUTH_START_PATH) return startAuthorization(request, { privateKey, pickedKeys, authorize })
97
+ */
98
+ export declare function startAuthorization(request: Request, options: StartAuthorizationOptions): Promise<Response>;
99
+ /** A provider's callback in the browser that started the authorization. */
100
+ export interface OAuthCallback {
101
+ /** The state the start sealed. */
102
+ state: OAuthState;
103
+ /** The callback URL, carrying the provider's `code` or `error`. */
104
+ url: URL;
105
+ }
106
+ /** What `finishAuthorization` is given. */
107
+ export interface FinishAuthorizationOptions {
108
+ /** The app's private key, from which the state's key derives. */
109
+ privateKey: string;
110
+ /** Exchanges the code and saves the connection, answering what the popup reports. */
111
+ complete: (callback: OAuthCallback) => Promise<{
112
+ ok: boolean;
113
+ message: string;
114
+ }>;
115
+ }
116
+ /**
117
+ * Answers a provider's callback. It refuses a forged, changed or expired state, and a state
118
+ * whose nonce differs from the binding cookie the request carries, leaving that cookie in
119
+ * place. Otherwise it clears the cookie, so the callback completes once, and answers the page
120
+ * reporting what `complete` returns.
121
+ *
122
+ * @param {Request} request - The callback request.
123
+ * @param {FinishAuthorizationOptions} options - The app's key and its completion.
124
+ * @returns {Promise<Response>} The callback page.
125
+ * @example if (pathname === '/oauth/callback') return finishAuthorization(request, { privateKey, complete })
126
+ */
127
+ export declare function finishAuthorization(request: Request, options: FinishAuthorizationOptions): Promise<Response>;
128
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAA;AAK7B,uEAAuE;AACvE,eAAO,MAAM,oBAAoB,4BAA4B,CAAA;AAE7D,mFAAmF;AACnF,eAAO,MAAM,yBAAyB,iCAAiC,CAAA;AAEvE,oGAAoG;AACpG,eAAO,MAAM,mBAAmB,2BAA2B,CAAA;AAE3D,+FAA+F;AAC/F,eAAO,MAAM,gBAAgB,iBAAiB,CAAA;AAE9C,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,cAAc,EAAE,MAAM,CAAA;IACtB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;CACd;AAwHD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAkBlF;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,IAAI,MAAM,CA0CvC;AA4GD,iDAAiD;AACjD,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,OAAO,EAAE,aAAa,CAAA;IACtB,uFAAuF;IACvF,YAAY,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAA;IACf;;;OAGG;IACH,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC/C;AAED,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACxC,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAA;IAClB,uDAAuD;IACvD,UAAU,EAAE,YAAY,CAAA;IACxB;;;OAGG;IACH,SAAS,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAA;CACrE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAiCnB;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,KAAK,EAAE,UAAU,CAAA;IACjB,mEAAmE;IACnE,GAAG,EAAE,GAAG,CAAA;CACT;AAED,2CAA2C;AAC3C,MAAM,WAAW,0BAA0B;IACzC,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAA;IAClB,qFAAqF;IACrF,QAAQ,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACjF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,QAAQ,CAAC,CAqBnB"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }