@camstack/addon-google-services 0.1.1 → 0.1.3
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 +318 -37
- package/dist/google-services.addon.mjs +315 -38
- package/dist/index.js +4 -0
- package/dist/index.mjs +2 -2
- package/package.json +7 -3
|
@@ -18195,14 +18195,14 @@ var LlmProfileSchema = object({
|
|
|
18195
18195
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
18196
18196
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
18197
18197
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
18198
|
-
var ConfigSchemaPassthrough$
|
|
18198
|
+
var ConfigSchemaPassthrough$2 = unknown();
|
|
18199
18199
|
var LlmProfileKindDescriptorSchema = object({
|
|
18200
18200
|
kind: LlmProfileKindSchema,
|
|
18201
18201
|
label: string(),
|
|
18202
18202
|
icon: string(),
|
|
18203
18203
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
18204
18204
|
addonId: string(),
|
|
18205
|
-
configSchema: ConfigSchemaPassthrough$
|
|
18205
|
+
configSchema: ConfigSchemaPassthrough$2
|
|
18206
18206
|
});
|
|
18207
18207
|
var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
|
|
18208
18208
|
var LlmDefaultSchema = object({
|
|
@@ -19323,7 +19323,7 @@ var TargetKindCapsSchema = object({
|
|
|
19323
19323
|
* the union is large and not meant for runtime validation here; the exported
|
|
19324
19324
|
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
19325
19325
|
*/
|
|
19326
|
-
var ConfigSchemaPassthrough = unknown();
|
|
19326
|
+
var ConfigSchemaPassthrough$1 = unknown();
|
|
19327
19327
|
var TargetKindSchema = object({
|
|
19328
19328
|
kind: string(),
|
|
19329
19329
|
label: string(),
|
|
@@ -19360,7 +19360,7 @@ var TargetKindSchema = object({
|
|
|
19360
19360
|
* its raster path, which is the safe default for an unknown image.
|
|
19361
19361
|
*/
|
|
19362
19362
|
iconMediaType: string().optional(),
|
|
19363
|
-
configSchema: ConfigSchemaPassthrough,
|
|
19363
|
+
configSchema: ConfigSchemaPassthrough$1,
|
|
19364
19364
|
/**
|
|
19365
19365
|
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
19366
19366
|
* base URL, instance, chat, recipient). A save that changes any of them
|
|
@@ -28708,15 +28708,28 @@ var SmtpStatusSchema = object({
|
|
|
28708
28708
|
/** Last successful verify timestamp (unix ms). */
|
|
28709
28709
|
lastVerifiedAt: number().optional()
|
|
28710
28710
|
});
|
|
28711
|
-
|
|
28712
|
-
|
|
28713
|
-
|
|
28714
|
-
|
|
28715
|
-
|
|
28716
|
-
|
|
28717
|
-
|
|
28718
|
-
|
|
28719
|
-
|
|
28711
|
+
var smtpProviderCapability = {
|
|
28712
|
+
name: "smtp-provider",
|
|
28713
|
+
scope: "system",
|
|
28714
|
+
mode: "collection",
|
|
28715
|
+
internal: true,
|
|
28716
|
+
providerKind: "email",
|
|
28717
|
+
methods: {
|
|
28718
|
+
sendEmail: method(SendEmailInputSchema, SendEmailResultSchema, {
|
|
28719
|
+
kind: "mutation",
|
|
28720
|
+
auth: "admin",
|
|
28721
|
+
access: "create"
|
|
28722
|
+
}),
|
|
28723
|
+
/** Round-trip ping against the SMTP relay (EHLO + AUTH if configured).
|
|
28724
|
+
* Used by the operator's "Test connection" button. */
|
|
28725
|
+
verify: method(_void(), SmtpStatusSchema, {
|
|
28726
|
+
kind: "mutation",
|
|
28727
|
+
auth: "admin",
|
|
28728
|
+
access: "view"
|
|
28729
|
+
}),
|
|
28730
|
+
getStatus: method(_void(), SmtpStatusSchema, { auth: "admin" })
|
|
28731
|
+
}
|
|
28732
|
+
};
|
|
28720
28733
|
/**
|
|
28721
28734
|
* A single device snapshot returned as base64 JPEG/PNG.
|
|
28722
28735
|
*
|
|
@@ -31683,6 +31696,7 @@ var calendarSourceCapability = {
|
|
|
31683
31696
|
scope: "system",
|
|
31684
31697
|
mode: "collection",
|
|
31685
31698
|
internal: true,
|
|
31699
|
+
providerKind: "calendar",
|
|
31686
31700
|
methods: {
|
|
31687
31701
|
listCalendars: method(_void(), array(CalendarInfoSchema).readonly(), { auth: "admin" }),
|
|
31688
31702
|
listEvents: method(CalendarListEventsInputSchema, array(CalendarEventSchema).readonly(), { auth: "admin" })
|
|
@@ -35164,6 +35178,7 @@ var OsdNamedTemplateSchema = object({
|
|
|
35164
35178
|
});
|
|
35165
35179
|
/** Where a slot's value comes from. */
|
|
35166
35180
|
var OsdSourceSchema = discriminatedUnion("kind", [
|
|
35181
|
+
object({ kind: literal("none") }),
|
|
35167
35182
|
object({
|
|
35168
35183
|
kind: literal("static"),
|
|
35169
35184
|
text: string().max(64)
|
|
@@ -39487,6 +39502,12 @@ var TrainingProviderDefaultsSchema = object({
|
|
|
39487
39502
|
/** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
|
|
39488
39503
|
failedDatasetGraceHours: number().nonnegative()
|
|
39489
39504
|
});
|
|
39505
|
+
/**
|
|
39506
|
+
* Entry form schema for a provider type — same seam as notification
|
|
39507
|
+
* `TargetKind.configSchema` (`z.unknown()` at the wire; callers tighten to
|
|
39508
|
+
* {@link ConfigUISchema}). Drives `ConfigFormBuilder` on Training → Providers.
|
|
39509
|
+
*/
|
|
39510
|
+
var ConfigSchemaPassthrough = unknown();
|
|
39490
39511
|
/** One creatable provider type, for the Add-entry dialog. */
|
|
39491
39512
|
var TrainingProviderTypeSchema = object({
|
|
39492
39513
|
typeId: string(),
|
|
@@ -39494,7 +39515,13 @@ var TrainingProviderTypeSchema = object({
|
|
|
39494
39515
|
/** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
|
|
39495
39516
|
iconAsset: string(),
|
|
39496
39517
|
/** Owning `training-provider` addon — collection selector for createEntry. */
|
|
39497
|
-
addonId: string()
|
|
39518
|
+
addonId: string(),
|
|
39519
|
+
/**
|
|
39520
|
+
* Fields for one entry of this type (credentials + defaults). Absent on a
|
|
39521
|
+
* type that only needs a name. Password fields are keychain-backed on the
|
|
39522
|
+
* provider addon (`secretStatePaths`), not D634 plaintext.
|
|
39523
|
+
*/
|
|
39524
|
+
configSchema: ConfigSchemaPassthrough.optional()
|
|
39498
39525
|
});
|
|
39499
39526
|
/** One configured account / workspace under a provider type. */
|
|
39500
39527
|
var TrainingProviderEntrySchema = object({
|
|
@@ -39524,7 +39551,30 @@ var TrainingProviderDescriptorSchema = object({
|
|
|
39524
39551
|
dataResidencyNote: string(),
|
|
39525
39552
|
/** Credentials are present — never the values. */
|
|
39526
39553
|
configured: boolean(),
|
|
39527
|
-
defaults: TrainingProviderDefaultsSchema
|
|
39554
|
+
defaults: TrainingProviderDefaultsSchema,
|
|
39555
|
+
/**
|
|
39556
|
+
* Values for {@link TrainingProviderTypeSchema}'s `configSchema` — password
|
|
39557
|
+
* fields are keychain {@link SecretFieldView} or `null` / `''` (create),
|
|
39558
|
+
* never plaintext. Same hydrate contract as settings forms (D789).
|
|
39559
|
+
*/
|
|
39560
|
+
formValues: record(string(), unknown()).optional()
|
|
39561
|
+
});
|
|
39562
|
+
/**
|
|
39563
|
+
* Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
|
|
39564
|
+
* An omitted or empty `tokenSecret` means KEEP the stored secret (D787).
|
|
39565
|
+
* `{ $secretClear: true }` clears it.
|
|
39566
|
+
*/
|
|
39567
|
+
var TrainingProviderUpdateEntryInputSchema = object({
|
|
39568
|
+
entryId: string().min(1),
|
|
39569
|
+
name: string().min(1),
|
|
39570
|
+
tokenId: string().optional(),
|
|
39571
|
+
tokenSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
|
|
39572
|
+
environment: string().optional(),
|
|
39573
|
+
gpuType: string().optional(),
|
|
39574
|
+
region: string().optional(),
|
|
39575
|
+
costCapUsd: number().nonnegative().optional(),
|
|
39576
|
+
maxRuntimeSec: number().int().positive().optional(),
|
|
39577
|
+
failedDatasetGraceHours: number().nonnegative().optional()
|
|
39528
39578
|
});
|
|
39529
39579
|
var TrainingCredentialCheckSchema = object({
|
|
39530
39580
|
ok: boolean(),
|
|
@@ -39594,6 +39644,9 @@ method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "
|
|
|
39594
39644
|
}), method(object({ entryId: string().min(1) }), object({ ok: literal(true) }), {
|
|
39595
39645
|
kind: "mutation",
|
|
39596
39646
|
auth: "admin"
|
|
39647
|
+
}), method(TrainingProviderUpdateEntryInputSchema, object({ ok: literal(true) }), {
|
|
39648
|
+
kind: "mutation",
|
|
39649
|
+
auth: "admin"
|
|
39597
39650
|
}), method(object({ entryId: string().min(1) }), TrainingCredentialCheckSchema, {
|
|
39598
39651
|
kind: "mutation",
|
|
39599
39652
|
auth: "admin",
|
|
@@ -47155,6 +47208,12 @@ Object.freeze({
|
|
|
47155
47208
|
addonId: null,
|
|
47156
47209
|
access: "create"
|
|
47157
47210
|
},
|
|
47211
|
+
"trainingProvider.updateEntry": {
|
|
47212
|
+
capName: "training-provider",
|
|
47213
|
+
capScope: "system",
|
|
47214
|
+
addonId: null,
|
|
47215
|
+
access: "create"
|
|
47216
|
+
},
|
|
47158
47217
|
"trainingProvider.uploadDataset": {
|
|
47159
47218
|
capName: "training-provider",
|
|
47160
47219
|
capScope: "system",
|
|
@@ -51700,6 +51759,7 @@ Object.freeze({
|
|
|
51700
51759
|
});
|
|
51701
51760
|
Object.freeze({
|
|
51702
51761
|
"broker": "broker",
|
|
51762
|
+
"calendar-source": "calendar",
|
|
51703
51763
|
"device-export": "device-export",
|
|
51704
51764
|
"llm": "ai",
|
|
51705
51765
|
"mesh-network": "mesh",
|
|
@@ -52744,7 +52804,12 @@ var GoogleServicesConfigSchema = object({
|
|
|
52744
52804
|
clientId: string().default(""),
|
|
52745
52805
|
clientSecret: string().default("").meta({ secret: true }),
|
|
52746
52806
|
/** Comma-separated Google calendar ids to expose; empty = all. */
|
|
52747
|
-
calendarFilter: string().default("")
|
|
52807
|
+
calendarFilter: string().default(""),
|
|
52808
|
+
/** Offer Gmail send via `smtp-provider` (same OAuth account). */
|
|
52809
|
+
gmailSendEnabled: boolean().default(false),
|
|
52810
|
+
/** Default From for Gmail send — usually the connected account email. */
|
|
52811
|
+
fromAddress: string().default(""),
|
|
52812
|
+
fromName: string().default("")
|
|
52748
52813
|
});
|
|
52749
52814
|
var DEFAULT_GOOGLE_CONFIG = GoogleServicesConfigSchema.parse({});
|
|
52750
52815
|
var DEFAULT_OAUTH_STATE = {
|
|
@@ -52754,9 +52819,14 @@ var DEFAULT_OAUTH_STATE = {
|
|
|
52754
52819
|
accountEmail: ""
|
|
52755
52820
|
};
|
|
52756
52821
|
var GOOGLE_CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
52822
|
+
var GOOGLE_GMAIL_SEND_SCOPE = "https://www.googleapis.com/auth/gmail.send";
|
|
52823
|
+
/** Space-separated scopes requested on Connect (Calendar read + Gmail send). */
|
|
52824
|
+
var GOOGLE_OAUTH_SCOPES = `${GOOGLE_CALENDAR_SCOPE} ${GOOGLE_GMAIL_SEND_SCOPE}`;
|
|
52757
52825
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
52758
52826
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
52759
52827
|
var GOOGLE_CALENDAR_API = "https://www.googleapis.com/calendar/v3";
|
|
52828
|
+
var GOOGLE_GMAIL_API = "https://gmail.googleapis.com/gmail/v1";
|
|
52829
|
+
var GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
52760
52830
|
//#endregion
|
|
52761
52831
|
//#region src/google-calendar-source.ts
|
|
52762
52832
|
function parseGoogleInstant(value) {
|
|
@@ -52859,6 +52929,148 @@ function createGoogleCalendarSource(deps) {
|
|
|
52859
52929
|
};
|
|
52860
52930
|
}
|
|
52861
52931
|
//#endregion
|
|
52932
|
+
//#region src/google-gmail-smtp.ts
|
|
52933
|
+
function encodeBase64Url(bytes) {
|
|
52934
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
52935
|
+
}
|
|
52936
|
+
function encodeHeaderWord(value) {
|
|
52937
|
+
if (/^[\x20-\x7E]*$/.test(value)) return value;
|
|
52938
|
+
return `=?UTF-8?B?${Buffer.from(value, "utf8").toString("base64")}?=`;
|
|
52939
|
+
}
|
|
52940
|
+
function formatAddressList(addrs) {
|
|
52941
|
+
return (Array.isArray(addrs) ? addrs : [addrs]).join(", ");
|
|
52942
|
+
}
|
|
52943
|
+
function buildFromHeader(cfg, accountEmail) {
|
|
52944
|
+
const addr = cfg.fromAddress.trim() || accountEmail;
|
|
52945
|
+
if (!addr) return "";
|
|
52946
|
+
if (cfg.fromName.trim()) return `${encodeHeaderWord(cfg.fromName.trim())} <${addr}>`;
|
|
52947
|
+
return addr;
|
|
52948
|
+
}
|
|
52949
|
+
/** Build a minimal multipart/alternative-capable plain (and optional HTML) message. */
|
|
52950
|
+
function buildRawMime(input) {
|
|
52951
|
+
const lines = [`From: ${input.from}`, `To: ${formatAddressList(input.to)}`];
|
|
52952
|
+
if (input.cc !== void 0 && input.cc.length > 0) lines.push(`Cc: ${formatAddressList(input.cc)}`);
|
|
52953
|
+
if (input.bcc !== void 0 && input.bcc.length > 0) lines.push(`Bcc: ${formatAddressList(input.bcc)}`);
|
|
52954
|
+
if (input.replyTo !== void 0 && input.replyTo.length > 0) lines.push(`Reply-To: ${input.replyTo}`);
|
|
52955
|
+
lines.push(`Subject: ${encodeHeaderWord(input.subject)}`);
|
|
52956
|
+
lines.push("MIME-Version: 1.0");
|
|
52957
|
+
if (input.html !== void 0 && input.html.length > 0) {
|
|
52958
|
+
const boundary = `camstack_${Date.now().toString(36)}`;
|
|
52959
|
+
lines.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
|
|
52960
|
+
lines.push("");
|
|
52961
|
+
lines.push(`--${boundary}`);
|
|
52962
|
+
lines.push("Content-Type: text/plain; charset=\"UTF-8\"");
|
|
52963
|
+
lines.push("Content-Transfer-Encoding: 8bit");
|
|
52964
|
+
lines.push("");
|
|
52965
|
+
lines.push(input.text);
|
|
52966
|
+
lines.push(`--${boundary}`);
|
|
52967
|
+
lines.push("Content-Type: text/html; charset=\"UTF-8\"");
|
|
52968
|
+
lines.push("Content-Transfer-Encoding: 8bit");
|
|
52969
|
+
lines.push("");
|
|
52970
|
+
lines.push(input.html);
|
|
52971
|
+
lines.push(`--${boundary}--`);
|
|
52972
|
+
} else {
|
|
52973
|
+
lines.push("Content-Type: text/plain; charset=\"UTF-8\"");
|
|
52974
|
+
lines.push("Content-Transfer-Encoding: 8bit");
|
|
52975
|
+
lines.push("");
|
|
52976
|
+
lines.push(input.text);
|
|
52977
|
+
}
|
|
52978
|
+
return lines.join("\r\n");
|
|
52979
|
+
}
|
|
52980
|
+
function createGmailSmtpProvider(deps) {
|
|
52981
|
+
const http = deps.fetch ?? fetch;
|
|
52982
|
+
let lastVerifiedAt;
|
|
52983
|
+
let lastError;
|
|
52984
|
+
const status = () => {
|
|
52985
|
+
const cfg = deps.getConfig();
|
|
52986
|
+
const email = deps.getAccountEmail() || cfg.fromAddress.trim() || "gmail";
|
|
52987
|
+
if (!cfg.gmailSendEnabled) return {
|
|
52988
|
+
ready: false,
|
|
52989
|
+
host: email,
|
|
52990
|
+
error: "Gmail send is disabled in Google Services settings.",
|
|
52991
|
+
lastVerifiedAt
|
|
52992
|
+
};
|
|
52993
|
+
if (!deps.connected()) return {
|
|
52994
|
+
ready: false,
|
|
52995
|
+
host: email,
|
|
52996
|
+
error: "Google account is not connected — open Connect Google.",
|
|
52997
|
+
lastVerifiedAt
|
|
52998
|
+
};
|
|
52999
|
+
return {
|
|
53000
|
+
ready: lastError === void 0 && lastVerifiedAt !== void 0,
|
|
53001
|
+
host: email,
|
|
53002
|
+
error: lastError,
|
|
53003
|
+
lastVerifiedAt
|
|
53004
|
+
};
|
|
53005
|
+
};
|
|
53006
|
+
return {
|
|
53007
|
+
getStatus: async () => status(),
|
|
53008
|
+
verify: async () => {
|
|
53009
|
+
if (!deps.getConfig().gmailSendEnabled) {
|
|
53010
|
+
lastError = "Gmail send is disabled in Google Services settings.";
|
|
53011
|
+
return status();
|
|
53012
|
+
}
|
|
53013
|
+
if (!deps.connected()) {
|
|
53014
|
+
lastError = "Google account is not connected.";
|
|
53015
|
+
return status();
|
|
53016
|
+
}
|
|
53017
|
+
try {
|
|
53018
|
+
const token = await deps.getAccessToken();
|
|
53019
|
+
const res = await http(`${GOOGLE_GMAIL_API}/users/me/profile`, { headers: { Authorization: `Bearer ${token}` } });
|
|
53020
|
+
if (!res.ok) {
|
|
53021
|
+
const text = await res.text();
|
|
53022
|
+
lastError = `Gmail profile failed (${res.status}): ${text.slice(0, 200)}`;
|
|
53023
|
+
return status();
|
|
53024
|
+
}
|
|
53025
|
+
lastError = void 0;
|
|
53026
|
+
lastVerifiedAt = Date.now();
|
|
53027
|
+
return status();
|
|
53028
|
+
} catch (err) {
|
|
53029
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
53030
|
+
return status();
|
|
53031
|
+
}
|
|
53032
|
+
},
|
|
53033
|
+
sendEmail: async (input) => {
|
|
53034
|
+
const cfg = deps.getConfig();
|
|
53035
|
+
if (!cfg.gmailSendEnabled) throw new Error("Gmail send is disabled in Google Services settings.");
|
|
53036
|
+
if (!deps.connected()) throw new Error("Google account is not connected — open Connect Google.");
|
|
53037
|
+
const from = input.from?.trim() || buildFromHeader(cfg, deps.getAccountEmail());
|
|
53038
|
+
if (!from) throw new Error("Set From address in Google Services settings (or connect an account).");
|
|
53039
|
+
const raw = buildRawMime({
|
|
53040
|
+
from,
|
|
53041
|
+
to: input.to,
|
|
53042
|
+
cc: input.cc,
|
|
53043
|
+
bcc: input.bcc,
|
|
53044
|
+
replyTo: input.replyTo,
|
|
53045
|
+
subject: input.subject,
|
|
53046
|
+
text: input.text,
|
|
53047
|
+
html: input.html
|
|
53048
|
+
});
|
|
53049
|
+
const token = await deps.getAccessToken();
|
|
53050
|
+
const res = await http(`${GOOGLE_GMAIL_API}/users/me/messages/send`, {
|
|
53051
|
+
method: "POST",
|
|
53052
|
+
headers: {
|
|
53053
|
+
Authorization: `Bearer ${token}`,
|
|
53054
|
+
"Content-Type": "application/json"
|
|
53055
|
+
},
|
|
53056
|
+
body: JSON.stringify({ raw: encodeBase64Url(Buffer.from(raw, "utf8")) })
|
|
53057
|
+
});
|
|
53058
|
+
if (!res.ok) {
|
|
53059
|
+
const text = await res.text();
|
|
53060
|
+
throw new Error(`Gmail send failed (${res.status}): ${text.slice(0, 400)}`);
|
|
53061
|
+
}
|
|
53062
|
+
const messageId = (await res.json()).id ?? `gmail-${Date.now()}`;
|
|
53063
|
+
lastError = void 0;
|
|
53064
|
+
lastVerifiedAt = Date.now();
|
|
53065
|
+
return {
|
|
53066
|
+
messageId,
|
|
53067
|
+
accepted: [...Array.isArray(input.to) ? input.to : [input.to]],
|
|
53068
|
+
rejected: []
|
|
53069
|
+
};
|
|
53070
|
+
}
|
|
53071
|
+
};
|
|
53072
|
+
}
|
|
53073
|
+
//#endregion
|
|
52862
53074
|
//#region src/google-oauth.ts
|
|
52863
53075
|
/**
|
|
52864
53076
|
* Google OAuth2 authorization-code + refresh for Calendar API.
|
|
@@ -52868,7 +53080,7 @@ function buildAuthorizeUrl(cfg, state) {
|
|
|
52868
53080
|
u.searchParams.set("client_id", cfg.clientId);
|
|
52869
53081
|
u.searchParams.set("redirect_uri", cfg.redirectUri);
|
|
52870
53082
|
u.searchParams.set("response_type", "code");
|
|
52871
|
-
u.searchParams.set("scope",
|
|
53083
|
+
u.searchParams.set("scope", GOOGLE_OAUTH_SCOPES);
|
|
52872
53084
|
u.searchParams.set("access_type", "offline");
|
|
52873
53085
|
u.searchParams.set("prompt", "consent");
|
|
52874
53086
|
u.searchParams.set("state", state);
|
|
@@ -52926,9 +53138,19 @@ function needsRefresh(state, nowMs = Date.now()) {
|
|
|
52926
53138
|
return state.accessExpiresAt <= nowMs;
|
|
52927
53139
|
}
|
|
52928
53140
|
//#endregion
|
|
53141
|
+
//#region src/oauth-redirect-origin.ts
|
|
53142
|
+
/**
|
|
53143
|
+
* Google OAuth redirect origin — same role as hub OAuth2 / sign-in
|
|
53144
|
+
* (`local-network` cap: `roles.publicEndpoint`), never `internal` (LAN / agents).
|
|
53145
|
+
* Google rejects non-localhost `http://` redirect URIs for Web clients.
|
|
53146
|
+
*/
|
|
53147
|
+
function pickOauthRedirectOrigin(book) {
|
|
53148
|
+
return book.roles.publicEndpoint.resolved ?? book.roles.internal.resolved ?? null;
|
|
53149
|
+
}
|
|
53150
|
+
//#endregion
|
|
52929
53151
|
//#region src/google-services.addon.ts
|
|
52930
53152
|
/**
|
|
52931
|
-
* Google Services — hub-only calendar-source
|
|
53153
|
+
* Google Services — hub-only calendar-source + Gmail smtp-provider (same OAuth2).
|
|
52932
53154
|
*/
|
|
52933
53155
|
var ADDON_ID = "google-services";
|
|
52934
53156
|
var CALLBACK_PATH = "/oauth/callback";
|
|
@@ -52952,17 +53174,23 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
52952
53174
|
this.oauthHandle = this.state("oauth", OauthStateSchema, { ...DEFAULT_OAUTH_STATE });
|
|
52953
53175
|
this.oauth = await this.oauthHandle.get();
|
|
52954
53176
|
const api = createGoogleCalendarApi(async () => this.ensureAccessToken());
|
|
52955
|
-
const
|
|
53177
|
+
const calendarProvider = createGoogleCalendarSource({
|
|
52956
53178
|
logger: this.ctx.logger,
|
|
52957
53179
|
api,
|
|
52958
53180
|
connected: () => this.oauth.refreshToken.length > 0,
|
|
52959
53181
|
filterIds: () => this.parseFilter()
|
|
52960
53182
|
});
|
|
53183
|
+
const smtpProvider = createGmailSmtpProvider({
|
|
53184
|
+
getAccessToken: () => this.ensureAccessToken(),
|
|
53185
|
+
getConfig: () => this.config,
|
|
53186
|
+
getAccountEmail: () => this.oauth.accountEmail,
|
|
53187
|
+
connected: () => this.oauth.refreshToken.length > 0
|
|
53188
|
+
});
|
|
52961
53189
|
const routeProvider = buildAddonRouteProvider(ADDON_ID, [{
|
|
52962
53190
|
method: "GET",
|
|
52963
53191
|
path: START_PATH,
|
|
52964
53192
|
access: "admin",
|
|
52965
|
-
description: "Begin Google Calendar
|
|
53193
|
+
description: "Begin Google OAuth (Calendar + Gmail send)",
|
|
52966
53194
|
handler: async (_req, reply) => this.handleStart(reply)
|
|
52967
53195
|
}, {
|
|
52968
53196
|
method: "GET",
|
|
@@ -52972,17 +53200,25 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
52972
53200
|
description: "Google OAuth callback",
|
|
52973
53201
|
handler: async (req, reply) => this.handleCallback(req, reply)
|
|
52974
53202
|
}]);
|
|
52975
|
-
this.ctx.logger.info("Initialized — google calendar-source", { meta: {
|
|
53203
|
+
this.ctx.logger.info("Initialized — google calendar-source + gmail smtp", { meta: {
|
|
52976
53204
|
connected: this.oauth.refreshToken.length > 0,
|
|
52977
|
-
accountEmail: this.oauth.accountEmail || void 0
|
|
53205
|
+
accountEmail: this.oauth.accountEmail || void 0,
|
|
53206
|
+
gmailSendEnabled: this.config.gmailSendEnabled
|
|
52978
53207
|
} });
|
|
52979
|
-
return { providers: [
|
|
52980
|
-
|
|
52981
|
-
|
|
52982
|
-
|
|
52983
|
-
|
|
52984
|
-
|
|
52985
|
-
|
|
53208
|
+
return { providers: [
|
|
53209
|
+
{
|
|
53210
|
+
capability: calendarSourceCapability,
|
|
53211
|
+
provider: calendarProvider
|
|
53212
|
+
},
|
|
53213
|
+
{
|
|
53214
|
+
capability: smtpProviderCapability,
|
|
53215
|
+
provider: smtpProvider
|
|
53216
|
+
},
|
|
53217
|
+
{
|
|
53218
|
+
capability: addonRoutesCapability,
|
|
53219
|
+
provider: routeProvider
|
|
53220
|
+
}
|
|
53221
|
+
] };
|
|
52986
53222
|
}
|
|
52987
53223
|
async handleStart(reply) {
|
|
52988
53224
|
if (!this.config.clientId || !this.config.clientSecret) {
|
|
@@ -53028,15 +53264,33 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53028
53264
|
}, code);
|
|
53029
53265
|
if (!tokens.refresh_token && !this.oauth.refreshToken) throw new Error("Google did not return a refresh_token. Revoke CamStack access in Google Account → Security and retry with prompt=consent.");
|
|
53030
53266
|
this.oauth = applyTokenResponse(this.oauth, tokens);
|
|
53267
|
+
const email = await this.fetchAccountEmail(this.oauth.accessToken);
|
|
53268
|
+
if (email) this.oauth = {
|
|
53269
|
+
...this.oauth,
|
|
53270
|
+
accountEmail: email
|
|
53271
|
+
};
|
|
53031
53272
|
await this.persistOauth();
|
|
53032
|
-
this.ctx.logger.info("google oauth connected", { meta: {
|
|
53033
|
-
|
|
53273
|
+
this.ctx.logger.info("google oauth connected", { meta: {
|
|
53274
|
+
hasRefresh: this.oauth.refreshToken.length > 0,
|
|
53275
|
+
accountEmail: this.oauth.accountEmail || void 0
|
|
53276
|
+
} });
|
|
53277
|
+
this.html(reply, 200, "Google account connected (Calendar + Gmail send). You can close this window.");
|
|
53034
53278
|
} catch (e) {
|
|
53035
53279
|
const message = e instanceof Error ? e.message : String(e);
|
|
53036
53280
|
this.ctx.logger.warn("google oauth callback failed", { meta: { error: message } });
|
|
53037
53281
|
this.html(reply, 500, `OAuth failed: ${message}`);
|
|
53038
53282
|
}
|
|
53039
53283
|
}
|
|
53284
|
+
async fetchAccountEmail(accessToken) {
|
|
53285
|
+
try {
|
|
53286
|
+
const res = await fetch(GOOGLE_USERINFO_URL, { headers: { Authorization: `Bearer ${accessToken}` } });
|
|
53287
|
+
if (!res.ok) return "";
|
|
53288
|
+
const body = await res.json();
|
|
53289
|
+
return typeof body.email === "string" ? body.email.trim() : "";
|
|
53290
|
+
} catch {
|
|
53291
|
+
return "";
|
|
53292
|
+
}
|
|
53293
|
+
}
|
|
53040
53294
|
html(reply, code, body) {
|
|
53041
53295
|
reply.header("content-type", "text/html; charset=utf-8");
|
|
53042
53296
|
reply.status(code).send(`<!doctype html><html><body><p>${escapeHtml(body)}</p></body></html>`);
|
|
@@ -53066,16 +53320,15 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53066
53320
|
return new Set(raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0));
|
|
53067
53321
|
}
|
|
53068
53322
|
async resolveRedirectUri() {
|
|
53069
|
-
const
|
|
53070
|
-
|
|
53071
|
-
if (!origin) throw new Error("No hub address available from local-network — set an Internal address on the Network page.");
|
|
53323
|
+
const origin = pickOauthRedirectOrigin(await this.ctx.api.localNetwork.getAddresses.query({}));
|
|
53324
|
+
if (!origin) throw new Error("No hub public address available from local-network — set a Public endpoint on the Network page (OAuth2 redirects use that role, not Internal).");
|
|
53072
53325
|
return `${origin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
|
|
53073
53326
|
}
|
|
53074
53327
|
globalSettingsSchema() {
|
|
53075
53328
|
return this.schema({ sections: [{
|
|
53076
53329
|
id: "google-oauth",
|
|
53077
|
-
title: "Google
|
|
53078
|
-
description: "Create an OAuth client (Web application) in Google Cloud Console with the Calendar API enabled. Register the redirect URI `<hub>/addon/google-services/oauth/callback`
|
|
53330
|
+
title: "Google OAuth",
|
|
53331
|
+
description: "Create an OAuth client (Web application) in Google Cloud Console with the Calendar API and Gmail API enabled. Connect grants both Calendar read and Gmail send on the same account. Register the redirect URI `<public-hub>/addon/google-services/oauth/callback` — hub = Public endpoint from Network (same role as sign-in / OAuth2; Google rejects plain LAN http).",
|
|
53079
53332
|
columns: 1,
|
|
53080
53333
|
fields: [
|
|
53081
53334
|
{
|
|
@@ -53100,7 +53353,31 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53100
53353
|
key: "_connectInfo",
|
|
53101
53354
|
label: "Connect",
|
|
53102
53355
|
format: "html",
|
|
53103
|
-
content: `After saving client id/secret, open <a href="/addon/${ADDON_ID}${START_PATH}">Connect Google
|
|
53356
|
+
content: `After saving client id/secret, open <a href="/addon/${ADDON_ID}${START_PATH}">Connect Google</a> while signed in as admin. Redirect URI uses the Network <b>Public endpoint</b> (today typically <code>https://…/addon/google-services/oauth/callback</code>). If you connected before Gmail send was added, revoke CamStack in Google Account security and reconnect so the refresh token includes <code>gmail.send</code>.`
|
|
53357
|
+
}
|
|
53358
|
+
]
|
|
53359
|
+
}, {
|
|
53360
|
+
id: "gmail-send",
|
|
53361
|
+
title: "Gmail send",
|
|
53362
|
+
description: "Optional email delivery via the Gmail API (`smtp-provider`). Uses the same OAuth account — From must be that account or a verified send-as alias.",
|
|
53363
|
+
columns: 1,
|
|
53364
|
+
fields: [
|
|
53365
|
+
{
|
|
53366
|
+
type: "boolean",
|
|
53367
|
+
key: "gmailSendEnabled",
|
|
53368
|
+
label: "Enable Gmail send",
|
|
53369
|
+
description: "When off, Calendar still works; this addon reports smtp-provider not ready."
|
|
53370
|
+
},
|
|
53371
|
+
{
|
|
53372
|
+
type: "text",
|
|
53373
|
+
key: "fromAddress",
|
|
53374
|
+
label: "From address",
|
|
53375
|
+
description: "Defaults to the connected Google account email when empty."
|
|
53376
|
+
},
|
|
53377
|
+
{
|
|
53378
|
+
type: "text",
|
|
53379
|
+
key: "fromName",
|
|
53380
|
+
label: "From display name (optional)"
|
|
53104
53381
|
}
|
|
53105
53382
|
]
|
|
53106
53383
|
}] });
|
|
@@ -53111,11 +53388,15 @@ function escapeHtml(s) {
|
|
|
53111
53388
|
}
|
|
53112
53389
|
//#endregion
|
|
53113
53390
|
exports.DEFAULT_GOOGLE_CONFIG = DEFAULT_GOOGLE_CONFIG;
|
|
53391
|
+
exports.GOOGLE_GMAIL_SEND_SCOPE = GOOGLE_GMAIL_SEND_SCOPE;
|
|
53392
|
+
exports.GOOGLE_OAUTH_SCOPES = GOOGLE_OAUTH_SCOPES;
|
|
53114
53393
|
exports.GoogleServicesAddon = GoogleServicesAddon;
|
|
53115
53394
|
exports.default = GoogleServicesAddon;
|
|
53116
53395
|
exports.GoogleServicesConfigSchema = GoogleServicesConfigSchema;
|
|
53117
53396
|
exports.applyTokenResponse = applyTokenResponse;
|
|
53118
53397
|
exports.buildAuthorizeUrl = buildAuthorizeUrl;
|
|
53398
|
+
exports.buildRawMime = buildRawMime;
|
|
53399
|
+
exports.createGmailSmtpProvider = createGmailSmtpProvider;
|
|
53119
53400
|
exports.createGoogleCalendarApi = createGoogleCalendarApi;
|
|
53120
53401
|
exports.createGoogleCalendarSource = createGoogleCalendarSource;
|
|
53121
53402
|
exports.exchangeCode = exchangeCode;
|
|
@@ -18191,14 +18191,14 @@ var LlmProfileSchema = object({
|
|
|
18191
18191
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
18192
18192
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
18193
18193
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
18194
|
-
var ConfigSchemaPassthrough$
|
|
18194
|
+
var ConfigSchemaPassthrough$2 = unknown();
|
|
18195
18195
|
var LlmProfileKindDescriptorSchema = object({
|
|
18196
18196
|
kind: LlmProfileKindSchema,
|
|
18197
18197
|
label: string(),
|
|
18198
18198
|
icon: string(),
|
|
18199
18199
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
18200
18200
|
addonId: string(),
|
|
18201
|
-
configSchema: ConfigSchemaPassthrough$
|
|
18201
|
+
configSchema: ConfigSchemaPassthrough$2
|
|
18202
18202
|
});
|
|
18203
18203
|
var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
|
|
18204
18204
|
var LlmDefaultSchema = object({
|
|
@@ -19319,7 +19319,7 @@ var TargetKindCapsSchema = object({
|
|
|
19319
19319
|
* the union is large and not meant for runtime validation here; the exported
|
|
19320
19320
|
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
19321
19321
|
*/
|
|
19322
|
-
var ConfigSchemaPassthrough = unknown();
|
|
19322
|
+
var ConfigSchemaPassthrough$1 = unknown();
|
|
19323
19323
|
var TargetKindSchema = object({
|
|
19324
19324
|
kind: string(),
|
|
19325
19325
|
label: string(),
|
|
@@ -19356,7 +19356,7 @@ var TargetKindSchema = object({
|
|
|
19356
19356
|
* its raster path, which is the safe default for an unknown image.
|
|
19357
19357
|
*/
|
|
19358
19358
|
iconMediaType: string().optional(),
|
|
19359
|
-
configSchema: ConfigSchemaPassthrough,
|
|
19359
|
+
configSchema: ConfigSchemaPassthrough$1,
|
|
19360
19360
|
/**
|
|
19361
19361
|
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
19362
19362
|
* base URL, instance, chat, recipient). A save that changes any of them
|
|
@@ -28704,15 +28704,28 @@ var SmtpStatusSchema = object({
|
|
|
28704
28704
|
/** Last successful verify timestamp (unix ms). */
|
|
28705
28705
|
lastVerifiedAt: number().optional()
|
|
28706
28706
|
});
|
|
28707
|
-
|
|
28708
|
-
|
|
28709
|
-
|
|
28710
|
-
|
|
28711
|
-
|
|
28712
|
-
|
|
28713
|
-
|
|
28714
|
-
|
|
28715
|
-
|
|
28707
|
+
var smtpProviderCapability = {
|
|
28708
|
+
name: "smtp-provider",
|
|
28709
|
+
scope: "system",
|
|
28710
|
+
mode: "collection",
|
|
28711
|
+
internal: true,
|
|
28712
|
+
providerKind: "email",
|
|
28713
|
+
methods: {
|
|
28714
|
+
sendEmail: method(SendEmailInputSchema, SendEmailResultSchema, {
|
|
28715
|
+
kind: "mutation",
|
|
28716
|
+
auth: "admin",
|
|
28717
|
+
access: "create"
|
|
28718
|
+
}),
|
|
28719
|
+
/** Round-trip ping against the SMTP relay (EHLO + AUTH if configured).
|
|
28720
|
+
* Used by the operator's "Test connection" button. */
|
|
28721
|
+
verify: method(_void(), SmtpStatusSchema, {
|
|
28722
|
+
kind: "mutation",
|
|
28723
|
+
auth: "admin",
|
|
28724
|
+
access: "view"
|
|
28725
|
+
}),
|
|
28726
|
+
getStatus: method(_void(), SmtpStatusSchema, { auth: "admin" })
|
|
28727
|
+
}
|
|
28728
|
+
};
|
|
28716
28729
|
/**
|
|
28717
28730
|
* A single device snapshot returned as base64 JPEG/PNG.
|
|
28718
28731
|
*
|
|
@@ -31679,6 +31692,7 @@ var calendarSourceCapability = {
|
|
|
31679
31692
|
scope: "system",
|
|
31680
31693
|
mode: "collection",
|
|
31681
31694
|
internal: true,
|
|
31695
|
+
providerKind: "calendar",
|
|
31682
31696
|
methods: {
|
|
31683
31697
|
listCalendars: method(_void(), array(CalendarInfoSchema).readonly(), { auth: "admin" }),
|
|
31684
31698
|
listEvents: method(CalendarListEventsInputSchema, array(CalendarEventSchema).readonly(), { auth: "admin" })
|
|
@@ -35160,6 +35174,7 @@ var OsdNamedTemplateSchema = object({
|
|
|
35160
35174
|
});
|
|
35161
35175
|
/** Where a slot's value comes from. */
|
|
35162
35176
|
var OsdSourceSchema = discriminatedUnion("kind", [
|
|
35177
|
+
object({ kind: literal("none") }),
|
|
35163
35178
|
object({
|
|
35164
35179
|
kind: literal("static"),
|
|
35165
35180
|
text: string().max(64)
|
|
@@ -39483,6 +39498,12 @@ var TrainingProviderDefaultsSchema = object({
|
|
|
39483
39498
|
/** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
|
|
39484
39499
|
failedDatasetGraceHours: number().nonnegative()
|
|
39485
39500
|
});
|
|
39501
|
+
/**
|
|
39502
|
+
* Entry form schema for a provider type — same seam as notification
|
|
39503
|
+
* `TargetKind.configSchema` (`z.unknown()` at the wire; callers tighten to
|
|
39504
|
+
* {@link ConfigUISchema}). Drives `ConfigFormBuilder` on Training → Providers.
|
|
39505
|
+
*/
|
|
39506
|
+
var ConfigSchemaPassthrough = unknown();
|
|
39486
39507
|
/** One creatable provider type, for the Add-entry dialog. */
|
|
39487
39508
|
var TrainingProviderTypeSchema = object({
|
|
39488
39509
|
typeId: string(),
|
|
@@ -39490,7 +39511,13 @@ var TrainingProviderTypeSchema = object({
|
|
|
39490
39511
|
/** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
|
|
39491
39512
|
iconAsset: string(),
|
|
39492
39513
|
/** Owning `training-provider` addon — collection selector for createEntry. */
|
|
39493
|
-
addonId: string()
|
|
39514
|
+
addonId: string(),
|
|
39515
|
+
/**
|
|
39516
|
+
* Fields for one entry of this type (credentials + defaults). Absent on a
|
|
39517
|
+
* type that only needs a name. Password fields are keychain-backed on the
|
|
39518
|
+
* provider addon (`secretStatePaths`), not D634 plaintext.
|
|
39519
|
+
*/
|
|
39520
|
+
configSchema: ConfigSchemaPassthrough.optional()
|
|
39494
39521
|
});
|
|
39495
39522
|
/** One configured account / workspace under a provider type. */
|
|
39496
39523
|
var TrainingProviderEntrySchema = object({
|
|
@@ -39520,7 +39547,30 @@ var TrainingProviderDescriptorSchema = object({
|
|
|
39520
39547
|
dataResidencyNote: string(),
|
|
39521
39548
|
/** Credentials are present — never the values. */
|
|
39522
39549
|
configured: boolean(),
|
|
39523
|
-
defaults: TrainingProviderDefaultsSchema
|
|
39550
|
+
defaults: TrainingProviderDefaultsSchema,
|
|
39551
|
+
/**
|
|
39552
|
+
* Values for {@link TrainingProviderTypeSchema}'s `configSchema` — password
|
|
39553
|
+
* fields are keychain {@link SecretFieldView} or `null` / `''` (create),
|
|
39554
|
+
* never plaintext. Same hydrate contract as settings forms (D789).
|
|
39555
|
+
*/
|
|
39556
|
+
formValues: record(string(), unknown()).optional()
|
|
39557
|
+
});
|
|
39558
|
+
/**
|
|
39559
|
+
* Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
|
|
39560
|
+
* An omitted or empty `tokenSecret` means KEEP the stored secret (D787).
|
|
39561
|
+
* `{ $secretClear: true }` clears it.
|
|
39562
|
+
*/
|
|
39563
|
+
var TrainingProviderUpdateEntryInputSchema = object({
|
|
39564
|
+
entryId: string().min(1),
|
|
39565
|
+
name: string().min(1),
|
|
39566
|
+
tokenId: string().optional(),
|
|
39567
|
+
tokenSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
|
|
39568
|
+
environment: string().optional(),
|
|
39569
|
+
gpuType: string().optional(),
|
|
39570
|
+
region: string().optional(),
|
|
39571
|
+
costCapUsd: number().nonnegative().optional(),
|
|
39572
|
+
maxRuntimeSec: number().int().positive().optional(),
|
|
39573
|
+
failedDatasetGraceHours: number().nonnegative().optional()
|
|
39524
39574
|
});
|
|
39525
39575
|
var TrainingCredentialCheckSchema = object({
|
|
39526
39576
|
ok: boolean(),
|
|
@@ -39590,6 +39640,9 @@ method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "
|
|
|
39590
39640
|
}), method(object({ entryId: string().min(1) }), object({ ok: literal(true) }), {
|
|
39591
39641
|
kind: "mutation",
|
|
39592
39642
|
auth: "admin"
|
|
39643
|
+
}), method(TrainingProviderUpdateEntryInputSchema, object({ ok: literal(true) }), {
|
|
39644
|
+
kind: "mutation",
|
|
39645
|
+
auth: "admin"
|
|
39593
39646
|
}), method(object({ entryId: string().min(1) }), TrainingCredentialCheckSchema, {
|
|
39594
39647
|
kind: "mutation",
|
|
39595
39648
|
auth: "admin",
|
|
@@ -47151,6 +47204,12 @@ Object.freeze({
|
|
|
47151
47204
|
addonId: null,
|
|
47152
47205
|
access: "create"
|
|
47153
47206
|
},
|
|
47207
|
+
"trainingProvider.updateEntry": {
|
|
47208
|
+
capName: "training-provider",
|
|
47209
|
+
capScope: "system",
|
|
47210
|
+
addonId: null,
|
|
47211
|
+
access: "create"
|
|
47212
|
+
},
|
|
47154
47213
|
"trainingProvider.uploadDataset": {
|
|
47155
47214
|
capName: "training-provider",
|
|
47156
47215
|
capScope: "system",
|
|
@@ -51696,6 +51755,7 @@ Object.freeze({
|
|
|
51696
51755
|
});
|
|
51697
51756
|
Object.freeze({
|
|
51698
51757
|
"broker": "broker",
|
|
51758
|
+
"calendar-source": "calendar",
|
|
51699
51759
|
"device-export": "device-export",
|
|
51700
51760
|
"llm": "ai",
|
|
51701
51761
|
"mesh-network": "mesh",
|
|
@@ -52740,7 +52800,12 @@ var GoogleServicesConfigSchema = object({
|
|
|
52740
52800
|
clientId: string().default(""),
|
|
52741
52801
|
clientSecret: string().default("").meta({ secret: true }),
|
|
52742
52802
|
/** Comma-separated Google calendar ids to expose; empty = all. */
|
|
52743
|
-
calendarFilter: string().default("")
|
|
52803
|
+
calendarFilter: string().default(""),
|
|
52804
|
+
/** Offer Gmail send via `smtp-provider` (same OAuth account). */
|
|
52805
|
+
gmailSendEnabled: boolean().default(false),
|
|
52806
|
+
/** Default From for Gmail send — usually the connected account email. */
|
|
52807
|
+
fromAddress: string().default(""),
|
|
52808
|
+
fromName: string().default("")
|
|
52744
52809
|
});
|
|
52745
52810
|
var DEFAULT_GOOGLE_CONFIG = GoogleServicesConfigSchema.parse({});
|
|
52746
52811
|
var DEFAULT_OAUTH_STATE = {
|
|
@@ -52750,9 +52815,14 @@ var DEFAULT_OAUTH_STATE = {
|
|
|
52750
52815
|
accountEmail: ""
|
|
52751
52816
|
};
|
|
52752
52817
|
var GOOGLE_CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
52818
|
+
var GOOGLE_GMAIL_SEND_SCOPE = "https://www.googleapis.com/auth/gmail.send";
|
|
52819
|
+
/** Space-separated scopes requested on Connect (Calendar read + Gmail send). */
|
|
52820
|
+
var GOOGLE_OAUTH_SCOPES = `${GOOGLE_CALENDAR_SCOPE} ${GOOGLE_GMAIL_SEND_SCOPE}`;
|
|
52753
52821
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
52754
52822
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
52755
52823
|
var GOOGLE_CALENDAR_API = "https://www.googleapis.com/calendar/v3";
|
|
52824
|
+
var GOOGLE_GMAIL_API = "https://gmail.googleapis.com/gmail/v1";
|
|
52825
|
+
var GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
52756
52826
|
//#endregion
|
|
52757
52827
|
//#region src/google-calendar-source.ts
|
|
52758
52828
|
function parseGoogleInstant(value) {
|
|
@@ -52855,6 +52925,148 @@ function createGoogleCalendarSource(deps) {
|
|
|
52855
52925
|
};
|
|
52856
52926
|
}
|
|
52857
52927
|
//#endregion
|
|
52928
|
+
//#region src/google-gmail-smtp.ts
|
|
52929
|
+
function encodeBase64Url(bytes) {
|
|
52930
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
52931
|
+
}
|
|
52932
|
+
function encodeHeaderWord(value) {
|
|
52933
|
+
if (/^[\x20-\x7E]*$/.test(value)) return value;
|
|
52934
|
+
return `=?UTF-8?B?${Buffer.from(value, "utf8").toString("base64")}?=`;
|
|
52935
|
+
}
|
|
52936
|
+
function formatAddressList(addrs) {
|
|
52937
|
+
return (Array.isArray(addrs) ? addrs : [addrs]).join(", ");
|
|
52938
|
+
}
|
|
52939
|
+
function buildFromHeader(cfg, accountEmail) {
|
|
52940
|
+
const addr = cfg.fromAddress.trim() || accountEmail;
|
|
52941
|
+
if (!addr) return "";
|
|
52942
|
+
if (cfg.fromName.trim()) return `${encodeHeaderWord(cfg.fromName.trim())} <${addr}>`;
|
|
52943
|
+
return addr;
|
|
52944
|
+
}
|
|
52945
|
+
/** Build a minimal multipart/alternative-capable plain (and optional HTML) message. */
|
|
52946
|
+
function buildRawMime(input) {
|
|
52947
|
+
const lines = [`From: ${input.from}`, `To: ${formatAddressList(input.to)}`];
|
|
52948
|
+
if (input.cc !== void 0 && input.cc.length > 0) lines.push(`Cc: ${formatAddressList(input.cc)}`);
|
|
52949
|
+
if (input.bcc !== void 0 && input.bcc.length > 0) lines.push(`Bcc: ${formatAddressList(input.bcc)}`);
|
|
52950
|
+
if (input.replyTo !== void 0 && input.replyTo.length > 0) lines.push(`Reply-To: ${input.replyTo}`);
|
|
52951
|
+
lines.push(`Subject: ${encodeHeaderWord(input.subject)}`);
|
|
52952
|
+
lines.push("MIME-Version: 1.0");
|
|
52953
|
+
if (input.html !== void 0 && input.html.length > 0) {
|
|
52954
|
+
const boundary = `camstack_${Date.now().toString(36)}`;
|
|
52955
|
+
lines.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
|
|
52956
|
+
lines.push("");
|
|
52957
|
+
lines.push(`--${boundary}`);
|
|
52958
|
+
lines.push("Content-Type: text/plain; charset=\"UTF-8\"");
|
|
52959
|
+
lines.push("Content-Transfer-Encoding: 8bit");
|
|
52960
|
+
lines.push("");
|
|
52961
|
+
lines.push(input.text);
|
|
52962
|
+
lines.push(`--${boundary}`);
|
|
52963
|
+
lines.push("Content-Type: text/html; charset=\"UTF-8\"");
|
|
52964
|
+
lines.push("Content-Transfer-Encoding: 8bit");
|
|
52965
|
+
lines.push("");
|
|
52966
|
+
lines.push(input.html);
|
|
52967
|
+
lines.push(`--${boundary}--`);
|
|
52968
|
+
} else {
|
|
52969
|
+
lines.push("Content-Type: text/plain; charset=\"UTF-8\"");
|
|
52970
|
+
lines.push("Content-Transfer-Encoding: 8bit");
|
|
52971
|
+
lines.push("");
|
|
52972
|
+
lines.push(input.text);
|
|
52973
|
+
}
|
|
52974
|
+
return lines.join("\r\n");
|
|
52975
|
+
}
|
|
52976
|
+
function createGmailSmtpProvider(deps) {
|
|
52977
|
+
const http = deps.fetch ?? fetch;
|
|
52978
|
+
let lastVerifiedAt;
|
|
52979
|
+
let lastError;
|
|
52980
|
+
const status = () => {
|
|
52981
|
+
const cfg = deps.getConfig();
|
|
52982
|
+
const email = deps.getAccountEmail() || cfg.fromAddress.trim() || "gmail";
|
|
52983
|
+
if (!cfg.gmailSendEnabled) return {
|
|
52984
|
+
ready: false,
|
|
52985
|
+
host: email,
|
|
52986
|
+
error: "Gmail send is disabled in Google Services settings.",
|
|
52987
|
+
lastVerifiedAt
|
|
52988
|
+
};
|
|
52989
|
+
if (!deps.connected()) return {
|
|
52990
|
+
ready: false,
|
|
52991
|
+
host: email,
|
|
52992
|
+
error: "Google account is not connected — open Connect Google.",
|
|
52993
|
+
lastVerifiedAt
|
|
52994
|
+
};
|
|
52995
|
+
return {
|
|
52996
|
+
ready: lastError === void 0 && lastVerifiedAt !== void 0,
|
|
52997
|
+
host: email,
|
|
52998
|
+
error: lastError,
|
|
52999
|
+
lastVerifiedAt
|
|
53000
|
+
};
|
|
53001
|
+
};
|
|
53002
|
+
return {
|
|
53003
|
+
getStatus: async () => status(),
|
|
53004
|
+
verify: async () => {
|
|
53005
|
+
if (!deps.getConfig().gmailSendEnabled) {
|
|
53006
|
+
lastError = "Gmail send is disabled in Google Services settings.";
|
|
53007
|
+
return status();
|
|
53008
|
+
}
|
|
53009
|
+
if (!deps.connected()) {
|
|
53010
|
+
lastError = "Google account is not connected.";
|
|
53011
|
+
return status();
|
|
53012
|
+
}
|
|
53013
|
+
try {
|
|
53014
|
+
const token = await deps.getAccessToken();
|
|
53015
|
+
const res = await http(`${GOOGLE_GMAIL_API}/users/me/profile`, { headers: { Authorization: `Bearer ${token}` } });
|
|
53016
|
+
if (!res.ok) {
|
|
53017
|
+
const text = await res.text();
|
|
53018
|
+
lastError = `Gmail profile failed (${res.status}): ${text.slice(0, 200)}`;
|
|
53019
|
+
return status();
|
|
53020
|
+
}
|
|
53021
|
+
lastError = void 0;
|
|
53022
|
+
lastVerifiedAt = Date.now();
|
|
53023
|
+
return status();
|
|
53024
|
+
} catch (err) {
|
|
53025
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
53026
|
+
return status();
|
|
53027
|
+
}
|
|
53028
|
+
},
|
|
53029
|
+
sendEmail: async (input) => {
|
|
53030
|
+
const cfg = deps.getConfig();
|
|
53031
|
+
if (!cfg.gmailSendEnabled) throw new Error("Gmail send is disabled in Google Services settings.");
|
|
53032
|
+
if (!deps.connected()) throw new Error("Google account is not connected — open Connect Google.");
|
|
53033
|
+
const from = input.from?.trim() || buildFromHeader(cfg, deps.getAccountEmail());
|
|
53034
|
+
if (!from) throw new Error("Set From address in Google Services settings (or connect an account).");
|
|
53035
|
+
const raw = buildRawMime({
|
|
53036
|
+
from,
|
|
53037
|
+
to: input.to,
|
|
53038
|
+
cc: input.cc,
|
|
53039
|
+
bcc: input.bcc,
|
|
53040
|
+
replyTo: input.replyTo,
|
|
53041
|
+
subject: input.subject,
|
|
53042
|
+
text: input.text,
|
|
53043
|
+
html: input.html
|
|
53044
|
+
});
|
|
53045
|
+
const token = await deps.getAccessToken();
|
|
53046
|
+
const res = await http(`${GOOGLE_GMAIL_API}/users/me/messages/send`, {
|
|
53047
|
+
method: "POST",
|
|
53048
|
+
headers: {
|
|
53049
|
+
Authorization: `Bearer ${token}`,
|
|
53050
|
+
"Content-Type": "application/json"
|
|
53051
|
+
},
|
|
53052
|
+
body: JSON.stringify({ raw: encodeBase64Url(Buffer.from(raw, "utf8")) })
|
|
53053
|
+
});
|
|
53054
|
+
if (!res.ok) {
|
|
53055
|
+
const text = await res.text();
|
|
53056
|
+
throw new Error(`Gmail send failed (${res.status}): ${text.slice(0, 400)}`);
|
|
53057
|
+
}
|
|
53058
|
+
const messageId = (await res.json()).id ?? `gmail-${Date.now()}`;
|
|
53059
|
+
lastError = void 0;
|
|
53060
|
+
lastVerifiedAt = Date.now();
|
|
53061
|
+
return {
|
|
53062
|
+
messageId,
|
|
53063
|
+
accepted: [...Array.isArray(input.to) ? input.to : [input.to]],
|
|
53064
|
+
rejected: []
|
|
53065
|
+
};
|
|
53066
|
+
}
|
|
53067
|
+
};
|
|
53068
|
+
}
|
|
53069
|
+
//#endregion
|
|
52858
53070
|
//#region src/google-oauth.ts
|
|
52859
53071
|
/**
|
|
52860
53072
|
* Google OAuth2 authorization-code + refresh for Calendar API.
|
|
@@ -52864,7 +53076,7 @@ function buildAuthorizeUrl(cfg, state) {
|
|
|
52864
53076
|
u.searchParams.set("client_id", cfg.clientId);
|
|
52865
53077
|
u.searchParams.set("redirect_uri", cfg.redirectUri);
|
|
52866
53078
|
u.searchParams.set("response_type", "code");
|
|
52867
|
-
u.searchParams.set("scope",
|
|
53079
|
+
u.searchParams.set("scope", GOOGLE_OAUTH_SCOPES);
|
|
52868
53080
|
u.searchParams.set("access_type", "offline");
|
|
52869
53081
|
u.searchParams.set("prompt", "consent");
|
|
52870
53082
|
u.searchParams.set("state", state);
|
|
@@ -52922,9 +53134,19 @@ function needsRefresh(state, nowMs = Date.now()) {
|
|
|
52922
53134
|
return state.accessExpiresAt <= nowMs;
|
|
52923
53135
|
}
|
|
52924
53136
|
//#endregion
|
|
53137
|
+
//#region src/oauth-redirect-origin.ts
|
|
53138
|
+
/**
|
|
53139
|
+
* Google OAuth redirect origin — same role as hub OAuth2 / sign-in
|
|
53140
|
+
* (`local-network` cap: `roles.publicEndpoint`), never `internal` (LAN / agents).
|
|
53141
|
+
* Google rejects non-localhost `http://` redirect URIs for Web clients.
|
|
53142
|
+
*/
|
|
53143
|
+
function pickOauthRedirectOrigin(book) {
|
|
53144
|
+
return book.roles.publicEndpoint.resolved ?? book.roles.internal.resolved ?? null;
|
|
53145
|
+
}
|
|
53146
|
+
//#endregion
|
|
52925
53147
|
//#region src/google-services.addon.ts
|
|
52926
53148
|
/**
|
|
52927
|
-
* Google Services — hub-only calendar-source
|
|
53149
|
+
* Google Services — hub-only calendar-source + Gmail smtp-provider (same OAuth2).
|
|
52928
53150
|
*/
|
|
52929
53151
|
var ADDON_ID = "google-services";
|
|
52930
53152
|
var CALLBACK_PATH = "/oauth/callback";
|
|
@@ -52948,17 +53170,23 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
52948
53170
|
this.oauthHandle = this.state("oauth", OauthStateSchema, { ...DEFAULT_OAUTH_STATE });
|
|
52949
53171
|
this.oauth = await this.oauthHandle.get();
|
|
52950
53172
|
const api = createGoogleCalendarApi(async () => this.ensureAccessToken());
|
|
52951
|
-
const
|
|
53173
|
+
const calendarProvider = createGoogleCalendarSource({
|
|
52952
53174
|
logger: this.ctx.logger,
|
|
52953
53175
|
api,
|
|
52954
53176
|
connected: () => this.oauth.refreshToken.length > 0,
|
|
52955
53177
|
filterIds: () => this.parseFilter()
|
|
52956
53178
|
});
|
|
53179
|
+
const smtpProvider = createGmailSmtpProvider({
|
|
53180
|
+
getAccessToken: () => this.ensureAccessToken(),
|
|
53181
|
+
getConfig: () => this.config,
|
|
53182
|
+
getAccountEmail: () => this.oauth.accountEmail,
|
|
53183
|
+
connected: () => this.oauth.refreshToken.length > 0
|
|
53184
|
+
});
|
|
52957
53185
|
const routeProvider = buildAddonRouteProvider(ADDON_ID, [{
|
|
52958
53186
|
method: "GET",
|
|
52959
53187
|
path: START_PATH,
|
|
52960
53188
|
access: "admin",
|
|
52961
|
-
description: "Begin Google Calendar
|
|
53189
|
+
description: "Begin Google OAuth (Calendar + Gmail send)",
|
|
52962
53190
|
handler: async (_req, reply) => this.handleStart(reply)
|
|
52963
53191
|
}, {
|
|
52964
53192
|
method: "GET",
|
|
@@ -52968,17 +53196,25 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
52968
53196
|
description: "Google OAuth callback",
|
|
52969
53197
|
handler: async (req, reply) => this.handleCallback(req, reply)
|
|
52970
53198
|
}]);
|
|
52971
|
-
this.ctx.logger.info("Initialized — google calendar-source", { meta: {
|
|
53199
|
+
this.ctx.logger.info("Initialized — google calendar-source + gmail smtp", { meta: {
|
|
52972
53200
|
connected: this.oauth.refreshToken.length > 0,
|
|
52973
|
-
accountEmail: this.oauth.accountEmail || void 0
|
|
53201
|
+
accountEmail: this.oauth.accountEmail || void 0,
|
|
53202
|
+
gmailSendEnabled: this.config.gmailSendEnabled
|
|
52974
53203
|
} });
|
|
52975
|
-
return { providers: [
|
|
52976
|
-
|
|
52977
|
-
|
|
52978
|
-
|
|
52979
|
-
|
|
52980
|
-
|
|
52981
|
-
|
|
53204
|
+
return { providers: [
|
|
53205
|
+
{
|
|
53206
|
+
capability: calendarSourceCapability,
|
|
53207
|
+
provider: calendarProvider
|
|
53208
|
+
},
|
|
53209
|
+
{
|
|
53210
|
+
capability: smtpProviderCapability,
|
|
53211
|
+
provider: smtpProvider
|
|
53212
|
+
},
|
|
53213
|
+
{
|
|
53214
|
+
capability: addonRoutesCapability,
|
|
53215
|
+
provider: routeProvider
|
|
53216
|
+
}
|
|
53217
|
+
] };
|
|
52982
53218
|
}
|
|
52983
53219
|
async handleStart(reply) {
|
|
52984
53220
|
if (!this.config.clientId || !this.config.clientSecret) {
|
|
@@ -53024,15 +53260,33 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53024
53260
|
}, code);
|
|
53025
53261
|
if (!tokens.refresh_token && !this.oauth.refreshToken) throw new Error("Google did not return a refresh_token. Revoke CamStack access in Google Account → Security and retry with prompt=consent.");
|
|
53026
53262
|
this.oauth = applyTokenResponse(this.oauth, tokens);
|
|
53263
|
+
const email = await this.fetchAccountEmail(this.oauth.accessToken);
|
|
53264
|
+
if (email) this.oauth = {
|
|
53265
|
+
...this.oauth,
|
|
53266
|
+
accountEmail: email
|
|
53267
|
+
};
|
|
53027
53268
|
await this.persistOauth();
|
|
53028
|
-
this.ctx.logger.info("google oauth connected", { meta: {
|
|
53029
|
-
|
|
53269
|
+
this.ctx.logger.info("google oauth connected", { meta: {
|
|
53270
|
+
hasRefresh: this.oauth.refreshToken.length > 0,
|
|
53271
|
+
accountEmail: this.oauth.accountEmail || void 0
|
|
53272
|
+
} });
|
|
53273
|
+
this.html(reply, 200, "Google account connected (Calendar + Gmail send). You can close this window.");
|
|
53030
53274
|
} catch (e) {
|
|
53031
53275
|
const message = e instanceof Error ? e.message : String(e);
|
|
53032
53276
|
this.ctx.logger.warn("google oauth callback failed", { meta: { error: message } });
|
|
53033
53277
|
this.html(reply, 500, `OAuth failed: ${message}`);
|
|
53034
53278
|
}
|
|
53035
53279
|
}
|
|
53280
|
+
async fetchAccountEmail(accessToken) {
|
|
53281
|
+
try {
|
|
53282
|
+
const res = await fetch(GOOGLE_USERINFO_URL, { headers: { Authorization: `Bearer ${accessToken}` } });
|
|
53283
|
+
if (!res.ok) return "";
|
|
53284
|
+
const body = await res.json();
|
|
53285
|
+
return typeof body.email === "string" ? body.email.trim() : "";
|
|
53286
|
+
} catch {
|
|
53287
|
+
return "";
|
|
53288
|
+
}
|
|
53289
|
+
}
|
|
53036
53290
|
html(reply, code, body) {
|
|
53037
53291
|
reply.header("content-type", "text/html; charset=utf-8");
|
|
53038
53292
|
reply.status(code).send(`<!doctype html><html><body><p>${escapeHtml(body)}</p></body></html>`);
|
|
@@ -53062,16 +53316,15 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53062
53316
|
return new Set(raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0));
|
|
53063
53317
|
}
|
|
53064
53318
|
async resolveRedirectUri() {
|
|
53065
|
-
const
|
|
53066
|
-
|
|
53067
|
-
if (!origin) throw new Error("No hub address available from local-network — set an Internal address on the Network page.");
|
|
53319
|
+
const origin = pickOauthRedirectOrigin(await this.ctx.api.localNetwork.getAddresses.query({}));
|
|
53320
|
+
if (!origin) throw new Error("No hub public address available from local-network — set a Public endpoint on the Network page (OAuth2 redirects use that role, not Internal).");
|
|
53068
53321
|
return `${origin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
|
|
53069
53322
|
}
|
|
53070
53323
|
globalSettingsSchema() {
|
|
53071
53324
|
return this.schema({ sections: [{
|
|
53072
53325
|
id: "google-oauth",
|
|
53073
|
-
title: "Google
|
|
53074
|
-
description: "Create an OAuth client (Web application) in Google Cloud Console with the Calendar API enabled. Register the redirect URI `<hub>/addon/google-services/oauth/callback`
|
|
53326
|
+
title: "Google OAuth",
|
|
53327
|
+
description: "Create an OAuth client (Web application) in Google Cloud Console with the Calendar API and Gmail API enabled. Connect grants both Calendar read and Gmail send on the same account. Register the redirect URI `<public-hub>/addon/google-services/oauth/callback` — hub = Public endpoint from Network (same role as sign-in / OAuth2; Google rejects plain LAN http).",
|
|
53075
53328
|
columns: 1,
|
|
53076
53329
|
fields: [
|
|
53077
53330
|
{
|
|
@@ -53096,7 +53349,31 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53096
53349
|
key: "_connectInfo",
|
|
53097
53350
|
label: "Connect",
|
|
53098
53351
|
format: "html",
|
|
53099
|
-
content: `After saving client id/secret, open <a href="/addon/${ADDON_ID}${START_PATH}">Connect Google
|
|
53352
|
+
content: `After saving client id/secret, open <a href="/addon/${ADDON_ID}${START_PATH}">Connect Google</a> while signed in as admin. Redirect URI uses the Network <b>Public endpoint</b> (today typically <code>https://…/addon/google-services/oauth/callback</code>). If you connected before Gmail send was added, revoke CamStack in Google Account security and reconnect so the refresh token includes <code>gmail.send</code>.`
|
|
53353
|
+
}
|
|
53354
|
+
]
|
|
53355
|
+
}, {
|
|
53356
|
+
id: "gmail-send",
|
|
53357
|
+
title: "Gmail send",
|
|
53358
|
+
description: "Optional email delivery via the Gmail API (`smtp-provider`). Uses the same OAuth account — From must be that account or a verified send-as alias.",
|
|
53359
|
+
columns: 1,
|
|
53360
|
+
fields: [
|
|
53361
|
+
{
|
|
53362
|
+
type: "boolean",
|
|
53363
|
+
key: "gmailSendEnabled",
|
|
53364
|
+
label: "Enable Gmail send",
|
|
53365
|
+
description: "When off, Calendar still works; this addon reports smtp-provider not ready."
|
|
53366
|
+
},
|
|
53367
|
+
{
|
|
53368
|
+
type: "text",
|
|
53369
|
+
key: "fromAddress",
|
|
53370
|
+
label: "From address",
|
|
53371
|
+
description: "Defaults to the connected Google account email when empty."
|
|
53372
|
+
},
|
|
53373
|
+
{
|
|
53374
|
+
type: "text",
|
|
53375
|
+
key: "fromName",
|
|
53376
|
+
label: "From display name (optional)"
|
|
53100
53377
|
}
|
|
53101
53378
|
]
|
|
53102
53379
|
}] });
|
|
@@ -53106,4 +53383,4 @@ function escapeHtml(s) {
|
|
|
53106
53383
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
53107
53384
|
}
|
|
53108
53385
|
//#endregion
|
|
53109
|
-
export { GoogleServicesAddon, GoogleServicesAddon as default, refreshAccessToken as a,
|
|
53386
|
+
export { GoogleServicesAddon, GoogleServicesAddon as default, refreshAccessToken as a, createGoogleCalendarApi as c, DEFAULT_GOOGLE_CONFIG as d, GOOGLE_GMAIL_SEND_SCOPE as f, needsRefresh as i, createGoogleCalendarSource as l, GoogleServicesConfigSchema as m, buildAuthorizeUrl as n, buildRawMime as o, GOOGLE_OAUTH_SCOPES as p, exchangeCode as r, createGmailSmtpProvider as s, applyTokenResponse as t, mapGoogleEvent as u };
|
package/dist/index.js
CHANGED
|
@@ -4,10 +4,14 @@ Object.defineProperties(exports, {
|
|
|
4
4
|
});
|
|
5
5
|
const require_google_services_addon = require("./google-services.addon.js");
|
|
6
6
|
exports.DEFAULT_GOOGLE_CONFIG = require_google_services_addon.DEFAULT_GOOGLE_CONFIG;
|
|
7
|
+
exports.GOOGLE_GMAIL_SEND_SCOPE = require_google_services_addon.GOOGLE_GMAIL_SEND_SCOPE;
|
|
8
|
+
exports.GOOGLE_OAUTH_SCOPES = require_google_services_addon.GOOGLE_OAUTH_SCOPES;
|
|
7
9
|
exports.GoogleServicesAddon = require_google_services_addon.GoogleServicesAddon;
|
|
8
10
|
exports.GoogleServicesConfigSchema = require_google_services_addon.GoogleServicesConfigSchema;
|
|
9
11
|
exports.applyTokenResponse = require_google_services_addon.applyTokenResponse;
|
|
10
12
|
exports.buildAuthorizeUrl = require_google_services_addon.buildAuthorizeUrl;
|
|
13
|
+
exports.buildRawMime = require_google_services_addon.buildRawMime;
|
|
14
|
+
exports.createGmailSmtpProvider = require_google_services_addon.createGmailSmtpProvider;
|
|
11
15
|
exports.createGoogleCalendarApi = require_google_services_addon.createGoogleCalendarApi;
|
|
12
16
|
exports.createGoogleCalendarSource = require_google_services_addon.createGoogleCalendarSource;
|
|
13
17
|
exports.default = require_google_services_addon.GoogleServicesAddon;
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { GoogleServicesAddon, a as refreshAccessToken, c as
|
|
2
|
-
export { DEFAULT_GOOGLE_CONFIG, GoogleServicesAddon, GoogleServicesAddon as default, GoogleServicesConfigSchema, applyTokenResponse, buildAuthorizeUrl, createGoogleCalendarApi, createGoogleCalendarSource, exchangeCode, mapGoogleEvent, needsRefresh, refreshAccessToken };
|
|
1
|
+
import { GoogleServicesAddon, a as refreshAccessToken, c as createGoogleCalendarApi, d as DEFAULT_GOOGLE_CONFIG, f as GOOGLE_GMAIL_SEND_SCOPE, i as needsRefresh, l as createGoogleCalendarSource, m as GoogleServicesConfigSchema, n as buildAuthorizeUrl, o as buildRawMime, p as GOOGLE_OAUTH_SCOPES, r as exchangeCode, s as createGmailSmtpProvider, t as applyTokenResponse, u as mapGoogleEvent } from "./google-services.addon.mjs";
|
|
2
|
+
export { DEFAULT_GOOGLE_CONFIG, GOOGLE_GMAIL_SEND_SCOPE, GOOGLE_OAUTH_SCOPES, GoogleServicesAddon, GoogleServicesAddon as default, GoogleServicesConfigSchema, applyTokenResponse, buildAuthorizeUrl, buildRawMime, createGmailSmtpProvider, createGoogleCalendarApi, createGoogleCalendarSource, exchangeCode, mapGoogleEvent, needsRefresh, refreshAccessToken };
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-google-services",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Google Services — Google Calendar
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Google Services — Google Calendar (calendar-source) and Gmail send (smtp-provider) via one OAuth2 account.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
7
7
|
"addon",
|
|
8
8
|
"camstack-addon",
|
|
9
9
|
"google",
|
|
10
10
|
"calendar",
|
|
11
|
+
"gmail",
|
|
11
12
|
"oauth"
|
|
12
13
|
],
|
|
13
14
|
"license": "MIT",
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"id": "google-services",
|
|
28
29
|
"category": "messaging",
|
|
29
30
|
"name": "Google Services",
|
|
30
|
-
"description": "Connects a Google account (OAuth2)
|
|
31
|
+
"description": "Connects a Google account (OAuth2): Calendar as calendar-source, optional Gmail send as smtp-provider.",
|
|
31
32
|
"entry": "./dist/google-services.addon.js",
|
|
32
33
|
"execution": {
|
|
33
34
|
"placement": "hub-only"
|
|
@@ -36,6 +37,9 @@
|
|
|
36
37
|
{
|
|
37
38
|
"name": "calendar-source"
|
|
38
39
|
},
|
|
40
|
+
{
|
|
41
|
+
"name": "smtp-provider"
|
|
42
|
+
},
|
|
39
43
|
{
|
|
40
44
|
"name": "addon-routes"
|
|
41
45
|
}
|