initiative-plugin-sdk 2.0.0
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/LICENSE +21 -0
- package/README.md +656 -0
- package/dist/build.d.ts +52 -0
- package/dist/build.js +229 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.js +160 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +288 -0
- package/dist/client.js +539 -0
- package/dist/client.js.map +1 -0
- package/dist/contract.d.ts +1247 -0
- package/dist/contract.js +167 -0
- package/dist/contract.js.map +1 -0
- package/dist/define.d.ts +336 -0
- package/dist/define.js +120 -0
- package/dist/define.js.map +1 -0
- package/dist/dev.d.ts +28 -0
- package/dist/dev.js +147 -0
- package/dist/dev.js.map +1 -0
- package/dist/expression.d.ts +28 -0
- package/dist/expression.js +67 -0
- package/dist/expression.js.map +1 -0
- package/dist/init.d.ts +14 -0
- package/dist/init.js +52 -0
- package/dist/init.js.map +1 -0
- package/dist/keys.d.ts +85 -0
- package/dist/keys.js +109 -0
- package/dist/keys.js.map +1 -0
- package/dist/manifest.d.ts +8 -0
- package/dist/manifest.js +8 -0
- package/dist/manifest.js.map +1 -0
- package/dist/pack.d.ts +35 -0
- package/dist/pack.js +84 -0
- package/dist/pack.js.map +1 -0
- package/dist/plugin-api.generated.d.ts +4263 -0
- package/dist/plugin-api.generated.js +1019 -0
- package/dist/plugin-api.generated.js.map +1 -0
- package/dist/server.d.ts +75 -0
- package/dist/server.js +408 -0
- package/dist/server.js.map +1 -0
- package/dist/testing.d.ts +116 -0
- package/dist/testing.js +386 -0
- package/dist/testing.js.map +1 -0
- package/dist/tokens.d.ts +161 -0
- package/dist/tokens.js +215 -0
- package/dist/tokens.js.map +1 -0
- package/dist/validate.d.ts +86 -0
- package/dist/validate.js +997 -0
- package/dist/validate.js.map +1 -0
- package/dist/widget.d.ts +235 -0
- package/dist/widget.js +26 -0
- package/dist/widget.js.map +1 -0
- package/examples/minimal/README.md +48 -0
- package/examples/minimal/assets/avatar.png +0 -0
- package/examples/minimal/package.json +22 -0
- package/examples/minimal/src/plugin.ts +77 -0
- package/examples/minimal/src/widgets/open-issues.ts +11 -0
- package/examples/minimal/test/fixtures/issues-statistics.json +9 -0
- package/examples/minimal/test/issue-counts.test.ts +48 -0
- package/examples/minimal/tsconfig.json +14 -0
- package/manifest.contract.json +1886 -0
- package/package.json +92 -0
- package/schemas/plugin-manifest.json +1747 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initiative, as a plug-in reaches it.
|
|
3
|
+
*
|
|
4
|
+
* A plug-in authenticates with its own key (`private_key_jwt`, RFC 7523) at
|
|
5
|
+
* `POST {baseUrl}/plugin-platform/oauth/token` and acts with one of two tokens:
|
|
6
|
+
*
|
|
7
|
+
* - an **installation token** (`client_credentials` + `installation`), acting as
|
|
8
|
+
* the community that installed the plug-in, with the scopes it granted;
|
|
9
|
+
* - a **member token** (the JWT-bearer grant, RFC 7523 §2.1), acting for one
|
|
10
|
+
* member, within what that member consented to.
|
|
11
|
+
*
|
|
12
|
+
* Either can be narrowed to one initiative the plug-in is placed in (RFC 8707) and
|
|
13
|
+
* to fewer scopes (RFC 6749 §3.3). {@link Initiative.asInstallation} and
|
|
14
|
+
* {@link Initiative.asMember} each give a {@link Client} acting that way; a
|
|
15
|
+
* handler is handed one already acting for its call. {@link Client.api} holds
|
|
16
|
+
* a typed method for every route Initiative's plug-in API describes, generated
|
|
17
|
+
* from that description.
|
|
18
|
+
*
|
|
19
|
+
* Tokens are opaque and never read. The token response says how long each
|
|
20
|
+
* lives and which scopes it holds: a call needing a scope the token does not
|
|
21
|
+
* hold fails before it is sent, naming the scope. Tokens are cached until 30
|
|
22
|
+
* seconds before they expire, concurrent callers share one request, and a
|
|
23
|
+
* call answered 401 is sent once more on a fresh token.
|
|
24
|
+
*/
|
|
25
|
+
import { PluginApi } from "./plugin-api.generated.js";
|
|
26
|
+
import type { ActorKind, PluginScope, Scope } from "./contract.js";
|
|
27
|
+
import type { Actor } from "./define.js";
|
|
28
|
+
import { type PluginSigningKey } from "./keys.js";
|
|
29
|
+
export { generatePluginKeys, jwkThumbprint, loadPrivateKey, publicJwks, type PluginKeyAlgorithm, type PluginSigningKey, type GeneratedPluginKeys, type Jwks, type PublicJwk, } from "./keys.js";
|
|
30
|
+
export type { PluginApi, PluginApiSchemas } from "./plugin-api.generated.js";
|
|
31
|
+
export interface InitiativeOptions {
|
|
32
|
+
/** The deployment's API base, such as `https://initiative.example.com/api/v1`. */
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
/** The plug-in's public id: its OAuth client id. */
|
|
35
|
+
publicId: string;
|
|
36
|
+
/** The plug-in's key, as `loadPrivateKey` gives it. */
|
|
37
|
+
key: PluginSigningKey;
|
|
38
|
+
fetch?: typeof fetch;
|
|
39
|
+
/** Milliseconds since the epoch. */
|
|
40
|
+
now?: () => number;
|
|
41
|
+
}
|
|
42
|
+
/** Narrowing for a token: fewer scopes than were granted, and one initiative. */
|
|
43
|
+
export interface Narrowing {
|
|
44
|
+
scopes?: ReadonlyArray<Scope | PluginScope>;
|
|
45
|
+
initiative?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A standing beyond the plug-in's own reach, which a community grants with its
|
|
49
|
+
* scope: `moderator` (`initiatives:moderate`) in the one initiative the token
|
|
50
|
+
* is narrowed to, or `community_admin` (`community:admin`) across the community.
|
|
51
|
+
*/
|
|
52
|
+
export type InstallLevel = "moderator" | "community_admin";
|
|
53
|
+
export interface InstallationOptions extends Narrowing {
|
|
54
|
+
/** Absent: the plug-in's own reach. */
|
|
55
|
+
level?: InstallLevel;
|
|
56
|
+
}
|
|
57
|
+
export interface MemberOptions extends Narrowing {
|
|
58
|
+
/** The purpose the member consented to. Absent: consent to the whole plug-in. */
|
|
59
|
+
purpose?: string;
|
|
60
|
+
}
|
|
61
|
+
/** One community that has installed the plug-in. */
|
|
62
|
+
export interface Installation {
|
|
63
|
+
installation: string;
|
|
64
|
+
/** False while it is switched off or its community is on hold: keep what you hold for it. */
|
|
65
|
+
active: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** What another plug-in answered, through Initiative. */
|
|
68
|
+
export interface InvokeOutcome {
|
|
69
|
+
endpoint: string;
|
|
70
|
+
actor: ActorKind;
|
|
71
|
+
result: Record<string, unknown>;
|
|
72
|
+
}
|
|
73
|
+
/** One member's stored values, by the handle the plug-in knows. */
|
|
74
|
+
export interface MemberConnectionConfig {
|
|
75
|
+
connectionId: string;
|
|
76
|
+
connectionRef: string;
|
|
77
|
+
status: string;
|
|
78
|
+
values: Record<string, unknown>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The installation's configuration: each community-wide connection's values
|
|
82
|
+
* and the managed values of each member's. Vendor tokens are never in it; ask
|
|
83
|
+
* for one with {@link Client.connectionToken}. Hold it in memory only.
|
|
84
|
+
*/
|
|
85
|
+
export interface InstallationConfig {
|
|
86
|
+
communityRef: string;
|
|
87
|
+
installId: number;
|
|
88
|
+
listingUid: string;
|
|
89
|
+
listingVersion: string;
|
|
90
|
+
enabled: boolean;
|
|
91
|
+
/** The plug-in's last verdict on this configuration: `unverified`, `ok` or `invalid`. */
|
|
92
|
+
configState: string;
|
|
93
|
+
configStateDetail: string | null;
|
|
94
|
+
/** Whether an admin still has a community-wide connection to fill in. */
|
|
95
|
+
needsConfig: boolean;
|
|
96
|
+
/** Connection id → field key → value. */
|
|
97
|
+
connections: Record<string, Record<string, unknown>>;
|
|
98
|
+
/** Connection id → the handle of each community-wide connection that has one. */
|
|
99
|
+
connectionRefs: Record<string, string>;
|
|
100
|
+
memberConnections: MemberConnectionConfig[];
|
|
101
|
+
}
|
|
102
|
+
/** One member's connection, with its state and no values. */
|
|
103
|
+
export interface InstallationConnection {
|
|
104
|
+
connectionId: string;
|
|
105
|
+
connectionRef: string;
|
|
106
|
+
status: string;
|
|
107
|
+
blocked: boolean;
|
|
108
|
+
accountLabel: string | null;
|
|
109
|
+
createdAt: string;
|
|
110
|
+
updatedAt: string;
|
|
111
|
+
}
|
|
112
|
+
/** A vendor access token for one connection. Send it to the vendor; never store it. */
|
|
113
|
+
export interface ConnectionAccessToken {
|
|
114
|
+
accessToken: string;
|
|
115
|
+
/** Milliseconds since the epoch, or null when the vendor did not say. */
|
|
116
|
+
expiresAt: number | null;
|
|
117
|
+
}
|
|
118
|
+
export interface ConfigStatusReport {
|
|
119
|
+
state: "ok" | "invalid";
|
|
120
|
+
/** A short code shown beside `invalid`, such as `missing_scope`. */
|
|
121
|
+
detail?: string;
|
|
122
|
+
}
|
|
123
|
+
export interface ConfigStatus {
|
|
124
|
+
communityRef: string;
|
|
125
|
+
installId: number;
|
|
126
|
+
configState: string;
|
|
127
|
+
configStateDetail: string | null;
|
|
128
|
+
}
|
|
129
|
+
export interface InstallationEvent {
|
|
130
|
+
/** An `emit` endpoint the plug-in declares, by its manifest id. */
|
|
131
|
+
eventType: string;
|
|
132
|
+
/** At most 8 KiB as JSON. */
|
|
133
|
+
payload?: Record<string, unknown>;
|
|
134
|
+
/** The initiative the event is about, when it is about one. */
|
|
135
|
+
initiativeId?: number;
|
|
136
|
+
}
|
|
137
|
+
export interface ConsentRequest {
|
|
138
|
+
member: string;
|
|
139
|
+
/** The plug-in's own id for what the member is consenting to. Absent: the whole plug-in. */
|
|
140
|
+
purpose?: string;
|
|
141
|
+
/** Shown to the member as the plug-in's own words. */
|
|
142
|
+
label: string;
|
|
143
|
+
initiativeId?: number;
|
|
144
|
+
/** What the plug-in asks for. The member may grant less. */
|
|
145
|
+
access: "read" | "read_write";
|
|
146
|
+
}
|
|
147
|
+
/** The token endpoint refused (RFC 6749 §5.2). */
|
|
148
|
+
export declare class InitiativeAuthError extends Error {
|
|
149
|
+
/** `invalid_client`, `invalid_grant`, `invalid_scope`, … */
|
|
150
|
+
readonly error: string;
|
|
151
|
+
readonly errorDescription: string | undefined;
|
|
152
|
+
readonly status: number;
|
|
153
|
+
constructor(
|
|
154
|
+
/** `invalid_client`, `invalid_grant`, `invalid_scope`, … */
|
|
155
|
+
error: string, errorDescription: string | undefined, status: number);
|
|
156
|
+
}
|
|
157
|
+
/** The member has not consented to this purpose, or no longer can be acted for. */
|
|
158
|
+
export declare class ConsentRequiredError extends InitiativeAuthError {
|
|
159
|
+
constructor(errorDescription: string | undefined, status: number);
|
|
160
|
+
}
|
|
161
|
+
/** A call to Initiative answered with an error status. */
|
|
162
|
+
export declare class InitiativeApiError extends Error {
|
|
163
|
+
readonly status: number;
|
|
164
|
+
/** Initiative's machine-readable `detail`, when the body carried one. */
|
|
165
|
+
readonly detail: unknown;
|
|
166
|
+
constructor(status: number,
|
|
167
|
+
/** Initiative's machine-readable `detail`, when the body carried one. */
|
|
168
|
+
detail: unknown);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* A call needs a scope the token does not hold. Nothing was sent. When any one
|
|
172
|
+
* of several would do, `scope` lists them, separated by spaces.
|
|
173
|
+
*/
|
|
174
|
+
export declare class MissingScopeError extends Error {
|
|
175
|
+
readonly scope: string;
|
|
176
|
+
constructor(scope: string);
|
|
177
|
+
}
|
|
178
|
+
/** Whether `held` covers `scope`. Writing implies reading. */
|
|
179
|
+
export declare function grants(held: readonly string[], scope: string): boolean;
|
|
180
|
+
interface Grant {
|
|
181
|
+
installation: string;
|
|
182
|
+
member?: string;
|
|
183
|
+
purpose?: string;
|
|
184
|
+
scopes: string[];
|
|
185
|
+
initiative?: number;
|
|
186
|
+
level?: InstallLevel;
|
|
187
|
+
}
|
|
188
|
+
interface AccessToken {
|
|
189
|
+
token: string;
|
|
190
|
+
scopes: string[];
|
|
191
|
+
expiresAt: number;
|
|
192
|
+
}
|
|
193
|
+
/** Issues, caches and spends the plug-in's tokens. */
|
|
194
|
+
declare class Tokens {
|
|
195
|
+
private readonly options;
|
|
196
|
+
readonly baseUrl: string;
|
|
197
|
+
readonly endpoint: string;
|
|
198
|
+
private readonly cache;
|
|
199
|
+
private readonly inflight;
|
|
200
|
+
readonly fetch: typeof fetch;
|
|
201
|
+
readonly now: () => number;
|
|
202
|
+
constructor(options: InitiativeOptions);
|
|
203
|
+
/** A token for the grant, from the cache while it is fresh. */
|
|
204
|
+
token(grant: Grant | null): Promise<AccessToken>;
|
|
205
|
+
/**
|
|
206
|
+
* `fetch` against the API on the grant's token. A 401 drops the token and the
|
|
207
|
+
* call is sent once more on a fresh one.
|
|
208
|
+
*/
|
|
209
|
+
send(grant: Grant | null, url: string, init: RequestInit): Promise<Response>;
|
|
210
|
+
private issue;
|
|
211
|
+
/** A signed assertion addressed to the token endpoint. */
|
|
212
|
+
private assertion;
|
|
213
|
+
}
|
|
214
|
+
/** Initiative, for one plug-in on one deployment. */
|
|
215
|
+
export declare class Initiative {
|
|
216
|
+
private readonly tokens;
|
|
217
|
+
constructor(options: InitiativeOptions);
|
|
218
|
+
/**
|
|
219
|
+
* Every community that has installed the plug-in, following Initiative's
|
|
220
|
+
* `Link` pages to the end. One that is gone is not listed.
|
|
221
|
+
*/
|
|
222
|
+
installations(): Promise<Installation[]>;
|
|
223
|
+
/** The plug-in acting as the community that installed it. */
|
|
224
|
+
asInstallation(installation: string, options?: InstallationOptions): Client;
|
|
225
|
+
/** The plug-in acting for one member, within what they consented to. */
|
|
226
|
+
asMember(installation: string, member: string, options?: MemberOptions): Client;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Initiative, acting as one actor in one installation.
|
|
230
|
+
*
|
|
231
|
+
* Content calls ({@link Client.request}, {@link Client.callPlugin}) go on the
|
|
232
|
+
* actor's own token. The installation's own configuration, connections, status
|
|
233
|
+
* and events go on the installation's token, narrowed to the same initiative.
|
|
234
|
+
*/
|
|
235
|
+
export declare class Client {
|
|
236
|
+
private readonly tokens;
|
|
237
|
+
private readonly grant;
|
|
238
|
+
private pluginApi?;
|
|
239
|
+
constructor(tokens: Tokens, grant: Grant);
|
|
240
|
+
/**
|
|
241
|
+
* Every route Initiative's plug-in API describes, as a typed method on the
|
|
242
|
+
* actor's token: `client.api.updateTask({ path: { task_id: 7 }, body })`.
|
|
243
|
+
* Each is checked against the token's scopes before it is sent, like
|
|
244
|
+
* {@link Client.request}.
|
|
245
|
+
*/
|
|
246
|
+
get api(): PluginApi;
|
|
247
|
+
get installation(): string;
|
|
248
|
+
get actor(): Actor;
|
|
249
|
+
get initiative(): number | null;
|
|
250
|
+
/** The scopes the actor's token holds. */
|
|
251
|
+
scopes(): Promise<string[]>;
|
|
252
|
+
/**
|
|
253
|
+
* One call to a community route, the path after `/c/{community}`, on the actor's
|
|
254
|
+
* token. It is not sent unless the token holds `scope`. Answers the parsed
|
|
255
|
+
* JSON body (a `Blob` when the answer is not JSON), or throws
|
|
256
|
+
* {@link InitiativeApiError}.
|
|
257
|
+
*/
|
|
258
|
+
request<T = unknown>(method: string, path: string, options: {
|
|
259
|
+
scope: Scope | PluginScope;
|
|
260
|
+
body?: unknown;
|
|
261
|
+
}): Promise<T>;
|
|
262
|
+
/**
|
|
263
|
+
* Call another plug-in's public endpoint through Initiative, as this actor. Needs
|
|
264
|
+
* `plugins:<publicId>`. A write is sent once and never retried by Initiative.
|
|
265
|
+
*/
|
|
266
|
+
callPlugin(publicId: string, endpointId: string, params?: Record<string, unknown>): Promise<InvokeOutcome>;
|
|
267
|
+
/** The installation's configuration. */
|
|
268
|
+
config(): Promise<InstallationConfig>;
|
|
269
|
+
/** The installation's member connections: which handles are live, with no values. */
|
|
270
|
+
connections(): Promise<InstallationConnection[]>;
|
|
271
|
+
/**
|
|
272
|
+
* A usable vendor access token for one connection, by its handle. Initiative
|
|
273
|
+
* refreshes it, or mints it for a `jwt_bearer` connection. Ask each time.
|
|
274
|
+
*/
|
|
275
|
+
connectionToken(connectionRef: string): Promise<ConnectionAccessToken>;
|
|
276
|
+
/** Tell Initiative whether the configuration the plug-in was handed works. */
|
|
277
|
+
reportConfigStatus(report: ConfigStatusReport): Promise<ConfigStatus>;
|
|
278
|
+
/** Emit one of the plug-in's declared events, for Initiative to keep and deliver. */
|
|
279
|
+
emitEvent(event: InstallationEvent): Promise<void>;
|
|
280
|
+
/** Ask a member to let the plug-in act for them, on Initiative's own consent screen. */
|
|
281
|
+
requestConsent(request: ConsentRequest): Promise<Record<string, unknown>>;
|
|
282
|
+
/** One operation of the plug-in API, its scope resolved from the operations table. */
|
|
283
|
+
private operation;
|
|
284
|
+
private installationGrant;
|
|
285
|
+
private installationCall;
|
|
286
|
+
private call;
|
|
287
|
+
private send;
|
|
288
|
+
}
|