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