@camstack/addon-google-services 0.2.11 → 0.2.13

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.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  let node_crypto = require("node:crypto");
6
- //#region ../types/dist/event-category-BvOFi6PY.mjs
6
+ //#region ../types/dist/event-category-DOl51-Ij.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -82,9 +82,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
82
82
  /**
83
83
  * The node's one-shot runner boot check (D877): planned runners against the
84
84
  * process service, `degraded` when any is failed, refused or missing. The
85
- * alert centre turns it into one alert and a direct critical push.
85
+ * alert centre turns it into one critical alert.
86
86
  */
87
87
  EventCategory["SystemRunnerCheck"] = "system.runner-check";
88
+ /**
89
+ * An alert EPISODE opened or closed, emitted by the alert centre for alerts
90
+ * of severity `critical` or `error` (`AlertEpisodePayload`). The alert
91
+ * centre sends no notification of its own: Notification Center turns this
92
+ * into the `alert-critical` / `alert-error` / `alert-critical-resolved`
93
+ * system-event kinds, and reconciles from the alerts store because a bus
94
+ * event may be dropped (D8, D11).
95
+ */
96
+ EventCategory["AlertEpisode"] = "alert.episode";
88
97
  EventCategory["AddonError"] = "addon.error";
89
98
  EventCategory["AddonPageReady"] = "addon.page-ready";
90
99
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -5542,7 +5551,7 @@ var ZodIssueCode = {
5542
5551
  var ZodFirstPartyTypeKind;
5543
5552
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5544
5553
  //#endregion
5545
- //#region ../types/dist/sleep-BegKjwUu.mjs
5554
+ //#region ../types/dist/sleep-BxjU_fJM.mjs
5546
5555
  /**
5547
5556
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5548
5557
  * window to float samples (D455).
@@ -7924,6 +7933,12 @@ function normalizeAddonInitResult(result) {
7924
7933
  if (Array.isArray(result)) return { providers: result };
7925
7934
  return result;
7926
7935
  }
7936
+ /**
7937
+ * The share-view scope kinds a `csv_*` share token can carry. The hub mints
7938
+ * and validates them (`share-token.service.ts` builds its enum from this
7939
+ * list); a data plane names the kinds it ACCEPTS in `share`.
7940
+ */
7941
+ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
7927
7942
  /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
7928
7943
  var PeerBytesTicketSchema = object({
7929
7944
  /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
@@ -12906,6 +12921,229 @@ var ConvertResultSchema = object({
12906
12921
  files: array(string()).readonly()
12907
12922
  })).readonly()
12908
12923
  });
12924
+ /**
12925
+ * Zod schemas for persisted record types.
12926
+ *
12927
+ * These schemas serve as the single source of truth for types that are
12928
+ * stored in the settings-store backend and retrieved via tRPC. They
12929
+ * replace the manual TypeScript interfaces with schema-derived types,
12930
+ * enabling runtime validation when reading from the backend.
12931
+ *
12932
+ * Convention: each schema is named `<Type>Schema`, the inferred TS type
12933
+ * keeps the original name for backward compatibility.
12934
+ */
12935
+ /**
12936
+ * Auth model (v2 — 2026-05-12 caps-only):
12937
+ *
12938
+ * Two principals:
12939
+ * - `isAdmin: true` — full bypass. Admin scope set is ignored.
12940
+ * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
12941
+ *
12942
+ * No role enum. There used to be `viewer / agent / scoped` and earlier
12943
+ * `super_admin` — collapsed onto the single boolean because every
12944
+ * non-admin caller is governed by the same scope-check rules; the
12945
+ * distinction was only ever used to route role-specific procedures
12946
+ * which we no longer have. CLI / webhook integrations are `cst_*` tokens
12947
+ * with `isAdmin: false` + an explicit scope list.
12948
+ */
12949
+ /** Per-method access flavour. Materialised on every cap method via codegen. */
12950
+ var MethodAccessSchema = _enum([
12951
+ "view",
12952
+ "create",
12953
+ "delete"
12954
+ ]);
12955
+ /**
12956
+ * A single scope grant. Three forms — checked in matcher with OR
12957
+ * semantics; the first match decides whether the call passes:
12958
+ *
12959
+ * - `category` — broadest. `target: 'device' | 'system'` selects every
12960
+ * cap with the matching `scope` declaration. New caps inherit grants
12961
+ * automatically — adding a device cap to the codebase makes it
12962
+ * reachable to every user with `category:device [...]` without a
12963
+ * re-grant. Use this for "family viewer" / "operator" tiers.
12964
+ *
12965
+ * - `addon` — every cap that ships inside a given addon id. Niche;
12966
+ * useful when an integration needs to interact with one specific
12967
+ * addon's surface regardless of how many caps it exports.
12968
+ *
12969
+ * - `capability` — the most precise form. Single cap name. Use for
12970
+ * integration tokens where you want to grant `addons.installPackage`
12971
+ * and nothing else.
12972
+ *
12973
+ * `access` is REQUIRED with min 1 element. `admin` bypass is the
12974
+ * "give me everything" escape; there is no wildcard scope.
12975
+ */
12976
+ var AllowedProviderSchema = union([literal("*"), array(string())]);
12977
+ var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
12978
+ var CapScopeSchema = _enum(["device", "system"]);
12979
+ /**
12980
+ * DeviceSelector (scope model v3 — 2026-08-12).
12981
+ *
12982
+ * A `device` grant no longer carries a frozen list of deviceIds. It carries
12983
+ * a SELECTOR the matcher resolves against the live fleet, so the grant can be
12984
+ * DYNAMIC: a `types:['camera']` selector automatically covers a camera added
12985
+ * AFTER the grant was minted — no re-grant, no re-login.
12986
+ *
12987
+ * - `all` — every device in the deployment. The broad viewer/operator
12988
+ * lever without a `category` grant (a `category` grant also covers device
12989
+ * caps that carry no deviceId; `all` is specifically the device set).
12990
+ * - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
12991
+ * grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
12992
+ * is NOT covered until the grant is edited.
12993
+ * - `types` — every device of a `DeviceType` (e.g. every `camera`).
12994
+ * DYNAMIC. A device that changes type, or a new device of the type,
12995
+ * re-resolves on the next request.
12996
+ * - `locations` — every device whose operator-assigned `location` label is
12997
+ * in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
12998
+ * null/unset location matches NO `locations` selector.
12999
+ */
13000
+ var DeviceSelectorSchema = discriminatedUnion("kind", [
13001
+ object({ kind: literal("all") }),
13002
+ object({
13003
+ kind: literal("ids"),
13004
+ ids: array(number().int()).min(1)
13005
+ }),
13006
+ object({
13007
+ kind: literal("types"),
13008
+ types: array(_enum(DeviceType)).min(1)
13009
+ }),
13010
+ object({
13011
+ kind: literal("locations"),
13012
+ locations: array(string().min(1)).min(1)
13013
+ })
13014
+ ]);
13015
+ var DeviceTokenScopeSchema = object({
13016
+ type: literal("device"),
13017
+ /** The device SET this grant covers — resolved against the live fleet. */
13018
+ selector: DeviceSelectorSchema,
13019
+ access: array(MethodAccessSchema).min(1),
13020
+ /**
13021
+ * Whether a grant on a PARENT device transparently covers its accessory
13022
+ * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
13023
+ * Direction is parent → children ONLY.
13024
+ *
13025
+ * Absent → the matcher DERIVES it from the access flavour: `view`
13026
+ * inherits (a camera viewer sees the camera's accessories), `create` /
13027
+ * `delete` do NOT (actuating/removing a child is an explicit act the
13028
+ * operator must grant on the child, not inherit from the parent). Set it
13029
+ * explicitly to override that default per grant.
13030
+ */
13031
+ includeLinked: boolean().optional()
13032
+ });
13033
+ /**
13034
+ * v2 → v3 lazy migration. A pre-v3 `device` grant carried
13035
+ * `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
13036
+ * `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
13037
+ * EVERY parse path — stored records AND the JWT-carried scope arrays
13038
+ * normalised at the request boundary ({@link normalizeTokenScopes} in
13039
+ * `device-selector.ts`). Chosen over a one-time DB migration because a
13040
+ * migration cannot reach a JWT already in a client's hands; parse-time
13041
+ * migration covers both without a flag day. No cast — the raw object is read
13042
+ * through `Reflect.get` (its static type is `unknown`).
13043
+ */
13044
+ function migrateLegacyTokenScope(raw) {
13045
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
13046
+ if (Reflect.get(raw, "type") !== "device") return raw;
13047
+ if (Reflect.get(raw, "selector") !== void 0) return raw;
13048
+ const targets = Reflect.get(raw, "targets");
13049
+ if (!Array.isArray(targets)) return raw;
13050
+ return {
13051
+ type: "device",
13052
+ selector: {
13053
+ kind: "ids",
13054
+ ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
13055
+ },
13056
+ access: Reflect.get(raw, "access")
13057
+ };
13058
+ }
13059
+ var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
13060
+ object({
13061
+ type: literal("category"),
13062
+ target: CapScopeSchema,
13063
+ access: array(MethodAccessSchema).min(1)
13064
+ }),
13065
+ object({
13066
+ type: literal("capability"),
13067
+ target: string(),
13068
+ access: array(MethodAccessSchema).min(1)
13069
+ }),
13070
+ object({
13071
+ type: literal("addon"),
13072
+ target: string(),
13073
+ access: array(MethodAccessSchema).min(1)
13074
+ }),
13075
+ DeviceTokenScopeSchema
13076
+ ]));
13077
+ object({
13078
+ id: string(),
13079
+ username: string(),
13080
+ passwordHash: string(),
13081
+ /**
13082
+ * Admin bypass. When true, the middleware skips the scope-access
13083
+ * check entirely. There is no other axis of privilege; the legacy
13084
+ * role enum collapsed onto this boolean in v2.
13085
+ */
13086
+ isAdmin: boolean().default(false),
13087
+ allowedProviders: AllowedProviderSchema,
13088
+ allowedDevices: AllowedDevicesSchema,
13089
+ /**
13090
+ * Scopes granted to this user. Admins bypass; their `scopes` is
13091
+ * ignored. Non-admins without scopes are locked out of every
13092
+ * protected call.
13093
+ */
13094
+ scopes: array(TokenScopeSchema).default([]),
13095
+ createdAt: number(),
13096
+ updatedAt: number()
13097
+ });
13098
+ object({
13099
+ id: string(),
13100
+ label: string(),
13101
+ isAdmin: boolean().default(false),
13102
+ allowedProviders: AllowedProviderSchema,
13103
+ allowedDevices: AllowedDevicesSchema,
13104
+ tokenHash: string(),
13105
+ tokenPrefix: string(),
13106
+ createdAt: number(),
13107
+ lastUsedAt: number().optional()
13108
+ });
13109
+ object({
13110
+ id: string(),
13111
+ userId: string(),
13112
+ name: string(),
13113
+ tokenHash: string(),
13114
+ tokenPrefix: string(),
13115
+ scopes: array(TokenScopeSchema),
13116
+ expiresAt: number().nullish(),
13117
+ lastUsedAt: number().nullish(),
13118
+ createdAt: number()
13119
+ });
13120
+ /** What a share-view grant lets its holder see. */
13121
+ var ShareViewGrantSchema = object({
13122
+ kind: _enum(SHARE_VIEW_KINDS),
13123
+ deviceIds: array(number().int().nonnegative()).min(1).max(64),
13124
+ /** Talk-back, opt-in at mint time; absent means off. */
13125
+ talk: boolean().optional()
13126
+ });
13127
+ var ShareViewScopeSchema = object({
13128
+ type: literal("share-view"),
13129
+ grant: ShareViewGrantSchema
13130
+ });
13131
+ var AddonRoutesScopeSchema = object({
13132
+ type: literal("addon-routes"),
13133
+ access: array(MethodAccessSchema).min(1)
13134
+ });
13135
+ var CredentialScopeSchema = union([
13136
+ literal("*"),
13137
+ ShareViewScopeSchema,
13138
+ AddonRoutesScopeSchema,
13139
+ TokenScopeSchema
13140
+ ]);
13141
+ object({
13142
+ kind: literal("credential"),
13143
+ sub: string().min(1),
13144
+ jti: string().min(16),
13145
+ scopes: array(CredentialScopeSchema).min(1)
13146
+ });
12909
13147
  _enum(["new-session", "step-up"]);
12910
13148
  /** The languages the hub's app landing pages are written in (D858). */
12911
13149
  var AppPageLangSchema = _enum(["en", "it"]);
@@ -13641,7 +13879,14 @@ var AlertSeveritySchema = _enum([
13641
13879
  ]);
13642
13880
  var AlertActionSchema = object({
13643
13881
  label: string(),
13644
- href: string().optional()
13882
+ href: string().optional(),
13883
+ /**
13884
+ * How `href` is followed. Absent / `app`: an admin-ui route (client-side).
13885
+ * `server`: a route the HUB serves (an addon HTTP route such as an OAuth
13886
+ * start) — the browser must leave the SPA with a full navigation, because
13887
+ * the SPA has no such route and would bounce to the home page.
13888
+ */
13889
+ navigation: _enum(["app", "server"]).optional()
13645
13890
  });
13646
13891
  var AlertStatusSchema = _enum([
13647
13892
  "active",
@@ -42066,6 +42311,9 @@ var NcSystemEventKindSchema = _enum([
42066
42311
  "server-updated",
42067
42312
  "export-completed",
42068
42313
  "backup-completed",
42314
+ "alert-critical",
42315
+ "alert-error",
42316
+ "alert-critical-resolved",
42069
42317
  "camera-online",
42070
42318
  "camera-offline",
42071
42319
  "camera-disabled",
@@ -44101,202 +44349,6 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
44101
44349
  auth: "admin"
44102
44350
  }), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
44103
44351
  /**
44104
- * Zod schemas for persisted record types.
44105
- *
44106
- * These schemas serve as the single source of truth for types that are
44107
- * stored in the settings-store backend and retrieved via tRPC. They
44108
- * replace the manual TypeScript interfaces with schema-derived types,
44109
- * enabling runtime validation when reading from the backend.
44110
- *
44111
- * Convention: each schema is named `<Type>Schema`, the inferred TS type
44112
- * keeps the original name for backward compatibility.
44113
- */
44114
- /**
44115
- * Auth model (v2 — 2026-05-12 caps-only):
44116
- *
44117
- * Two principals:
44118
- * - `isAdmin: true` — full bypass. Admin scope set is ignored.
44119
- * - `isAdmin: false` — every call gated by `scopes` (see TokenScope).
44120
- *
44121
- * No role enum. There used to be `viewer / agent / scoped` and earlier
44122
- * `super_admin` — collapsed onto the single boolean because every
44123
- * non-admin caller is governed by the same scope-check rules; the
44124
- * distinction was only ever used to route role-specific procedures
44125
- * which we no longer have. CLI / webhook integrations are `cst_*` tokens
44126
- * with `isAdmin: false` + an explicit scope list.
44127
- */
44128
- /** Per-method access flavour. Materialised on every cap method via codegen. */
44129
- var MethodAccessSchema = _enum([
44130
- "view",
44131
- "create",
44132
- "delete"
44133
- ]);
44134
- /**
44135
- * A single scope grant. Three forms — checked in matcher with OR
44136
- * semantics; the first match decides whether the call passes:
44137
- *
44138
- * - `category` — broadest. `target: 'device' | 'system'` selects every
44139
- * cap with the matching `scope` declaration. New caps inherit grants
44140
- * automatically — adding a device cap to the codebase makes it
44141
- * reachable to every user with `category:device [...]` without a
44142
- * re-grant. Use this for "family viewer" / "operator" tiers.
44143
- *
44144
- * - `addon` — every cap that ships inside a given addon id. Niche;
44145
- * useful when an integration needs to interact with one specific
44146
- * addon's surface regardless of how many caps it exports.
44147
- *
44148
- * - `capability` — the most precise form. Single cap name. Use for
44149
- * integration tokens where you want to grant `addons.installPackage`
44150
- * and nothing else.
44151
- *
44152
- * `access` is REQUIRED with min 1 element. `admin` bypass is the
44153
- * "give me everything" escape; there is no wildcard scope.
44154
- */
44155
- var AllowedProviderSchema = union([literal("*"), array(string())]);
44156
- var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
44157
- var CapScopeSchema = _enum(["device", "system"]);
44158
- /**
44159
- * DeviceSelector (scope model v3 — 2026-08-12).
44160
- *
44161
- * A `device` grant no longer carries a frozen list of deviceIds. It carries
44162
- * a SELECTOR the matcher resolves against the live fleet, so the grant can be
44163
- * DYNAMIC: a `types:['camera']` selector automatically covers a camera added
44164
- * AFTER the grant was minted — no re-grant, no re-login.
44165
- *
44166
- * - `all` — every device in the deployment. The broad viewer/operator
44167
- * lever without a `category` grant (a `category` grant also covers device
44168
- * caps that carry no deviceId; `all` is specifically the device set).
44169
- * - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
44170
- * grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
44171
- * is NOT covered until the grant is edited.
44172
- * - `types` — every device of a `DeviceType` (e.g. every `camera`).
44173
- * DYNAMIC. A device that changes type, or a new device of the type,
44174
- * re-resolves on the next request.
44175
- * - `locations` — every device whose operator-assigned `location` label is
44176
- * in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
44177
- * null/unset location matches NO `locations` selector.
44178
- */
44179
- var DeviceSelectorSchema = discriminatedUnion("kind", [
44180
- object({ kind: literal("all") }),
44181
- object({
44182
- kind: literal("ids"),
44183
- ids: array(number().int()).min(1)
44184
- }),
44185
- object({
44186
- kind: literal("types"),
44187
- types: array(_enum(DeviceType)).min(1)
44188
- }),
44189
- object({
44190
- kind: literal("locations"),
44191
- locations: array(string().min(1)).min(1)
44192
- })
44193
- ]);
44194
- var DeviceTokenScopeSchema = object({
44195
- type: literal("device"),
44196
- /** The device SET this grant covers — resolved against the live fleet. */
44197
- selector: DeviceSelectorSchema,
44198
- access: array(MethodAccessSchema).min(1),
44199
- /**
44200
- * Whether a grant on a PARENT device transparently covers its accessory
44201
- * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
44202
- * Direction is parent → children ONLY.
44203
- *
44204
- * Absent → the matcher DERIVES it from the access flavour: `view`
44205
- * inherits (a camera viewer sees the camera's accessories), `create` /
44206
- * `delete` do NOT (actuating/removing a child is an explicit act the
44207
- * operator must grant on the child, not inherit from the parent). Set it
44208
- * explicitly to override that default per grant.
44209
- */
44210
- includeLinked: boolean().optional()
44211
- });
44212
- /**
44213
- * v2 → v3 lazy migration. A pre-v3 `device` grant carried
44214
- * `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
44215
- * `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
44216
- * EVERY parse path — stored records AND the JWT-carried scope arrays
44217
- * normalised at the request boundary ({@link normalizeTokenScopes} in
44218
- * `device-selector.ts`). Chosen over a one-time DB migration because a
44219
- * migration cannot reach a JWT already in a client's hands; parse-time
44220
- * migration covers both without a flag day. No cast — the raw object is read
44221
- * through `Reflect.get` (its static type is `unknown`).
44222
- */
44223
- function migrateLegacyTokenScope(raw) {
44224
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
44225
- if (Reflect.get(raw, "type") !== "device") return raw;
44226
- if (Reflect.get(raw, "selector") !== void 0) return raw;
44227
- const targets = Reflect.get(raw, "targets");
44228
- if (!Array.isArray(targets)) return raw;
44229
- return {
44230
- type: "device",
44231
- selector: {
44232
- kind: "ids",
44233
- ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
44234
- },
44235
- access: Reflect.get(raw, "access")
44236
- };
44237
- }
44238
- var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
44239
- object({
44240
- type: literal("category"),
44241
- target: CapScopeSchema,
44242
- access: array(MethodAccessSchema).min(1)
44243
- }),
44244
- object({
44245
- type: literal("capability"),
44246
- target: string(),
44247
- access: array(MethodAccessSchema).min(1)
44248
- }),
44249
- object({
44250
- type: literal("addon"),
44251
- target: string(),
44252
- access: array(MethodAccessSchema).min(1)
44253
- }),
44254
- DeviceTokenScopeSchema
44255
- ]));
44256
- object({
44257
- id: string(),
44258
- username: string(),
44259
- passwordHash: string(),
44260
- /**
44261
- * Admin bypass. When true, the middleware skips the scope-access
44262
- * check entirely. There is no other axis of privilege; the legacy
44263
- * role enum collapsed onto this boolean in v2.
44264
- */
44265
- isAdmin: boolean().default(false),
44266
- allowedProviders: AllowedProviderSchema,
44267
- allowedDevices: AllowedDevicesSchema,
44268
- /**
44269
- * Scopes granted to this user. Admins bypass; their `scopes` is
44270
- * ignored. Non-admins without scopes are locked out of every
44271
- * protected call.
44272
- */
44273
- scopes: array(TokenScopeSchema).default([]),
44274
- createdAt: number(),
44275
- updatedAt: number()
44276
- });
44277
- object({
44278
- id: string(),
44279
- label: string(),
44280
- isAdmin: boolean().default(false),
44281
- allowedProviders: AllowedProviderSchema,
44282
- allowedDevices: AllowedDevicesSchema,
44283
- tokenHash: string(),
44284
- tokenPrefix: string(),
44285
- createdAt: number(),
44286
- lastUsedAt: number().optional()
44287
- });
44288
- object({
44289
- id: string(),
44290
- userId: string(),
44291
- name: string(),
44292
- tokenHash: string(),
44293
- tokenPrefix: string(),
44294
- scopes: array(TokenScopeSchema),
44295
- expiresAt: number().nullish(),
44296
- lastUsedAt: number().nullish(),
44297
- createdAt: number()
44298
- });
44299
- /**
44300
44352
  * `oauth-integration` — a collection cap by which an addon declares
44301
44353
  * itself a linkable OAuth client of the hub's core `/oauth2/*` server.
44302
44354
  * Each provider returns a static descriptor; the core enumerates them
@@ -51111,7 +51163,10 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
51111
51163
  }), method(object({ jobId: string().optional() }), StorageMigrationJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
51112
51164
  kind: "mutation",
51113
51165
  auth: "admin"
51114
- }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
51166
+ }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({ locationIds: array(string()).readonly() }), object({
51167
+ clear: boolean(),
51168
+ repointLocationId: string().optional()
51169
+ }), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
51115
51170
  kind: "mutation",
51116
51171
  auth: "admin"
51117
51172
  }), method(StorageCleanupInputSchema, object({ jobId: string() }), {
@@ -54556,6 +54611,11 @@ object({
54556
54611
  *
54557
54612
  * `kind: 'poll'` — cloud REST APIs typically have no push channel; the
54558
54613
  * provider refreshes the slice on an interval and eagerly after commands.
54614
+ *
54615
+ * UNKNOWN IS NOT OFF. Every field the appliance reports separately is
54616
+ * nullable: `null` means "not known right now" (the appliance has not said,
54617
+ * the link is down, or the description does not carry it) and is never
54618
+ * rendered or exported as `false` / `0`. `availability` says why.
54559
54619
  */
54560
54620
  var DishwasherStateSchema = _enum([
54561
54621
  "idle",
@@ -54565,112 +54625,227 @@ var DishwasherStateSchema = _enum([
54565
54625
  "error",
54566
54626
  "unavailable"
54567
54627
  ]);
54628
+ /**
54629
+ * The appliance's own operation state, uncollapsed. `state` above is the coarse
54630
+ * view for compact surfaces; this is what the appliance said.
54631
+ */
54632
+ var DishwasherOperationStateSchema = _enum([
54633
+ "Inactive",
54634
+ "Ready",
54635
+ "DelayedStart",
54636
+ "Run",
54637
+ "Pause",
54638
+ "ActionRequired",
54639
+ "Finished",
54640
+ "Error",
54641
+ "Aborting"
54642
+ ]);
54643
+ /**
54644
+ * Why fields may be `null`:
54645
+ * - `available` — the link is up and the description is loaded;
54646
+ * - `offline` — the local link is down (values are unknown, not last-known);
54647
+ * - `description-missing` — the link is up but the appliance's description could not
54648
+ * be loaded, so its values cannot be decoded.
54649
+ */
54650
+ var DishwasherAvailabilitySchema = _enum([
54651
+ "available",
54652
+ "offline",
54653
+ "description-missing"
54654
+ ]);
54568
54655
  var DishwasherConsumableLevelSchema = _enum([
54569
54656
  "ok",
54570
54657
  "low",
54571
54658
  "empty"
54572
54659
  ]);
54573
54660
  object({
54661
+ availability: DishwasherAvailabilitySchema,
54662
+ /** Coarse state for compact surfaces; `unavailable` whenever the appliance state is not known. */
54574
54663
  state: DishwasherStateSchema,
54575
- /** Active program id/name, or null when none selected / unknown. */
54664
+ /** The appliance's operation state as reported; null when not known. */
54665
+ operationState: DishwasherOperationStateSchema.nullable(),
54666
+ /** Feature key of the ACTIVE (running) program, or null when none runs / not known. */
54576
54667
  program: string().nullable(),
54577
- /** Program tokens the hardware accepts — drives the UI selector. */
54668
+ /** Feature key of the SELECTED program (what Start would run), or null when none / not known. */
54669
+ selectedProgram: string().nullable(),
54670
+ /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
54578
54671
  availablePrograms: array(string()),
54579
- /** Minutes remaining on the active cycle, or null when not running. */
54580
- remainingMinutes: number().int().nonnegative().nullable(),
54672
+ /** Seconds remaining on the active cycle, or null when not running / not known. */
54673
+ remainingSeconds: number().int().nonnegative().nullable(),
54674
+ /** Seconds the delayed start is set to (time until the cycle starts), or null. */
54675
+ delayedStartSeconds: number().int().nonnegative().nullable(),
54581
54676
  /** Cycle completion 0..100, or null when not applicable. */
54582
54677
  progressPercent: number().min(0).max(100).nullable(),
54583
- /** True when the door is reported open. */
54584
- doorOpen: boolean(),
54585
- /** True when remote start is allowed (door closed, power on, etc.). */
54586
- remoteStartAllowed: boolean(),
54587
- /** Main power / switched-on state. */
54588
- powerOn: boolean(),
54678
+ /** True when the door is reported open; null when the door state is not known. */
54679
+ doorOpen: boolean().nullable(),
54680
+ /** True when the door is latched (locked) during a cycle; null when not reported. */
54681
+ doorLocked: boolean().nullable(),
54682
+ /** True when remote start is allowed (door closed, power on, etc.); null when not known. */
54683
+ remoteStartAllowed: boolean().nullable(),
54684
+ /** True when the appliance accepts remote control now; null when not known. */
54685
+ remoteControlActive: boolean().nullable(),
54686
+ /** True while somebody operates the appliance at its panel; null when not reported. */
54687
+ localControlActive: boolean().nullable(),
54688
+ /** Main power / switched-on state; null when not known. */
54689
+ powerOn: boolean().nullable(),
54690
+ /** The raw power state (`On`, `Off`, `Standby`, …); null when not known. */
54691
+ powerState: string().nullable(),
54589
54692
  saltLevel: DishwasherConsumableLevelSchema.nullable(),
54590
54693
  rinseAidLevel: DishwasherConsumableLevelSchema.nullable(),
54591
- halfLoad: boolean(),
54592
- hygienePlus: boolean(),
54694
+ /** Option states: null when the appliance does not expose the option or has not said. */
54695
+ halfLoad: boolean().nullable(),
54696
+ hygienePlus: boolean().nullable(),
54593
54697
  /** Extra dry option when the appliance exposes it. */
54594
- extraDry: boolean(),
54698
+ extraDry: boolean().nullable(),
54595
54699
  /** Intensive zone (lower basket) when the appliance exposes it. */
54596
- intensiveZone: boolean(),
54700
+ intensiveZone: boolean().nullable(),
54597
54701
  /** VarioSpeed / VarioSpeed+ when the appliance exposes it. */
54598
- varioSpeedPlus: boolean(),
54599
- /** Child lock (controls disabled on the appliance). */
54600
- childLock: boolean(),
54702
+ varioSpeedPlus: boolean().nullable(),
54703
+ /** Child lock (controls disabled on the appliance); null when not known. */
54704
+ childLock: boolean().nullable(),
54705
+ /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
54706
+ activeEvents: array(string()),
54707
+ /** Numeric error code when the appliance reports one; null otherwise. */
54601
54708
  errorCode: number().nullable(),
54709
+ /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
54602
54710
  errorLabel: string().nullable(),
54711
+ /** Ms epoch of the last value received from the appliance; 0 = never. */
54603
54712
  lastFetchedAt: number()
54604
54713
  });
54605
- DeviceType.Dishwasher, method(object({ deviceId: number().int().nonnegative() }), _void(), {
54714
+ var DeviceIdSchema = number().int().nonnegative();
54715
+ /** A value a program option or setting can take. */
54716
+ var DishwasherValueSchema = union([
54717
+ boolean(),
54718
+ string(),
54719
+ number()
54720
+ ]);
54721
+ var DishwasherAccessSchema = _enum([
54722
+ "none",
54723
+ "read",
54724
+ "readWrite",
54725
+ "writeOnly",
54726
+ "readStatic"
54727
+ ]);
54728
+ /**
54729
+ * One option or setting the appliance declares, with the constraints its description
54730
+ * gives. A `readWrite` descriptor that is `available` is what `setOption` accepts.
54731
+ */
54732
+ var DishwasherOptionDescriptorSchema = object({
54733
+ /** Feature key (`Dishcare.Dishwasher.Option.ExtraDry`). */
54734
+ key: string(),
54735
+ /** Last segment of the key, for display. */
54736
+ name: string(),
54737
+ type: _enum([
54738
+ "boolean",
54739
+ "number",
54740
+ "enum",
54741
+ "string"
54742
+ ]),
54743
+ access: DishwasherAccessSchema,
54744
+ /** The appliance reports this entity as currently available. */
54745
+ available: boolean(),
54746
+ /** Unit the description declares for a number; null when it declares none. */
54747
+ unit: string().nullable(),
54748
+ min: number().nullable(),
54749
+ max: number().nullable(),
54750
+ step: number().nullable(),
54751
+ /** Allowed members of an enum; null for other types. */
54752
+ enumValues: array(string()).nullable(),
54753
+ defaultValue: DishwasherValueSchema.nullable()
54754
+ });
54755
+ var DishwasherProgramSchema = object({
54756
+ /** Feature key (`Dishcare.Dishwasher.Program.Eco50`) — what `setProgram` / `start` take. */
54757
+ key: string(),
54758
+ name: string(),
54759
+ /** The appliance currently offers this program. */
54760
+ available: boolean(),
54761
+ /** The options this program lists, with the program-level access / availability / default. */
54762
+ options: array(DishwasherOptionDescriptorSchema)
54763
+ });
54764
+ var DishwasherOptionsSchema = object({
54765
+ availability: DishwasherAvailabilitySchema,
54766
+ /** What the appliance can be told to do, from the commands its description declares. */
54767
+ supportsStart: boolean(),
54768
+ supportsPause: boolean(),
54769
+ supportsResume: boolean(),
54770
+ supportsStop: boolean(),
54771
+ supportsPower: boolean(),
54772
+ /** Every program the description lists, with its options. */
54773
+ programs: array(DishwasherProgramSchema),
54774
+ /** Every option and setting entity the description lists (programs reference these by key). */
54775
+ options: array(DishwasherOptionDescriptorSchema)
54776
+ });
54777
+ DeviceType.Dishwasher, method(object({
54778
+ deviceId: DeviceIdSchema,
54779
+ program: string().min(1).optional(),
54780
+ options: record(string(), DishwasherValueSchema).optional()
54781
+ }), _void(), {
54606
54782
  kind: "mutation",
54607
54783
  auth: "protected"
54608
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
54784
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
54609
54785
  kind: "mutation",
54610
54786
  auth: "protected"
54611
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
54787
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
54788
+ kind: "mutation",
54789
+ auth: "protected"
54790
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
54612
54791
  kind: "mutation",
54613
54792
  auth: "protected"
54614
54793
  }), method(object({
54615
- deviceId: number().int().nonnegative(),
54794
+ deviceId: DeviceIdSchema,
54616
54795
  program: string().min(1)
54617
54796
  }), _void(), {
54618
54797
  kind: "mutation",
54619
54798
  auth: "protected"
54620
54799
  }), method(object({
54621
- deviceId: number().int().nonnegative(),
54800
+ deviceId: DeviceIdSchema,
54622
54801
  on: boolean()
54623
54802
  }), _void(), {
54624
54803
  kind: "mutation",
54625
54804
  auth: "protected"
54626
54805
  }), method(object({
54627
- deviceId: number().int().nonnegative(),
54806
+ deviceId: DeviceIdSchema,
54628
54807
  on: boolean()
54629
54808
  }), _void(), {
54630
54809
  kind: "mutation",
54631
54810
  auth: "protected"
54632
54811
  }), method(object({
54633
- deviceId: number().int().nonnegative(),
54812
+ deviceId: DeviceIdSchema,
54634
54813
  on: boolean()
54635
54814
  }), _void(), {
54636
54815
  kind: "mutation",
54637
54816
  auth: "protected"
54638
54817
  }), method(object({
54639
- deviceId: number().int().nonnegative(),
54818
+ deviceId: DeviceIdSchema,
54640
54819
  on: boolean()
54641
54820
  }), _void(), {
54642
54821
  kind: "mutation",
54643
54822
  auth: "protected"
54644
54823
  }), method(object({
54645
- deviceId: number().int().nonnegative(),
54824
+ deviceId: DeviceIdSchema,
54646
54825
  on: boolean()
54647
54826
  }), _void(), {
54648
54827
  kind: "mutation",
54649
54828
  auth: "protected"
54650
54829
  }), method(object({
54651
- deviceId: number().int().nonnegative(),
54830
+ deviceId: DeviceIdSchema,
54652
54831
  on: boolean()
54653
54832
  }), _void(), {
54654
54833
  kind: "mutation",
54655
54834
  auth: "protected"
54656
54835
  }), method(object({
54657
- deviceId: number().int().nonnegative(),
54836
+ deviceId: DeviceIdSchema,
54658
54837
  on: boolean()
54659
54838
  }), _void(), {
54660
54839
  kind: "mutation",
54661
54840
  auth: "protected"
54662
54841
  }), method(object({
54663
- deviceId: number().int().nonnegative(),
54842
+ deviceId: DeviceIdSchema,
54664
54843
  key: string().min(1),
54665
- value: union([
54666
- boolean(),
54667
- string(),
54668
- number()
54669
- ])
54844
+ value: DishwasherValueSchema
54670
54845
  }), _void(), {
54671
54846
  kind: "mutation",
54672
54847
  auth: "protected"
54673
- });
54848
+ }), method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" });
54674
54849
  object({
54675
54850
  /** Ms epoch of the last press. null = never observed since this provider started. */
54676
54851
  lastPressedAt: number().nullable(),
@@ -64730,6 +64905,12 @@ Object.freeze({
64730
64905
  addonId: null,
64731
64906
  access: "view"
64732
64907
  },
64908
+ "dishwasher.getOptions": {
64909
+ capName: "dishwasher",
64910
+ capScope: "device",
64911
+ addonId: null,
64912
+ access: "view"
64913
+ },
64733
64914
  "dishwasher.getStatus": {
64734
64915
  capName: "dishwasher",
64735
64916
  capScope: "device",
@@ -64742,6 +64923,12 @@ Object.freeze({
64742
64923
  addonId: null,
64743
64924
  access: "create"
64744
64925
  },
64926
+ "dishwasher.resume": {
64927
+ capName: "dishwasher",
64928
+ capScope: "device",
64929
+ addonId: null,
64930
+ access: "create"
64931
+ },
64745
64932
  "dishwasher.setChildLock": {
64746
64933
  capName: "dishwasher",
64747
64934
  capScope: "device",
@@ -69242,6 +69429,12 @@ Object.freeze({
69242
69429
  addonId: null,
69243
69430
  access: "view"
69244
69431
  },
69432
+ "storageMigration.moverClearance": {
69433
+ capName: "storage-migration",
69434
+ capScope: "system",
69435
+ addonId: null,
69436
+ access: "view"
69437
+ },
69245
69438
  "storageMigration.movers": {
69246
69439
  capName: "storage-migration",
69247
69440
  capScope: "system",
@@ -71435,6 +71628,11 @@ Object.freeze({
71435
71628
  form: "single",
71436
71629
  optional: false
71437
71630
  }],
71631
+ "dishwasher.getOptions": [{
71632
+ name: "deviceId",
71633
+ form: "single",
71634
+ optional: false
71635
+ }],
71438
71636
  "dishwasher.getStatus": [{
71439
71637
  name: "deviceId",
71440
71638
  form: "single",
@@ -71445,6 +71643,11 @@ Object.freeze({
71445
71643
  form: "single",
71446
71644
  optional: false
71447
71645
  }],
71646
+ "dishwasher.resume": [{
71647
+ name: "deviceId",
71648
+ form: "single",
71649
+ optional: false
71650
+ }],
71448
71651
  "dishwasher.setChildLock": [{
71449
71652
  name: "deviceId",
71450
71653
  form: "single",
@@ -73531,6 +73734,9 @@ new Map(Object.entries({
73531
73734
  "wrapper-update-available": "system",
73532
73735
  "backup-completed": "system",
73533
73736
  "export-completed": "system",
73737
+ "alert-critical": "system",
73738
+ "alert-error": "system",
73739
+ "alert-critical-resolved": "system",
73534
73740
  "storage-location-low": "system",
73535
73741
  "storage-location-critical": "system",
73536
73742
  "storage-location-ok": "system",
@@ -73655,6 +73861,16 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
73655
73861
  "storage-location-ok"
73656
73862
  ]
73657
73863
  },
73864
+ {
73865
+ id: "alerts",
73866
+ family: "system",
73867
+ label: "Alerts",
73868
+ kinds: [
73869
+ "alert-critical",
73870
+ "alert-error",
73871
+ "alert-critical-resolved"
73872
+ ]
73873
+ },
73658
73874
  {
73659
73875
  id: "alarm",
73660
73876
  family: "alarm",
@@ -73699,6 +73915,21 @@ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.ent
73699
73915
  return [[descriptor.kind, field]];
73700
73916
  })));
73701
73917
  Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
73918
+ object({
73919
+ phase: _enum(["raised", "resolved"]),
73920
+ episodeId: string().min(1),
73921
+ alertId: string().min(1),
73922
+ severity: AlertSeveritySchema,
73923
+ category: string(),
73924
+ title: string(),
73925
+ message: string(),
73926
+ sourceType: string().optional(),
73927
+ sourceId: string().optional(),
73928
+ nodeId: string().optional(),
73929
+ /** The alert's own action link (where the operator acts on it), when it has one. */
73930
+ actionUrl: string().optional(),
73931
+ at: number()
73932
+ });
73702
73933
  var RULE_BODY_ONLY = ["body"];
73703
73934
  var RULE_TEXT = ["title", "body"];
73704
73935
  var BATTERY = ["device-battery-low", "device-battery-normal"];
@@ -73708,6 +73939,11 @@ var TANK = [
73708
73939
  "device-tank-full",
73709
73940
  "device-tank-normal"
73710
73941
  ];
73942
+ var ALERT_EPISODE = [
73943
+ "alert-critical",
73944
+ "alert-error",
73945
+ "alert-critical-resolved"
73946
+ ];
73711
73947
  var UPDATES = [
73712
73948
  "addon-update-available",
73713
73949
  "server-update-available",
@@ -73900,14 +74136,14 @@ var STATE_SUMMARY_VARS = [
73900
74136
  ([...RULE_TEXT], [...CONSUMABLE]),
73901
74137
  ([...RULE_TEXT], [...TANK]),
73902
74138
  [...RULE_TEXT],
73903
- ([...RULE_TEXT], [...UPDATES]),
73904
- ([...RULE_TEXT], [...UPDATES]),
73905
- [...RULE_TEXT],
74139
+ ([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
74140
+ ([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
73906
74141
  [...RULE_TEXT],
73907
74142
  [...RULE_TEXT],
73908
74143
  [...RULE_TEXT],
73909
74144
  [...RULE_TEXT],
73910
74145
  [...RULE_TEXT],
74146
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
73911
74147
  [...RULE_TEXT],
73912
74148
  [...RULE_TEXT],
73913
74149
  [...RULE_TEXT],
@@ -73925,7 +74161,11 @@ var STATE_SUMMARY_VARS = [
73925
74161
  [...RULE_TEXT],
73926
74162
  [...RULE_TEXT],
73927
74163
  [...RULE_TEXT],
73928
- [...RULE_TEXT],
74164
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
74165
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
74166
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
74167
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
74168
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
73929
74169
  [...RULE_TEXT],
73930
74170
  [...RULE_TEXT],
73931
74171
  [...RULE_TEXT],
@@ -74463,6 +74703,37 @@ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
74463
74703
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
74464
74704
  DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
74465
74705
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
74706
+ /**
74707
+ * Where an OAuth round-trip ends: the admin-ui completion route
74708
+ * (`/sso-complete`, the page every popup sign-in already lands on), with the
74709
+ * OUTCOME in the URL fragment.
74710
+ *
74711
+ * The outcome is a status plus a reason CODE from a closed list. It never
74712
+ * carries a token, a code, a client secret or a free-form error message: the
74713
+ * page words each code itself, and an unknown code reads as `unknown` rather
74714
+ * than being echoed. Fragment only, so nothing here reaches a server log.
74715
+ *
74716
+ * Pure and browser-safe.
74717
+ */
74718
+ /** The admin-ui route the hub's OAuth completions land on (relative to its mount). */
74719
+ var OAUTH_LANDING_PATH = "/sso-complete";
74720
+ /**
74721
+ * The landing URL for `outcome`. `origin` is the hub address the flow already
74722
+ * runs on (from the `local-network` palette role, never a literal); `null`
74723
+ * gives a hub-relative URL.
74724
+ */
74725
+ function oauthLandingUrl(origin, outcome) {
74726
+ const params = new URLSearchParams({
74727
+ return: "oauth",
74728
+ status: outcome.status
74729
+ });
74730
+ if (outcome.addonId !== void 0) params.set("addon", outcome.addonId);
74731
+ if (outcome.brokerId !== void 0) params.set("broker", outcome.brokerId);
74732
+ if (outcome.integration !== void 0) params.set("integration", outcome.integration);
74733
+ if (outcome.status === "error") params.set("reason", outcome.reason ?? "unknown");
74734
+ if (outcome.action === "disconnect") params.set("action", "disconnect");
74735
+ return `${origin === null ? "" : origin.replace(/\/+$/, "")}${OAUTH_LANDING_PATH}#${params.toString()}`;
74736
+ }
74466
74737
  var MB = 1024 * 1024;
74467
74738
  1024 * MB, 3072 * MB;
74468
74739
  //#endregion
@@ -74510,13 +74781,22 @@ var DEFAULT_OAUTH_STATE = {
74510
74781
  accessExpiresAt: 0,
74511
74782
  accountEmail: ""
74512
74783
  };
74513
- var GoogleOauthAccountsStateSchema = object({ accounts: array(object({
74784
+ /** Per-broker OAuth tokens (durable state `oauthAccounts`). */
74785
+ var GoogleOauthAccountSchema = object({
74514
74786
  id: string().min(1),
74515
74787
  refreshToken: string().default("").meta({ secret: true }),
74516
74788
  accessToken: string().default("").meta({ secret: true }),
74517
74789
  accessExpiresAt: number().default(0),
74518
- accountEmail: string().default("")
74519
- })).default([]) });
74790
+ accountEmail: string().default(""),
74791
+ /**
74792
+ * Google refused the refresh token (`invalid_grant`): it is dead and only a
74793
+ * new sign-in replaces it. Set where the refusal is seen, cleared by the
74794
+ * OAuth callback that stores a fresh token — so the status, the Sign-in
74795
+ * button and the alert all read the same fact.
74796
+ */
74797
+ needsReauth: boolean().default(false)
74798
+ });
74799
+ var GoogleOauthAccountsStateSchema = object({ accounts: array(GoogleOauthAccountSchema).default([]) });
74520
74800
  var GOOGLE_BROKER_KIND = "google";
74521
74801
  var GOOGLE_BROKER_KIND_LABEL = "Google";
74522
74802
  var GOOGLE_CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
@@ -74605,7 +74885,8 @@ function migrateLegacyToBrokers(rawConfig, legacyOauth) {
74605
74885
  refreshToken: legacyOauth.refreshToken,
74606
74886
  accessToken: legacyOauth.accessToken,
74607
74887
  accessExpiresAt: legacyOauth.accessExpiresAt,
74608
- accountEmail: legacyOauth.accountEmail
74888
+ accountEmail: legacyOauth.accountEmail,
74889
+ needsReauth: false
74609
74890
  }] : [];
74610
74891
  return {
74611
74892
  brokers: [entry],
@@ -74640,7 +74921,10 @@ var GoogleBrokerRegistry = class {
74640
74921
  oauthById = /* @__PURE__ */ new Map();
74641
74922
  seedFrom(entries, accounts) {
74642
74923
  this.entries = entries.map((e) => GoogleBrokerEntrySchema.parse(e));
74643
- this.oauthById = new Map(accounts.map((a) => [a.id, { ...a }]));
74924
+ this.oauthById = new Map(accounts.map((a) => {
74925
+ const account = GoogleOauthAccountSchema.parse(a);
74926
+ return [account.id, account];
74927
+ }));
74644
74928
  this.seedCounterFromEntries(this.entries);
74645
74929
  }
74646
74930
  listEntries() {
@@ -74656,14 +74940,28 @@ var GoogleBrokerRegistry = class {
74656
74940
  return [...this.oauthById.values()];
74657
74941
  }
74658
74942
  setOauth(account) {
74659
- this.oauthById.set(account.id, { ...account });
74943
+ const parsed = GoogleOauthAccountSchema.parse(account);
74944
+ this.oauthById.set(parsed.id, parsed);
74660
74945
  }
74661
74946
  removeOauth(id) {
74662
74947
  this.oauthById.delete(id);
74663
74948
  }
74664
74949
  isConnected(id) {
74665
74950
  const o = this.oauthById.get(id);
74666
- return o !== void 0 && o.refreshToken.length > 0;
74951
+ return o !== void 0 && o.refreshToken.length > 0 && !o.needsReauth;
74952
+ }
74953
+ /** A refresh token Google refused: kept (a sign-in replaces it) but not usable. */
74954
+ needsReauth(id) {
74955
+ return this.oauthById.get(id)?.needsReauth === true;
74956
+ }
74957
+ markNeedsReauth(id) {
74958
+ const o = this.oauthById.get(id);
74959
+ if (o === void 0 || o.needsReauth) return false;
74960
+ this.oauthById.set(id, {
74961
+ ...o,
74962
+ needsReauth: true
74963
+ });
74964
+ return true;
74667
74965
  }
74668
74966
  allocateId() {
74669
74967
  const id = `google_${String(this.nextIdCounter).padStart(3, "0")}`;
@@ -74713,18 +75011,19 @@ var GoogleBrokerRegistry = class {
74713
75011
  toBrokerInfo(entry, addonId) {
74714
75012
  const oauth = this.oauthById.get(entry.id);
74715
75013
  const connected = this.isConnected(entry.id);
75014
+ const reauth = this.needsReauth(entry.id);
74716
75015
  return {
74717
75016
  id: entry.id,
74718
75017
  addonId,
74719
75018
  name: entry.name,
74720
75019
  kind: GOOGLE_BROKER_KIND,
74721
- status: connected ? "connected" : "disconnected",
75020
+ status: connected ? "connected" : reauth ? "auth-failed" : "disconnected",
74722
75021
  info: {
74723
75022
  accountEmail: oauth?.accountEmail ?? "",
74724
75023
  hasClient: entry.clientId.length > 0
74725
75024
  },
74726
75025
  lastCheckedAt: connected ? Date.now() : null,
74727
- error: connected ? null : entry.clientId.length === 0 ? "Configure Client ID / secret, then Connect Google" : "Not connected — open Connect Google on this broker"
75026
+ error: connected ? null : reauth ? "Google session expired — sign in again" : entry.clientId.length === 0 ? "Configure Client ID / secret, then Connect Google" : "Not connected — open Connect Google on this broker"
74728
75027
  };
74729
75028
  }
74730
75029
  listBrokerInfos(addonId, kind) {
@@ -74776,7 +75075,7 @@ function consoleLinksHtml$1() {
74776
75075
  ].join("");
74777
75076
  }
74778
75077
  function introHtml(cap, publicOrigin) {
74779
- const redirect = escapeHtml$2(redirectUriDisplay(publicOrigin));
75078
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
74780
75079
  const console = consoleLinksHtml$1();
74781
75080
  if (cap === "smtp-provider") return [
74782
75081
  "<ol>",
@@ -74809,7 +75108,7 @@ function introHtml(cap, publicOrigin) {
74809
75108
  ].join("");
74810
75109
  }
74811
75110
  function connectHtml(cap, publicOrigin) {
74812
- const redirect = escapeHtml$2(redirectUriDisplay(publicOrigin));
75111
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
74813
75112
  if (cap === "smtp-provider") return [
74814
75113
  `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
74815
75114
  "Connect requests <code>gmail.send</code> (and Calendar read for the Calendars page). ",
@@ -74829,7 +75128,7 @@ function connectHtml(cap, publicOrigin) {
74829
75128
  `Console: ${consoleLinksHtml$1()}.`
74830
75129
  ].join("");
74831
75130
  }
74832
- function escapeHtml$2(s) {
75131
+ function escapeHtml$1(s) {
74833
75132
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
74834
75133
  }
74835
75134
  //#endregion
@@ -74848,7 +75147,7 @@ function consoleLinksHtml() {
74848
75147
  }
74849
75148
  /** Setup steps shown on Add / Edit — includes Audience / Test users. */
74850
75149
  function googleBrokerSetupHtml(publicOrigin) {
74851
- const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
75150
+ const redirect = escapeHtml(redirectUriDisplay(publicOrigin));
74852
75151
  return [
74853
75152
  "<ol>",
74854
75153
  `<li>Open Google Cloud Console (${consoleLinksHtml()}). Create a <strong>new project</strong> (or pick an existing one) from the project picker at the top.</li>`,
@@ -74924,7 +75223,7 @@ function buildGoogleBrokerSettingsSchema(publicOrigin) {
74924
75223
  }
74925
75224
  /** Connectivity Calendars / Email pages — calendar import picker lives here (not on broker Add). */
74926
75225
  function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
74927
- const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
75226
+ const redirect = escapeHtml(redirectUriDisplay(publicOrigin));
74928
75227
  if (cap === "calendar-source") return { sections: [{
74929
75228
  id: "setup-intro",
74930
75229
  title: "Google brokers",
@@ -74982,7 +75281,7 @@ function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
74982
75281
  }]
74983
75282
  }] };
74984
75283
  }
74985
- function escapeHtml$1(s) {
75284
+ function escapeHtml(s) {
74986
75285
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
74987
75286
  }
74988
75287
  //#endregion
@@ -75277,7 +75576,6 @@ function isInvalidGrantRefreshError(err) {
75277
75576
  return (err instanceof Error ? err.message : String(err)).includes("invalid_grant");
75278
75577
  }
75279
75578
  function buildGoogleBrokerReconnectAlert(brokerId, brokerName) {
75280
- const pushLine = `${brokerName}: Google session expired — sign in again`;
75281
75579
  return {
75282
75580
  id: googleBrokerReconnectAlertId(brokerId),
75283
75581
  category: "google.broker-reconnect",
@@ -75286,7 +75584,8 @@ function buildGoogleBrokerReconnectAlert(brokerId, brokerName) {
75286
75584
  message: `${brokerName} needs you to sign in to Google again.`,
75287
75585
  action: {
75288
75586
  label: "Sign in again",
75289
- href: `/addon/google-services/oauth/start?brokerId=${encodeURIComponent(brokerId)}`
75587
+ href: `/addon/google-services/oauth/start?brokerId=${encodeURIComponent(brokerId)}`,
75588
+ navigation: "server"
75290
75589
  },
75291
75590
  status: "active",
75292
75591
  read: false,
@@ -75296,10 +75595,7 @@ function buildGoogleBrokerReconnectAlert(brokerId, brokerName) {
75296
75595
  type: "google-broker",
75297
75596
  id: brokerId
75298
75597
  },
75299
- metadata: {
75300
- episodeKey: "reconnect",
75301
- pushLine
75302
- }
75598
+ metadata: { episodeKey: "reconnect" }
75303
75599
  };
75304
75600
  }
75305
75601
  //#endregion
@@ -75508,6 +75804,14 @@ var GoogleBrokerStore = class GoogleBrokerStore {
75508
75804
  * One OAuth account = one broker entry; feature caps aggregate connected brokers.
75509
75805
  */
75510
75806
  var ADDON_ID = "google-services";
75807
+ /** A callback failure the landing page can name by code. */
75808
+ var GoogleCallbackError = class extends Error {
75809
+ reason;
75810
+ constructor(reason, message) {
75811
+ super(message);
75812
+ this.reason = reason;
75813
+ }
75814
+ };
75511
75815
  var CALLBACK_PATH = "/oauth/callback";
75512
75816
  var START_PATH = "/oauth/start";
75513
75817
  var DISCONNECT_PATH = "/oauth/disconnect";
@@ -75528,7 +75832,7 @@ var GoogleServicesAddon = class extends BaseAddon {
75528
75832
  registry = new GoogleBrokerRegistry();
75529
75833
  brokerStore = null;
75530
75834
  activeBrokers = [];
75531
- /** CSRF state → { brokerId, popup, issuedAt } — popup mirrors OIDC D794. */
75835
+ /** CSRF state → { brokerId, issuedAt } for the in-flight OAuth consents. */
75532
75836
  pendingStates = /* @__PURE__ */ new Map();
75533
75837
  publicOrigin = null;
75534
75838
  constructor() {
@@ -75564,7 +75868,8 @@ var GoogleServicesAddon = class extends BaseAddon {
75564
75868
  refreshToken: legacyOauth.refreshToken,
75565
75869
  accessToken: legacyOauth.accessToken,
75566
75870
  accessExpiresAt: legacyOauth.accessExpiresAt,
75567
- accountEmail: legacyOauth.accountEmail
75871
+ accountEmail: legacyOauth.accountEmail,
75872
+ needsReauth: false
75568
75873
  }] };
75569
75874
  await this.oauthAccountsHandle.set(oauthAccounts);
75570
75875
  await legacyHandle.set({ ...DEFAULT_OAUTH_STATE });
@@ -75835,12 +76140,9 @@ var GoogleServicesAddon = class extends BaseAddon {
75835
76140
  reply.status(500).send({ error: err instanceof Error ? err.message : String(err) });
75836
76141
  return;
75837
76142
  }
75838
- const popupRaw = req.query["popup"];
75839
- const popup = popupRaw === "1" || popupRaw === "true";
75840
76143
  const state = (0, node_crypto.randomBytes)(16).toString("hex");
75841
76144
  this.pendingStates.set(state, {
75842
76145
  brokerId,
75843
- popup,
75844
76146
  issuedAt: Date.now()
75845
76147
  });
75846
76148
  this.prunePendingStates();
@@ -75852,42 +76154,39 @@ var GoogleServicesAddon = class extends BaseAddon {
75852
76154
  reply.redirect(url);
75853
76155
  }
75854
76156
  async handleCallback(req, reply) {
76157
+ const state = typeof req.query["state"] === "string" ? req.query["state"] : "";
75855
76158
  const err = req.query["error"];
75856
76159
  if (err) {
75857
- const statePeek = typeof req.query["state"] === "string" ? req.query["state"] : "";
75858
- const pendingPeek = statePeek ? this.pendingStates.get(statePeek) : void 0;
75859
- if (pendingPeek?.popup) {
75860
- this.pendingStates.delete(statePeek);
75861
- this.popupComplete(reply, {
75862
- ok: false,
75863
- brokerId: pendingPeek.brokerId,
75864
- error: String(err)
75865
- });
75866
- return;
75867
- }
75868
- this.html(reply, 400, `Google OAuth error: ${err}`);
76160
+ const pendingPeek = state ? this.pendingStates.get(state) : void 0;
76161
+ if (pendingPeek !== void 0) this.pendingStates.delete(state);
76162
+ await this.landOnAdminUi(reply, {
76163
+ status: "error",
76164
+ addonId: ADDON_ID,
76165
+ ...pendingPeek !== void 0 ? { brokerId: pendingPeek.brokerId } : {},
76166
+ reason: err === "access_denied" ? "access_denied" : "invalid_request"
76167
+ });
75869
76168
  return;
75870
76169
  }
75871
76170
  const code = req.query["code"];
75872
- const state = req.query["state"];
75873
- const pending = typeof state === "string" ? this.pendingStates.get(state) : void 0;
76171
+ const pending = state ? this.pendingStates.get(state) : void 0;
75874
76172
  if (!code || !state || pending === void 0) {
75875
- this.html(reply, 400, "Invalid or expired OAuth state.");
76173
+ await this.landOnAdminUi(reply, {
76174
+ status: "error",
76175
+ addonId: ADDON_ID,
76176
+ reason: "invalid_state"
76177
+ });
75876
76178
  return;
75877
76179
  }
75878
76180
  this.pendingStates.delete(state);
75879
- const { brokerId, popup } = pending;
76181
+ const { brokerId } = pending;
75880
76182
  const entry = this.registry.getEntry(brokerId);
75881
76183
  if (entry === null) {
75882
- if (popup) {
75883
- this.popupComplete(reply, {
75884
- ok: false,
75885
- brokerId,
75886
- error: `Unknown Google broker: ${brokerId}`
75887
- });
75888
- return;
75889
- }
75890
- this.html(reply, 404, `Unknown Google broker: ${brokerId}`);
76184
+ await this.landOnAdminUi(reply, {
76185
+ status: "error",
76186
+ addonId: ADDON_ID,
76187
+ brokerId,
76188
+ reason: "unknown_broker"
76189
+ });
75891
76190
  return;
75892
76191
  }
75893
76192
  try {
@@ -75899,11 +76198,12 @@ var GoogleServicesAddon = class extends BaseAddon {
75899
76198
  }, code);
75900
76199
  const prev = this.registry.getOauth(brokerId) ?? {
75901
76200
  id: brokerId,
75902
- ...DEFAULT_OAUTH_STATE
76201
+ ...DEFAULT_OAUTH_STATE,
76202
+ needsReauth: false
75903
76203
  };
75904
- if (!tokens.refresh_token && !prev.refreshToken) throw new Error("Google did not return a refresh_token. Revoke CamStack access in Google Account → Security and retry with prompt=consent.");
76204
+ if (!tokens.refresh_token && (!prev.refreshToken || prev.needsReauth)) throw new GoogleCallbackError("no_refresh_token", "Google did not return a refresh_token. Revoke CamStack access in Google Account → Security and retry with prompt=consent.");
75905
76205
  const applied = applyTokenResponse({
75906
- refreshToken: prev.refreshToken,
76206
+ refreshToken: prev.needsReauth && tokens.refresh_token ? "" : prev.refreshToken,
75907
76207
  accessToken: prev.accessToken,
75908
76208
  accessExpiresAt: prev.accessExpiresAt,
75909
76209
  accountEmail: prev.accountEmail
@@ -75914,7 +76214,8 @@ var GoogleServicesAddon = class extends BaseAddon {
75914
76214
  refreshToken: applied.refreshToken,
75915
76215
  accessToken: applied.accessToken,
75916
76216
  accessExpiresAt: applied.accessExpiresAt,
75917
- accountEmail: email || applied.accountEmail
76217
+ accountEmail: email || applied.accountEmail,
76218
+ needsReauth: false
75918
76219
  };
75919
76220
  this.registry.setOauth(next);
75920
76221
  await this.persistOauthAccounts();
@@ -75927,65 +76228,44 @@ var GoogleServicesAddon = class extends BaseAddon {
75927
76228
  this.registry.replaceEntries(this.registry.listEntries().map((e) => e.id === brokerId ? renamed : e));
75928
76229
  await this.persistBrokers();
75929
76230
  }
75930
- this.ctx.logger.info("google oauth connected", { meta: {
75931
- brokerId,
75932
- hasRefresh: next.refreshToken.length > 0,
75933
- accountEmail: next.accountEmail || void 0
75934
- } });
75935
- if (popup) {
75936
- this.popupComplete(reply, {
75937
- ok: true,
76231
+ this.ctx.logger.info("google oauth connected", {
76232
+ tags: { brokerId },
76233
+ meta: {
75938
76234
  brokerId,
75939
- message: "Google account connected. This window should close."
75940
- });
75941
- return;
75942
- }
75943
- this.html(reply, 200, `Google account connected for ${brokerId} (Calendar + Gmail send). You can close this window.`);
76235
+ hasRefresh: next.refreshToken.length > 0,
76236
+ accountEmail: next.accountEmail || void 0
76237
+ }
76238
+ });
76239
+ await this.landOnAdminUi(reply, {
76240
+ status: "success",
76241
+ addonId: ADDON_ID,
76242
+ brokerId
76243
+ });
75944
76244
  } catch (e) {
75945
76245
  const message = e instanceof Error ? e.message : String(e);
75946
76246
  this.ctx.logger.warn("google oauth callback failed", { meta: {
75947
76247
  brokerId,
75948
76248
  error: message
75949
76249
  } });
75950
- if (popup) {
75951
- this.popupComplete(reply, {
75952
- ok: false,
75953
- brokerId,
75954
- error: message
75955
- });
75956
- return;
75957
- }
75958
- this.html(reply, 500, `OAuth failed: ${message}`);
76250
+ await this.landOnAdminUi(reply, {
76251
+ status: "error",
76252
+ addonId: ADDON_ID,
76253
+ brokerId,
76254
+ reason: e instanceof GoogleCallbackError ? e.reason : "exchange_failed"
76255
+ });
75959
76256
  }
75960
76257
  }
75961
76258
  /**
75962
- * Popup completion page (OIDC D794 pattern): postMessage to opener at the
75963
- * hub origin, then close. Google never redirects back to admin-ui — this
75964
- * page is the only return surface.
76259
+ * Every OAuth completion ends in CamStack: a redirect to the admin-ui
76260
+ * completion page (`/sso-complete`) on the hub's public address (the
76261
+ * `local-network` palette role), carrying a status and a reason CODE — never
76262
+ * a token, a code or an error message. The page hands a popup's outcome to
76263
+ * its opener and closes it, or, in a full-page flow, shows the result and
76264
+ * returns to Connectivity.
75965
76265
  */
75966
- popupComplete(reply, outcome) {
75967
- const payload = JSON.stringify({
75968
- type: "camstack:broker-oauth",
75969
- ok: outcome.ok,
75970
- brokerId: outcome.brokerId,
75971
- ...outcome.error !== void 0 ? { error: outcome.error } : {}
75972
- });
75973
- const body = outcome.ok ? outcome.message ?? "Done. This window should close." : `OAuth failed: ${outcome.error ?? "unknown"}`;
75974
- reply.header("content-type", "text/html; charset=utf-8");
75975
- reply.status(outcome.ok ? 200 : 400).send(`<!doctype html><html><head><title>Google</title></head><body>
75976
- <p>${escapeHtml(body)}</p>
75977
- <script>
75978
- (function () {
75979
- var msg = ${payload};
75980
- try {
75981
- if (window.opener) {
75982
- window.opener.postMessage(msg, window.location.origin);
75983
- }
75984
- } catch (e) {}
75985
- setTimeout(function () { window.close(); }, 200);
75986
- })();
75987
- <\/script>
75988
- </body></html>`);
76266
+ async landOnAdminUi(reply, outcome) {
76267
+ await this.refreshPublicOrigin();
76268
+ reply.redirect(oauthLandingUrl(this.publicOrigin, outcome));
75989
76269
  }
75990
76270
  async fetchAccountEmail(accessToken) {
75991
76271
  try {
@@ -75997,10 +76277,6 @@ var GoogleServicesAddon = class extends BaseAddon {
75997
76277
  return "";
75998
76278
  }
75999
76279
  }
76000
- html(reply, code, body) {
76001
- reply.header("content-type", "text/html; charset=utf-8");
76002
- reply.status(code).send(`<!doctype html><html><body><p>${escapeHtml(body)}</p></body></html>`);
76003
- }
76004
76280
  prunePendingStates() {
76005
76281
  const cutoff = Date.now() - 10 * 6e4;
76006
76282
  for (const [k, v] of this.pendingStates) if (v.issuedAt < cutoff) this.pendingStates.delete(k);
@@ -76088,6 +76364,7 @@ var GoogleServicesAddon = class extends BaseAddon {
76088
76364
  const entry = this.registry.getEntry(brokerId);
76089
76365
  const oauth = this.registry.getOauth(brokerId);
76090
76366
  if (entry === null || oauth === null || !oauth.refreshToken.trim()) throw new Error(`Google broker ${brokerId} is not connected — use Sign in under Connectivity → Brokers`);
76367
+ if (oauth.needsReauth) throw new Error(`Google broker ${brokerId} needs a new sign-in — use Sign in under Connectivity → Brokers`);
76091
76368
  if (!entry.clientId.trim() || !entry.clientSecret.trim()) throw userError("google.client-credentials-required", void 0, "Configure Client ID and Client secret before refreshing the Google token");
76092
76369
  const state = {
76093
76370
  refreshToken: oauth.refreshToken.trim(),
@@ -76103,7 +76380,10 @@ var GoogleServicesAddon = class extends BaseAddon {
76103
76380
  clientSecret: entry.clientSecret.trim()
76104
76381
  }, state.refreshToken);
76105
76382
  } catch (err) {
76106
- if (isInvalidGrantRefreshError(err)) this.emitGoogleBrokerReconnectAlert(brokerId, entry.name);
76383
+ if (isInvalidGrantRefreshError(err)) {
76384
+ if (this.registry.markNeedsReauth(brokerId)) await this.persistOauthAccounts();
76385
+ this.emitGoogleBrokerReconnectAlert(brokerId, entry.name);
76386
+ }
76107
76387
  throw err;
76108
76388
  }
76109
76389
  if (typeof tokens.access_token !== "string" || tokens.access_token.trim().length === 0) throw new Error("Google token refresh returned an empty access_token — Sign out and Sign in again");
@@ -76113,7 +76393,8 @@ var GoogleServicesAddon = class extends BaseAddon {
76113
76393
  refreshToken: nextState.refreshToken,
76114
76394
  accessToken: nextState.accessToken,
76115
76395
  accessExpiresAt: nextState.accessExpiresAt,
76116
- accountEmail: nextState.accountEmail
76396
+ accountEmail: nextState.accountEmail,
76397
+ needsReauth: false
76117
76398
  });
76118
76399
  await this.persistOauthAccounts();
76119
76400
  return nextState.accessToken;
@@ -76129,20 +76410,18 @@ var GoogleServicesAddon = class extends BaseAddon {
76129
76410
  reply.status(404).send({ error: `Unknown Google broker: ${brokerId}` });
76130
76411
  return;
76131
76412
  }
76132
- const popupRaw = req.query["popup"];
76133
- const popup = popupRaw === "1" || popupRaw === "true";
76134
76413
  this.registry.removeOauth(brokerId);
76135
76414
  await this.persistOauthAccounts();
76136
- this.ctx.logger.info("google oauth disconnected", { meta: { brokerId } });
76137
- if (popup) {
76138
- this.popupComplete(reply, {
76139
- ok: true,
76140
- brokerId,
76141
- message: "Google account disconnected. This window should close."
76142
- });
76143
- return;
76144
- }
76145
- this.html(reply, 200, `Google account disconnected for ${brokerId}. You can close this window.`);
76415
+ this.ctx.logger.info("google oauth disconnected", {
76416
+ tags: { brokerId },
76417
+ meta: { brokerId }
76418
+ });
76419
+ await this.landOnAdminUi(reply, {
76420
+ status: "success",
76421
+ addonId: ADDON_ID,
76422
+ brokerId,
76423
+ action: "disconnect"
76424
+ });
76146
76425
  }
76147
76426
  async resolveRedirectUri() {
76148
76427
  await this.refreshPublicOrigin();
@@ -76179,9 +76458,6 @@ var GoogleServicesAddon = class extends BaseAddon {
76179
76458
  function asSettingsCap(cap) {
76180
76459
  if (cap === "calendar-source" || cap === "smtp-provider") return cap;
76181
76460
  }
76182
- function escapeHtml(s) {
76183
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
76184
- }
76185
76461
  //#endregion
76186
76462
  exports.DEFAULT_GOOGLE_CONFIG = DEFAULT_GOOGLE_CONFIG;
76187
76463
  exports.GOOGLE_BROKER_KIND = GOOGLE_BROKER_KIND;