@camstack/addon-google-services 0.2.39 → 0.2.40
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/CHANGELOG.md +6 -0
- package/dist/google-services.addon.js +566 -172
- package/dist/google-services.addon.mjs +567 -173
- package/i18n/en.json +0 -4
- package/i18n/it.json +0 -4
- package/package.json +2 -2
|
@@ -13521,6 +13521,39 @@ function buildCapturingReply(envelope) {
|
|
|
13521
13521
|
return wrapper;
|
|
13522
13522
|
}
|
|
13523
13523
|
/**
|
|
13524
|
+
* The link primitive's declaration half (D1013 rule 5): what a link field names and how a kind
|
|
13525
|
+
* records it. Split from `instance-link.ts` so `RecordIntegrationInstances` can read the links of
|
|
13526
|
+
* its kind without importing the builders that import it.
|
|
13527
|
+
*/
|
|
13528
|
+
/** The body field of a link: the id of the instance it names; the target rides in the field meta. */
|
|
13529
|
+
function linkField(target) {
|
|
13530
|
+
return string().meta({ link: target });
|
|
13531
|
+
}
|
|
13532
|
+
function linkTargetOf(meta) {
|
|
13533
|
+
if (typeof meta !== "object" || meta === null) return void 0;
|
|
13534
|
+
const link = Reflect.get(meta, "link");
|
|
13535
|
+
const parsed = LinkTargetSchema.safeParse(link);
|
|
13536
|
+
return parsed.success ? parsed.data : void 0;
|
|
13537
|
+
}
|
|
13538
|
+
var LinkTargetSchema = object({
|
|
13539
|
+
cap: string().min(1),
|
|
13540
|
+
kind: string().min(1).optional()
|
|
13541
|
+
});
|
|
13542
|
+
/** The links a kind declares, read from the meta of its body fields. */
|
|
13543
|
+
function linksOfKind(kind) {
|
|
13544
|
+
const schema = kind.schema;
|
|
13545
|
+
if (!(schema instanceof ZodObject)) return [];
|
|
13546
|
+
const links = [];
|
|
13547
|
+
for (const [field, fieldSchema] of Object.entries(schema.shape)) {
|
|
13548
|
+
const to = linkTargetOf(globalRegistry.get(fieldSchema));
|
|
13549
|
+
if (to !== void 0) links.push({
|
|
13550
|
+
field,
|
|
13551
|
+
to
|
|
13552
|
+
});
|
|
13553
|
+
}
|
|
13554
|
+
return links;
|
|
13555
|
+
}
|
|
13556
|
+
/**
|
|
13524
13557
|
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13525
13558
|
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13526
13559
|
*
|
|
@@ -13641,9 +13674,18 @@ var RecordIntegrationInstances = class {
|
|
|
13641
13674
|
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13642
13675
|
};
|
|
13643
13676
|
}
|
|
13644
|
-
describe = async () =>
|
|
13645
|
-
|
|
13646
|
-
|
|
13677
|
+
describe = async () => {
|
|
13678
|
+
const declared = linksOfKind(this.kind);
|
|
13679
|
+
const links = this.hooks.description.links !== void 0 || declared.length === 0 ? {} : { links: declared.map((link) => ({
|
|
13680
|
+
kind: this.kind.name,
|
|
13681
|
+
field: link.field,
|
|
13682
|
+
to: link.to
|
|
13683
|
+
})) };
|
|
13684
|
+
return {
|
|
13685
|
+
...this.hooks.description,
|
|
13686
|
+
...links,
|
|
13687
|
+
...this.hooks.mintId === void 0 ? {} : { mintsId: true }
|
|
13688
|
+
};
|
|
13647
13689
|
};
|
|
13648
13690
|
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13649
13691
|
listInstances = async () => {
|
|
@@ -13726,6 +13768,80 @@ var RecordIntegrationInstances = class {
|
|
|
13726
13768
|
};
|
|
13727
13769
|
}
|
|
13728
13770
|
};
|
|
13771
|
+
/** No part holds the id a call named. */
|
|
13772
|
+
var UnknownInstanceError = class extends Error {
|
|
13773
|
+
instanceId;
|
|
13774
|
+
constructor(instanceId) {
|
|
13775
|
+
super(`Unknown instance '${instanceId}'`);
|
|
13776
|
+
this.instanceId = instanceId;
|
|
13777
|
+
this.name = "UnknownInstanceError";
|
|
13778
|
+
}
|
|
13779
|
+
};
|
|
13780
|
+
/** A create or a form named no kind, or a kind no part serves. */
|
|
13781
|
+
var UnknownInstanceKindError = class extends Error {
|
|
13782
|
+
kindId;
|
|
13783
|
+
constructor(kindId) {
|
|
13784
|
+
super(kindId === void 0 ? "This provider has several kinds: name one" : `Unknown instance kind '${kindId}'`);
|
|
13785
|
+
this.kindId = kindId;
|
|
13786
|
+
this.name = "UnknownInstanceKindError";
|
|
13787
|
+
}
|
|
13788
|
+
};
|
|
13789
|
+
function composeInstanceKinds(parts, base) {
|
|
13790
|
+
const byKind = (kindId) => {
|
|
13791
|
+
if (kindId === void 0) {
|
|
13792
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
13793
|
+
if (only === void 0) throw new UnknownInstanceKindError(void 0);
|
|
13794
|
+
return only;
|
|
13795
|
+
}
|
|
13796
|
+
const part = parts.find((candidate) => candidate.kind.id === kindId);
|
|
13797
|
+
if (part === void 0) throw new UnknownInstanceKindError(kindId);
|
|
13798
|
+
return part;
|
|
13799
|
+
};
|
|
13800
|
+
const holderOf = async (id) => {
|
|
13801
|
+
for (const part of parts) if (await part.provider.getInstance({ id }) !== null) return part;
|
|
13802
|
+
throw new UnknownInstanceError(id);
|
|
13803
|
+
};
|
|
13804
|
+
const tagged = (part, instance) => ({
|
|
13805
|
+
...instance,
|
|
13806
|
+
kind: part.kind.id
|
|
13807
|
+
});
|
|
13808
|
+
return {
|
|
13809
|
+
describe: async () => {
|
|
13810
|
+
const links = (await Promise.all(parts.map(async (part) => (await part.provider.describe()).links ?? []))).flat();
|
|
13811
|
+
return {
|
|
13812
|
+
...base,
|
|
13813
|
+
kinds: parts.map((part) => part.kind),
|
|
13814
|
+
...links.length > 0 ? { links } : {}
|
|
13815
|
+
};
|
|
13816
|
+
},
|
|
13817
|
+
instanceFormSchema: async ({ id, kind }) => {
|
|
13818
|
+
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13819
|
+
},
|
|
13820
|
+
listInstances: async () => {
|
|
13821
|
+
return (await Promise.all(parts.map(async (part) => (await part.provider.listInstances()).map((instance) => tagged(part, instance))))).flat();
|
|
13822
|
+
},
|
|
13823
|
+
getInstance: async ({ id }) => {
|
|
13824
|
+
for (const part of parts) {
|
|
13825
|
+
const found = await part.provider.getInstance({ id });
|
|
13826
|
+
if (found !== null) return tagged(part, found);
|
|
13827
|
+
}
|
|
13828
|
+
return null;
|
|
13829
|
+
},
|
|
13830
|
+
createInstance: async (input) => byKind(input.kind).provider.createInstance(input),
|
|
13831
|
+
updateInstance: async (input) => (await holderOf(input.id)).provider.updateInstance(input),
|
|
13832
|
+
setInstanceEnabled: async (input) => (await holderOf(input.id)).provider.setInstanceEnabled(input),
|
|
13833
|
+
deleteInstance: async (input) => (await holderOf(input.id)).provider.deleteInstance(input),
|
|
13834
|
+
testInstance: async (input) => {
|
|
13835
|
+
const test = (input.id === null ? byKind(input.kind) : await holderOf(input.id)).provider.testInstance;
|
|
13836
|
+
if (test === void 0) return {
|
|
13837
|
+
success: false,
|
|
13838
|
+
status: "inconclusive",
|
|
13839
|
+
error: "This kind cannot be tested"
|
|
13840
|
+
};
|
|
13841
|
+
return test(input);
|
|
13842
|
+
}
|
|
13843
|
+
};
|
|
13844
|
+
}
|
|
13729
13845
|
var EncodeProfileSchema = object({
|
|
13730
13846
|
video: object({
|
|
13731
13847
|
codec: _enum([
|
|
@@ -54211,18 +54327,38 @@ var IntegrationInstanceKindSchema = object({
|
|
|
54211
54327
|
/** Relative hub path to disconnect that account. */
|
|
54212
54328
|
disconnectPath: string().min(1).optional()
|
|
54213
54329
|
});
|
|
54330
|
+
/**
|
|
54331
|
+
* How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
|
|
54332
|
+
* scans the network before any instance exists (the wizard offers the candidates); `account`: the
|
|
54333
|
+
* devices appear after the instance signs in. `scopedToInstance`: a discovery run belongs to ONE
|
|
54334
|
+
* instance and its candidates are claimed by it.
|
|
54335
|
+
*/
|
|
54336
|
+
var IntegrationInstanceDiscoverySchema = object({
|
|
54337
|
+
mode: _enum([
|
|
54338
|
+
"none",
|
|
54339
|
+
"lan",
|
|
54340
|
+
"account"
|
|
54341
|
+
]),
|
|
54342
|
+
scopedToInstance: boolean()
|
|
54343
|
+
});
|
|
54344
|
+
/**
|
|
54345
|
+
* One link a kind of this provider declares (D1013 rule 5): the body `field` of an instance of
|
|
54346
|
+
* `kind` holds the id of an instance of the function cap `to.cap` (of kind `to.kind`, when set).
|
|
54347
|
+
* The hub indexes these so the target's delete is refused while a link names it.
|
|
54348
|
+
*/
|
|
54349
|
+
var IntegrationInstanceLinkSchema = object({
|
|
54350
|
+
kind: string().min(1),
|
|
54351
|
+
field: string().min(1),
|
|
54352
|
+
to: object({
|
|
54353
|
+
cap: string().min(1),
|
|
54354
|
+
kind: string().min(1).optional()
|
|
54355
|
+
})
|
|
54356
|
+
});
|
|
54214
54357
|
var IntegrationInstanceDescriptionSchema = object({
|
|
54215
54358
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
54216
54359
|
role: IntegrationInstanceRoleSchema,
|
|
54217
54360
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
54218
|
-
discovery:
|
|
54219
|
-
mode: _enum([
|
|
54220
|
-
"none",
|
|
54221
|
-
"lan",
|
|
54222
|
-
"account"
|
|
54223
|
-
]),
|
|
54224
|
-
scopedToInstance: boolean()
|
|
54225
|
-
}).optional(),
|
|
54361
|
+
discovery: IntegrationInstanceDiscoverySchema.optional(),
|
|
54226
54362
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
54227
54363
|
instanceMode: IntegrationInstanceModeSchema,
|
|
54228
54364
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
@@ -54243,7 +54379,9 @@ var IntegrationInstanceDescriptionSchema = object({
|
|
|
54243
54379
|
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
54244
54380
|
* send an id (the UI then mints an opaque one).
|
|
54245
54381
|
*/
|
|
54246
|
-
mintsId: boolean().optional()
|
|
54382
|
+
mintsId: boolean().optional(),
|
|
54383
|
+
/** The links to other instances the provider's kinds declare; absent means none. */
|
|
54384
|
+
links: array(IntegrationInstanceLinkSchema).optional()
|
|
54247
54385
|
});
|
|
54248
54386
|
var IntegrationInstanceStatusSchema = object({
|
|
54249
54387
|
state: _enum([
|
|
@@ -91234,7 +91372,165 @@ object({
|
|
|
91234
91372
|
delay: number().nonnegative().optional(),
|
|
91235
91373
|
commands: record(string(), unknown())
|
|
91236
91374
|
});
|
|
91375
|
+
/** The `instance-select` field of a link, keyed like the body field it fills. */
|
|
91376
|
+
function linkFieldDescriptor(key, target, labels) {
|
|
91377
|
+
return {
|
|
91378
|
+
type: "instance-select",
|
|
91379
|
+
key,
|
|
91380
|
+
label: labels.label,
|
|
91381
|
+
...labels.description !== void 0 ? { description: labels.description } : {},
|
|
91382
|
+
required: labels.required ?? true,
|
|
91383
|
+
...labels.noneLabel !== void 0 ? { noneLabel: labels.noneLabel } : {},
|
|
91384
|
+
functionCap: target.cap,
|
|
91385
|
+
...target.kind !== void 0 ? { kind: target.kind } : {}
|
|
91386
|
+
};
|
|
91387
|
+
}
|
|
91388
|
+
/**
|
|
91389
|
+
* Declare the record kind of a service that links to other instances. The body is
|
|
91390
|
+
* `{ name, ...links, ...fields }`; the links are recorded in the kind (read back by
|
|
91391
|
+
* {@link linksOfKind}) so the hub can index who points at what.
|
|
91392
|
+
*/
|
|
91393
|
+
function declareLinkKind(spec) {
|
|
91394
|
+
const linkShape = {};
|
|
91395
|
+
for (const [field, target] of Object.entries(spec.links)) linkShape[field] = linkField(target);
|
|
91396
|
+
const typed = entryBody({
|
|
91397
|
+
...linkShape,
|
|
91398
|
+
...spec.fields
|
|
91399
|
+
});
|
|
91400
|
+
return defineEntryKind(spec.name, {
|
|
91401
|
+
title: spec.title,
|
|
91402
|
+
schema: typed,
|
|
91403
|
+
writeTrigger: spec.writeTrigger
|
|
91404
|
+
});
|
|
91405
|
+
}
|
|
91406
|
+
/** The id a record holds in a link field; undefined while it names none. */
|
|
91407
|
+
function linkedId(body, field) {
|
|
91408
|
+
const value = body[field];
|
|
91409
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
91410
|
+
}
|
|
91411
|
+
/** The `integration-instances` provider of a service whose records link to other instances. */
|
|
91412
|
+
function buildLinkedInstances(records, spec) {
|
|
91413
|
+
const links = linksOfKind(spec.kind);
|
|
91414
|
+
const kindId = spec.instanceKind ?? spec.kind.name;
|
|
91415
|
+
const instances = new RecordIntegrationInstances(records, spec.kind, {
|
|
91416
|
+
description: {
|
|
91417
|
+
...spec.description,
|
|
91418
|
+
links: links.map((link) => ({
|
|
91419
|
+
kind: kindId,
|
|
91420
|
+
field: link.field,
|
|
91421
|
+
to: link.to
|
|
91422
|
+
}))
|
|
91423
|
+
},
|
|
91424
|
+
formSchema: async (id) => {
|
|
91425
|
+
const first = links[0];
|
|
91426
|
+
const chosen = id === null || first === void 0 || spec.dynamicSections === void 0 ? void 0 : linkedId(await instances.getResolved(id) ?? {}, first.field);
|
|
91427
|
+
const dynamic = chosen !== void 0 && spec.dynamicSections !== void 0 ? await spec.dynamicSections(chosen) : [];
|
|
91428
|
+
return { sections: [
|
|
91429
|
+
{
|
|
91430
|
+
id: "links",
|
|
91431
|
+
title: "Linked instances",
|
|
91432
|
+
columns: 1,
|
|
91433
|
+
fields: links.map((link) => linkFieldDescriptor(link.field, link.to, spec.labels?.[link.field] ?? { label: link.field }))
|
|
91434
|
+
},
|
|
91435
|
+
...spec.linkSections ?? [],
|
|
91436
|
+
...dynamic
|
|
91437
|
+
] };
|
|
91438
|
+
},
|
|
91439
|
+
initialBody: (name) => spec.kind.schema.parse({
|
|
91440
|
+
name,
|
|
91441
|
+
...Object.fromEntries(links.map((link) => [link.field, ""]))
|
|
91442
|
+
}),
|
|
91443
|
+
configured: (body) => links.every((link) => linkedId(body, link.field) !== void 0) && (spec.alsoConfigured?.(body) ?? true),
|
|
91444
|
+
...spec.mintId !== void 0 ? { mintId: spec.mintId } : {},
|
|
91445
|
+
...spec.onChanged !== void 0 ? { onChanged: spec.onChanged } : {}
|
|
91446
|
+
});
|
|
91447
|
+
return instances;
|
|
91448
|
+
}
|
|
91237
91449
|
entryBody({ brokerId: string() });
|
|
91450
|
+
/**
|
|
91451
|
+
* The calendars an account imports leave the account (D1013). An account record used to carry a
|
|
91452
|
+
* `calendarIds` array; each calendar is now a record of its own that LINKS to the account
|
|
91453
|
+
* (`calendars`, one per account and calendar). For every account row with an array:
|
|
91454
|
+
*
|
|
91455
|
+
* 1. each entry becomes a calendar record under a deterministic id (an existing one is kept);
|
|
91456
|
+
* 2. each record is read back and must name this account and calendar;
|
|
91457
|
+
* 3. only then is the array cleared (a patch: nothing else on the account is touched).
|
|
91458
|
+
*
|
|
91459
|
+
* An entry that names another account, or is not a calendar id at all, selected nothing before
|
|
91460
|
+
* (the reader only matched its own account's ids): it is counted, named in a warning and goes with
|
|
91461
|
+
* the array. An array that is not a list of strings is left where it is. A failure on one account
|
|
91462
|
+
* does not stop the others; the run then throws, and the accounts that failed keep their array.
|
|
91463
|
+
* Accounts are never created or deleted. Idempotent: with the arrays cleared nothing is left to do.
|
|
91464
|
+
*/
|
|
91465
|
+
/** The id of the calendar record of one (account, calendar): the same pair is always the same row. */
|
|
91466
|
+
function calendarRecordId(accountId, calendarId) {
|
|
91467
|
+
return `cal_${(0, node_crypto.createHash)("sha1").update(`${accountId}\0${calendarId}`).digest("hex").slice(0, 16)}`;
|
|
91468
|
+
}
|
|
91469
|
+
function entriesOf(value) {
|
|
91470
|
+
if (!Array.isArray(value)) return null;
|
|
91471
|
+
const out = [];
|
|
91472
|
+
for (const item of value) {
|
|
91473
|
+
if (typeof item !== "string") return null;
|
|
91474
|
+
out.push(item);
|
|
91475
|
+
}
|
|
91476
|
+
return out;
|
|
91477
|
+
}
|
|
91478
|
+
async function moveCalendarIdsToRecords(ports, spec) {
|
|
91479
|
+
let created = 0;
|
|
91480
|
+
let cleared = 0;
|
|
91481
|
+
let dropped = 0;
|
|
91482
|
+
const failed = [];
|
|
91483
|
+
for (const account of await ports.listAccounts()) {
|
|
91484
|
+
if (account.calendarIds === void 0 || account.calendarIds === null) continue;
|
|
91485
|
+
const entries = entriesOf(account.calendarIds);
|
|
91486
|
+
if (entries === null) {
|
|
91487
|
+
ports.warn(`account '${account.id}': calendarIds is not a list of ids and stays as it is`);
|
|
91488
|
+
failed.push(account.id);
|
|
91489
|
+
continue;
|
|
91490
|
+
}
|
|
91491
|
+
try {
|
|
91492
|
+
const own = /* @__PURE__ */ new Set();
|
|
91493
|
+
const inert = [];
|
|
91494
|
+
for (const entry of entries) {
|
|
91495
|
+
const calendarId = spec.calendarIdOf(account.id, entry);
|
|
91496
|
+
if (calendarId === null || calendarId === "") inert.push(entry);
|
|
91497
|
+
else own.add(calendarId);
|
|
91498
|
+
}
|
|
91499
|
+
for (const calendarId of own) {
|
|
91500
|
+
const recordId = calendarRecordId(account.id, calendarId);
|
|
91501
|
+
const body = {
|
|
91502
|
+
name: calendarId,
|
|
91503
|
+
accountId: account.id,
|
|
91504
|
+
calendarId
|
|
91505
|
+
};
|
|
91506
|
+
if (await ports.readCalendar(recordId) === null) {
|
|
91507
|
+
await ports.putCalendar(recordId, body);
|
|
91508
|
+
created += 1;
|
|
91509
|
+
}
|
|
91510
|
+
const back = await ports.readCalendar(recordId);
|
|
91511
|
+
if (back?.accountId !== account.id || back.calendarId !== calendarId) throw new Error(`account '${account.id}': the calendar record '${recordId}' did not read back; the array stays`);
|
|
91512
|
+
}
|
|
91513
|
+
if (inert.length > 0) {
|
|
91514
|
+
ports.warn(`account '${account.id}': ${inert.length} entr${inert.length === 1 ? "y" : "ies"} of calendarIds named another account or no calendar and selected nothing: ${inert.join(", ")}`);
|
|
91515
|
+
dropped += inert.length;
|
|
91516
|
+
}
|
|
91517
|
+
await ports.clearCalendarIds(account.id);
|
|
91518
|
+
cleared += 1;
|
|
91519
|
+
} catch (error) {
|
|
91520
|
+
ports.warn(error instanceof Error ? error.message : String(error));
|
|
91521
|
+
failed.push(account.id);
|
|
91522
|
+
}
|
|
91523
|
+
}
|
|
91524
|
+
if (failed.length > 0) throw new Error(`calendarIds kept on ${failed.length} account(s) (${failed.join(", ")}); ${created} calendar record(s) written, ${cleared} account(s) cleared`);
|
|
91525
|
+
return {
|
|
91526
|
+
outcome: created === 0 && cleared === 0 ? "already-done" : "done",
|
|
91527
|
+
summary: {
|
|
91528
|
+
created,
|
|
91529
|
+
cleared,
|
|
91530
|
+
dropped
|
|
91531
|
+
}
|
|
91532
|
+
};
|
|
91533
|
+
}
|
|
91238
91534
|
//#endregion
|
|
91239
91535
|
//#region src/google-types.ts
|
|
91240
91536
|
/** One Google account connection (broker entry). Credentials for the OAuth
|
|
@@ -91245,28 +91541,16 @@ var GoogleBrokerEntrySchema = object({
|
|
|
91245
91541
|
clientId: string().default(""),
|
|
91246
91542
|
clientSecret: string().default("").meta({ secret: true }),
|
|
91247
91543
|
/**
|
|
91248
|
-
* @deprecated Per-broker filter;
|
|
91249
|
-
*
|
|
91544
|
+
* @deprecated Per-broker filter; the calendars an account imports are `calendars` records
|
|
91545
|
+
* (Connectivity → Calendars). Kept for migrate only.
|
|
91250
91546
|
*/
|
|
91251
91547
|
calendarFilter: string().default(""),
|
|
91252
91548
|
/** Default From for Gmail send — usually the connected account email. */
|
|
91253
91549
|
fromAddress: string().default(""),
|
|
91254
|
-
fromName: string().default("")
|
|
91255
|
-
/**
|
|
91256
|
-
* The stamped calendar ids (`<brokerId>:<googleCalendarId>`) this broker imports. `undefined`
|
|
91257
|
-
* means "not moved here yet": the reader falls back to the shared `calendarIds` setting of
|
|
91258
|
-
* the addon until the migration copied it. Empty = import none.
|
|
91259
|
-
*/
|
|
91260
|
-
calendarIds: array(string()).optional()
|
|
91550
|
+
fromName: string().default("")
|
|
91261
91551
|
});
|
|
91262
91552
|
var GoogleServicesConfigSchema = object({
|
|
91263
91553
|
brokers: array(GoogleBrokerEntrySchema).default([]),
|
|
91264
|
-
/**
|
|
91265
|
-
* @deprecated The shared set (`brokerId:googleCalendarId`). Each broker owns its own
|
|
91266
|
-
* `calendarIds` now; this key is read only while a broker has none, and the migration
|
|
91267
|
-
* `google-services/2026-11-03-drop-shared-calendar-ids` deletes it.
|
|
91268
|
-
*/
|
|
91269
|
-
calendarIds: array(string()).default([]),
|
|
91270
91554
|
/** @deprecated Pre-broker singleton — cleared after migrateLegacyToBrokers. */
|
|
91271
91555
|
clientId: string().optional(),
|
|
91272
91556
|
/** @deprecated Pre-broker singleton — cleared after migrateLegacyToBrokers. */
|
|
@@ -91327,34 +91611,25 @@ var OAUTH_CALLBACK_SUFFIX = `/addon/google-services/oauth/callback`;
|
|
|
91327
91611
|
var GoogleAddSettingsSchema = object({
|
|
91328
91612
|
clientId: string().min(1),
|
|
91329
91613
|
clientSecret: string().min(1).meta({ secret: true }),
|
|
91330
|
-
/**
|
|
91331
|
-
calendarIds: array(string()).optional(),
|
|
91332
|
-
/** @deprecated Prefer calendarIds — still accepted on save. */
|
|
91614
|
+
/** @deprecated Calendars are `calendars` records now — still accepted on save. */
|
|
91333
91615
|
calendarFilter: string().optional(),
|
|
91334
91616
|
fromAddress: string().optional(),
|
|
91335
91617
|
fromName: string().optional()
|
|
91336
91618
|
});
|
|
91337
|
-
/** Persist UI calendarIds (or legacy filter string) as comma-separated filter. */
|
|
91338
|
-
function calendarIdsToFilter(settings) {
|
|
91339
|
-
if (settings.calendarIds !== void 0) return settings.calendarIds.map((s) => s.trim()).filter((s) => s.length > 0).join(",");
|
|
91340
|
-
return settings.calendarFilter ?? "";
|
|
91341
|
-
}
|
|
91342
91619
|
function settingsToBrokerFields(settings) {
|
|
91343
91620
|
const parsed = GoogleAddSettingsSchema.parse(settings);
|
|
91344
91621
|
return {
|
|
91345
91622
|
clientId: parsed.clientId,
|
|
91346
91623
|
clientSecret: parsed.clientSecret,
|
|
91347
|
-
calendarFilter:
|
|
91624
|
+
calendarFilter: parsed.calendarFilter ?? "",
|
|
91348
91625
|
fromAddress: parsed.fromAddress ?? "",
|
|
91349
|
-
fromName: parsed.fromName ?? ""
|
|
91350
|
-
...parsed.calendarIds !== void 0 ? { calendarIds: [...parsed.calendarIds] } : {}
|
|
91626
|
+
fromName: parsed.fromName ?? ""
|
|
91351
91627
|
};
|
|
91352
91628
|
}
|
|
91353
91629
|
function brokerEntryToSettings(entry) {
|
|
91354
91630
|
return {
|
|
91355
91631
|
clientId: entry.clientId,
|
|
91356
91632
|
clientSecret: entry.clientSecret,
|
|
91357
|
-
calendarIds: [...entry.calendarIds ?? parseCalendarFilter(entry.calendarFilter)],
|
|
91358
91633
|
fromAddress: entry.fromAddress,
|
|
91359
91634
|
fromName: entry.fromName
|
|
91360
91635
|
};
|
|
@@ -91405,18 +91680,6 @@ function migrateLegacyToBrokers(rawConfig, legacyOauth) {
|
|
|
91405
91680
|
didMigrate: true
|
|
91406
91681
|
};
|
|
91407
91682
|
}
|
|
91408
|
-
/**
|
|
91409
|
-
* The stamped calendar ids one broker imports: its own set, or — while the migration has not
|
|
91410
|
-
* reached it — the shared setting older versions wrote, so the move never changes what is imported.
|
|
91411
|
-
*/
|
|
91412
|
-
function calendarIdsOfEntry(entry, shared) {
|
|
91413
|
-
return new Set(entry?.calendarIds ?? shared);
|
|
91414
|
-
}
|
|
91415
|
-
function parseCalendarFilter(raw) {
|
|
91416
|
-
const trimmed = raw.trim();
|
|
91417
|
-
if (!trimmed) return /* @__PURE__ */ new Set();
|
|
91418
|
-
return new Set(trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0));
|
|
91419
|
-
}
|
|
91420
91683
|
/** Local calendar id stamped as `brokerId:googleCalendarId`. */
|
|
91421
91684
|
function stampCalendarId(brokerId, googleCalendarId) {
|
|
91422
91685
|
return `${brokerId}:${googleCalendarId}`;
|
|
@@ -91498,7 +91761,6 @@ var GoogleBrokerRegistry = class {
|
|
|
91498
91761
|
const entry = GoogleBrokerEntrySchema.parse({
|
|
91499
91762
|
id,
|
|
91500
91763
|
name,
|
|
91501
|
-
calendarIds: [],
|
|
91502
91764
|
...fields
|
|
91503
91765
|
});
|
|
91504
91766
|
this.entries = [...this.entries, entry];
|
|
@@ -91512,7 +91774,6 @@ var GoogleBrokerRegistry = class {
|
|
|
91512
91774
|
const next = GoogleBrokerEntrySchema.parse({
|
|
91513
91775
|
id: prev.id,
|
|
91514
91776
|
name: prev.name,
|
|
91515
|
-
...prev.calendarIds !== void 0 ? { calendarIds: prev.calendarIds } : {},
|
|
91516
91777
|
...fields
|
|
91517
91778
|
});
|
|
91518
91779
|
this.entries = this.entries.map((e, i) => i === idx ? next : e);
|
|
@@ -91534,11 +91795,8 @@ var GoogleBrokerRegistry = class {
|
|
|
91534
91795
|
connectedSize() {
|
|
91535
91796
|
return this.entries.filter((e) => this.isConnected(e.id)).length;
|
|
91536
91797
|
}
|
|
91537
|
-
toConfig(
|
|
91538
|
-
return {
|
|
91539
|
-
brokers: [...this.entries],
|
|
91540
|
-
calendarIds: [...calendarIds]
|
|
91541
|
-
};
|
|
91798
|
+
toConfig() {
|
|
91799
|
+
return { brokers: [...this.entries] };
|
|
91542
91800
|
}
|
|
91543
91801
|
seedCounterFromEntries(entries) {
|
|
91544
91802
|
let maxSeen = 0;
|
|
@@ -91696,24 +91954,6 @@ function buildGoogleBrokerSettingsSchema(publicOrigin) {
|
|
|
91696
91954
|
showToggle: true
|
|
91697
91955
|
}]
|
|
91698
91956
|
},
|
|
91699
|
-
{
|
|
91700
|
-
id: "google-calendars-import",
|
|
91701
|
-
title: "Calendars to import",
|
|
91702
|
-
titleKey: "google-services:googleBrokerSettings.s_google-calendars-import.title",
|
|
91703
|
-
description: "Nothing selected = no calendars imported from this account. Options appear once the account is signed in.",
|
|
91704
|
-
descriptionKey: "google-services:googleBrokerSettings.s_google-calendars-import.description",
|
|
91705
|
-
columns: 1,
|
|
91706
|
-
fields: [{
|
|
91707
|
-
type: "multiselect",
|
|
91708
|
-
key: "calendarIds",
|
|
91709
|
-
label: "Calendars",
|
|
91710
|
-
labelKey: "google-services:googleBrokerSettings.s_google-calendars-import.calendarIds.label",
|
|
91711
|
-
options: [],
|
|
91712
|
-
optionsFrom: "_calendarCatalog",
|
|
91713
|
-
description: "The calendars of this Google account that CamStack imports.",
|
|
91714
|
-
descriptionKey: "google-services:googleBrokerSettings.s_google-calendars-import.calendarIds.description"
|
|
91715
|
-
}]
|
|
91716
|
-
},
|
|
91717
91957
|
{
|
|
91718
91958
|
id: "gmail-send",
|
|
91719
91959
|
title: "Gmail send",
|
|
@@ -91753,7 +91993,7 @@ function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
|
|
|
91753
91993
|
format: "html",
|
|
91754
91994
|
content: [
|
|
91755
91995
|
"<p>Link a Google account under Connectivity → <strong>Brokers</strong> ",
|
|
91756
|
-
"(Client ID / secret + Sign in)
|
|
91996
|
+
"(Client ID / secret + Sign in), then add a calendar under Connectivity → <strong>Calendars</strong>. ",
|
|
91757
91997
|
`OAuth redirect URI: <code>${redirect}</code>.</p>`
|
|
91758
91998
|
].join("")
|
|
91759
91999
|
}]
|
|
@@ -92233,11 +92473,76 @@ var googleBrokersEntryKind = defineEntryKind("brokers", {
|
|
|
92233
92473
|
clientSecret: GoogleBrokerEntrySchema.shape.clientSecret,
|
|
92234
92474
|
calendarFilter: GoogleBrokerEntrySchema.shape.calendarFilter,
|
|
92235
92475
|
fromAddress: GoogleBrokerEntrySchema.shape.fromAddress,
|
|
92236
|
-
fromName: GoogleBrokerEntrySchema.shape.fromName
|
|
92237
|
-
calendarIds: GoogleBrokerEntrySchema.shape.calendarIds
|
|
92476
|
+
fromName: GoogleBrokerEntrySchema.shape.fromName
|
|
92238
92477
|
}),
|
|
92239
92478
|
writeTrigger: "an operator adds or edits a Google OAuth broker"
|
|
92240
92479
|
});
|
|
92480
|
+
/**
|
|
92481
|
+
* One calendar an account imports (D1013): a record that LINKS to the account it belongs to and
|
|
92482
|
+
* names the Google calendar by its own id (`primary`, `name@group.calendar.google.com`). The id the
|
|
92483
|
+
* calendar answers to elsewhere stays `<accountId>:<calendarId>` (`stampCalendarId`).
|
|
92484
|
+
*/
|
|
92485
|
+
var googleCalendarsEntryKind = declareLinkKind({
|
|
92486
|
+
name: "calendars",
|
|
92487
|
+
title: "Google calendars",
|
|
92488
|
+
writeTrigger: "an operator adds or removes a calendar an account imports",
|
|
92489
|
+
links: { accountId: {
|
|
92490
|
+
cap: "broker",
|
|
92491
|
+
kind: GOOGLE_BROKER_KIND
|
|
92492
|
+
} },
|
|
92493
|
+
fields: { calendarId: string().default("") }
|
|
92494
|
+
});
|
|
92495
|
+
//#endregion
|
|
92496
|
+
//#region src/google-calendar-instances.ts
|
|
92497
|
+
/**
|
|
92498
|
+
* The calendars a Google account imports as `integration-instances` (D1013): one `calendars`
|
|
92499
|
+
* record per (account, calendar), linking to its account through the link primitive. The calendar
|
|
92500
|
+
* source reads the records; nothing is stored on the account.
|
|
92501
|
+
*/
|
|
92502
|
+
var GOOGLE_CALENDAR_INSTANCE_KIND = "calendar";
|
|
92503
|
+
/** The stamped ids each account imports, from the calendar records. */
|
|
92504
|
+
function selectedStampedIds(rows) {
|
|
92505
|
+
const out = /* @__PURE__ */ new Map();
|
|
92506
|
+
for (const row of rows) {
|
|
92507
|
+
const { accountId, calendarId } = row.body;
|
|
92508
|
+
if (!row.enabled || accountId === "" || calendarId === "") continue;
|
|
92509
|
+
const set = out.get(accountId) ?? /* @__PURE__ */ new Set();
|
|
92510
|
+
set.add(stampCalendarId(accountId, calendarId));
|
|
92511
|
+
out.set(accountId, set);
|
|
92512
|
+
}
|
|
92513
|
+
return out;
|
|
92514
|
+
}
|
|
92515
|
+
function buildGoogleCalendarInstances(records, deps) {
|
|
92516
|
+
return buildLinkedInstances(records, {
|
|
92517
|
+
kind: googleCalendarsEntryKind,
|
|
92518
|
+
instanceKind: GOOGLE_CALENDAR_INSTANCE_KIND,
|
|
92519
|
+
description: {
|
|
92520
|
+
role: "services",
|
|
92521
|
+
instanceMode: "multiple",
|
|
92522
|
+
kind: GOOGLE_CALENDAR_INSTANCE_KIND,
|
|
92523
|
+
canTest: false
|
|
92524
|
+
},
|
|
92525
|
+
labels: { accountId: {
|
|
92526
|
+
label: "Google account",
|
|
92527
|
+
description: "The account this calendar belongs to."
|
|
92528
|
+
} },
|
|
92529
|
+
alsoConfigured: (body) => body.calendarId !== "",
|
|
92530
|
+
dynamicSections: async (accountId) => [{
|
|
92531
|
+
id: "google-calendar-pick",
|
|
92532
|
+
title: "Calendar",
|
|
92533
|
+
columns: 1,
|
|
92534
|
+
fields: [{
|
|
92535
|
+
type: "select",
|
|
92536
|
+
key: "calendarId",
|
|
92537
|
+
label: "Calendar",
|
|
92538
|
+
options: [...await deps.catalog(accountId)],
|
|
92539
|
+
description: "The calendars of that account. Sign the account in to see them."
|
|
92540
|
+
}]
|
|
92541
|
+
}],
|
|
92542
|
+
mintId: () => `cal_${(0, node_crypto.randomBytes)(6).toString("hex")}`,
|
|
92543
|
+
onChanged: deps.onChanged
|
|
92544
|
+
});
|
|
92545
|
+
}
|
|
92241
92546
|
//#endregion
|
|
92242
92547
|
//#region src/google-instances.ts
|
|
92243
92548
|
/**
|
|
@@ -92257,6 +92562,17 @@ function asTestResult(r) {
|
|
|
92257
92562
|
error: r.error ?? "Google rejected the check"
|
|
92258
92563
|
};
|
|
92259
92564
|
}
|
|
92565
|
+
/** The `account` kind of the composed provider: where the OAuth sign-in starts and ends. */
|
|
92566
|
+
function googleAccountKind(deps) {
|
|
92567
|
+
return {
|
|
92568
|
+
id: GOOGLE_BROKER_KIND,
|
|
92569
|
+
label: GOOGLE_BROKER_KIND_LABEL,
|
|
92570
|
+
functionCap: "broker",
|
|
92571
|
+
brokerKind: GOOGLE_BROKER_KIND,
|
|
92572
|
+
connectPath: deps.connectPath,
|
|
92573
|
+
disconnectPath: deps.disconnectPath
|
|
92574
|
+
};
|
|
92575
|
+
}
|
|
92260
92576
|
function buildGoogleInstances(records, deps) {
|
|
92261
92577
|
return new RecordIntegrationInstances(records, googleBrokersEntryKind, {
|
|
92262
92578
|
description: {
|
|
@@ -92264,31 +92580,16 @@ function buildGoogleInstances(records, deps) {
|
|
|
92264
92580
|
instanceMode: "multiple",
|
|
92265
92581
|
kind: "account",
|
|
92266
92582
|
canTest: true,
|
|
92267
|
-
kinds: [{
|
|
92268
|
-
id: GOOGLE_BROKER_KIND,
|
|
92269
|
-
label: GOOGLE_BROKER_KIND_LABEL,
|
|
92270
|
-
functionCap: "broker",
|
|
92271
|
-
brokerKind: GOOGLE_BROKER_KIND,
|
|
92272
|
-
connectPath: deps.connectPath,
|
|
92273
|
-
disconnectPath: deps.disconnectPath
|
|
92274
|
-
}],
|
|
92275
92583
|
allowRestoreId: true
|
|
92276
92584
|
},
|
|
92277
|
-
formSchema: () =>
|
|
92278
|
-
const full = buildGoogleBrokerSettingsSchema(deps.publicOrigin());
|
|
92279
|
-
return {
|
|
92280
|
-
...full,
|
|
92281
|
-
sections: full.sections.filter((s) => s.id !== "google-calendars-import")
|
|
92282
|
-
};
|
|
92283
|
-
},
|
|
92585
|
+
formSchema: () => buildGoogleBrokerSettingsSchema(deps.publicOrigin()),
|
|
92284
92586
|
initialBody: (name) => ({
|
|
92285
92587
|
name,
|
|
92286
92588
|
clientId: "",
|
|
92287
92589
|
clientSecret: "",
|
|
92288
92590
|
calendarFilter: "",
|
|
92289
92591
|
fromAddress: "",
|
|
92290
|
-
fromName: ""
|
|
92291
|
-
calendarIds: []
|
|
92592
|
+
fromName: ""
|
|
92292
92593
|
}),
|
|
92293
92594
|
mintId: deps.mintId,
|
|
92294
92595
|
kindOf: () => GOOGLE_BROKER_KIND,
|
|
@@ -92407,42 +92708,66 @@ async function planDropSharedCalendarIds(ports) {
|
|
|
92407
92708
|
} })
|
|
92408
92709
|
}] };
|
|
92409
92710
|
}
|
|
92410
|
-
//#endregion
|
|
92411
|
-
//#region src/migrations/index.ts
|
|
92412
92711
|
/** The `google-services` catalogue (D987): offered to the engine when the addon initialises. */
|
|
92413
|
-
var ADDON_MIGRATIONS = [
|
|
92414
|
-
|
|
92415
|
-
|
|
92416
|
-
|
|
92417
|
-
|
|
92418
|
-
|
|
92419
|
-
|
|
92420
|
-
|
|
92421
|
-
|
|
92422
|
-
|
|
92423
|
-
|
|
92424
|
-
|
|
92425
|
-
|
|
92426
|
-
|
|
92427
|
-
|
|
92428
|
-
|
|
92429
|
-
|
|
92430
|
-
|
|
92431
|
-
|
|
92432
|
-
|
|
92433
|
-
|
|
92434
|
-
|
|
92435
|
-
|
|
92436
|
-
|
|
92437
|
-
|
|
92438
|
-
|
|
92439
|
-
|
|
92440
|
-
|
|
92441
|
-
|
|
92442
|
-
|
|
92443
|
-
}
|
|
92712
|
+
var ADDON_MIGRATIONS = [
|
|
92713
|
+
{
|
|
92714
|
+
meta: {
|
|
92715
|
+
id: "google-services/2026-11-03-calendar-ids-to-brokers",
|
|
92716
|
+
owner: "google-services",
|
|
92717
|
+
level: "addon-state",
|
|
92718
|
+
seq: 1,
|
|
92719
|
+
dependsOn: [],
|
|
92720
|
+
store: "settings",
|
|
92721
|
+
scope: "cluster",
|
|
92722
|
+
onFailure: "tolerate",
|
|
92723
|
+
bodyKind: "procedure",
|
|
92724
|
+
description: "Copy the shared calendar set to every Google broker record that has none of its own",
|
|
92725
|
+
fixtureMark: "the client secret marker of a broker record the migration does not know"
|
|
92726
|
+
},
|
|
92727
|
+
procedure: copyCalendarIdsToBrokers
|
|
92728
|
+
},
|
|
92729
|
+
{
|
|
92730
|
+
meta: {
|
|
92731
|
+
id: "google-services/2026-11-03-drop-shared-calendar-ids",
|
|
92732
|
+
owner: "google-services",
|
|
92733
|
+
level: "addon-state",
|
|
92734
|
+
seq: 2,
|
|
92735
|
+
dependsOn: ["google-services/2026-11-03-calendar-ids-to-brokers"],
|
|
92736
|
+
store: "settings",
|
|
92737
|
+
scope: "cluster",
|
|
92738
|
+
onFailure: "tolerate",
|
|
92739
|
+
bodyKind: "plan",
|
|
92740
|
+
description: "Delete the shared calendar set once every broker shows its own",
|
|
92741
|
+
fixtureMark: "the shared set parked when a broker holds a different one"
|
|
92742
|
+
},
|
|
92743
|
+
plan: planDropSharedCalendarIds
|
|
92744
|
+
},
|
|
92745
|
+
{
|
|
92746
|
+
meta: {
|
|
92747
|
+
id: "google-services/2026-11-07-calendar-ids-to-calendar-records",
|
|
92748
|
+
owner: "google-services",
|
|
92749
|
+
level: "addon-state",
|
|
92750
|
+
seq: 3,
|
|
92751
|
+
dependsOn: ["google-services/2026-11-03-drop-shared-calendar-ids"],
|
|
92752
|
+
store: "settings",
|
|
92753
|
+
scope: "cluster",
|
|
92754
|
+
onFailure: "tolerate",
|
|
92755
|
+
bodyKind: "procedure",
|
|
92756
|
+
description: "Write one calendar record per calendar an account imports (linked to the account), read each back, then clear the calendar array of the account row",
|
|
92757
|
+
fixtureMark: "the client secret marker of the account that keeps its calendars"
|
|
92758
|
+
},
|
|
92759
|
+
procedure: (ports) => moveCalendarIdsToRecords(ports, { calendarIdOf: (accountId, entry) => {
|
|
92760
|
+
const parsed = parseStampedCalendarId(entry);
|
|
92761
|
+
return parsed?.brokerId === accountId ? parsed.googleCalendarId : null;
|
|
92762
|
+
} })
|
|
92763
|
+
}
|
|
92764
|
+
];
|
|
92444
92765
|
//#endregion
|
|
92445
92766
|
//#region src/migrations/ports.ts
|
|
92767
|
+
function listOf(value) {
|
|
92768
|
+
if (!Array.isArray(value)) return void 0;
|
|
92769
|
+
return value.filter((item) => typeof item === "string");
|
|
92770
|
+
}
|
|
92446
92771
|
/** The migration's ports over the `brokers` record kind and the addon's raw settings read. */
|
|
92447
92772
|
function calendarIdsPortsOver(kind, readStored) {
|
|
92448
92773
|
return {
|
|
@@ -92450,18 +92775,43 @@ function calendarIdsPortsOver(kind, readStored) {
|
|
|
92450
92775
|
listBrokers: async () => {
|
|
92451
92776
|
return (await kind.query({ limit: 1e3 })).records.map((record) => ({
|
|
92452
92777
|
id: record.id,
|
|
92453
|
-
calendarIds: record.data
|
|
92778
|
+
calendarIds: listOf(record.data["calendarIds"])
|
|
92454
92779
|
}));
|
|
92455
92780
|
},
|
|
92456
92781
|
setBrokerCalendarIds: async (id, ids) => {
|
|
92457
92782
|
const row = await kind.get(id);
|
|
92458
92783
|
if (row === null) return false;
|
|
92459
|
-
if (row.data
|
|
92784
|
+
if (row.data["calendarIds"] !== void 0) return true;
|
|
92460
92785
|
await kind.patch(id, { calendarIds: [...ids] });
|
|
92461
92786
|
return true;
|
|
92462
92787
|
}
|
|
92463
92788
|
};
|
|
92464
92789
|
}
|
|
92790
|
+
/** The ports of the calendars move: the raw `brokers` rows and the `calendars` records. */
|
|
92791
|
+
function calendarRecordPortsOver(deps) {
|
|
92792
|
+
return {
|
|
92793
|
+
warn: deps.warn,
|
|
92794
|
+
listAccounts: async () => {
|
|
92795
|
+
return (await deps.brokers.query({ limit: 1e3 })).records.map((record) => ({
|
|
92796
|
+
id: record.id,
|
|
92797
|
+
calendarIds: record.data["calendarIds"]
|
|
92798
|
+
}));
|
|
92799
|
+
},
|
|
92800
|
+
readCalendar: async (recordId) => {
|
|
92801
|
+
const row = await deps.calendars.get(recordId);
|
|
92802
|
+
return row === null ? null : row.data;
|
|
92803
|
+
},
|
|
92804
|
+
putCalendar: async (recordId, body) => {
|
|
92805
|
+
await deps.calendars.put({
|
|
92806
|
+
id: recordId,
|
|
92807
|
+
data: body
|
|
92808
|
+
}, { ifRev: 0 });
|
|
92809
|
+
},
|
|
92810
|
+
clearCalendarIds: async (accountId) => {
|
|
92811
|
+
await deps.brokers.patch(accountId, { calendarIds: null });
|
|
92812
|
+
}
|
|
92813
|
+
};
|
|
92814
|
+
}
|
|
92465
92815
|
//#endregion
|
|
92466
92816
|
//#region src/google-broker-store.ts
|
|
92467
92817
|
function rowToEntry(id, data) {
|
|
@@ -92500,15 +92850,14 @@ var GoogleBrokerStore = class GoogleBrokerStore {
|
|
|
92500
92850
|
const nextIds = new Set(entries.map((e) => e.id));
|
|
92501
92851
|
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
92502
92852
|
for (const entry of entries) {
|
|
92503
|
-
const { id, name, clientId, clientSecret, calendarFilter, fromAddress, fromName
|
|
92853
|
+
const { id, name, clientId, clientSecret, calendarFilter, fromAddress, fromName } = entry;
|
|
92504
92854
|
const body = {
|
|
92505
92855
|
name,
|
|
92506
92856
|
clientId,
|
|
92507
92857
|
clientSecret,
|
|
92508
92858
|
calendarFilter,
|
|
92509
92859
|
fromAddress,
|
|
92510
|
-
fromName
|
|
92511
|
-
...calendarIds !== void 0 ? { calendarIds } : {}
|
|
92860
|
+
fromName
|
|
92512
92861
|
};
|
|
92513
92862
|
const parsed = googleBrokersEntryKind.schema.safeParse(body);
|
|
92514
92863
|
if (!parsed.success) throw new Error(`invalid Google broker '${id}': ${parsed.error.message}`);
|
|
@@ -92554,6 +92903,9 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92554
92903
|
registry = new GoogleBrokerRegistry();
|
|
92555
92904
|
brokerStore = null;
|
|
92556
92905
|
instances = null;
|
|
92906
|
+
calendarInstances = null;
|
|
92907
|
+
/** The stamped calendar ids each account imports, from the `calendars` records. */
|
|
92908
|
+
calendarSelection = /* @__PURE__ */ new Map();
|
|
92557
92909
|
activeBrokers = [];
|
|
92558
92910
|
/** CSRF state → { brokerId, issuedAt } for the in-flight OAuth consents. */
|
|
92559
92911
|
pendingStates = /* @__PURE__ */ new Map();
|
|
@@ -92599,15 +92951,23 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92599
92951
|
}
|
|
92600
92952
|
const records = this.ctx.records;
|
|
92601
92953
|
if (records === void 0) throw new Error("google-services: ctx.records is required (hub-only addon)");
|
|
92602
|
-
await records.declare({
|
|
92954
|
+
await records.declare({
|
|
92955
|
+
brokers: googleBrokersEntryKind,
|
|
92956
|
+
calendars: googleCalendarsEntryKind
|
|
92957
|
+
});
|
|
92603
92958
|
this.brokerStore = GoogleBrokerStore.of(records);
|
|
92604
92959
|
this.instances = this.buildInstances(records);
|
|
92960
|
+
this.calendarInstances = buildGoogleCalendarInstances(records, {
|
|
92961
|
+
catalog: (accountId) => this.loadCalendarCatalog(accountId),
|
|
92962
|
+
onChanged: () => this.refreshCalendarSelection()
|
|
92963
|
+
});
|
|
92605
92964
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
92606
92965
|
if ((await this.brokerStore.listResolved()).length === 0 && this.config.brokers.length > 0) {
|
|
92607
92966
|
await this.brokerStore.replaceAll(this.config.brokers);
|
|
92608
92967
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
92609
92968
|
}
|
|
92610
92969
|
await this.migrateCalendarIds(records);
|
|
92970
|
+
await this.refreshCalendarSelection();
|
|
92611
92971
|
this.registry.seedFrom(this.activeBrokers, oauthAccounts.accounts);
|
|
92612
92972
|
await this.refreshPublicOrigin();
|
|
92613
92973
|
const calendarProvider = createGoogleCalendarSource({
|
|
@@ -92617,7 +92977,7 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92617
92977
|
connected: this.registry.isConnected(entry.id),
|
|
92618
92978
|
api: createGoogleCalendarApi(() => this.ensureAccessToken(entry.id))
|
|
92619
92979
|
})),
|
|
92620
|
-
selectedCalendarIds: (brokerId) => this.
|
|
92980
|
+
selectedCalendarIds: (brokerId) => this.calendarSelection.get(brokerId) ?? /* @__PURE__ */ new Set()
|
|
92621
92981
|
});
|
|
92622
92982
|
const smtpProvider = createGmailSmtpProvider({ accounts: () => this.registry.listEntries().map((entry) => {
|
|
92623
92983
|
const oauth = this.registry.getOauth(entry.id);
|
|
@@ -92666,7 +93026,7 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92666
93026
|
},
|
|
92667
93027
|
{
|
|
92668
93028
|
capability: integrationInstancesCapability,
|
|
92669
|
-
provider: this.
|
|
93029
|
+
provider: this.buildInstancesProvider()
|
|
92670
93030
|
},
|
|
92671
93031
|
{
|
|
92672
93032
|
capability: calendarSourceCapability,
|
|
@@ -92691,6 +93051,41 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92691
93051
|
if (this.instances === null) throw new Error("google-services: instances used before initialize");
|
|
92692
93052
|
return this.instances;
|
|
92693
93053
|
}
|
|
93054
|
+
/** One provider over the two record kinds: the Google accounts and the calendars they import. */
|
|
93055
|
+
buildInstancesProvider() {
|
|
93056
|
+
const calendars = this.calendarInstances;
|
|
93057
|
+
if (calendars === null) throw new Error("google-services: instances used before initialize");
|
|
93058
|
+
return composeInstanceKinds([{
|
|
93059
|
+
kind: {
|
|
93060
|
+
...googleAccountKind({
|
|
93061
|
+
connectPath: `/addon/${ADDON_ID}${START_PATH}?brokerId={{brokerId}}`,
|
|
93062
|
+
disconnectPath: `/addon/${ADDON_ID}${DISCONNECT_PATH}?brokerId={{brokerId}}`
|
|
93063
|
+
}),
|
|
93064
|
+
functionCap: "broker"
|
|
93065
|
+
},
|
|
93066
|
+
provider: this.requireInstances().toProvider()
|
|
93067
|
+
}, {
|
|
93068
|
+
kind: {
|
|
93069
|
+
id: GOOGLE_CALENDAR_INSTANCE_KIND,
|
|
93070
|
+
label: "Google calendar",
|
|
93071
|
+
functionCap: "calendar-source",
|
|
93072
|
+
role: "services"
|
|
93073
|
+
},
|
|
93074
|
+
provider: calendars.toProvider()
|
|
93075
|
+
}], {
|
|
93076
|
+
role: "services",
|
|
93077
|
+
instanceMode: "multiple",
|
|
93078
|
+
kind: "account",
|
|
93079
|
+
canTest: true,
|
|
93080
|
+
allowRestoreId: true
|
|
93081
|
+
});
|
|
93082
|
+
}
|
|
93083
|
+
/** Re-read which calendars each account imports from the records. */
|
|
93084
|
+
async refreshCalendarSelection() {
|
|
93085
|
+
const calendars = this.calendarInstances;
|
|
93086
|
+
if (calendars === null) return;
|
|
93087
|
+
this.calendarSelection = selectedStampedIds(await calendars.listResolved());
|
|
93088
|
+
}
|
|
92694
93089
|
/** The accounts as `integration-instances`, over the same `brokers` records (planning#408 P3). */
|
|
92695
93090
|
buildInstances(records) {
|
|
92696
93091
|
return buildGoogleInstances(records, {
|
|
@@ -92724,7 +93119,6 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92724
93119
|
const oauth = this.registry.getOauth(id);
|
|
92725
93120
|
return {
|
|
92726
93121
|
...brokerEntryToSettings(entry),
|
|
92727
|
-
_calendarCatalog: await this.loadStampedCalendarCatalog(id),
|
|
92728
93122
|
accountEmail: oauth?.accountEmail ?? "",
|
|
92729
93123
|
connected: this.registry.isConnected(id)
|
|
92730
93124
|
};
|
|
@@ -92993,11 +93387,25 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92993
93387
|
for (const [k, v] of this.pendingStates) if (v.issuedAt < cutoff) this.pendingStates.delete(k);
|
|
92994
93388
|
}
|
|
92995
93389
|
/**
|
|
92996
|
-
* The shared calendar set becomes each broker's own (D985, D987)
|
|
92997
|
-
*
|
|
93390
|
+
* The shared calendar set becomes each broker's own (D985, D987), then each broker's set becomes
|
|
93391
|
+
* `calendars` records linked to the account (D1013). Brokers are reloaded once a migration
|
|
93392
|
+
* landed, so the registry is seeded with what it wrote.
|
|
92998
93393
|
*/
|
|
92999
93394
|
async migrateCalendarIds(records) {
|
|
93000
|
-
|
|
93395
|
+
const brokers = records.kind({
|
|
93396
|
+
name: googleBrokersEntryKind.name,
|
|
93397
|
+
declaration: googleBrokersEntryKind.declaration,
|
|
93398
|
+
schema: record(string(), unknown())
|
|
93399
|
+
});
|
|
93400
|
+
const ports = {
|
|
93401
|
+
...calendarIdsPortsOver(brokers, (key) => this.readStoredGlobalKey(key)),
|
|
93402
|
+
...calendarRecordPortsOver({
|
|
93403
|
+
brokers,
|
|
93404
|
+
calendars: records.kind(googleCalendarsEntryKind),
|
|
93405
|
+
warn: (message) => this.ctx.logger.warn(message)
|
|
93406
|
+
})
|
|
93407
|
+
};
|
|
93408
|
+
if ((await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, ports))).applied.length > 0) this.activeBrokers = await this.loadPersistedBrokers();
|
|
93001
93409
|
}
|
|
93002
93410
|
async loadPersistedBrokers() {
|
|
93003
93411
|
const store = this.brokerStore;
|
|
@@ -93021,30 +93429,24 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
93021
93429
|
if (!this.oauthAccountsHandle) return;
|
|
93022
93430
|
await this.oauthAccountsHandle.set({ accounts: [...this.registry.listOauthAccounts()] });
|
|
93023
93431
|
}
|
|
93024
|
-
/**
|
|
93025
|
-
async
|
|
93026
|
-
const
|
|
93027
|
-
|
|
93028
|
-
|
|
93029
|
-
|
|
93030
|
-
|
|
93031
|
-
|
|
93032
|
-
|
|
93033
|
-
|
|
93034
|
-
|
|
93035
|
-
|
|
93036
|
-
|
|
93037
|
-
|
|
93038
|
-
|
|
93039
|
-
|
|
93040
|
-
|
|
93041
|
-
this.ctx.logger.warn("google calendar catalog failed", { meta: {
|
|
93042
|
-
brokerId: entry.id,
|
|
93043
|
-
error: err instanceof Error ? err.message : String(err)
|
|
93044
|
-
} });
|
|
93045
|
-
}
|
|
93432
|
+
/** The calendars one signed-in account lists, by their own id (the picker of a calendar record). */
|
|
93433
|
+
async loadCalendarCatalog(accountId) {
|
|
93434
|
+
const entry = this.registry.getEntry(accountId);
|
|
93435
|
+
if (entry === null || !this.registry.isConnected(entry.id)) return [];
|
|
93436
|
+
try {
|
|
93437
|
+
const api = createGoogleCalendarApi(() => this.ensureAccessToken(entry.id));
|
|
93438
|
+
const accountLabel = this.registry.getOauth(entry.id)?.accountEmail || entry.name;
|
|
93439
|
+
return (await api.listCalendars()).map((c) => ({
|
|
93440
|
+
value: c.id,
|
|
93441
|
+
label: `${c.summary.trim().length > 0 ? c.summary : c.id} (${accountLabel})`
|
|
93442
|
+
})).toSorted((a, b) => a.label.localeCompare(b.label));
|
|
93443
|
+
} catch (err) {
|
|
93444
|
+
this.ctx.logger.warn("google calendar catalog failed", { meta: {
|
|
93445
|
+
brokerId: entry.id,
|
|
93446
|
+
error: err instanceof Error ? err.message : String(err)
|
|
93447
|
+
} });
|
|
93448
|
+
return [];
|
|
93046
93449
|
}
|
|
93047
|
-
return out.toSorted((a, b) => a.label.localeCompare(b.label));
|
|
93048
93450
|
}
|
|
93049
93451
|
async emitGoogleBrokerReconnectAlert(brokerId, brokerName) {
|
|
93050
93452
|
const emit = this.ctx.api.alerts?.emit;
|
|
@@ -93167,14 +93569,6 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
93167
93569
|
await this.refreshPublicOrigin();
|
|
93168
93570
|
return super.getGlobalSettings(overlay, cap, nodeId);
|
|
93169
93571
|
}
|
|
93170
|
-
/**
|
|
93171
|
-
* The stamped calendar ids ONE broker imports. A broker the migration has not reached yet
|
|
93172
|
-
* (no `calendarIds` of its own) reads the shared setting older versions wrote, so the move
|
|
93173
|
-
* never changes what is imported mid-way.
|
|
93174
|
-
*/
|
|
93175
|
-
calendarIdsOf(brokerId) {
|
|
93176
|
-
return calendarIdsOfEntry(this.registry.getEntry(brokerId), this.config.calendarIds ?? []);
|
|
93177
|
-
}
|
|
93178
93572
|
};
|
|
93179
93573
|
function asSettingsCap(cap) {
|
|
93180
93574
|
if (cap === "calendar-source" || cap === "smtp-provider") return cap;
|