@skillcdn/console 0.4.3 → 0.4.5
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 +6 -6
- package/dist/api.d.ts +4 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +255 -33
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +20 -5
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +98 -26
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +39 -15
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +5 -1
- package/dist/cli/plugin-check.d.ts.map +1 -1
- package/dist/cli/plugin-check.js +23 -4
- package/dist/cli/plugin-check.js.map +1 -1
- package/dist/cli/plugin-cli.d.ts.map +1 -1
- package/dist/cli/plugin-cli.js +10 -1
- package/dist/cli/plugin-cli.js.map +1 -1
- package/dist/cli/plugin-init.d.ts +2 -0
- package/dist/cli/plugin-init.d.ts.map +1 -1
- package/dist/cli/plugin-init.js +6 -0
- package/dist/cli/plugin-init.js.map +1 -1
- package/dist/client.d.ts +32 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -2
- package/dist/client.js.map +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +49 -18
- package/dist/components/board.js.map +1 -1
- package/dist/components/decision-form.d.ts +6 -3
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +24 -18
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +5 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +25 -5
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +34 -8
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/goals.d.ts.map +1 -1
- package/dist/components/goals.js +2 -2
- package/dist/components/goals.js.map +1 -1
- package/dist/components/home.d.ts +35 -0
- package/dist/components/home.d.ts.map +1 -0
- package/dist/components/home.js +326 -0
- package/dist/components/home.js.map +1 -0
- package/dist/components/my-decisions.d.ts +20 -3
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +14 -1
- package/dist/components/my-decisions.js.map +1 -1
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +5 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/people.d.ts +45 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +125 -4
- package/dist/components/people.js.map +1 -1
- package/dist/components/picker.d.ts +8 -0
- package/dist/components/picker.d.ts.map +1 -1
- package/dist/components/picker.js +2 -2
- package/dist/components/picker.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +3 -13
- package/dist/components/plugin-frame.d.ts.map +1 -1
- package/dist/components/plugin-frame.js +75 -21
- package/dist/components/plugin-frame.js.map +1 -1
- package/dist/components/plugin-settings.d.ts +5 -1
- package/dist/components/plugin-settings.d.ts.map +1 -1
- package/dist/components/plugin-settings.js +283 -45
- package/dist/components/plugin-settings.js.map +1 -1
- package/dist/components/projects.d.ts +0 -8
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +4 -14
- package/dist/components/projects.js.map +1 -1
- package/dist/components/searches.d.ts +11 -0
- package/dist/components/searches.d.ts.map +1 -0
- package/dist/components/searches.js +30 -0
- package/dist/components/searches.js.map +1 -0
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +6 -5
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -1
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +16 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +15 -2
- package/dist/components/ui.js.map +1 -1
- package/dist/connect-page.d.ts.map +1 -1
- package/dist/connect-page.js +5 -6
- package/dist/connect-page.js.map +1 -1
- package/dist/console.css +1123 -258
- package/dist/console.d.ts +12 -4
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +123 -27
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +18 -3
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +41 -1
- package/dist/data.js.map +1 -1
- package/dist/docs-page.d.ts.map +1 -1
- package/dist/docs-page.js +6 -5
- package/dist/docs-page.js.map +1 -1
- package/dist/home-tiles.d.ts +92 -0
- package/dist/home-tiles.d.ts.map +1 -0
- package/dist/home-tiles.js +294 -0
- package/dist/home-tiles.js.map +1 -0
- package/dist/i18n/en.d.ts +264 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +260 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/index.d.ts +4 -2
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +10 -11
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +255 -4
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +12 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +12 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/args.d.ts +6 -1
- package/dist/plugin/args.d.ts.map +1 -1
- package/dist/plugin/args.js +16 -11
- package/dist/plugin/args.js.map +1 -1
- package/dist/plugin/bridge.d.ts +63 -3
- package/dist/plugin/bridge.d.ts.map +1 -1
- package/dist/plugin/bridge.js +54 -0
- package/dist/plugin/bridge.js.map +1 -1
- package/dist/plugin/capabilities.d.ts +7 -1
- package/dist/plugin/capabilities.d.ts.map +1 -1
- package/dist/plugin/capabilities.js +24 -1
- package/dist/plugin/capabilities.js.map +1 -1
- package/dist/plugin/context.d.ts +174 -12
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/define.d.ts +14 -1
- package/dist/plugin/define.d.ts.map +1 -1
- package/dist/plugin/define.js +18 -2
- package/dist/plugin/define.js.map +1 -1
- package/dist/plugin/limits.d.ts +9 -2
- package/dist/plugin/limits.d.ts.map +1 -1
- package/dist/plugin/limits.js +9 -2
- package/dist/plugin/limits.js.map +1 -1
- package/dist/plugin/manifest.d.ts +98 -6
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +171 -10
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/rest-schemas.d.ts +120 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -1
- package/dist/plugin/rest-schemas.js +78 -5
- package/dist/plugin/rest-schemas.js.map +1 -1
- package/dist/plugin/sandbox.d.ts +4 -3
- package/dist/plugin/sandbox.d.ts.map +1 -1
- package/dist/plugin/sandbox.js +33 -6
- package/dist/plugin/sandbox.js.map +1 -1
- package/dist/plugin/semver.d.ts +26 -0
- package/dist/plugin/semver.d.ts.map +1 -0
- package/dist/plugin/semver.js +76 -0
- package/dist/plugin/semver.js.map +1 -0
- package/dist/plugin/testing.d.ts +15 -1
- package/dist/plugin/testing.d.ts.map +1 -1
- package/dist/plugin/testing.js +42 -5
- package/dist/plugin/testing.js.map +1 -1
- package/dist/plugin/web.d.ts +16 -1
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +44 -1
- package/dist/plugin/web.js.map +1 -1
- package/dist/plugin.d.ts +3 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -2
- package/dist/plugin.js.map +1 -1
- package/dist/router.d.ts +19 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +11 -2
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +14 -1
- package/dist/routes.js.map +1 -1
- package/dist/rules.d.ts +2 -2
- package/dist/rules.js +3 -3
- package/dist/schemas.d.ts +864 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +156 -9
- package/dist/schemas.js.map +1 -1
- package/dist/use-action.d.ts +8 -2
- package/dist/use-action.d.ts.map +1 -1
- package/dist/use-action.js +13 -6
- package/dist/use-action.js.map +1 -1
- package/dist/vocabulary.d.ts +3 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +9 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +10 -6
- package/docs/specs/plugins.md +83 -62
- package/docs/specs/rest.md +34 -16
- package/package.json +1 -1
- package/skills/building-a-plugin/SKILL.md +15 -12
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +23 -0
- package/src/cli/cli.ts +323 -30
- package/src/cli/format.ts +122 -26
- package/src/cli/help.ts +43 -14
- package/src/cli/plugin-check.ts +41 -2
- package/src/cli/plugin-cli.ts +10 -1
- package/src/cli/plugin-init.ts +8 -0
- package/src/client.ts +78 -0
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +86 -45
- package/src/components/decision-form.tsx +73 -36
- package/src/components/decision-list.tsx +45 -13
- package/src/components/event-feed.tsx +40 -7
- package/src/components/goals.tsx +4 -1
- package/src/components/home.tsx +615 -0
- package/src/components/my-decisions.tsx +50 -5
- package/src/components/pack-connect.tsx +5 -4
- package/src/components/people.tsx +500 -61
- package/src/components/picker.tsx +11 -2
- package/src/components/plugin-frame.tsx +85 -63
- package/src/components/plugin-settings.tsx +735 -185
- package/src/components/projects.tsx +14 -69
- package/src/components/searches.ts +47 -0
- package/src/components/task-form.tsx +23 -28
- package/src/components/task-view.tsx +7 -0
- package/src/components/ui.tsx +42 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +303 -64
- package/src/data.ts +62 -3
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +274 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +267 -4
- package/src/index.ts +18 -5
- package/src/limits.ts +12 -0
- package/src/plugin/args.ts +16 -11
- package/src/plugin/bridge.ts +122 -1
- package/src/plugin/capabilities.ts +28 -1
- package/src/plugin/context.ts +186 -9
- package/src/plugin/define.ts +38 -1
- package/src/plugin/limits.ts +9 -2
- package/src/plugin/manifest.ts +228 -13
- package/src/plugin/rest-schemas.ts +89 -6
- package/src/plugin/sandbox.ts +36 -8
- package/src/plugin/semver.ts +89 -0
- package/src/plugin/testing.ts +63 -6
- package/src/plugin/web.ts +66 -0
- package/src/plugin.ts +19 -0
- package/src/router.ts +25 -0
- package/src/routes.ts +17 -2
- package/src/rules.ts +3 -3
- package/src/schemas.ts +197 -10
- package/src/styles/console.css +1123 -258
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +10 -0
- package/template/plugin/console-plugin.json +3 -1
package/src/plugin/bridge.ts
CHANGED
|
@@ -3,8 +3,18 @@
|
|
|
3
3
|
// frame the console's origin alone. The shapes and their guards, with no DOM in them, so that the
|
|
4
4
|
// page's side and the frame's side read the same words and the tests need no browser.
|
|
5
5
|
|
|
6
|
+
import { WEEK_STARTS, type WeekStart } from "../vocabulary.js";
|
|
7
|
+
|
|
6
8
|
export const PLUGIN_BRIDGE_VERSION = 1;
|
|
7
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The sizes a widget comes in (ADR-0061): columns and rows of the front page's grid of square
|
|
12
|
+
* cells, `small` 2×2, `medium` 4×2, `large` 4×4, `wide` 8×2, `full` 8×4. Here, so that a bundle
|
|
13
|
+
* learns them without the manifest's schema.
|
|
14
|
+
*/
|
|
15
|
+
export const PLUGIN_WIDGET_SIZE_NAMES = ["small", "medium", "large", "wide", "full"] as const;
|
|
16
|
+
export type PluginWidgetSizeName = (typeof PLUGIN_WIDGET_SIZE_NAMES)[number];
|
|
17
|
+
|
|
8
18
|
/** What the frame may ask the page to perform, each under `/api/v1/plugins/<key>/` and nothing else. */
|
|
9
19
|
export const BRIDGE_METHODS = [
|
|
10
20
|
"command",
|
|
@@ -14,6 +24,7 @@ export const BRIDGE_METHODS = [
|
|
|
14
24
|
"records.versions",
|
|
15
25
|
"files.get",
|
|
16
26
|
"files.put",
|
|
27
|
+
"people.picture",
|
|
17
28
|
] as const;
|
|
18
29
|
|
|
19
30
|
export type BridgeMethod = (typeof BRIDGE_METHODS)[number];
|
|
@@ -31,6 +42,17 @@ export interface BridgePerson {
|
|
|
31
42
|
readonly admin: boolean;
|
|
32
43
|
}
|
|
33
44
|
|
|
45
|
+
/** The workspace as a page lays itself out for it: what it is called, the languages it writes in, its clock and the day its week begins on. */
|
|
46
|
+
export interface BridgeWorkspace {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly languages: readonly string[];
|
|
49
|
+
readonly timeZone: string;
|
|
50
|
+
/** The workspace's setting (ADR-0035): `auto` leaves the week to the language of the pages. */
|
|
51
|
+
readonly weekStart: WeekStart;
|
|
52
|
+
/** The day a week begins on with the setting applied, for a calendar to start from: 1 for Monday, 7 for Sunday. */
|
|
53
|
+
readonly firstDay: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
/** What the page tells the frame once it is ready: who looks, at what, in which words and colours. */
|
|
35
57
|
export interface BridgeInit {
|
|
36
58
|
readonly v: 1;
|
|
@@ -38,8 +60,12 @@ export interface BridgeInit {
|
|
|
38
60
|
readonly plugin: { readonly key: string; readonly version: string };
|
|
39
61
|
readonly page?: string;
|
|
40
62
|
readonly widget?: string;
|
|
63
|
+
/** The size the widget's frame has (ADR-0059), one of the sizes the manifest declared for it; none for a page. */
|
|
64
|
+
readonly size?: PluginWidgetSizeName;
|
|
41
65
|
readonly project: { readonly key: string; readonly name: string } | null;
|
|
42
66
|
readonly person: BridgePerson;
|
|
67
|
+
/** The workspace; none from a console older than this. */
|
|
68
|
+
readonly workspace?: BridgeWorkspace;
|
|
43
69
|
readonly roles: readonly string[];
|
|
44
70
|
/** What follows the page's key in the address. */
|
|
45
71
|
readonly path: string;
|
|
@@ -82,7 +108,20 @@ export interface BridgePath {
|
|
|
82
108
|
readonly path: string;
|
|
83
109
|
}
|
|
84
110
|
|
|
85
|
-
|
|
111
|
+
/** The person gave the widget another size (ADR-0059): the frame lays itself out for it. */
|
|
112
|
+
export interface BridgeSize {
|
|
113
|
+
readonly v: 1;
|
|
114
|
+
readonly t: "size";
|
|
115
|
+
readonly size: PluginWidgetSizeName;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type BridgeToFrame =
|
|
119
|
+
| BridgeInit
|
|
120
|
+
| BridgeResult
|
|
121
|
+
| BridgeEvent
|
|
122
|
+
| BridgeTheme
|
|
123
|
+
| BridgePath
|
|
124
|
+
| BridgeSize;
|
|
86
125
|
|
|
87
126
|
export interface BridgeReady {
|
|
88
127
|
readonly v: 1;
|
|
@@ -121,19 +160,41 @@ export interface BridgeDownload {
|
|
|
121
160
|
readonly id: string;
|
|
122
161
|
}
|
|
123
162
|
|
|
163
|
+
/**
|
|
164
|
+
* A link a person pressed in the frame, for the console's page to open in a new tab (ADR-0067):
|
|
165
|
+
* the frame's sandbox opens no window itself, and the page opens one only for a web or a mail
|
|
166
|
+
* address, and only right after a person pressed.
|
|
167
|
+
*/
|
|
168
|
+
export interface BridgeOpen {
|
|
169
|
+
readonly v: 1;
|
|
170
|
+
readonly t: "open";
|
|
171
|
+
readonly url: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
124
174
|
export interface BridgeSubscribe {
|
|
125
175
|
readonly v: 1;
|
|
126
176
|
readonly t: "subscribe";
|
|
127
177
|
readonly kinds: readonly BridgeEventKind[];
|
|
128
178
|
}
|
|
129
179
|
|
|
180
|
+
/**
|
|
181
|
+
* The frame has nothing to show: the plugin is off, or the page is not one of its own. The console
|
|
182
|
+
* serves such a frame a document with no words, and the console's page says so in its own.
|
|
183
|
+
*/
|
|
184
|
+
export interface BridgeUnavailable {
|
|
185
|
+
readonly v: 1;
|
|
186
|
+
readonly t: "unavailable";
|
|
187
|
+
}
|
|
188
|
+
|
|
130
189
|
export type BridgeToPage =
|
|
131
190
|
| BridgeReady
|
|
191
|
+
| BridgeUnavailable
|
|
132
192
|
| BridgeCall
|
|
133
193
|
| BridgeNavigate
|
|
134
194
|
| BridgeResize
|
|
135
195
|
| BridgeTitle
|
|
136
196
|
| BridgeDownload
|
|
197
|
+
| BridgeOpen
|
|
137
198
|
| BridgeSubscribe;
|
|
138
199
|
|
|
139
200
|
/** A path of the console's, as `navigate` and `path` carry one: absolute, no scheme, no host, one line. */
|
|
@@ -163,6 +224,37 @@ export function isBridgePath(value: unknown): value is string {
|
|
|
163
224
|
);
|
|
164
225
|
}
|
|
165
226
|
|
|
227
|
+
/** The schemes a link pressed in a frame may open in a new tab: the web, and mail. Nothing that runs. */
|
|
228
|
+
const OPENABLE_SCHEMES: ReadonlySet<string> = new Set(["https:", "http:", "mailto:"]);
|
|
229
|
+
export const MAX_BRIDGE_URL_LENGTH = 2048;
|
|
230
|
+
|
|
231
|
+
/** True for an address the page may open in a new tab for a frame: an absolute web or mail address, one line. */
|
|
232
|
+
export function isBridgeUrl(value: unknown): value is string {
|
|
233
|
+
if (
|
|
234
|
+
typeof value !== "string" ||
|
|
235
|
+
value.length === 0 ||
|
|
236
|
+
value.length > MAX_BRIDGE_URL_LENGTH ||
|
|
237
|
+
/\s/.test(value) ||
|
|
238
|
+
[...value].some((character) => {
|
|
239
|
+
const code = character.codePointAt(0) ?? 0;
|
|
240
|
+
return code < 0x20 || code === 0x7f;
|
|
241
|
+
}) ||
|
|
242
|
+
!URL.canParse(value)
|
|
243
|
+
) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
return OPENABLE_SCHEMES.has(new URL(value).protocol);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* True for an address the page opens for a frame (ADR-0067): a web or a mail address that is not
|
|
251
|
+
* the console's own, given as `origin`, since a page of the console's is `navigate`'s, in the
|
|
252
|
+
* person's tab.
|
|
253
|
+
*/
|
|
254
|
+
export function isOpenableFrom(value: unknown, origin: string): value is string {
|
|
255
|
+
return isBridgeUrl(value) && new URL(value).origin !== origin;
|
|
256
|
+
}
|
|
257
|
+
|
|
166
258
|
function isTheme(value: unknown): value is Record<string, string> {
|
|
167
259
|
if (!isRecord(value)) {
|
|
168
260
|
return false;
|
|
@@ -177,6 +269,21 @@ function isTheme(value: unknown): value is Record<string, string> {
|
|
|
177
269
|
);
|
|
178
270
|
}
|
|
179
271
|
|
|
272
|
+
function isWorkspace(value: unknown): boolean {
|
|
273
|
+
return (
|
|
274
|
+
isRecord(value) &&
|
|
275
|
+
typeof value.name === "string" &&
|
|
276
|
+
Array.isArray(value.languages) &&
|
|
277
|
+
value.languages.every((language) => typeof language === "string") &&
|
|
278
|
+
isLine(value.timeZone, 100) &&
|
|
279
|
+
(WEEK_STARTS as readonly unknown[]).includes(value.weekStart) &&
|
|
280
|
+
typeof value.firstDay === "number" &&
|
|
281
|
+
Number.isInteger(value.firstDay) &&
|
|
282
|
+
value.firstDay >= 1 &&
|
|
283
|
+
value.firstDay <= 7
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
180
287
|
function isEnvelope(value: unknown): value is Record<string, unknown> & { t: string } {
|
|
181
288
|
return isRecord(value) && value.v === PLUGIN_BRIDGE_VERSION && typeof value.t === "string";
|
|
182
289
|
}
|
|
@@ -194,6 +301,8 @@ export function isBridgeToFrame(value: unknown): value is BridgeToFrame {
|
|
|
194
301
|
isLine(value.plugin.version, 64) &&
|
|
195
302
|
(value.page === undefined || isLine(value.page, 32)) &&
|
|
196
303
|
(value.widget === undefined || isLine(value.widget, 32)) &&
|
|
304
|
+
(value.size === undefined ||
|
|
305
|
+
(PLUGIN_WIDGET_SIZE_NAMES as readonly unknown[]).includes(value.size)) &&
|
|
197
306
|
(value.project === null ||
|
|
198
307
|
(isRecord(value.project) &&
|
|
199
308
|
isLine(value.project.key, 64) &&
|
|
@@ -204,6 +313,7 @@ export function isBridgeToFrame(value: unknown): value is BridgeToFrame {
|
|
|
204
313
|
(value.person.name === null || typeof value.person.name === "string") &&
|
|
205
314
|
typeof value.person.language === "string" &&
|
|
206
315
|
typeof value.person.admin === "boolean" &&
|
|
316
|
+
(value.workspace === undefined || isWorkspace(value.workspace)) &&
|
|
207
317
|
Array.isArray(value.roles) &&
|
|
208
318
|
value.roles.every((role) => typeof role === "string") &&
|
|
209
319
|
typeof value.path === "string" &&
|
|
@@ -229,6 +339,8 @@ export function isBridgeToFrame(value: unknown): value is BridgeToFrame {
|
|
|
229
339
|
return isTheme(value.theme);
|
|
230
340
|
case "path":
|
|
231
341
|
return typeof value.path === "string" && value.path.length <= MAX_BRIDGE_PATH_LENGTH;
|
|
342
|
+
case "size":
|
|
343
|
+
return (PLUGIN_WIDGET_SIZE_NAMES as readonly unknown[]).includes(value.size);
|
|
232
344
|
default:
|
|
233
345
|
return false;
|
|
234
346
|
}
|
|
@@ -241,6 +353,7 @@ export function isBridgeToPage(value: unknown): value is BridgeToPage {
|
|
|
241
353
|
}
|
|
242
354
|
switch (value.t) {
|
|
243
355
|
case "ready":
|
|
356
|
+
case "unavailable":
|
|
244
357
|
return true;
|
|
245
358
|
case "call":
|
|
246
359
|
return (
|
|
@@ -261,6 +374,8 @@ export function isBridgeToPage(value: unknown): value is BridgeToPage {
|
|
|
261
374
|
);
|
|
262
375
|
case "download":
|
|
263
376
|
return isLine(value.id, MAX_ID_LENGTH);
|
|
377
|
+
case "open":
|
|
378
|
+
return isBridgeUrl(value.url);
|
|
264
379
|
case "subscribe":
|
|
265
380
|
return (
|
|
266
381
|
Array.isArray(value.kinds) &&
|
|
@@ -307,6 +422,12 @@ export interface BridgeFilesPutParams {
|
|
|
307
422
|
}
|
|
308
423
|
|
|
309
424
|
/** What `files.get` answers: the bytes, transferred, with the file's name and type. */
|
|
425
|
+
/** A person's picture, as the page hands it to the frame (ADR-0063). */
|
|
426
|
+
export interface BridgePicture {
|
|
427
|
+
readonly type: string;
|
|
428
|
+
readonly bytes: ArrayBuffer;
|
|
429
|
+
}
|
|
430
|
+
|
|
310
431
|
export interface BridgeFile {
|
|
311
432
|
readonly name: string;
|
|
312
433
|
readonly type: string;
|
|
@@ -8,6 +8,8 @@ export const PLUGIN_CAPABILITIES = [
|
|
|
8
8
|
"files",
|
|
9
9
|
"people:read",
|
|
10
10
|
"people:read:all",
|
|
11
|
+
"people:read:email",
|
|
12
|
+
"people:act",
|
|
11
13
|
"projects:read",
|
|
12
14
|
"projects:read:all",
|
|
13
15
|
"board:read",
|
|
@@ -19,10 +21,18 @@ export const PLUGIN_CAPABILITIES = [
|
|
|
19
21
|
"jobs",
|
|
20
22
|
"endpoints",
|
|
21
23
|
"storage:sql",
|
|
24
|
+
"outbound:webrtc",
|
|
22
25
|
] as const;
|
|
23
26
|
|
|
24
27
|
export type PluginCapability = (typeof PLUGIN_CAPABILITIES)[number];
|
|
25
28
|
|
|
29
|
+
/**
|
|
30
|
+
* WebRTC on a plugin's pages (ADR-0066). It stands with `outbound:<host>` as a way to the internet,
|
|
31
|
+
* and it is no host: a connection goes from a person's browser to any address a page names, which
|
|
32
|
+
* no host could bound, so that consent to a host does not open it, and it opens no host.
|
|
33
|
+
*/
|
|
34
|
+
export const OUTBOUND_WEBRTC: PluginCapability = "outbound:webrtc";
|
|
35
|
+
|
|
26
36
|
/** The capabilities that carry a parameter: a host to reach, a plugin to call. */
|
|
27
37
|
export const PLUGIN_CAPABILITY_PREFIXES = ["outbound:", "plugins:"] as const;
|
|
28
38
|
|
|
@@ -52,6 +62,15 @@ export const CAPABILITY_WORDS: Readonly<Record<PluginCapability, CapabilityWords
|
|
|
52
62
|
opens: "everyone in the workspace, for its jobs and hooks too",
|
|
53
63
|
reach: "the directory",
|
|
54
64
|
},
|
|
65
|
+
"people:read:email": {
|
|
66
|
+
opens: "the work email of the people it may read, to match them to accounts elsewhere",
|
|
67
|
+
reach: "the addresses of the people its role holders may see",
|
|
68
|
+
},
|
|
69
|
+
"people:act": {
|
|
70
|
+
opens:
|
|
71
|
+
"acting for a person who did something elsewhere: a command run and a decision answered as them, within what they may do, recorded as theirs through the plugin",
|
|
72
|
+
reach: "what its role holders may do, done in their name from elsewhere",
|
|
73
|
+
},
|
|
55
74
|
"projects:read": {
|
|
56
75
|
opens: "the projects and members the caller may see",
|
|
57
76
|
reach: "what the person sees",
|
|
@@ -97,6 +116,10 @@ export const CAPABILITY_WORDS: Readonly<Record<PluginCapability, CapabilityWords
|
|
|
97
116
|
opens: "a schema and a login role of its own in the console's database",
|
|
98
117
|
reach: "statements up to the timeout, within its schema",
|
|
99
118
|
},
|
|
119
|
+
"outbound:webrtc": {
|
|
120
|
+
opens: "WebRTC on its pages: connections from each person's browser to any address",
|
|
121
|
+
reach: "what its pages are shown, sent anywhere from the person's browser; their address",
|
|
122
|
+
},
|
|
100
123
|
};
|
|
101
124
|
|
|
102
125
|
/** True for a capability the catalogue knows, parameter included. */
|
|
@@ -137,7 +160,11 @@ export function capabilityWords(capability: string): CapabilityWords | undefined
|
|
|
137
160
|
|
|
138
161
|
/** True when the request's host is within what `outbound:<host>` names: the host itself, or a subdomain where the capability starts with `*.`. */
|
|
139
162
|
export function outboundAllows(capability: string, host: string): boolean {
|
|
140
|
-
if (
|
|
163
|
+
if (
|
|
164
|
+
!capability.startsWith("outbound:") ||
|
|
165
|
+
capability === OUTBOUND_WEBRTC ||
|
|
166
|
+
!isPluginCapability(capability)
|
|
167
|
+
) {
|
|
141
168
|
return false;
|
|
142
169
|
}
|
|
143
170
|
const named = capability.slice("outbound:".length);
|
package/src/plugin/context.ts
CHANGED
|
@@ -15,7 +15,9 @@ import type {
|
|
|
15
15
|
RestTask,
|
|
16
16
|
RestTaskInput,
|
|
17
17
|
RestTaskPatch,
|
|
18
|
+
RestTeam,
|
|
18
19
|
} from "../schemas.js";
|
|
20
|
+
import type { WeekStart } from "../vocabulary.js";
|
|
19
21
|
import type { PluginFileHandle } from "./args.js";
|
|
20
22
|
|
|
21
23
|
/** A schema as the Standard Schema interface spells one: what every build of zod implements, so that a plugin's own copy validates its records. */
|
|
@@ -40,6 +42,16 @@ export type RecordSchemaResult<T> =
|
|
|
40
42
|
/** The data a record of a collection holds, from its schema. */
|
|
41
43
|
export type RecordData<S> = S extends RecordSchema<infer T> ? T : never;
|
|
42
44
|
|
|
45
|
+
/**
|
|
46
|
+
* How long a collection keeps a record: `days` after the moment `by` names, `createdAt` or
|
|
47
|
+
* `updatedAt` of the record, or an indexed field of its data that holds a day or an instant as
|
|
48
|
+
* ISO 8601 text; then the console removes the record, with its versions, links and files.
|
|
49
|
+
*/
|
|
50
|
+
export interface CollectionRetention {
|
|
51
|
+
readonly days: number;
|
|
52
|
+
readonly by: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
/** A collection as `defineCollection` yields it: the name, the schema, and what the console indexes and who reads. */
|
|
44
56
|
export interface CollectionDefinition<S extends RecordSchema = RecordSchema> {
|
|
45
57
|
readonly name: string;
|
|
@@ -51,6 +63,8 @@ export interface CollectionDefinition<S extends RecordSchema = RecordSchema> {
|
|
|
51
63
|
readonly read: readonly string[];
|
|
52
64
|
readonly readOwn: readonly string[];
|
|
53
65
|
readonly history: boolean;
|
|
66
|
+
/** How long a record is kept, or `null` for as long as the plugin is. */
|
|
67
|
+
readonly retention: CollectionRetention | null;
|
|
54
68
|
}
|
|
55
69
|
|
|
56
70
|
export interface PluginRecord<T = Record<string, unknown>> {
|
|
@@ -199,10 +213,23 @@ export interface PluginInfo {
|
|
|
199
213
|
readonly dataVersion: number;
|
|
200
214
|
/** The settings as the administrator set them, by key, without the secrets. */
|
|
201
215
|
readonly settings: Readonly<Record<string, unknown>>;
|
|
216
|
+
/**
|
|
217
|
+
* The capabilities the plugin holds: those it requires, and the optional ones an administrator
|
|
218
|
+
* turned on (ADR-0060), so that it offers what it can do. One it does not hold is refused.
|
|
219
|
+
*/
|
|
220
|
+
readonly capabilities: readonly string[];
|
|
202
221
|
/** The board project, or none. */
|
|
203
222
|
readonly board: { readonly id: string; readonly key: string } | null;
|
|
204
223
|
/** The projects the plugin is on: all, or their keys. */
|
|
205
224
|
readonly projects: "all" | readonly string[];
|
|
225
|
+
/** The roles other plugins granted to this one (ADR-0072), by their key, so that a plugin says what it lacks. */
|
|
226
|
+
readonly grants: readonly PluginGrantHeld[];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** A role of another plugin, held by this one. */
|
|
230
|
+
export interface PluginGrantHeld {
|
|
231
|
+
readonly plugin: string;
|
|
232
|
+
readonly role: string;
|
|
206
233
|
}
|
|
207
234
|
|
|
208
235
|
export interface CallerPerson {
|
|
@@ -239,12 +266,43 @@ export interface ConsoleCaller {
|
|
|
239
266
|
readonly project: CallerProject | null;
|
|
240
267
|
}
|
|
241
268
|
|
|
242
|
-
|
|
269
|
+
/**
|
|
270
|
+
* Another plugin, calling with the roles granted to it (ADR-0072): from its own job, hook or
|
|
271
|
+
* webhook, as itself; or for a person who did something elsewhere, with `for`, and then with the
|
|
272
|
+
* roles the plugin and the person both hold, in the person's language. A plugin that knows only
|
|
273
|
+
* `person` and `console` refuses the kind, which fails safe.
|
|
274
|
+
*/
|
|
275
|
+
export interface PluginAsCaller {
|
|
276
|
+
readonly kind: "plugin";
|
|
277
|
+
/** The calling plugin's key. */
|
|
278
|
+
readonly plugin: string;
|
|
279
|
+
readonly roles: readonly string[];
|
|
280
|
+
/** The person the plugin acts for, or `null` when it acts as itself; never an administrator by that. */
|
|
281
|
+
readonly for: CallerPerson | null;
|
|
282
|
+
/** The project the call is on, if any: the one the person's own call was on. */
|
|
283
|
+
readonly project: CallerProject | null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export type PluginCaller = PersonCaller | ConsoleCaller | PluginAsCaller;
|
|
243
287
|
|
|
244
288
|
export interface PluginWorkspace {
|
|
245
289
|
readonly name: string;
|
|
246
290
|
readonly languages: readonly string[];
|
|
291
|
+
/** The zone the workspace's times of day are read in; `UTC` where none is set, as `timeZoneSet` says. */
|
|
247
292
|
readonly timeZone: string;
|
|
293
|
+
/**
|
|
294
|
+
* Whether an administrator set the time zone: `false` where the workspace runs on UTC because
|
|
295
|
+
* nobody said otherwise, so that a plugin that reads a time of day says so, instead of taking
|
|
296
|
+
* UTC for a choice.
|
|
297
|
+
*/
|
|
298
|
+
readonly timeZoneSet: boolean;
|
|
299
|
+
/** The day the week begins on (ADR-0035): `auto` leaves it to the language of whoever reads. */
|
|
300
|
+
readonly weekStart: WeekStart;
|
|
301
|
+
/**
|
|
302
|
+
* The console's public origin (`PUBLIC_URL`), for a link to the console in what a plugin
|
|
303
|
+
* writes elsewhere; `null` where the deployment names none.
|
|
304
|
+
*/
|
|
305
|
+
readonly url: string | null;
|
|
248
306
|
readonly categories: readonly {
|
|
249
307
|
readonly key: string;
|
|
250
308
|
readonly name: string;
|
|
@@ -252,7 +310,15 @@ export interface PluginWorkspace {
|
|
|
252
310
|
}[];
|
|
253
311
|
}
|
|
254
312
|
|
|
313
|
+
/** An answer given for a person (ADR-0072): the option by its key or by its label, one of the two, and a note. */
|
|
314
|
+
export interface DecisionAnswer {
|
|
315
|
+
readonly optionKey?: string;
|
|
316
|
+
readonly option?: string;
|
|
317
|
+
readonly note?: string;
|
|
318
|
+
}
|
|
319
|
+
|
|
255
320
|
export interface DecisionAsk {
|
|
321
|
+
/** In the language of whoever is to answer, as `people.get` says it (ADR-0065). */
|
|
256
322
|
readonly question: string;
|
|
257
323
|
readonly body?: string;
|
|
258
324
|
/**
|
|
@@ -261,8 +327,10 @@ export interface DecisionAsk {
|
|
|
261
327
|
* without comparing labels.
|
|
262
328
|
*/
|
|
263
329
|
readonly options: readonly { readonly label: string; readonly key?: string }[];
|
|
264
|
-
/** Who is to answer: a person who may work in the project; the
|
|
330
|
+
/** Who is to answer: a person who may work in the project; the administrator who enabled the plugin when left out. */
|
|
265
331
|
readonly deciderId?: string;
|
|
332
|
+
/** Several to answer (ADR-0057), in place of `deciderId`: any of them may, and the first answer ends it; up to MAX_DECIDERS. */
|
|
333
|
+
readonly deciderIds?: readonly string[];
|
|
266
334
|
readonly taskId?: string;
|
|
267
335
|
/** The record the decision is for, by which the plugin maps the answer back. */
|
|
268
336
|
readonly about: { readonly collection: string; readonly id: string };
|
|
@@ -274,6 +342,19 @@ export interface PluginConsole {
|
|
|
274
342
|
people: {
|
|
275
343
|
list(options?: { readonly q?: string }): Promise<readonly RestPerson[]>;
|
|
276
344
|
get(id: string): Promise<RestPerson | null>;
|
|
345
|
+
/**
|
|
346
|
+
* The active people a role of the plugin was granted to, by name, through a project or with
|
|
347
|
+
* everyone, and everyone for `defaultRole` (ADR-0063); for a role that follows the reporting
|
|
348
|
+
* line, whoever manages someone besides (ADR-0068). An administrator holds every role, and is
|
|
349
|
+
* listed only where they were granted it or manage someone.
|
|
350
|
+
*/
|
|
351
|
+
granted(role: string): Promise<readonly RestPerson[]>;
|
|
352
|
+
};
|
|
353
|
+
/** The teams of the workspace (ADR-0073): a name and its members, among the people the caller may see. */
|
|
354
|
+
teams: {
|
|
355
|
+
list(): Promise<readonly RestTeam[]>;
|
|
356
|
+
/** One team by its key (ADR-0074), what a grant and a person's `teams` name it by. */
|
|
357
|
+
get(key: string): Promise<RestTeam | null>;
|
|
277
358
|
};
|
|
278
359
|
projects: {
|
|
279
360
|
list(): Promise<readonly RestProject[]>;
|
|
@@ -291,6 +372,14 @@ export interface PluginConsole {
|
|
|
291
372
|
list(project: string): Promise<readonly RestDecision[]>;
|
|
292
373
|
get(id: string): Promise<RestDecision | null>;
|
|
293
374
|
ask(project: string, decision: DecisionAsk): Promise<RestDecision>;
|
|
375
|
+
/**
|
|
376
|
+
* Answers a decision for a person who answered elsewhere (ADR-0072), with `people:act`: one on
|
|
377
|
+
* the person's own plate, addressed to them or a plugin's addressed to someone they manage, up
|
|
378
|
+
* the reporting line (ADR-0068), and `plugin.forbidden` for any other, whatever they
|
|
379
|
+
* administer or are a member of. The answer is theirs, through this plugin. A plugin answers
|
|
380
|
+
* no decision itself.
|
|
381
|
+
*/
|
|
382
|
+
answer(id: string, answer: DecisionAnswer, as: { readonly for: string }): Promise<RestDecision>;
|
|
294
383
|
};
|
|
295
384
|
documents: {
|
|
296
385
|
list(project: string, folder?: string): Promise<readonly RestDocumentSummary[]>;
|
|
@@ -325,28 +414,90 @@ export interface PluginSql extends PluginSqlClient {
|
|
|
325
414
|
transaction<T>(work: (tx: PluginSqlClient) => Promise<T>): Promise<T>;
|
|
326
415
|
}
|
|
327
416
|
|
|
328
|
-
/**
|
|
329
|
-
|
|
417
|
+
/**
|
|
418
|
+
* Where the console puts a secret into an outbound request: a header, as `Authorization: Bearer`,
|
|
419
|
+
* a query parameter, as basic authentication (`user:password`), or a field of a JSON or a form
|
|
420
|
+
* body.
|
|
421
|
+
*/
|
|
422
|
+
export type SecretPlacement =
|
|
423
|
+
| `header:${string}`
|
|
424
|
+
| "bearer"
|
|
425
|
+
| `query:${string}`
|
|
426
|
+
| "basic"
|
|
427
|
+
| `body:${string}`;
|
|
428
|
+
|
|
429
|
+
/** One secret setting and where it goes. */
|
|
430
|
+
export interface SecretUse {
|
|
431
|
+
readonly name: string;
|
|
432
|
+
readonly as: SecretPlacement;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* A value of the answer to seal as a secret setting (ADR-0070): the path to it in the answer's
|
|
437
|
+
* JSON (`$.access_token`, `$.credentials.signing_secret`, `$.items.0.token`), and, where it
|
|
438
|
+
* expires, how long it lasts, in seconds or as a path to the seconds (`$.expires_in`). The
|
|
439
|
+
* console seals the value and hands the plugin the answer with `"[sealed]"` in its place.
|
|
440
|
+
*/
|
|
441
|
+
export type SecretSeal =
|
|
442
|
+
| string
|
|
443
|
+
| { readonly path: string; readonly expiresIn?: number | string | undefined };
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* What became of a value `seal` named (ADR-0070). `sealed`: sealed as the setting, the answer
|
|
447
|
+
* saying `"[sealed]"` in its place. `absent`: nothing at the path (no such key, `null` or an
|
|
448
|
+
* empty string); nothing sealed, nothing taken out. `not_a_string`, `too_long` and `echoed`: a
|
|
449
|
+
* value that is no string, is over the bound of a secret (4,096 characters), or holds a secret
|
|
450
|
+
* the request carried from another setting; never sealed and never handed over, the answer saying
|
|
451
|
+
* `"[withheld]"` in its place. `not_json`: an answer that is no JSON object or array, whose body
|
|
452
|
+
* is withheld whole. Only `sealed` changed the setting.
|
|
453
|
+
*/
|
|
454
|
+
export type SealOutcome = "sealed" | "absent" | "not_a_string" | "too_long" | "echoed" | "not_json";
|
|
330
455
|
|
|
331
456
|
export interface PluginFetchInit {
|
|
457
|
+
/** `GET` unless said; `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS`, and no other. */
|
|
332
458
|
readonly method?: string;
|
|
333
459
|
readonly headers?: Readonly<Record<string, string>>;
|
|
334
460
|
readonly body?: string | Uint8Array;
|
|
335
|
-
/** A secret setting, put where `as` says
|
|
336
|
-
readonly secret?:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
/**
|
|
461
|
+
/** A secret setting, put where `as` says. */
|
|
462
|
+
readonly secret?: SecretUse;
|
|
463
|
+
/** Several, where a request carries more than one: a client secret in the body and a token as a bearer, say. */
|
|
464
|
+
readonly secrets?: readonly SecretUse[];
|
|
465
|
+
/** Values of the answer to seal as secret settings, by the setting's name, never handed to the plugin. */
|
|
466
|
+
readonly seal?: Readonly<Record<string, SecretSeal>>;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* An outbound answer, whole: the host reads it to the bound before handing it over, a redirect as
|
|
471
|
+
* it is, never followed. Every form of a secret the request carried, and of a value sealed or
|
|
472
|
+
* withheld, is taken out of its headers and its body first, `"[withheld]"` in its place.
|
|
473
|
+
*/
|
|
340
474
|
export interface PluginFetchResponse {
|
|
341
475
|
readonly status: number;
|
|
342
476
|
readonly headers: Readonly<Record<string, string>>;
|
|
343
477
|
readonly bytes: Uint8Array;
|
|
478
|
+
/** What became of each value `seal` named, by the setting's name; there where the request named `seal`. */
|
|
479
|
+
readonly sealed?: Readonly<Record<string, SealOutcome>>;
|
|
344
480
|
text(): string;
|
|
345
481
|
json(): unknown;
|
|
346
482
|
}
|
|
347
483
|
|
|
484
|
+
/** How a secret stands: whether it is set and not expired, when it expires, and where it came from. */
|
|
485
|
+
export interface SecretStatus {
|
|
486
|
+
/** Set, and not past its expiry. */
|
|
487
|
+
readonly set: boolean;
|
|
488
|
+
readonly setAt: string | null;
|
|
489
|
+
/** When it expires, or `null` for a secret that does not. */
|
|
490
|
+
readonly expiresAt: string | null;
|
|
491
|
+
/** `administrator` for one an administrator set; the host of the answer it was sealed from otherwise; `null` while unset. */
|
|
492
|
+
readonly source: string | null;
|
|
493
|
+
}
|
|
494
|
+
|
|
348
495
|
export interface PluginSecrets {
|
|
496
|
+
/** Whether the secret is set and not past its expiry. */
|
|
349
497
|
has(name: string): Promise<boolean>;
|
|
498
|
+
/** How the secret stands, for a settings page to say and a job to decide whether to renew it. */
|
|
499
|
+
status(name: string): Promise<SecretStatus>;
|
|
500
|
+
/** Signs with the secret; refused for a secret declared `use: "verify"`, which signs nothing. */
|
|
350
501
|
sign(name: string, payload: string | Uint8Array, algorithm: "hmac-sha256"): Promise<string>;
|
|
351
502
|
verify(
|
|
352
503
|
name: string,
|
|
@@ -359,6 +510,23 @@ export interface PluginSecrets {
|
|
|
359
510
|
export interface OtherPlugin {
|
|
360
511
|
readonly version: string;
|
|
361
512
|
readonly api: Readonly<Record<string, (input: unknown) => Promise<unknown>>>;
|
|
513
|
+
/**
|
|
514
|
+
* The same plugin, called for a person who did something elsewhere (ADR-0072): with the roles
|
|
515
|
+
* this plugin and the person both hold in it, in the person's language, what it reads of the
|
|
516
|
+
* console the person's as a member's, whatever they administer, recorded as theirs through this
|
|
517
|
+
* plugin. Needs `people:act`; the person is active, or the call is `plugin.not_found`.
|
|
518
|
+
*/
|
|
519
|
+
for(personId: string): OtherPlugin;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** The plugin's own endpoints, as a service elsewhere is told to call them. */
|
|
523
|
+
export interface PluginEndpoints {
|
|
524
|
+
/**
|
|
525
|
+
* The full address of an endpoint the plugin defines, under the console's public origin, to
|
|
526
|
+
* register a callback or a webhook with a service elsewhere; `plugin.not_found` for a name the
|
|
527
|
+
* plugin does not define, `plugin.unavailable` where the console has no public address.
|
|
528
|
+
*/
|
|
529
|
+
url(name: string): Promise<string>;
|
|
362
530
|
}
|
|
363
531
|
|
|
364
532
|
export interface PluginLog {
|
|
@@ -382,6 +550,7 @@ export interface PluginContext {
|
|
|
382
550
|
fetch(url: string, init?: PluginFetchInit): Promise<PluginFetchResponse>;
|
|
383
551
|
readonly secrets: PluginSecrets;
|
|
384
552
|
readonly plugins: { get(key: string): Promise<OtherPlugin> };
|
|
553
|
+
readonly endpoints: PluginEndpoints;
|
|
385
554
|
readonly log: PluginLog;
|
|
386
555
|
now(): Date;
|
|
387
556
|
/** The caller's language, `en` for the console. */
|
|
@@ -393,6 +562,14 @@ export interface PluginContext {
|
|
|
393
562
|
/** An event a hook receives: the console's, of the plugin's board projects, or another plugin's. */
|
|
394
563
|
export interface PluginHookEvent {
|
|
395
564
|
readonly source: "console" | "plugin";
|
|
565
|
+
/**
|
|
566
|
+
* The event's id, one per event across both streams (`console:<number>` or `plugin:<number>`):
|
|
567
|
+
* delivery is at least once, so a hook that must not act twice keeps the ids it acted on and
|
|
568
|
+
* passes over one it has seen.
|
|
569
|
+
*/
|
|
570
|
+
readonly id: string;
|
|
571
|
+
/** Which delivery of the event this is: 1 the first time, more after a failure and its retries. */
|
|
572
|
+
readonly attempt: number;
|
|
396
573
|
/** `console:<kind>` or `plugin:<key>:<kind>`, as the hook was named. */
|
|
397
574
|
readonly kind: string;
|
|
398
575
|
readonly at: string;
|