@camstack/addon-google-services 0.2.11 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/google-services.addon.js +411 -168
- package/dist/google-services.addon.mjs +411 -168
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
//#region ../types/dist/event-category-
|
|
2
|
+
//#region ../types/dist/event-category-DOl51-Ij.mjs
|
|
3
3
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4
4
|
EventCategory["SystemBoot"] = "system.boot";
|
|
5
5
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -78,9 +78,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
78
78
|
/**
|
|
79
79
|
* The node's one-shot runner boot check (D877): planned runners against the
|
|
80
80
|
* process service, `degraded` when any is failed, refused or missing. The
|
|
81
|
-
* alert centre turns it into one
|
|
81
|
+
* alert centre turns it into one critical alert.
|
|
82
82
|
*/
|
|
83
83
|
EventCategory["SystemRunnerCheck"] = "system.runner-check";
|
|
84
|
+
/**
|
|
85
|
+
* An alert EPISODE opened or closed, emitted by the alert centre for alerts
|
|
86
|
+
* of severity `critical` or `error` (`AlertEpisodePayload`). The alert
|
|
87
|
+
* centre sends no notification of its own: Notification Center turns this
|
|
88
|
+
* into the `alert-critical` / `alert-error` / `alert-critical-resolved`
|
|
89
|
+
* system-event kinds, and reconciles from the alerts store because a bus
|
|
90
|
+
* event may be dropped (D8, D11).
|
|
91
|
+
*/
|
|
92
|
+
EventCategory["AlertEpisode"] = "alert.episode";
|
|
84
93
|
EventCategory["AddonError"] = "addon.error";
|
|
85
94
|
EventCategory["AddonPageReady"] = "addon.page-ready";
|
|
86
95
|
EventCategory["AddonWidgetReady"] = "addon.widget-ready";
|
|
@@ -5538,7 +5547,7 @@ var ZodIssueCode = {
|
|
|
5538
5547
|
var ZodFirstPartyTypeKind;
|
|
5539
5548
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5540
5549
|
//#endregion
|
|
5541
|
-
//#region ../types/dist/sleep-
|
|
5550
|
+
//#region ../types/dist/sleep-BxjU_fJM.mjs
|
|
5542
5551
|
/**
|
|
5543
5552
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5544
5553
|
* window to float samples (D455).
|
|
@@ -13637,7 +13646,14 @@ var AlertSeveritySchema = _enum([
|
|
|
13637
13646
|
]);
|
|
13638
13647
|
var AlertActionSchema = object({
|
|
13639
13648
|
label: string(),
|
|
13640
|
-
href: string().optional()
|
|
13649
|
+
href: string().optional(),
|
|
13650
|
+
/**
|
|
13651
|
+
* How `href` is followed. Absent / `app`: an admin-ui route (client-side).
|
|
13652
|
+
* `server`: a route the HUB serves (an addon HTTP route such as an OAuth
|
|
13653
|
+
* start) — the browser must leave the SPA with a full navigation, because
|
|
13654
|
+
* the SPA has no such route and would bounce to the home page.
|
|
13655
|
+
*/
|
|
13656
|
+
navigation: _enum(["app", "server"]).optional()
|
|
13641
13657
|
});
|
|
13642
13658
|
var AlertStatusSchema = _enum([
|
|
13643
13659
|
"active",
|
|
@@ -42062,6 +42078,9 @@ var NcSystemEventKindSchema = _enum([
|
|
|
42062
42078
|
"server-updated",
|
|
42063
42079
|
"export-completed",
|
|
42064
42080
|
"backup-completed",
|
|
42081
|
+
"alert-critical",
|
|
42082
|
+
"alert-error",
|
|
42083
|
+
"alert-critical-resolved",
|
|
42065
42084
|
"camera-online",
|
|
42066
42085
|
"camera-offline",
|
|
42067
42086
|
"camera-disabled",
|
|
@@ -51107,7 +51126,10 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
|
|
|
51107
51126
|
}), method(object({ jobId: string().optional() }), StorageMigrationJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
51108
51127
|
kind: "mutation",
|
|
51109
51128
|
auth: "admin"
|
|
51110
|
-
}), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({
|
|
51129
|
+
}), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({ locationIds: array(string()).readonly() }), object({
|
|
51130
|
+
clear: boolean(),
|
|
51131
|
+
repointLocationId: string().optional()
|
|
51132
|
+
}), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
|
|
51111
51133
|
kind: "mutation",
|
|
51112
51134
|
auth: "admin"
|
|
51113
51135
|
}), method(StorageCleanupInputSchema, object({ jobId: string() }), {
|
|
@@ -54552,6 +54574,11 @@ object({
|
|
|
54552
54574
|
*
|
|
54553
54575
|
* `kind: 'poll'` — cloud REST APIs typically have no push channel; the
|
|
54554
54576
|
* provider refreshes the slice on an interval and eagerly after commands.
|
|
54577
|
+
*
|
|
54578
|
+
* UNKNOWN IS NOT OFF. Every field the appliance reports separately is
|
|
54579
|
+
* nullable: `null` means "not known right now" (the appliance has not said,
|
|
54580
|
+
* the link is down, or the description does not carry it) and is never
|
|
54581
|
+
* rendered or exported as `false` / `0`. `availability` says why.
|
|
54555
54582
|
*/
|
|
54556
54583
|
var DishwasherStateSchema = _enum([
|
|
54557
54584
|
"idle",
|
|
@@ -54561,112 +54588,227 @@ var DishwasherStateSchema = _enum([
|
|
|
54561
54588
|
"error",
|
|
54562
54589
|
"unavailable"
|
|
54563
54590
|
]);
|
|
54591
|
+
/**
|
|
54592
|
+
* The appliance's own operation state, uncollapsed. `state` above is the coarse
|
|
54593
|
+
* view for compact surfaces; this is what the appliance said.
|
|
54594
|
+
*/
|
|
54595
|
+
var DishwasherOperationStateSchema = _enum([
|
|
54596
|
+
"Inactive",
|
|
54597
|
+
"Ready",
|
|
54598
|
+
"DelayedStart",
|
|
54599
|
+
"Run",
|
|
54600
|
+
"Pause",
|
|
54601
|
+
"ActionRequired",
|
|
54602
|
+
"Finished",
|
|
54603
|
+
"Error",
|
|
54604
|
+
"Aborting"
|
|
54605
|
+
]);
|
|
54606
|
+
/**
|
|
54607
|
+
* Why fields may be `null`:
|
|
54608
|
+
* - `available` — the link is up and the description is loaded;
|
|
54609
|
+
* - `offline` — the local link is down (values are unknown, not last-known);
|
|
54610
|
+
* - `description-missing` — the link is up but the appliance's description could not
|
|
54611
|
+
* be loaded, so its values cannot be decoded.
|
|
54612
|
+
*/
|
|
54613
|
+
var DishwasherAvailabilitySchema = _enum([
|
|
54614
|
+
"available",
|
|
54615
|
+
"offline",
|
|
54616
|
+
"description-missing"
|
|
54617
|
+
]);
|
|
54564
54618
|
var DishwasherConsumableLevelSchema = _enum([
|
|
54565
54619
|
"ok",
|
|
54566
54620
|
"low",
|
|
54567
54621
|
"empty"
|
|
54568
54622
|
]);
|
|
54569
54623
|
object({
|
|
54624
|
+
availability: DishwasherAvailabilitySchema,
|
|
54625
|
+
/** Coarse state for compact surfaces; `unavailable` whenever the appliance state is not known. */
|
|
54570
54626
|
state: DishwasherStateSchema,
|
|
54571
|
-
/**
|
|
54627
|
+
/** The appliance's operation state as reported; null when not known. */
|
|
54628
|
+
operationState: DishwasherOperationStateSchema.nullable(),
|
|
54629
|
+
/** Feature key of the ACTIVE (running) program, or null when none runs / not known. */
|
|
54572
54630
|
program: string().nullable(),
|
|
54573
|
-
/**
|
|
54631
|
+
/** Feature key of the SELECTED program (what Start would run), or null when none / not known. */
|
|
54632
|
+
selectedProgram: string().nullable(),
|
|
54633
|
+
/** Feature keys of the programs the appliance currently offers — drives the UI selector. */
|
|
54574
54634
|
availablePrograms: array(string()),
|
|
54575
|
-
/**
|
|
54576
|
-
|
|
54635
|
+
/** Seconds remaining on the active cycle, or null when not running / not known. */
|
|
54636
|
+
remainingSeconds: number().int().nonnegative().nullable(),
|
|
54637
|
+
/** Seconds the delayed start is set to (time until the cycle starts), or null. */
|
|
54638
|
+
delayedStartSeconds: number().int().nonnegative().nullable(),
|
|
54577
54639
|
/** Cycle completion 0..100, or null when not applicable. */
|
|
54578
54640
|
progressPercent: number().min(0).max(100).nullable(),
|
|
54579
|
-
/** True when the door is reported open. */
|
|
54580
|
-
doorOpen: boolean(),
|
|
54581
|
-
/** True when
|
|
54582
|
-
|
|
54583
|
-
/**
|
|
54584
|
-
|
|
54641
|
+
/** True when the door is reported open; null when the door state is not known. */
|
|
54642
|
+
doorOpen: boolean().nullable(),
|
|
54643
|
+
/** True when the door is latched (locked) during a cycle; null when not reported. */
|
|
54644
|
+
doorLocked: boolean().nullable(),
|
|
54645
|
+
/** True when remote start is allowed (door closed, power on, etc.); null when not known. */
|
|
54646
|
+
remoteStartAllowed: boolean().nullable(),
|
|
54647
|
+
/** True when the appliance accepts remote control now; null when not known. */
|
|
54648
|
+
remoteControlActive: boolean().nullable(),
|
|
54649
|
+
/** True while somebody operates the appliance at its panel; null when not reported. */
|
|
54650
|
+
localControlActive: boolean().nullable(),
|
|
54651
|
+
/** Main power / switched-on state; null when not known. */
|
|
54652
|
+
powerOn: boolean().nullable(),
|
|
54653
|
+
/** The raw power state (`On`, `Off`, `Standby`, …); null when not known. */
|
|
54654
|
+
powerState: string().nullable(),
|
|
54585
54655
|
saltLevel: DishwasherConsumableLevelSchema.nullable(),
|
|
54586
54656
|
rinseAidLevel: DishwasherConsumableLevelSchema.nullable(),
|
|
54587
|
-
|
|
54588
|
-
|
|
54657
|
+
/** Option states: null when the appliance does not expose the option or has not said. */
|
|
54658
|
+
halfLoad: boolean().nullable(),
|
|
54659
|
+
hygienePlus: boolean().nullable(),
|
|
54589
54660
|
/** Extra dry option when the appliance exposes it. */
|
|
54590
|
-
extraDry: boolean(),
|
|
54661
|
+
extraDry: boolean().nullable(),
|
|
54591
54662
|
/** Intensive zone (lower basket) when the appliance exposes it. */
|
|
54592
|
-
intensiveZone: boolean(),
|
|
54663
|
+
intensiveZone: boolean().nullable(),
|
|
54593
54664
|
/** VarioSpeed / VarioSpeed+ when the appliance exposes it. */
|
|
54594
|
-
varioSpeedPlus: boolean(),
|
|
54595
|
-
/** Child lock (controls disabled on the appliance). */
|
|
54596
|
-
childLock: boolean(),
|
|
54665
|
+
varioSpeedPlus: boolean().nullable(),
|
|
54666
|
+
/** Child lock (controls disabled on the appliance); null when not known. */
|
|
54667
|
+
childLock: boolean().nullable(),
|
|
54668
|
+
/** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
|
|
54669
|
+
activeEvents: array(string()),
|
|
54670
|
+
/** Numeric error code when the appliance reports one; null otherwise. */
|
|
54597
54671
|
errorCode: number().nullable(),
|
|
54672
|
+
/** Name of the raised alert-level event while the appliance is in error; null otherwise. */
|
|
54598
54673
|
errorLabel: string().nullable(),
|
|
54674
|
+
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
54599
54675
|
lastFetchedAt: number()
|
|
54600
54676
|
});
|
|
54601
|
-
|
|
54677
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
54678
|
+
/** A value a program option or setting can take. */
|
|
54679
|
+
var DishwasherValueSchema = union([
|
|
54680
|
+
boolean(),
|
|
54681
|
+
string(),
|
|
54682
|
+
number()
|
|
54683
|
+
]);
|
|
54684
|
+
var DishwasherAccessSchema = _enum([
|
|
54685
|
+
"none",
|
|
54686
|
+
"read",
|
|
54687
|
+
"readWrite",
|
|
54688
|
+
"writeOnly",
|
|
54689
|
+
"readStatic"
|
|
54690
|
+
]);
|
|
54691
|
+
/**
|
|
54692
|
+
* One option or setting the appliance declares, with the constraints its description
|
|
54693
|
+
* gives. A `readWrite` descriptor that is `available` is what `setOption` accepts.
|
|
54694
|
+
*/
|
|
54695
|
+
var DishwasherOptionDescriptorSchema = object({
|
|
54696
|
+
/** Feature key (`Dishcare.Dishwasher.Option.ExtraDry`). */
|
|
54697
|
+
key: string(),
|
|
54698
|
+
/** Last segment of the key, for display. */
|
|
54699
|
+
name: string(),
|
|
54700
|
+
type: _enum([
|
|
54701
|
+
"boolean",
|
|
54702
|
+
"number",
|
|
54703
|
+
"enum",
|
|
54704
|
+
"string"
|
|
54705
|
+
]),
|
|
54706
|
+
access: DishwasherAccessSchema,
|
|
54707
|
+
/** The appliance reports this entity as currently available. */
|
|
54708
|
+
available: boolean(),
|
|
54709
|
+
/** Unit the description declares for a number; null when it declares none. */
|
|
54710
|
+
unit: string().nullable(),
|
|
54711
|
+
min: number().nullable(),
|
|
54712
|
+
max: number().nullable(),
|
|
54713
|
+
step: number().nullable(),
|
|
54714
|
+
/** Allowed members of an enum; null for other types. */
|
|
54715
|
+
enumValues: array(string()).nullable(),
|
|
54716
|
+
defaultValue: DishwasherValueSchema.nullable()
|
|
54717
|
+
});
|
|
54718
|
+
var DishwasherProgramSchema = object({
|
|
54719
|
+
/** Feature key (`Dishcare.Dishwasher.Program.Eco50`) — what `setProgram` / `start` take. */
|
|
54720
|
+
key: string(),
|
|
54721
|
+
name: string(),
|
|
54722
|
+
/** The appliance currently offers this program. */
|
|
54723
|
+
available: boolean(),
|
|
54724
|
+
/** The options this program lists, with the program-level access / availability / default. */
|
|
54725
|
+
options: array(DishwasherOptionDescriptorSchema)
|
|
54726
|
+
});
|
|
54727
|
+
var DishwasherOptionsSchema = object({
|
|
54728
|
+
availability: DishwasherAvailabilitySchema,
|
|
54729
|
+
/** What the appliance can be told to do, from the commands its description declares. */
|
|
54730
|
+
supportsStart: boolean(),
|
|
54731
|
+
supportsPause: boolean(),
|
|
54732
|
+
supportsResume: boolean(),
|
|
54733
|
+
supportsStop: boolean(),
|
|
54734
|
+
supportsPower: boolean(),
|
|
54735
|
+
/** Every program the description lists, with its options. */
|
|
54736
|
+
programs: array(DishwasherProgramSchema),
|
|
54737
|
+
/** Every option and setting entity the description lists (programs reference these by key). */
|
|
54738
|
+
options: array(DishwasherOptionDescriptorSchema)
|
|
54739
|
+
});
|
|
54740
|
+
DeviceType.Dishwasher, method(object({
|
|
54741
|
+
deviceId: DeviceIdSchema,
|
|
54742
|
+
program: string().min(1).optional(),
|
|
54743
|
+
options: record(string(), DishwasherValueSchema).optional()
|
|
54744
|
+
}), _void(), {
|
|
54602
54745
|
kind: "mutation",
|
|
54603
54746
|
auth: "protected"
|
|
54604
|
-
}), method(object({ deviceId:
|
|
54747
|
+
}), method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
54605
54748
|
kind: "mutation",
|
|
54606
54749
|
auth: "protected"
|
|
54607
|
-
}), method(object({ deviceId:
|
|
54750
|
+
}), method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
54751
|
+
kind: "mutation",
|
|
54752
|
+
auth: "protected"
|
|
54753
|
+
}), method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
54608
54754
|
kind: "mutation",
|
|
54609
54755
|
auth: "protected"
|
|
54610
54756
|
}), method(object({
|
|
54611
|
-
deviceId:
|
|
54757
|
+
deviceId: DeviceIdSchema,
|
|
54612
54758
|
program: string().min(1)
|
|
54613
54759
|
}), _void(), {
|
|
54614
54760
|
kind: "mutation",
|
|
54615
54761
|
auth: "protected"
|
|
54616
54762
|
}), method(object({
|
|
54617
|
-
deviceId:
|
|
54763
|
+
deviceId: DeviceIdSchema,
|
|
54618
54764
|
on: boolean()
|
|
54619
54765
|
}), _void(), {
|
|
54620
54766
|
kind: "mutation",
|
|
54621
54767
|
auth: "protected"
|
|
54622
54768
|
}), method(object({
|
|
54623
|
-
deviceId:
|
|
54769
|
+
deviceId: DeviceIdSchema,
|
|
54624
54770
|
on: boolean()
|
|
54625
54771
|
}), _void(), {
|
|
54626
54772
|
kind: "mutation",
|
|
54627
54773
|
auth: "protected"
|
|
54628
54774
|
}), method(object({
|
|
54629
|
-
deviceId:
|
|
54775
|
+
deviceId: DeviceIdSchema,
|
|
54630
54776
|
on: boolean()
|
|
54631
54777
|
}), _void(), {
|
|
54632
54778
|
kind: "mutation",
|
|
54633
54779
|
auth: "protected"
|
|
54634
54780
|
}), method(object({
|
|
54635
|
-
deviceId:
|
|
54781
|
+
deviceId: DeviceIdSchema,
|
|
54636
54782
|
on: boolean()
|
|
54637
54783
|
}), _void(), {
|
|
54638
54784
|
kind: "mutation",
|
|
54639
54785
|
auth: "protected"
|
|
54640
54786
|
}), method(object({
|
|
54641
|
-
deviceId:
|
|
54787
|
+
deviceId: DeviceIdSchema,
|
|
54642
54788
|
on: boolean()
|
|
54643
54789
|
}), _void(), {
|
|
54644
54790
|
kind: "mutation",
|
|
54645
54791
|
auth: "protected"
|
|
54646
54792
|
}), method(object({
|
|
54647
|
-
deviceId:
|
|
54793
|
+
deviceId: DeviceIdSchema,
|
|
54648
54794
|
on: boolean()
|
|
54649
54795
|
}), _void(), {
|
|
54650
54796
|
kind: "mutation",
|
|
54651
54797
|
auth: "protected"
|
|
54652
54798
|
}), method(object({
|
|
54653
|
-
deviceId:
|
|
54799
|
+
deviceId: DeviceIdSchema,
|
|
54654
54800
|
on: boolean()
|
|
54655
54801
|
}), _void(), {
|
|
54656
54802
|
kind: "mutation",
|
|
54657
54803
|
auth: "protected"
|
|
54658
54804
|
}), method(object({
|
|
54659
|
-
deviceId:
|
|
54805
|
+
deviceId: DeviceIdSchema,
|
|
54660
54806
|
key: string().min(1),
|
|
54661
|
-
value:
|
|
54662
|
-
boolean(),
|
|
54663
|
-
string(),
|
|
54664
|
-
number()
|
|
54665
|
-
])
|
|
54807
|
+
value: DishwasherValueSchema
|
|
54666
54808
|
}), _void(), {
|
|
54667
54809
|
kind: "mutation",
|
|
54668
54810
|
auth: "protected"
|
|
54669
|
-
});
|
|
54811
|
+
}), method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" });
|
|
54670
54812
|
object({
|
|
54671
54813
|
/** Ms epoch of the last press. null = never observed since this provider started. */
|
|
54672
54814
|
lastPressedAt: number().nullable(),
|
|
@@ -64726,6 +64868,12 @@ Object.freeze({
|
|
|
64726
64868
|
addonId: null,
|
|
64727
64869
|
access: "view"
|
|
64728
64870
|
},
|
|
64871
|
+
"dishwasher.getOptions": {
|
|
64872
|
+
capName: "dishwasher",
|
|
64873
|
+
capScope: "device",
|
|
64874
|
+
addonId: null,
|
|
64875
|
+
access: "view"
|
|
64876
|
+
},
|
|
64729
64877
|
"dishwasher.getStatus": {
|
|
64730
64878
|
capName: "dishwasher",
|
|
64731
64879
|
capScope: "device",
|
|
@@ -64738,6 +64886,12 @@ Object.freeze({
|
|
|
64738
64886
|
addonId: null,
|
|
64739
64887
|
access: "create"
|
|
64740
64888
|
},
|
|
64889
|
+
"dishwasher.resume": {
|
|
64890
|
+
capName: "dishwasher",
|
|
64891
|
+
capScope: "device",
|
|
64892
|
+
addonId: null,
|
|
64893
|
+
access: "create"
|
|
64894
|
+
},
|
|
64741
64895
|
"dishwasher.setChildLock": {
|
|
64742
64896
|
capName: "dishwasher",
|
|
64743
64897
|
capScope: "device",
|
|
@@ -69238,6 +69392,12 @@ Object.freeze({
|
|
|
69238
69392
|
addonId: null,
|
|
69239
69393
|
access: "view"
|
|
69240
69394
|
},
|
|
69395
|
+
"storageMigration.moverClearance": {
|
|
69396
|
+
capName: "storage-migration",
|
|
69397
|
+
capScope: "system",
|
|
69398
|
+
addonId: null,
|
|
69399
|
+
access: "view"
|
|
69400
|
+
},
|
|
69241
69401
|
"storageMigration.movers": {
|
|
69242
69402
|
capName: "storage-migration",
|
|
69243
69403
|
capScope: "system",
|
|
@@ -71431,6 +71591,11 @@ Object.freeze({
|
|
|
71431
71591
|
form: "single",
|
|
71432
71592
|
optional: false
|
|
71433
71593
|
}],
|
|
71594
|
+
"dishwasher.getOptions": [{
|
|
71595
|
+
name: "deviceId",
|
|
71596
|
+
form: "single",
|
|
71597
|
+
optional: false
|
|
71598
|
+
}],
|
|
71434
71599
|
"dishwasher.getStatus": [{
|
|
71435
71600
|
name: "deviceId",
|
|
71436
71601
|
form: "single",
|
|
@@ -71441,6 +71606,11 @@ Object.freeze({
|
|
|
71441
71606
|
form: "single",
|
|
71442
71607
|
optional: false
|
|
71443
71608
|
}],
|
|
71609
|
+
"dishwasher.resume": [{
|
|
71610
|
+
name: "deviceId",
|
|
71611
|
+
form: "single",
|
|
71612
|
+
optional: false
|
|
71613
|
+
}],
|
|
71444
71614
|
"dishwasher.setChildLock": [{
|
|
71445
71615
|
name: "deviceId",
|
|
71446
71616
|
form: "single",
|
|
@@ -73527,6 +73697,9 @@ new Map(Object.entries({
|
|
|
73527
73697
|
"wrapper-update-available": "system",
|
|
73528
73698
|
"backup-completed": "system",
|
|
73529
73699
|
"export-completed": "system",
|
|
73700
|
+
"alert-critical": "system",
|
|
73701
|
+
"alert-error": "system",
|
|
73702
|
+
"alert-critical-resolved": "system",
|
|
73530
73703
|
"storage-location-low": "system",
|
|
73531
73704
|
"storage-location-critical": "system",
|
|
73532
73705
|
"storage-location-ok": "system",
|
|
@@ -73651,6 +73824,16 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
|
|
|
73651
73824
|
"storage-location-ok"
|
|
73652
73825
|
]
|
|
73653
73826
|
},
|
|
73827
|
+
{
|
|
73828
|
+
id: "alerts",
|
|
73829
|
+
family: "system",
|
|
73830
|
+
label: "Alerts",
|
|
73831
|
+
kinds: [
|
|
73832
|
+
"alert-critical",
|
|
73833
|
+
"alert-error",
|
|
73834
|
+
"alert-critical-resolved"
|
|
73835
|
+
]
|
|
73836
|
+
},
|
|
73654
73837
|
{
|
|
73655
73838
|
id: "alarm",
|
|
73656
73839
|
family: "alarm",
|
|
@@ -73695,6 +73878,21 @@ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.ent
|
|
|
73695
73878
|
return [[descriptor.kind, field]];
|
|
73696
73879
|
})));
|
|
73697
73880
|
Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
|
|
73881
|
+
object({
|
|
73882
|
+
phase: _enum(["raised", "resolved"]),
|
|
73883
|
+
episodeId: string().min(1),
|
|
73884
|
+
alertId: string().min(1),
|
|
73885
|
+
severity: AlertSeveritySchema,
|
|
73886
|
+
category: string(),
|
|
73887
|
+
title: string(),
|
|
73888
|
+
message: string(),
|
|
73889
|
+
sourceType: string().optional(),
|
|
73890
|
+
sourceId: string().optional(),
|
|
73891
|
+
nodeId: string().optional(),
|
|
73892
|
+
/** The alert's own action link (where the operator acts on it), when it has one. */
|
|
73893
|
+
actionUrl: string().optional(),
|
|
73894
|
+
at: number()
|
|
73895
|
+
});
|
|
73698
73896
|
var RULE_BODY_ONLY = ["body"];
|
|
73699
73897
|
var RULE_TEXT = ["title", "body"];
|
|
73700
73898
|
var BATTERY = ["device-battery-low", "device-battery-normal"];
|
|
@@ -73704,6 +73902,11 @@ var TANK = [
|
|
|
73704
73902
|
"device-tank-full",
|
|
73705
73903
|
"device-tank-normal"
|
|
73706
73904
|
];
|
|
73905
|
+
var ALERT_EPISODE = [
|
|
73906
|
+
"alert-critical",
|
|
73907
|
+
"alert-error",
|
|
73908
|
+
"alert-critical-resolved"
|
|
73909
|
+
];
|
|
73707
73910
|
var UPDATES = [
|
|
73708
73911
|
"addon-update-available",
|
|
73709
73912
|
"server-update-available",
|
|
@@ -73896,14 +74099,14 @@ var STATE_SUMMARY_VARS = [
|
|
|
73896
74099
|
([...RULE_TEXT], [...CONSUMABLE]),
|
|
73897
74100
|
([...RULE_TEXT], [...TANK]),
|
|
73898
74101
|
[...RULE_TEXT],
|
|
73899
|
-
([...RULE_TEXT], [...UPDATES]),
|
|
73900
|
-
([...RULE_TEXT], [...UPDATES]),
|
|
73901
|
-
[...RULE_TEXT],
|
|
74102
|
+
([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
|
|
74103
|
+
([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
|
|
73902
74104
|
[...RULE_TEXT],
|
|
73903
74105
|
[...RULE_TEXT],
|
|
73904
74106
|
[...RULE_TEXT],
|
|
73905
74107
|
[...RULE_TEXT],
|
|
73906
74108
|
[...RULE_TEXT],
|
|
74109
|
+
([...RULE_TEXT], [...ALERT_EPISODE]),
|
|
73907
74110
|
[...RULE_TEXT],
|
|
73908
74111
|
[...RULE_TEXT],
|
|
73909
74112
|
[...RULE_TEXT],
|
|
@@ -73921,7 +74124,11 @@ var STATE_SUMMARY_VARS = [
|
|
|
73921
74124
|
[...RULE_TEXT],
|
|
73922
74125
|
[...RULE_TEXT],
|
|
73923
74126
|
[...RULE_TEXT],
|
|
73924
|
-
[...RULE_TEXT],
|
|
74127
|
+
([...RULE_TEXT], [...ALERT_EPISODE]),
|
|
74128
|
+
([...RULE_TEXT], [...ALERT_EPISODE]),
|
|
74129
|
+
([...RULE_TEXT], [...ALERT_EPISODE]),
|
|
74130
|
+
([...RULE_TEXT], [...ALERT_EPISODE]),
|
|
74131
|
+
([...RULE_TEXT], [...ALERT_EPISODE]),
|
|
73925
74132
|
[...RULE_TEXT],
|
|
73926
74133
|
[...RULE_TEXT],
|
|
73927
74134
|
[...RULE_TEXT],
|
|
@@ -74459,6 +74666,37 @@ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
|
74459
74666
|
DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
|
|
74460
74667
|
DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
|
|
74461
74668
|
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
74669
|
+
/**
|
|
74670
|
+
* Where an OAuth round-trip ends: the admin-ui completion route
|
|
74671
|
+
* (`/sso-complete`, the page every popup sign-in already lands on), with the
|
|
74672
|
+
* OUTCOME in the URL fragment.
|
|
74673
|
+
*
|
|
74674
|
+
* The outcome is a status plus a reason CODE from a closed list. It never
|
|
74675
|
+
* carries a token, a code, a client secret or a free-form error message: the
|
|
74676
|
+
* page words each code itself, and an unknown code reads as `unknown` rather
|
|
74677
|
+
* than being echoed. Fragment only, so nothing here reaches a server log.
|
|
74678
|
+
*
|
|
74679
|
+
* Pure and browser-safe.
|
|
74680
|
+
*/
|
|
74681
|
+
/** The admin-ui route the hub's OAuth completions land on (relative to its mount). */
|
|
74682
|
+
var OAUTH_LANDING_PATH = "/sso-complete";
|
|
74683
|
+
/**
|
|
74684
|
+
* The landing URL for `outcome`. `origin` is the hub address the flow already
|
|
74685
|
+
* runs on (from the `local-network` palette role, never a literal); `null`
|
|
74686
|
+
* gives a hub-relative URL.
|
|
74687
|
+
*/
|
|
74688
|
+
function oauthLandingUrl(origin, outcome) {
|
|
74689
|
+
const params = new URLSearchParams({
|
|
74690
|
+
return: "oauth",
|
|
74691
|
+
status: outcome.status
|
|
74692
|
+
});
|
|
74693
|
+
if (outcome.addonId !== void 0) params.set("addon", outcome.addonId);
|
|
74694
|
+
if (outcome.brokerId !== void 0) params.set("broker", outcome.brokerId);
|
|
74695
|
+
if (outcome.integration !== void 0) params.set("integration", outcome.integration);
|
|
74696
|
+
if (outcome.status === "error") params.set("reason", outcome.reason ?? "unknown");
|
|
74697
|
+
if (outcome.action === "disconnect") params.set("action", "disconnect");
|
|
74698
|
+
return `${origin === null ? "" : origin.replace(/\/+$/, "")}${OAUTH_LANDING_PATH}#${params.toString()}`;
|
|
74699
|
+
}
|
|
74462
74700
|
var MB = 1024 * 1024;
|
|
74463
74701
|
1024 * MB, 3072 * MB;
|
|
74464
74702
|
//#endregion
|
|
@@ -74506,13 +74744,22 @@ var DEFAULT_OAUTH_STATE = {
|
|
|
74506
74744
|
accessExpiresAt: 0,
|
|
74507
74745
|
accountEmail: ""
|
|
74508
74746
|
};
|
|
74509
|
-
|
|
74747
|
+
/** Per-broker OAuth tokens (durable state `oauthAccounts`). */
|
|
74748
|
+
var GoogleOauthAccountSchema = object({
|
|
74510
74749
|
id: string().min(1),
|
|
74511
74750
|
refreshToken: string().default("").meta({ secret: true }),
|
|
74512
74751
|
accessToken: string().default("").meta({ secret: true }),
|
|
74513
74752
|
accessExpiresAt: number().default(0),
|
|
74514
|
-
accountEmail: string().default("")
|
|
74515
|
-
|
|
74753
|
+
accountEmail: string().default(""),
|
|
74754
|
+
/**
|
|
74755
|
+
* Google refused the refresh token (`invalid_grant`): it is dead and only a
|
|
74756
|
+
* new sign-in replaces it. Set where the refusal is seen, cleared by the
|
|
74757
|
+
* OAuth callback that stores a fresh token — so the status, the Sign-in
|
|
74758
|
+
* button and the alert all read the same fact.
|
|
74759
|
+
*/
|
|
74760
|
+
needsReauth: boolean().default(false)
|
|
74761
|
+
});
|
|
74762
|
+
var GoogleOauthAccountsStateSchema = object({ accounts: array(GoogleOauthAccountSchema).default([]) });
|
|
74516
74763
|
var GOOGLE_BROKER_KIND = "google";
|
|
74517
74764
|
var GOOGLE_BROKER_KIND_LABEL = "Google";
|
|
74518
74765
|
var GOOGLE_CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
@@ -74601,7 +74848,8 @@ function migrateLegacyToBrokers(rawConfig, legacyOauth) {
|
|
|
74601
74848
|
refreshToken: legacyOauth.refreshToken,
|
|
74602
74849
|
accessToken: legacyOauth.accessToken,
|
|
74603
74850
|
accessExpiresAt: legacyOauth.accessExpiresAt,
|
|
74604
|
-
accountEmail: legacyOauth.accountEmail
|
|
74851
|
+
accountEmail: legacyOauth.accountEmail,
|
|
74852
|
+
needsReauth: false
|
|
74605
74853
|
}] : [];
|
|
74606
74854
|
return {
|
|
74607
74855
|
brokers: [entry],
|
|
@@ -74636,7 +74884,10 @@ var GoogleBrokerRegistry = class {
|
|
|
74636
74884
|
oauthById = /* @__PURE__ */ new Map();
|
|
74637
74885
|
seedFrom(entries, accounts) {
|
|
74638
74886
|
this.entries = entries.map((e) => GoogleBrokerEntrySchema.parse(e));
|
|
74639
|
-
this.oauthById = new Map(accounts.map((a) =>
|
|
74887
|
+
this.oauthById = new Map(accounts.map((a) => {
|
|
74888
|
+
const account = GoogleOauthAccountSchema.parse(a);
|
|
74889
|
+
return [account.id, account];
|
|
74890
|
+
}));
|
|
74640
74891
|
this.seedCounterFromEntries(this.entries);
|
|
74641
74892
|
}
|
|
74642
74893
|
listEntries() {
|
|
@@ -74652,14 +74903,28 @@ var GoogleBrokerRegistry = class {
|
|
|
74652
74903
|
return [...this.oauthById.values()];
|
|
74653
74904
|
}
|
|
74654
74905
|
setOauth(account) {
|
|
74655
|
-
|
|
74906
|
+
const parsed = GoogleOauthAccountSchema.parse(account);
|
|
74907
|
+
this.oauthById.set(parsed.id, parsed);
|
|
74656
74908
|
}
|
|
74657
74909
|
removeOauth(id) {
|
|
74658
74910
|
this.oauthById.delete(id);
|
|
74659
74911
|
}
|
|
74660
74912
|
isConnected(id) {
|
|
74661
74913
|
const o = this.oauthById.get(id);
|
|
74662
|
-
return o !== void 0 && o.refreshToken.length > 0;
|
|
74914
|
+
return o !== void 0 && o.refreshToken.length > 0 && !o.needsReauth;
|
|
74915
|
+
}
|
|
74916
|
+
/** A refresh token Google refused: kept (a sign-in replaces it) but not usable. */
|
|
74917
|
+
needsReauth(id) {
|
|
74918
|
+
return this.oauthById.get(id)?.needsReauth === true;
|
|
74919
|
+
}
|
|
74920
|
+
markNeedsReauth(id) {
|
|
74921
|
+
const o = this.oauthById.get(id);
|
|
74922
|
+
if (o === void 0 || o.needsReauth) return false;
|
|
74923
|
+
this.oauthById.set(id, {
|
|
74924
|
+
...o,
|
|
74925
|
+
needsReauth: true
|
|
74926
|
+
});
|
|
74927
|
+
return true;
|
|
74663
74928
|
}
|
|
74664
74929
|
allocateId() {
|
|
74665
74930
|
const id = `google_${String(this.nextIdCounter).padStart(3, "0")}`;
|
|
@@ -74709,18 +74974,19 @@ var GoogleBrokerRegistry = class {
|
|
|
74709
74974
|
toBrokerInfo(entry, addonId) {
|
|
74710
74975
|
const oauth = this.oauthById.get(entry.id);
|
|
74711
74976
|
const connected = this.isConnected(entry.id);
|
|
74977
|
+
const reauth = this.needsReauth(entry.id);
|
|
74712
74978
|
return {
|
|
74713
74979
|
id: entry.id,
|
|
74714
74980
|
addonId,
|
|
74715
74981
|
name: entry.name,
|
|
74716
74982
|
kind: GOOGLE_BROKER_KIND,
|
|
74717
|
-
status: connected ? "connected" : "disconnected",
|
|
74983
|
+
status: connected ? "connected" : reauth ? "auth-failed" : "disconnected",
|
|
74718
74984
|
info: {
|
|
74719
74985
|
accountEmail: oauth?.accountEmail ?? "",
|
|
74720
74986
|
hasClient: entry.clientId.length > 0
|
|
74721
74987
|
},
|
|
74722
74988
|
lastCheckedAt: connected ? Date.now() : null,
|
|
74723
|
-
error: connected ? null : entry.clientId.length === 0 ? "Configure Client ID / secret, then Connect Google" : "Not connected — open Connect Google on this broker"
|
|
74989
|
+
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"
|
|
74724
74990
|
};
|
|
74725
74991
|
}
|
|
74726
74992
|
listBrokerInfos(addonId, kind) {
|
|
@@ -74772,7 +75038,7 @@ function consoleLinksHtml$1() {
|
|
|
74772
75038
|
].join("");
|
|
74773
75039
|
}
|
|
74774
75040
|
function introHtml(cap, publicOrigin) {
|
|
74775
|
-
const redirect = escapeHtml$
|
|
75041
|
+
const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
|
|
74776
75042
|
const console = consoleLinksHtml$1();
|
|
74777
75043
|
if (cap === "smtp-provider") return [
|
|
74778
75044
|
"<ol>",
|
|
@@ -74805,7 +75071,7 @@ function introHtml(cap, publicOrigin) {
|
|
|
74805
75071
|
].join("");
|
|
74806
75072
|
}
|
|
74807
75073
|
function connectHtml(cap, publicOrigin) {
|
|
74808
|
-
const redirect = escapeHtml$
|
|
75074
|
+
const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
|
|
74809
75075
|
if (cap === "smtp-provider") return [
|
|
74810
75076
|
`After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
|
|
74811
75077
|
"Connect requests <code>gmail.send</code> (and Calendar read for the Calendars page). ",
|
|
@@ -74825,7 +75091,7 @@ function connectHtml(cap, publicOrigin) {
|
|
|
74825
75091
|
`Console: ${consoleLinksHtml$1()}.`
|
|
74826
75092
|
].join("");
|
|
74827
75093
|
}
|
|
74828
|
-
function escapeHtml$
|
|
75094
|
+
function escapeHtml$1(s) {
|
|
74829
75095
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
74830
75096
|
}
|
|
74831
75097
|
//#endregion
|
|
@@ -74844,7 +75110,7 @@ function consoleLinksHtml() {
|
|
|
74844
75110
|
}
|
|
74845
75111
|
/** Setup steps shown on Add / Edit — includes Audience / Test users. */
|
|
74846
75112
|
function googleBrokerSetupHtml(publicOrigin) {
|
|
74847
|
-
const redirect = escapeHtml
|
|
75113
|
+
const redirect = escapeHtml(redirectUriDisplay(publicOrigin));
|
|
74848
75114
|
return [
|
|
74849
75115
|
"<ol>",
|
|
74850
75116
|
`<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>`,
|
|
@@ -74920,7 +75186,7 @@ function buildGoogleBrokerSettingsSchema(publicOrigin) {
|
|
|
74920
75186
|
}
|
|
74921
75187
|
/** Connectivity Calendars / Email pages — calendar import picker lives here (not on broker Add). */
|
|
74922
75188
|
function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
|
|
74923
|
-
const redirect = escapeHtml
|
|
75189
|
+
const redirect = escapeHtml(redirectUriDisplay(publicOrigin));
|
|
74924
75190
|
if (cap === "calendar-source") return { sections: [{
|
|
74925
75191
|
id: "setup-intro",
|
|
74926
75192
|
title: "Google brokers",
|
|
@@ -74978,7 +75244,7 @@ function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
|
|
|
74978
75244
|
}]
|
|
74979
75245
|
}] };
|
|
74980
75246
|
}
|
|
74981
|
-
function escapeHtml
|
|
75247
|
+
function escapeHtml(s) {
|
|
74982
75248
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
74983
75249
|
}
|
|
74984
75250
|
//#endregion
|
|
@@ -75273,7 +75539,6 @@ function isInvalidGrantRefreshError(err) {
|
|
|
75273
75539
|
return (err instanceof Error ? err.message : String(err)).includes("invalid_grant");
|
|
75274
75540
|
}
|
|
75275
75541
|
function buildGoogleBrokerReconnectAlert(brokerId, brokerName) {
|
|
75276
|
-
const pushLine = `${brokerName}: Google session expired — sign in again`;
|
|
75277
75542
|
return {
|
|
75278
75543
|
id: googleBrokerReconnectAlertId(brokerId),
|
|
75279
75544
|
category: "google.broker-reconnect",
|
|
@@ -75282,7 +75547,8 @@ function buildGoogleBrokerReconnectAlert(brokerId, brokerName) {
|
|
|
75282
75547
|
message: `${brokerName} needs you to sign in to Google again.`,
|
|
75283
75548
|
action: {
|
|
75284
75549
|
label: "Sign in again",
|
|
75285
|
-
href: `/addon/google-services/oauth/start?brokerId=${encodeURIComponent(brokerId)}
|
|
75550
|
+
href: `/addon/google-services/oauth/start?brokerId=${encodeURIComponent(brokerId)}`,
|
|
75551
|
+
navigation: "server"
|
|
75286
75552
|
},
|
|
75287
75553
|
status: "active",
|
|
75288
75554
|
read: false,
|
|
@@ -75292,10 +75558,7 @@ function buildGoogleBrokerReconnectAlert(brokerId, brokerName) {
|
|
|
75292
75558
|
type: "google-broker",
|
|
75293
75559
|
id: brokerId
|
|
75294
75560
|
},
|
|
75295
|
-
metadata: {
|
|
75296
|
-
episodeKey: "reconnect",
|
|
75297
|
-
pushLine
|
|
75298
|
-
}
|
|
75561
|
+
metadata: { episodeKey: "reconnect" }
|
|
75299
75562
|
};
|
|
75300
75563
|
}
|
|
75301
75564
|
//#endregion
|
|
@@ -75504,6 +75767,14 @@ var GoogleBrokerStore = class GoogleBrokerStore {
|
|
|
75504
75767
|
* One OAuth account = one broker entry; feature caps aggregate connected brokers.
|
|
75505
75768
|
*/
|
|
75506
75769
|
var ADDON_ID = "google-services";
|
|
75770
|
+
/** A callback failure the landing page can name by code. */
|
|
75771
|
+
var GoogleCallbackError = class extends Error {
|
|
75772
|
+
reason;
|
|
75773
|
+
constructor(reason, message) {
|
|
75774
|
+
super(message);
|
|
75775
|
+
this.reason = reason;
|
|
75776
|
+
}
|
|
75777
|
+
};
|
|
75507
75778
|
var CALLBACK_PATH = "/oauth/callback";
|
|
75508
75779
|
var START_PATH = "/oauth/start";
|
|
75509
75780
|
var DISCONNECT_PATH = "/oauth/disconnect";
|
|
@@ -75524,7 +75795,7 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
75524
75795
|
registry = new GoogleBrokerRegistry();
|
|
75525
75796
|
brokerStore = null;
|
|
75526
75797
|
activeBrokers = [];
|
|
75527
|
-
/** CSRF state → { brokerId,
|
|
75798
|
+
/** CSRF state → { brokerId, issuedAt } for the in-flight OAuth consents. */
|
|
75528
75799
|
pendingStates = /* @__PURE__ */ new Map();
|
|
75529
75800
|
publicOrigin = null;
|
|
75530
75801
|
constructor() {
|
|
@@ -75560,7 +75831,8 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
75560
75831
|
refreshToken: legacyOauth.refreshToken,
|
|
75561
75832
|
accessToken: legacyOauth.accessToken,
|
|
75562
75833
|
accessExpiresAt: legacyOauth.accessExpiresAt,
|
|
75563
|
-
accountEmail: legacyOauth.accountEmail
|
|
75834
|
+
accountEmail: legacyOauth.accountEmail,
|
|
75835
|
+
needsReauth: false
|
|
75564
75836
|
}] };
|
|
75565
75837
|
await this.oauthAccountsHandle.set(oauthAccounts);
|
|
75566
75838
|
await legacyHandle.set({ ...DEFAULT_OAUTH_STATE });
|
|
@@ -75831,12 +76103,9 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
75831
76103
|
reply.status(500).send({ error: err instanceof Error ? err.message : String(err) });
|
|
75832
76104
|
return;
|
|
75833
76105
|
}
|
|
75834
|
-
const popupRaw = req.query["popup"];
|
|
75835
|
-
const popup = popupRaw === "1" || popupRaw === "true";
|
|
75836
76106
|
const state = randomBytes(16).toString("hex");
|
|
75837
76107
|
this.pendingStates.set(state, {
|
|
75838
76108
|
brokerId,
|
|
75839
|
-
popup,
|
|
75840
76109
|
issuedAt: Date.now()
|
|
75841
76110
|
});
|
|
75842
76111
|
this.prunePendingStates();
|
|
@@ -75848,42 +76117,39 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
75848
76117
|
reply.redirect(url);
|
|
75849
76118
|
}
|
|
75850
76119
|
async handleCallback(req, reply) {
|
|
76120
|
+
const state = typeof req.query["state"] === "string" ? req.query["state"] : "";
|
|
75851
76121
|
const err = req.query["error"];
|
|
75852
76122
|
if (err) {
|
|
75853
|
-
const
|
|
75854
|
-
|
|
75855
|
-
|
|
75856
|
-
|
|
75857
|
-
|
|
75858
|
-
|
|
75859
|
-
|
|
75860
|
-
|
|
75861
|
-
});
|
|
75862
|
-
return;
|
|
75863
|
-
}
|
|
75864
|
-
this.html(reply, 400, `Google OAuth error: ${err}`);
|
|
76123
|
+
const pendingPeek = state ? this.pendingStates.get(state) : void 0;
|
|
76124
|
+
if (pendingPeek !== void 0) this.pendingStates.delete(state);
|
|
76125
|
+
await this.landOnAdminUi(reply, {
|
|
76126
|
+
status: "error",
|
|
76127
|
+
addonId: ADDON_ID,
|
|
76128
|
+
...pendingPeek !== void 0 ? { brokerId: pendingPeek.brokerId } : {},
|
|
76129
|
+
reason: err === "access_denied" ? "access_denied" : "invalid_request"
|
|
76130
|
+
});
|
|
75865
76131
|
return;
|
|
75866
76132
|
}
|
|
75867
76133
|
const code = req.query["code"];
|
|
75868
|
-
const
|
|
75869
|
-
const pending = typeof state === "string" ? this.pendingStates.get(state) : void 0;
|
|
76134
|
+
const pending = state ? this.pendingStates.get(state) : void 0;
|
|
75870
76135
|
if (!code || !state || pending === void 0) {
|
|
75871
|
-
this.
|
|
76136
|
+
await this.landOnAdminUi(reply, {
|
|
76137
|
+
status: "error",
|
|
76138
|
+
addonId: ADDON_ID,
|
|
76139
|
+
reason: "invalid_state"
|
|
76140
|
+
});
|
|
75872
76141
|
return;
|
|
75873
76142
|
}
|
|
75874
76143
|
this.pendingStates.delete(state);
|
|
75875
|
-
const { brokerId
|
|
76144
|
+
const { brokerId } = pending;
|
|
75876
76145
|
const entry = this.registry.getEntry(brokerId);
|
|
75877
76146
|
if (entry === null) {
|
|
75878
|
-
|
|
75879
|
-
|
|
75880
|
-
|
|
75881
|
-
|
|
75882
|
-
|
|
75883
|
-
|
|
75884
|
-
return;
|
|
75885
|
-
}
|
|
75886
|
-
this.html(reply, 404, `Unknown Google broker: ${brokerId}`);
|
|
76147
|
+
await this.landOnAdminUi(reply, {
|
|
76148
|
+
status: "error",
|
|
76149
|
+
addonId: ADDON_ID,
|
|
76150
|
+
brokerId,
|
|
76151
|
+
reason: "unknown_broker"
|
|
76152
|
+
});
|
|
75887
76153
|
return;
|
|
75888
76154
|
}
|
|
75889
76155
|
try {
|
|
@@ -75895,11 +76161,12 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
75895
76161
|
}, code);
|
|
75896
76162
|
const prev = this.registry.getOauth(brokerId) ?? {
|
|
75897
76163
|
id: brokerId,
|
|
75898
|
-
...DEFAULT_OAUTH_STATE
|
|
76164
|
+
...DEFAULT_OAUTH_STATE,
|
|
76165
|
+
needsReauth: false
|
|
75899
76166
|
};
|
|
75900
|
-
if (!tokens.refresh_token && !prev.refreshToken) throw new
|
|
76167
|
+
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.");
|
|
75901
76168
|
const applied = applyTokenResponse({
|
|
75902
|
-
refreshToken: prev.refreshToken,
|
|
76169
|
+
refreshToken: prev.needsReauth && tokens.refresh_token ? "" : prev.refreshToken,
|
|
75903
76170
|
accessToken: prev.accessToken,
|
|
75904
76171
|
accessExpiresAt: prev.accessExpiresAt,
|
|
75905
76172
|
accountEmail: prev.accountEmail
|
|
@@ -75910,7 +76177,8 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
75910
76177
|
refreshToken: applied.refreshToken,
|
|
75911
76178
|
accessToken: applied.accessToken,
|
|
75912
76179
|
accessExpiresAt: applied.accessExpiresAt,
|
|
75913
|
-
accountEmail: email || applied.accountEmail
|
|
76180
|
+
accountEmail: email || applied.accountEmail,
|
|
76181
|
+
needsReauth: false
|
|
75914
76182
|
};
|
|
75915
76183
|
this.registry.setOauth(next);
|
|
75916
76184
|
await this.persistOauthAccounts();
|
|
@@ -75923,65 +76191,44 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
75923
76191
|
this.registry.replaceEntries(this.registry.listEntries().map((e) => e.id === brokerId ? renamed : e));
|
|
75924
76192
|
await this.persistBrokers();
|
|
75925
76193
|
}
|
|
75926
|
-
this.ctx.logger.info("google oauth connected", {
|
|
75927
|
-
brokerId,
|
|
75928
|
-
|
|
75929
|
-
accountEmail: next.accountEmail || void 0
|
|
75930
|
-
} });
|
|
75931
|
-
if (popup) {
|
|
75932
|
-
this.popupComplete(reply, {
|
|
75933
|
-
ok: true,
|
|
76194
|
+
this.ctx.logger.info("google oauth connected", {
|
|
76195
|
+
tags: { brokerId },
|
|
76196
|
+
meta: {
|
|
75934
76197
|
brokerId,
|
|
75935
|
-
|
|
75936
|
-
|
|
75937
|
-
|
|
75938
|
-
}
|
|
75939
|
-
this.
|
|
76198
|
+
hasRefresh: next.refreshToken.length > 0,
|
|
76199
|
+
accountEmail: next.accountEmail || void 0
|
|
76200
|
+
}
|
|
76201
|
+
});
|
|
76202
|
+
await this.landOnAdminUi(reply, {
|
|
76203
|
+
status: "success",
|
|
76204
|
+
addonId: ADDON_ID,
|
|
76205
|
+
brokerId
|
|
76206
|
+
});
|
|
75940
76207
|
} catch (e) {
|
|
75941
76208
|
const message = e instanceof Error ? e.message : String(e);
|
|
75942
76209
|
this.ctx.logger.warn("google oauth callback failed", { meta: {
|
|
75943
76210
|
brokerId,
|
|
75944
76211
|
error: message
|
|
75945
76212
|
} });
|
|
75946
|
-
|
|
75947
|
-
|
|
75948
|
-
|
|
75949
|
-
|
|
75950
|
-
|
|
75951
|
-
|
|
75952
|
-
return;
|
|
75953
|
-
}
|
|
75954
|
-
this.html(reply, 500, `OAuth failed: ${message}`);
|
|
76213
|
+
await this.landOnAdminUi(reply, {
|
|
76214
|
+
status: "error",
|
|
76215
|
+
addonId: ADDON_ID,
|
|
76216
|
+
brokerId,
|
|
76217
|
+
reason: e instanceof GoogleCallbackError ? e.reason : "exchange_failed"
|
|
76218
|
+
});
|
|
75955
76219
|
}
|
|
75956
76220
|
}
|
|
75957
76221
|
/**
|
|
75958
|
-
*
|
|
75959
|
-
*
|
|
75960
|
-
*
|
|
76222
|
+
* Every OAuth completion ends in CamStack: a redirect to the admin-ui
|
|
76223
|
+
* completion page (`/sso-complete`) on the hub's public address (the
|
|
76224
|
+
* `local-network` palette role), carrying a status and a reason CODE — never
|
|
76225
|
+
* a token, a code or an error message. The page hands a popup's outcome to
|
|
76226
|
+
* its opener and closes it, or, in a full-page flow, shows the result and
|
|
76227
|
+
* returns to Connectivity.
|
|
75961
76228
|
*/
|
|
75962
|
-
|
|
75963
|
-
|
|
75964
|
-
|
|
75965
|
-
ok: outcome.ok,
|
|
75966
|
-
brokerId: outcome.brokerId,
|
|
75967
|
-
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
75968
|
-
});
|
|
75969
|
-
const body = outcome.ok ? outcome.message ?? "Done. This window should close." : `OAuth failed: ${outcome.error ?? "unknown"}`;
|
|
75970
|
-
reply.header("content-type", "text/html; charset=utf-8");
|
|
75971
|
-
reply.status(outcome.ok ? 200 : 400).send(`<!doctype html><html><head><title>Google</title></head><body>
|
|
75972
|
-
<p>${escapeHtml(body)}</p>
|
|
75973
|
-
<script>
|
|
75974
|
-
(function () {
|
|
75975
|
-
var msg = ${payload};
|
|
75976
|
-
try {
|
|
75977
|
-
if (window.opener) {
|
|
75978
|
-
window.opener.postMessage(msg, window.location.origin);
|
|
75979
|
-
}
|
|
75980
|
-
} catch (e) {}
|
|
75981
|
-
setTimeout(function () { window.close(); }, 200);
|
|
75982
|
-
})();
|
|
75983
|
-
<\/script>
|
|
75984
|
-
</body></html>`);
|
|
76229
|
+
async landOnAdminUi(reply, outcome) {
|
|
76230
|
+
await this.refreshPublicOrigin();
|
|
76231
|
+
reply.redirect(oauthLandingUrl(this.publicOrigin, outcome));
|
|
75985
76232
|
}
|
|
75986
76233
|
async fetchAccountEmail(accessToken) {
|
|
75987
76234
|
try {
|
|
@@ -75993,10 +76240,6 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
75993
76240
|
return "";
|
|
75994
76241
|
}
|
|
75995
76242
|
}
|
|
75996
|
-
html(reply, code, body) {
|
|
75997
|
-
reply.header("content-type", "text/html; charset=utf-8");
|
|
75998
|
-
reply.status(code).send(`<!doctype html><html><body><p>${escapeHtml(body)}</p></body></html>`);
|
|
75999
|
-
}
|
|
76000
76243
|
prunePendingStates() {
|
|
76001
76244
|
const cutoff = Date.now() - 10 * 6e4;
|
|
76002
76245
|
for (const [k, v] of this.pendingStates) if (v.issuedAt < cutoff) this.pendingStates.delete(k);
|
|
@@ -76084,6 +76327,7 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
76084
76327
|
const entry = this.registry.getEntry(brokerId);
|
|
76085
76328
|
const oauth = this.registry.getOauth(brokerId);
|
|
76086
76329
|
if (entry === null || oauth === null || !oauth.refreshToken.trim()) throw new Error(`Google broker ${brokerId} is not connected — use Sign in under Connectivity → Brokers`);
|
|
76330
|
+
if (oauth.needsReauth) throw new Error(`Google broker ${brokerId} needs a new sign-in — use Sign in under Connectivity → Brokers`);
|
|
76087
76331
|
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");
|
|
76088
76332
|
const state = {
|
|
76089
76333
|
refreshToken: oauth.refreshToken.trim(),
|
|
@@ -76099,7 +76343,10 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
76099
76343
|
clientSecret: entry.clientSecret.trim()
|
|
76100
76344
|
}, state.refreshToken);
|
|
76101
76345
|
} catch (err) {
|
|
76102
|
-
if (isInvalidGrantRefreshError(err))
|
|
76346
|
+
if (isInvalidGrantRefreshError(err)) {
|
|
76347
|
+
if (this.registry.markNeedsReauth(brokerId)) await this.persistOauthAccounts();
|
|
76348
|
+
this.emitGoogleBrokerReconnectAlert(brokerId, entry.name);
|
|
76349
|
+
}
|
|
76103
76350
|
throw err;
|
|
76104
76351
|
}
|
|
76105
76352
|
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");
|
|
@@ -76109,7 +76356,8 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
76109
76356
|
refreshToken: nextState.refreshToken,
|
|
76110
76357
|
accessToken: nextState.accessToken,
|
|
76111
76358
|
accessExpiresAt: nextState.accessExpiresAt,
|
|
76112
|
-
accountEmail: nextState.accountEmail
|
|
76359
|
+
accountEmail: nextState.accountEmail,
|
|
76360
|
+
needsReauth: false
|
|
76113
76361
|
});
|
|
76114
76362
|
await this.persistOauthAccounts();
|
|
76115
76363
|
return nextState.accessToken;
|
|
@@ -76125,20 +76373,18 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
76125
76373
|
reply.status(404).send({ error: `Unknown Google broker: ${brokerId}` });
|
|
76126
76374
|
return;
|
|
76127
76375
|
}
|
|
76128
|
-
const popupRaw = req.query["popup"];
|
|
76129
|
-
const popup = popupRaw === "1" || popupRaw === "true";
|
|
76130
76376
|
this.registry.removeOauth(brokerId);
|
|
76131
76377
|
await this.persistOauthAccounts();
|
|
76132
|
-
this.ctx.logger.info("google oauth disconnected", {
|
|
76133
|
-
|
|
76134
|
-
|
|
76135
|
-
|
|
76136
|
-
|
|
76137
|
-
|
|
76138
|
-
|
|
76139
|
-
|
|
76140
|
-
|
|
76141
|
-
|
|
76378
|
+
this.ctx.logger.info("google oauth disconnected", {
|
|
76379
|
+
tags: { brokerId },
|
|
76380
|
+
meta: { brokerId }
|
|
76381
|
+
});
|
|
76382
|
+
await this.landOnAdminUi(reply, {
|
|
76383
|
+
status: "success",
|
|
76384
|
+
addonId: ADDON_ID,
|
|
76385
|
+
brokerId,
|
|
76386
|
+
action: "disconnect"
|
|
76387
|
+
});
|
|
76142
76388
|
}
|
|
76143
76389
|
async resolveRedirectUri() {
|
|
76144
76390
|
await this.refreshPublicOrigin();
|
|
@@ -76175,8 +76421,5 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
76175
76421
|
function asSettingsCap(cap) {
|
|
76176
76422
|
if (cap === "calendar-source" || cap === "smtp-provider") return cap;
|
|
76177
76423
|
}
|
|
76178
|
-
function escapeHtml(s) {
|
|
76179
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
76180
|
-
}
|
|
76181
76424
|
//#endregion
|
|
76182
76425
|
export { GOOGLE_GMAIL_SEND_SCOPE as C, OAUTH_START_PATH as E, GoogleServicesAddon, GoogleServicesAddon as default, GOOGLE_BROKER_KIND as S, GoogleServicesConfigSchema as T, GoogleBrokerRegistry as _, refreshAccessToken as a, stampCalendarId as b, selectGmailAccount as c, mapGoogleEvent as d, GOOGLE_CONSOLE_APIS_URL as f, redirectUriDisplay as g, introHtml as h, needsRefresh as i, createGoogleCalendarApi as l, connectHtml as m, buildAuthorizeUrl as n, buildRawMime as o, GOOGLE_CONSOLE_CREDENTIALS_URL as p, exchangeCode as r, createGmailSmtpProvider as s, applyTokenResponse as t, createGoogleCalendarSource as u, migrateLegacyToBrokers as v, GOOGLE_OAUTH_SCOPES as w, DEFAULT_GOOGLE_CONFIG as x, parseStampedCalendarId as y };
|