@picked/app-kit 0.0.1-dev.1794460939512 → 0.0.1-dev.1794522871942
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/README.md +2 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/client.d.ts +145 -0
- package/dist/cjs/client.d.ts.map +1 -0
- package/dist/cjs/frame.d.ts +124 -0
- package/dist/cjs/frame.d.ts.map +1 -0
- package/dist/cjs/index.d.ts +10 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/link.d.ts +41 -0
- package/dist/cjs/link.d.ts.map +1 -0
- package/dist/cjs/node.d.ts +19 -0
- package/dist/cjs/node.d.ts.map +1 -0
- package/dist/cjs/oauth.d.ts +128 -0
- package/dist/cjs/oauth.d.ts.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/server.d.ts +167 -0
- package/dist/cjs/server.d.ts.map +1 -0
- package/dist/cjs/testing.d.ts +84 -0
- package/dist/cjs/testing.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/server.d.ts +2 -2
- package/dist/types/testing.d.ts +1 -1
- package/package.json +53 -22
|
@@ -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":"AAaA,OAAO,KAAK,EACV,sBAAsB,EAEtB,gBAAgB,EAEjB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAA;AAK7B,qCAAqC;AACrC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAEjE,wFAAwF;AACxF,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,YAAY,CAAA;IAClB,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAA;IACb,0CAA0C;IAC1C,MAAM,EAAE,OAAO,CAAA;IACf,uCAAuC;IACvC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,YAAY,CAAA;IAClB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iEAAiE;IACjE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,iGAAiG;IACjG,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,8DAA8D;AAC9D,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,UAAU,EAAE,SAAS,iBAAiB,EAAE,CAAA;IAEjD;;;;;OAKG;IACH,YAAY,UAAU,EAAE,SAAS,iBAAiB,EAAE,EAInD;CACF;AAED,2FAA2F;AAC3F,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;IAC/B,wEAAwE;IACxE,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAA;IACtE;;;OAGG;IACH,GAAG,EAAE,CACH,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAC3B,OAAO,CAAC;QAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAAC,OAAO,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC,CAAA;IACzD,yEAAyE;IACzE,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CACrE;AAED,wCAAwC;AACxC,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,MAAM,EAAE,kBAAkB,CAAA;IAC1B;;;;OAIG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5B;;;;;;OAMG;IACH,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzD,QAAQ,EAAE,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,EAC7C,SAAS,EAAE,SAAS,EACpB,OAAO,CAAC,EAAE,sBAAsB,KAC7B,OAAO,CAAC,MAAM,CAAC,CAAA;CACrB;AAED,6DAA6D;AAC7D,MAAM,WAAW,yBAAyB;IACxC,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAA;IACd,wBAAwB;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAA;IAClB,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4BAA4B;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yDAAyD;IACzD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB;AAyDD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CAiF/F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,gBAAgB,GAAG,OAAO,GAAG,gBAAgB,CAAC,EACpF,OAAO,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CAAE,GAClF,kBAAkB,CAWpB"}
|
|
@@ -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"}
|