@openbrf/plugin-sdk 1.2.0 → 1.4.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/dist/catalog.d.ts +670 -11
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +174 -81
- package/dist/catalog.js.map +1 -1
- package/dist/findings.d.ts +3 -1
- package/dist/findings.d.ts.map +1 -1
- package/dist/findings.js +2 -0
- package/dist/findings.js.map +1 -1
- package/dist/host.d.ts +108 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +15 -0
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +129 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +154 -3
- package/dist/manifest.js.map +1 -1
- package/dist/package-check.d.ts.map +1 -1
- package/dist/package-check.js +9 -1
- package/dist/package-check.js.map +1 -1
- package/package.json +1 -1
- package/src/catalog.ts +231 -116
- package/src/findings.ts +2 -0
- package/src/host.ts +112 -2
- package/src/index.ts +16 -0
- package/src/manifest.ts +199 -7
- package/src/package-check.ts +15 -0
package/src/catalog.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { IntegrityError, parseSha512 } from "./integrity.ts";
|
|
|
5
5
|
import { localizedTextSchema } from "./localized-text.ts";
|
|
6
6
|
import {
|
|
7
7
|
declaredActionsSchema,
|
|
8
|
+
missingProcessingProblem,
|
|
9
|
+
undeclaredPersonalDataProblem,
|
|
8
10
|
declaredExtensionPointsSchema,
|
|
9
11
|
declaredExtensionsSchema,
|
|
10
12
|
extensionGroupRefusals,
|
|
@@ -21,6 +23,7 @@ import {
|
|
|
21
23
|
pluginDeviceRouteSchema,
|
|
22
24
|
pluginIdSchema,
|
|
23
25
|
pluginManifestSchema,
|
|
26
|
+
pluginProcessingSchema,
|
|
24
27
|
proposeImportPersonalDataIssues,
|
|
25
28
|
} from "./manifest.ts";
|
|
26
29
|
import {
|
|
@@ -57,6 +60,12 @@ import {
|
|
|
57
60
|
* any check of the manifest saw it. The index carries its version for exactly
|
|
58
61
|
* this, so a new field is a new index version rather than a key an older
|
|
59
62
|
* instance ignores.
|
|
63
|
+
*
|
|
64
|
+
* An instance reads both versions there are. Version 2 adds the device routes
|
|
65
|
+
* of ADR 0031 and the processing declaration of ADR 0035 to a plugin entry, and
|
|
66
|
+
* the other fields of ADR 0030 join them there as they are built; version 1 is
|
|
67
|
+
* the index as it stood before, which a catalog keeps serving to instances that
|
|
68
|
+
* read nothing newer.
|
|
60
69
|
*/
|
|
61
70
|
|
|
62
71
|
/** Whether `declared` is a digest `parseSha512` can read. */
|
|
@@ -275,6 +284,20 @@ export const catalogPluginEntrySchema = baseEntrySchema
|
|
|
275
284
|
}
|
|
276
285
|
});
|
|
277
286
|
|
|
287
|
+
/**
|
|
288
|
+
* A plugin entry in version 2 of the index: version 1's, and the processing
|
|
289
|
+
* declaration.
|
|
290
|
+
*
|
|
291
|
+
* Repeated from the manifest on the terms the permissions are, because the
|
|
292
|
+
* consent screen shows it before anything is downloaded and the install seeds
|
|
293
|
+
* the record of processing activities from what that screen showed. Whether it
|
|
294
|
+
* is there is decided with the personal data, by the index as a whole below,
|
|
295
|
+
* since a refined object cannot be extended into the next version's entry.
|
|
296
|
+
*/
|
|
297
|
+
export const catalogPluginEntryV2Schema = catalogPluginEntrySchema.extend({
|
|
298
|
+
processing: pluginProcessingSchema.optional(),
|
|
299
|
+
});
|
|
300
|
+
|
|
278
301
|
/**
|
|
279
302
|
* A theme entry. Themes install through the same download-and-verify path and
|
|
280
303
|
* are listed in the same index; what happens after the bytes are verified is
|
|
@@ -296,27 +319,181 @@ export const catalogEntrySchema = z.discriminatedUnion("type", [
|
|
|
296
319
|
catalogThemeEntrySchema,
|
|
297
320
|
]);
|
|
298
321
|
|
|
299
|
-
export
|
|
300
|
-
|
|
301
|
-
|
|
322
|
+
export const catalogEntryV2Schema = z.discriminatedUnion("type", [
|
|
323
|
+
catalogPluginEntryV2Schema,
|
|
324
|
+
catalogThemeEntrySchema,
|
|
325
|
+
]);
|
|
302
326
|
|
|
303
327
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
* Version 2 adds the fields ADR 0030 lists: `extensionPoints` and `extends`
|
|
307
|
-
* (ADR 0030), `recipients` (ADR 0037) and `deviceRoutes` (ADR 0031). An
|
|
308
|
-
* instance reads both, and the catalog serves version 1, without the entries
|
|
309
|
-
* that need version 2, to instances that have not upgraded.
|
|
328
|
+
* A plugin entry, as whichever version of the index it came from. A version 1
|
|
329
|
+
* entry is one with no processing and no device routes declared.
|
|
310
330
|
*/
|
|
311
|
-
export
|
|
331
|
+
export type CatalogPluginEntry = z.infer<typeof catalogPluginEntryV2Schema>;
|
|
332
|
+
export type CatalogThemeEntry = z.infer<typeof catalogThemeEntrySchema>;
|
|
333
|
+
export type CatalogEntry = CatalogPluginEntry | CatalogThemeEntry;
|
|
334
|
+
|
|
335
|
+
/** The index's rules about its entries taken together, in either version. */
|
|
336
|
+
function checkEntries(
|
|
337
|
+
catalog: { version: 1 | 2; entries: readonly CatalogEntry[] },
|
|
338
|
+
ctx: z.RefinementCtx,
|
|
339
|
+
): void {
|
|
340
|
+
for (const [index, entry] of catalog.entries.entries()) {
|
|
341
|
+
if (entry.type !== "plugin") {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
// The manifest's rule, so an entry the loader would refuse is never
|
|
345
|
+
// offered for consent.
|
|
346
|
+
for (const issue of proposeImportPersonalDataIssues(entry)) {
|
|
347
|
+
ctx.addIssue({
|
|
348
|
+
code: "custom",
|
|
349
|
+
path: ["entries", index, "personalData"],
|
|
350
|
+
message: issue.message,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
312
354
|
|
|
313
|
-
|
|
355
|
+
for (const [index, entry] of catalog.entries.entries()) {
|
|
356
|
+
if (entry.type !== "plugin") {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
/*
|
|
360
|
+
* A field version 1 does not define is refused there as any unknown key
|
|
361
|
+
* is, so an older instance and this one read the same index the same
|
|
362
|
+
* way.
|
|
363
|
+
*/
|
|
364
|
+
if (catalog.version === 1) {
|
|
365
|
+
for (const field of [
|
|
366
|
+
"extensionPoints",
|
|
367
|
+
"extends",
|
|
368
|
+
"deviceRoutes",
|
|
369
|
+
] as const) {
|
|
370
|
+
if (entry[field] !== undefined) {
|
|
371
|
+
ctx.addIssue({
|
|
372
|
+
code: "custom",
|
|
373
|
+
path: ["entries", index, field],
|
|
374
|
+
message: `${field} needs version 2 of the index`,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
// The manifest's own rules, so an entry whose own declaration breaks
|
|
381
|
+
// one is never offered for consent.
|
|
382
|
+
for (const issue of extensionRuleIssues({
|
|
383
|
+
id: entry.id,
|
|
384
|
+
permissions: entry.permissions,
|
|
385
|
+
personalData: entry.personalData,
|
|
386
|
+
deviceRoutes: entry.deviceRoutes ?? [],
|
|
387
|
+
extensionPoints: entry.extensionPoints ?? [],
|
|
388
|
+
extends: entry.extends ?? [],
|
|
389
|
+
})) {
|
|
390
|
+
ctx.addIssue({
|
|
391
|
+
code: "custom",
|
|
392
|
+
path: ["entries", index, ...issue.path],
|
|
393
|
+
message: issue.message,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
// The manifest's own rules, so an entry that the loader would refuse is
|
|
397
|
+
// never offered for consent.
|
|
398
|
+
for (const issue of deviceRouteRuleIssues({
|
|
399
|
+
deviceRoutes: entry.deviceRoutes ?? [],
|
|
400
|
+
oauthProtectedResource: entry.oauthProtectedResource,
|
|
401
|
+
permissions: entry.permissions,
|
|
402
|
+
})) {
|
|
403
|
+
ctx.addIssue({
|
|
404
|
+
code: "custom",
|
|
405
|
+
path: ["entries", index, ...issue.path],
|
|
406
|
+
message: issue.message,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/*
|
|
412
|
+
* A field is in the version that defines it and in no earlier one: an
|
|
413
|
+
* instance that reads only version 1 would refuse the entry, and an index
|
|
414
|
+
* claiming version 1 while carrying it is not one such an instance could
|
|
415
|
+
* read.
|
|
416
|
+
*/
|
|
417
|
+
if (catalog.version === 1) {
|
|
418
|
+
for (const [index, entry] of catalog.entries.entries()) {
|
|
419
|
+
if (entry.type === "plugin" && entry.recipients !== undefined) {
|
|
420
|
+
ctx.addIssue({
|
|
421
|
+
code: "custom",
|
|
422
|
+
path: ["entries", index, "recipients"],
|
|
423
|
+
message:
|
|
424
|
+
"recipients is not in version 1 of the index; an entry that declares them is listed in version 2",
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/*
|
|
431
|
+
* The manifest's rule on what a recipient may receive, so an entry the
|
|
432
|
+
* loader would refuse is not offered for consent with a category the
|
|
433
|
+
* plugin does not declare it handles. The comparison with the installed
|
|
434
|
+
* manifest comes later; this holds the entry to itself.
|
|
435
|
+
*/
|
|
436
|
+
for (const [index, entry] of catalog.entries.entries()) {
|
|
437
|
+
if (entry.type !== "plugin" || entry.recipients === undefined) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
for (const issue of recipientCategoryIssues(
|
|
441
|
+
entry.recipients,
|
|
442
|
+
entry.personalData,
|
|
443
|
+
)) {
|
|
444
|
+
ctx.addIssue({
|
|
445
|
+
code: "custom",
|
|
446
|
+
path: ["entries", index, ...issue.path],
|
|
447
|
+
message: issue.message,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/*
|
|
453
|
+
* An id names one entry in the whole index, not one per type. An install
|
|
454
|
+
* finds its entry by id and then refuses one of the wrong type, so a theme
|
|
455
|
+
* sharing a plugin's id would leave that plugin impossible to install, and
|
|
456
|
+
* which of the two a board got would depend on the order of the file.
|
|
457
|
+
*/
|
|
458
|
+
const firstIndexById = new Map<string, number>();
|
|
459
|
+
for (const [index, entry] of catalog.entries.entries()) {
|
|
460
|
+
const first = firstIndexById.get(entry.id);
|
|
461
|
+
if (first === undefined) {
|
|
462
|
+
firstIndexById.set(entry.id, index);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
ctx.addIssue({
|
|
466
|
+
code: "custom",
|
|
467
|
+
path: ["entries", index, "id"],
|
|
468
|
+
message: `"${entry.id}" is already the id of entry ${String(first)}; an id appears once in the index`,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const catalogV1Schema = z
|
|
314
474
|
.strictObject({
|
|
315
475
|
/** Index format version, so a future shape can be recognised and refused. */
|
|
316
|
-
version: z.literal(
|
|
476
|
+
version: z.literal(1),
|
|
317
477
|
entries: z.array(catalogEntrySchema).max(500),
|
|
318
478
|
})
|
|
319
479
|
.superRefine((catalog, ctx) => {
|
|
480
|
+
checkEntries(catalog, ctx);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const catalogV2Schema = z
|
|
484
|
+
.strictObject({
|
|
485
|
+
/**
|
|
486
|
+
* Version 2 adds the fields `CATALOG_INDEX_VERSIONS` lists to a plugin
|
|
487
|
+
* entry, `processing` (ADR 0035) among them, and changes nothing else.
|
|
488
|
+
* This package reads both versions; the catalog keeps serving version 1,
|
|
489
|
+
* without the entries that need version 2, to instances that have not
|
|
490
|
+
* upgraded.
|
|
491
|
+
*/
|
|
492
|
+
version: z.literal(2),
|
|
493
|
+
entries: z.array(catalogEntryV2Schema).max(500),
|
|
494
|
+
})
|
|
495
|
+
.superRefine((catalog, ctx) => {
|
|
496
|
+
checkEntries(catalog, ctx);
|
|
320
497
|
const plugins = new Map<string, CatalogPluginEntry>();
|
|
321
498
|
const pluginIndex = new Map<string, number>();
|
|
322
499
|
for (const [index, entry] of catalog.entries.entries()) {
|
|
@@ -327,64 +504,40 @@ export const catalogSchema = z
|
|
|
327
504
|
}
|
|
328
505
|
/** The addons already refused beside a plugin they extend. */
|
|
329
506
|
const pairRefused = new Set<string>();
|
|
507
|
+
/*
|
|
508
|
+
* The manifest's own rule, both ways: an entry that declares personal data
|
|
509
|
+
* declares its processing, and one that declares none declares no
|
|
510
|
+
* processing. One that reads the address book declares personal data, so
|
|
511
|
+
* it has a processing a person can object to. Held here rather than only
|
|
512
|
+
* by the catalog's check of the package, so an index written by hand is
|
|
513
|
+
* held to it as well.
|
|
514
|
+
*/
|
|
330
515
|
for (const [index, entry] of catalog.entries.entries()) {
|
|
331
516
|
if (entry.type !== "plugin") {
|
|
332
517
|
continue;
|
|
333
518
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
for (const issue of proposeImportPersonalDataIssues(entry)) {
|
|
519
|
+
const missing = missingProcessingProblem(entry);
|
|
520
|
+
if (missing !== null) {
|
|
337
521
|
ctx.addIssue({
|
|
338
522
|
code: "custom",
|
|
339
|
-
path: ["entries", index, "
|
|
340
|
-
message:
|
|
523
|
+
path: ["entries", index, "processing"],
|
|
524
|
+
message: missing,
|
|
341
525
|
});
|
|
342
526
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
* is, so an older instance and this one read the same index the same
|
|
346
|
-
* way.
|
|
347
|
-
*/
|
|
348
|
-
if (catalog.version === 1) {
|
|
349
|
-
for (const field of [
|
|
350
|
-
"extensionPoints",
|
|
351
|
-
"extends",
|
|
352
|
-
"deviceRoutes",
|
|
353
|
-
] as const) {
|
|
354
|
-
if (entry[field] !== undefined) {
|
|
355
|
-
ctx.addIssue({
|
|
356
|
-
code: "custom",
|
|
357
|
-
path: ["entries", index, field],
|
|
358
|
-
message: `${field} needs version 2 of the index`,
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
// The manifest's own rules, so an entry whose own declaration breaks
|
|
365
|
-
// one is never offered for consent.
|
|
366
|
-
for (const issue of extensionRuleIssues({
|
|
367
|
-
id: entry.id,
|
|
368
|
-
permissions: entry.permissions,
|
|
369
|
-
personalData: entry.personalData,
|
|
370
|
-
extensionPoints: entry.extensionPoints ?? [],
|
|
371
|
-
extends: entry.extends ?? [],
|
|
372
|
-
})) {
|
|
527
|
+
const undeclared = undeclaredPersonalDataProblem(entry);
|
|
528
|
+
if (undeclared !== null) {
|
|
373
529
|
ctx.addIssue({
|
|
374
530
|
code: "custom",
|
|
375
|
-
path: ["entries", index,
|
|
376
|
-
message:
|
|
531
|
+
path: ["entries", index, "personalData"],
|
|
532
|
+
message: undeclared,
|
|
377
533
|
});
|
|
378
534
|
}
|
|
379
|
-
|
|
380
|
-
deviceRoutes: entry.deviceRoutes ?? [],
|
|
381
|
-
oauthProtectedResource: entry.oauthProtectedResource,
|
|
382
|
-
permissions: entry.permissions,
|
|
383
|
-
})) {
|
|
535
|
+
if (entry.processing !== undefined && entry.personalData.length === 0) {
|
|
384
536
|
ctx.addIssue({
|
|
385
537
|
code: "custom",
|
|
386
|
-
path: ["entries", index,
|
|
387
|
-
message:
|
|
538
|
+
path: ["entries", index, "processing"],
|
|
539
|
+
message:
|
|
540
|
+
"a plugin that declares no personal data declares no processing",
|
|
388
541
|
});
|
|
389
542
|
}
|
|
390
543
|
/*
|
|
@@ -490,70 +643,32 @@ export const catalogSchema = z
|
|
|
490
643
|
});
|
|
491
644
|
}
|
|
492
645
|
}
|
|
646
|
+
});
|
|
493
647
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
code: "custom",
|
|
505
|
-
path: ["entries", index, "recipients"],
|
|
506
|
-
message:
|
|
507
|
-
"recipients is not in version 1 of the index; an entry that declares them is listed in version 2",
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
648
|
+
/**
|
|
649
|
+
* The index versions this package reads.
|
|
650
|
+
*
|
|
651
|
+
* Version 2 adds the fields ADR 0030 lists: `extensionPoints` and `extends`
|
|
652
|
+
* (ADR 0030), `recipients` (ADR 0037) and `deviceRoutes` (ADR 0031), and
|
|
653
|
+
* `processing` (ADR 0035). An instance reads both, and the catalog serves
|
|
654
|
+
* version 1, without the entries that need version 2, to instances that have
|
|
655
|
+
* not upgraded.
|
|
656
|
+
*/
|
|
657
|
+
export const CATALOG_INDEX_VERSIONS = [1, 2] as const;
|
|
512
658
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
* loader would refuse is not offered for consent with a category the
|
|
516
|
-
* plugin does not declare it handles. The comparison with the installed
|
|
517
|
-
* manifest comes later; this holds the entry to itself.
|
|
518
|
-
*/
|
|
519
|
-
for (const [index, entry] of catalog.entries.entries()) {
|
|
520
|
-
if (entry.type !== "plugin" || entry.recipients === undefined) {
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
for (const issue of recipientCategoryIssues(
|
|
524
|
-
entry.recipients,
|
|
525
|
-
entry.personalData,
|
|
526
|
-
)) {
|
|
527
|
-
ctx.addIssue({
|
|
528
|
-
code: "custom",
|
|
529
|
-
path: ["entries", index, ...issue.path],
|
|
530
|
-
message: issue.message,
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
}
|
|
659
|
+
/** The version of the index this SDK writes and reads first. */
|
|
660
|
+
export const CATALOG_INDEX_VERSION = 2;
|
|
534
661
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
* which of the two a board got would depend on the order of the file.
|
|
540
|
-
*/
|
|
541
|
-
const firstIndexById = new Map<string, number>();
|
|
542
|
-
for (const [index, entry] of catalog.entries.entries()) {
|
|
543
|
-
const first = firstIndexById.get(entry.id);
|
|
544
|
-
if (first === undefined) {
|
|
545
|
-
firstIndexById.set(entry.id, index);
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
ctx.addIssue({
|
|
549
|
-
code: "custom",
|
|
550
|
-
path: ["entries", index, "id"],
|
|
551
|
-
message: `"${entry.id}" is already the id of entry ${String(first)}; an id appears once in the index`,
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
});
|
|
662
|
+
export const catalogSchema = z.discriminatedUnion("version", [
|
|
663
|
+
catalogV1Schema,
|
|
664
|
+
catalogV2Schema,
|
|
665
|
+
]);
|
|
555
666
|
|
|
556
|
-
|
|
667
|
+
/** An index of either version, read as one shape. */
|
|
668
|
+
export interface Catalog {
|
|
669
|
+
version: z.infer<typeof catalogSchema>["version"];
|
|
670
|
+
entries: CatalogEntry[];
|
|
671
|
+
}
|
|
557
672
|
|
|
558
673
|
/**
|
|
559
674
|
* The refusals of an index that its reader is told in their own language.
|
package/src/findings.ts
CHANGED
|
@@ -158,6 +158,8 @@ export const PLUGIN_MANIFEST_RULES = [
|
|
|
158
158
|
"device-route-proposes-import",
|
|
159
159
|
/** A plugin with a website block also proposes imports. */
|
|
160
160
|
"blocks-propose-import",
|
|
161
|
+
/** A plugin with a device route offers an extension point accepting contact details. */
|
|
162
|
+
"device-route-extension-point-contact",
|
|
161
163
|
] as const;
|
|
162
164
|
|
|
163
165
|
export type PluginManifestRule = (typeof PLUGIN_MANIFEST_RULES)[number];
|
package/src/host.ts
CHANGED
|
@@ -174,6 +174,12 @@ export interface PluginApartment {
|
|
|
174
174
|
export interface PluginResident {
|
|
175
175
|
personId: string;
|
|
176
176
|
name: string;
|
|
177
|
+
/**
|
|
178
|
+
* The family name alone, for a reader that shows no first names: a household
|
|
179
|
+
* as the name on the door shows it, without the first names that say who
|
|
180
|
+
* lives alone and name the children.
|
|
181
|
+
*/
|
|
182
|
+
lastName: string;
|
|
177
183
|
/** MEMBER holds the tenant-ownership; RESIDENT occupies without holding it. */
|
|
178
184
|
role: "MEMBER" | "RESIDENT";
|
|
179
185
|
apartment: PluginApartment | null;
|
|
@@ -183,6 +189,22 @@ export interface PluginResident {
|
|
|
183
189
|
email?: string | null;
|
|
184
190
|
/** Present only with the addressBook:readContact permission. */
|
|
185
191
|
phone?: string | null;
|
|
192
|
+
/**
|
|
193
|
+
* Whether a publication consent to the person's name on screens inside the
|
|
194
|
+
* building stands (the `NAME_IN_BUILDING` scope), recorded by the board on
|
|
195
|
+
* the person's panel like every other scope. A different consent from a name
|
|
196
|
+
* on the website, because the two are different things to agree to.
|
|
197
|
+
*/
|
|
198
|
+
nameInBuilding: boolean;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Narrows a read of the residents. */
|
|
202
|
+
export interface PluginResidentFilter {
|
|
203
|
+
/**
|
|
204
|
+
* Only the residents of this entrance, so a plugin that shows one entrance
|
|
205
|
+
* never loads the others. An id no address has returns nobody.
|
|
206
|
+
*/
|
|
207
|
+
addressId?: string;
|
|
186
208
|
}
|
|
187
209
|
|
|
188
210
|
export interface PluginOccupancySummary {
|
|
@@ -202,14 +224,76 @@ export interface PluginOccupancySummary {
|
|
|
202
224
|
* personal identity number is never returned, and nothing is writable.
|
|
203
225
|
* Requires addressBook:read; the contact fields additionally require
|
|
204
226
|
* addressBook:readContact.
|
|
227
|
+
*
|
|
228
|
+
* A person who has objected to this plugin's processing, and whose objection
|
|
229
|
+
* the board granted, is absent as well (ADR 0035) - absent, not flagged, so a
|
|
230
|
+
* plugin cannot list who objected. Closing the objection brings the person
|
|
231
|
+
* back on the next read.
|
|
205
232
|
*/
|
|
206
233
|
export interface PluginAddressBook {
|
|
207
234
|
apartments(): Promise<PluginApartment[]>;
|
|
208
|
-
/**
|
|
209
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Current residencies only. Moved-out rows are not plugin business.
|
|
237
|
+
*
|
|
238
|
+
* The filter was added after this contract version shipped; an older host
|
|
239
|
+
* ignores it and returns every entrance, so a plugin that must not load the
|
|
240
|
+
* others filters the result by `apartment.address.id` as well.
|
|
241
|
+
*/
|
|
242
|
+
residents(filter?: PluginResidentFilter): Promise<PluginResident[]>;
|
|
210
243
|
summary(): Promise<PluginOccupancySummary>;
|
|
211
244
|
}
|
|
212
245
|
|
|
246
|
+
/** The bounds `host.audit.record` holds an entry to. */
|
|
247
|
+
export const PLUGIN_AUDIT_LIMITS = {
|
|
248
|
+
/** Characters in `event`, which is also part of a text key. */
|
|
249
|
+
event: 64,
|
|
250
|
+
/** Characters in `targetKind`. */
|
|
251
|
+
targetKind: 64,
|
|
252
|
+
/** Characters in `targetId`. */
|
|
253
|
+
targetId: 200,
|
|
254
|
+
/** Values in `detail`. */
|
|
255
|
+
detailValues: 8,
|
|
256
|
+
/** Characters in a key of `detail`, letters and digits. */
|
|
257
|
+
detailKey: 64,
|
|
258
|
+
/** Characters in a string value of `detail`. */
|
|
259
|
+
detailValue: 200,
|
|
260
|
+
} as const;
|
|
261
|
+
|
|
262
|
+
/** One entry a plugin writes to the instance's audit log. */
|
|
263
|
+
export interface PluginAuditEntry {
|
|
264
|
+
/**
|
|
265
|
+
* What happened, as lowercase letters, digits, dots and hyphens, starting
|
|
266
|
+
* with a letter: `screen.residents-enabled`. The audit log shows it through
|
|
267
|
+
* the plugin's own text key `audit.<event>`.
|
|
268
|
+
*/
|
|
269
|
+
event: string;
|
|
270
|
+
/** What the act was done to, in the plugin's own words: `screen`. */
|
|
271
|
+
targetKind?: string;
|
|
272
|
+
/** Which one: the screen's id. */
|
|
273
|
+
targetId?: string;
|
|
274
|
+
/**
|
|
275
|
+
* At most eight values about the act, never a copy of what the plugin read.
|
|
276
|
+
* A key is letters and digits, starting with a letter: `screenCount`. A
|
|
277
|
+
* person is named as the actor, never here, and a key or value holding a
|
|
278
|
+
* personal identity number is refused.
|
|
279
|
+
*/
|
|
280
|
+
detail?: Record<string, string | number | boolean>;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Writing to the instance's audit log (ADR 0035).
|
|
285
|
+
*
|
|
286
|
+
* The entry is written as `PLUGIN_RECORDED`, with the plugin's id and the
|
|
287
|
+
* event, on the plugin channel. With a request, the actor is the person the
|
|
288
|
+
* host marked on it, as for `actions`; with `null`, the entry has no actor - a
|
|
289
|
+
* plugin's own job, say. An entry that breaks a bound is refused with a
|
|
290
|
+
* RangeError and nothing is written. Needs no permission: an entry is about
|
|
291
|
+
* what the plugin did, which the association is entitled to a record of.
|
|
292
|
+
*/
|
|
293
|
+
export interface PluginAudit {
|
|
294
|
+
record(request: ActionRequest | null, entry: PluginAuditEntry): Promise<void>;
|
|
295
|
+
}
|
|
296
|
+
|
|
213
297
|
/** What a theme install is told about the package besides its bytes. */
|
|
214
298
|
export interface PluginThemeInstallOptions {
|
|
215
299
|
/**
|
|
@@ -306,6 +390,13 @@ export interface PluginNewsItem {
|
|
|
306
390
|
readonly audience: PluginContentAudience;
|
|
307
391
|
/** ISO 8601 instant. */
|
|
308
392
|
readonly publishedAt: string;
|
|
393
|
+
/**
|
|
394
|
+
* Whether the board marked the item "show on screens": that it may be shown
|
|
395
|
+
* on a screen in the building (ADR 0050). Off until the board sets it. A
|
|
396
|
+
* plugin that puts items where anybody passing can read them shows only the
|
|
397
|
+
* marked ones.
|
|
398
|
+
*/
|
|
399
|
+
readonly onScreens: boolean;
|
|
309
400
|
}
|
|
310
401
|
|
|
311
402
|
/**
|
|
@@ -358,6 +449,11 @@ export interface PluginOccurrence {
|
|
|
358
449
|
*/
|
|
359
450
|
readonly placesLeft: number | null;
|
|
360
451
|
readonly audience: PluginContentAudience;
|
|
452
|
+
/**
|
|
453
|
+
* Whether the board marked the event, with all its occurrences, "show on
|
|
454
|
+
* screens" (ADR 0050), as a news item's mark.
|
|
455
|
+
*/
|
|
456
|
+
readonly onScreens: boolean;
|
|
361
457
|
}
|
|
362
458
|
|
|
363
459
|
/**
|
|
@@ -508,6 +604,12 @@ export interface PluginMediaFile {
|
|
|
508
604
|
* picture's entity tag from, so a plugin may use it as one too.
|
|
509
605
|
*/
|
|
510
606
|
readonly checksum: string;
|
|
607
|
+
/**
|
|
608
|
+
* Who the picture is for: `anyone`, or the `members`, as the board held it.
|
|
609
|
+
* The host does not filter for you, because it cannot know where you put it;
|
|
610
|
+
* see {@link PluginContentAudience}.
|
|
611
|
+
*/
|
|
612
|
+
readonly audience: PluginContentAudience;
|
|
511
613
|
/**
|
|
512
614
|
* The picture's bytes. Read it to the end or cancel it: the host holds the
|
|
513
615
|
* stored file open until one or the other.
|
|
@@ -799,6 +901,14 @@ export interface PluginHost {
|
|
|
799
901
|
* before using it.
|
|
800
902
|
*/
|
|
801
903
|
blocks: PluginBlocks;
|
|
904
|
+
/**
|
|
905
|
+
* The instance's audit log, for what this plugin did.
|
|
906
|
+
*
|
|
907
|
+
* Added after this contract version shipped, like `actions`: a plugin built
|
|
908
|
+
* against a newer host than the one it is installed on must feature-detect
|
|
909
|
+
* before using it.
|
|
910
|
+
*/
|
|
911
|
+
audit: PluginAudit;
|
|
802
912
|
/**
|
|
803
913
|
* The extension points this plugin offers or extends (ADR 0030): a host
|
|
804
914
|
* plugin defines its points and calls the addons serving there, and an
|
package/src/index.ts
CHANGED
|
@@ -83,11 +83,14 @@ export {
|
|
|
83
83
|
type CatalogDiagnostic,
|
|
84
84
|
type CatalogEntry,
|
|
85
85
|
catalogEntrySchema,
|
|
86
|
+
catalogEntryV2Schema,
|
|
87
|
+
CATALOG_INDEX_VERSION,
|
|
86
88
|
type CatalogExtensionPoint,
|
|
87
89
|
catalogExtensionPointSchema,
|
|
88
90
|
type CatalogParseResult,
|
|
89
91
|
type CatalogPluginEntry,
|
|
90
92
|
catalogPluginEntrySchema,
|
|
93
|
+
catalogPluginEntryV2Schema,
|
|
91
94
|
type CatalogRefusal,
|
|
92
95
|
type CatalogRefusalCode,
|
|
93
96
|
catalogSchema,
|
|
@@ -170,6 +173,9 @@ export {
|
|
|
170
173
|
type PluginAddressBook,
|
|
171
174
|
type PluginAddresses,
|
|
172
175
|
type PluginApartment,
|
|
176
|
+
type PluginAudit,
|
|
177
|
+
type PluginAuditEntry,
|
|
178
|
+
PLUGIN_AUDIT_LIMITS,
|
|
173
179
|
type PluginBookableResource,
|
|
174
180
|
type PluginBookings,
|
|
175
181
|
type PluginBusyInterval,
|
|
@@ -198,6 +204,7 @@ export {
|
|
|
198
204
|
type PluginRecipientResponse,
|
|
199
205
|
type PluginRecipients,
|
|
200
206
|
type PluginResident,
|
|
207
|
+
type PluginResidentFilter,
|
|
201
208
|
type PluginSettings,
|
|
202
209
|
type PluginInstalledTheme,
|
|
203
210
|
type PluginSms,
|
|
@@ -227,6 +234,11 @@ export {
|
|
|
227
234
|
licenceKeyIdSchema,
|
|
228
235
|
licencePublicKeySchema,
|
|
229
236
|
type ManifestParseResult,
|
|
237
|
+
missingProcessingProblem,
|
|
238
|
+
readsAddressBook,
|
|
239
|
+
undeclaredPersonalDataProblem,
|
|
240
|
+
PLUGIN_DATA_SUBJECTS,
|
|
241
|
+
PLUGIN_LEGAL_BASES,
|
|
230
242
|
type PluginActionDeclaration,
|
|
231
243
|
pluginActionSchema,
|
|
232
244
|
pluginActionsSchema,
|
|
@@ -240,12 +252,16 @@ export {
|
|
|
240
252
|
pluginExtensionPointSchema,
|
|
241
253
|
pluginExtensionSchema,
|
|
242
254
|
pluginIdSchema,
|
|
255
|
+
type PluginDataSubject,
|
|
256
|
+
type PluginLegalBasis,
|
|
243
257
|
type PluginLicenceDeclaration,
|
|
244
258
|
pluginLicenceSchema,
|
|
245
259
|
type PluginManifest,
|
|
246
260
|
pluginManifestSchema,
|
|
247
261
|
type PluginPackage,
|
|
248
262
|
pluginPackageSchema,
|
|
263
|
+
type PluginProcessing,
|
|
264
|
+
pluginProcessingSchema,
|
|
249
265
|
type PluginView,
|
|
250
266
|
pluginViewSchema,
|
|
251
267
|
type PluginViewSection,
|