@camstack/addon-google-services 0.1.2 → 0.1.4
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 +328 -76
- package/dist/google-services.addon.mjs +325 -77
- 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" })
|
|
@@ -39488,6 +39502,12 @@ var TrainingProviderDefaultsSchema = object({
|
|
|
39488
39502
|
/** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
|
|
39489
39503
|
failedDatasetGraceHours: number().nonnegative()
|
|
39490
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();
|
|
39491
39511
|
/** One creatable provider type, for the Add-entry dialog. */
|
|
39492
39512
|
var TrainingProviderTypeSchema = object({
|
|
39493
39513
|
typeId: string(),
|
|
@@ -39495,7 +39515,13 @@ var TrainingProviderTypeSchema = object({
|
|
|
39495
39515
|
/** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
|
|
39496
39516
|
iconAsset: string(),
|
|
39497
39517
|
/** Owning `training-provider` addon — collection selector for createEntry. */
|
|
39498
|
-
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()
|
|
39499
39525
|
});
|
|
39500
39526
|
/** One configured account / workspace under a provider type. */
|
|
39501
39527
|
var TrainingProviderEntrySchema = object({
|
|
@@ -39527,24 +39553,11 @@ var TrainingProviderDescriptorSchema = object({
|
|
|
39527
39553
|
configured: boolean(),
|
|
39528
39554
|
defaults: TrainingProviderDefaultsSchema,
|
|
39529
39555
|
/**
|
|
39530
|
-
*
|
|
39531
|
-
*
|
|
39532
|
-
*
|
|
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).
|
|
39533
39559
|
*/
|
|
39534
|
-
|
|
39535
|
-
environment: string().optional(),
|
|
39536
|
-
hasTokenSecret: boolean().optional(),
|
|
39537
|
-
/**
|
|
39538
|
-
* Keychain view for the entry's token secret — for `SecretFieldControl` on
|
|
39539
|
-
* edit. Create has no view (`null` / omitted); the form uses a plain
|
|
39540
|
-
* `PasswordInput` instead.
|
|
39541
|
-
*/
|
|
39542
|
-
tokenSecretView: object({
|
|
39543
|
-
$secret: string().nullable(),
|
|
39544
|
-
v: literal(1),
|
|
39545
|
-
set: literal(true),
|
|
39546
|
-
inline: literal(true).optional()
|
|
39547
|
-
}).nullable().optional()
|
|
39560
|
+
formValues: record(string(), unknown()).optional()
|
|
39548
39561
|
});
|
|
39549
39562
|
/**
|
|
39550
39563
|
* Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
|
|
@@ -51746,6 +51759,7 @@ Object.freeze({
|
|
|
51746
51759
|
});
|
|
51747
51760
|
Object.freeze({
|
|
51748
51761
|
"broker": "broker",
|
|
51762
|
+
"calendar-source": "calendar",
|
|
51749
51763
|
"device-export": "device-export",
|
|
51750
51764
|
"llm": "ai",
|
|
51751
51765
|
"mesh-network": "mesh",
|
|
@@ -52790,7 +52804,12 @@ var GoogleServicesConfigSchema = object({
|
|
|
52790
52804
|
clientId: string().default(""),
|
|
52791
52805
|
clientSecret: string().default("").meta({ secret: true }),
|
|
52792
52806
|
/** Comma-separated Google calendar ids to expose; empty = all. */
|
|
52793
|
-
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("")
|
|
52794
52813
|
});
|
|
52795
52814
|
var DEFAULT_GOOGLE_CONFIG = GoogleServicesConfigSchema.parse({});
|
|
52796
52815
|
var DEFAULT_OAUTH_STATE = {
|
|
@@ -52800,9 +52819,14 @@ var DEFAULT_OAUTH_STATE = {
|
|
|
52800
52819
|
accountEmail: ""
|
|
52801
52820
|
};
|
|
52802
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}`;
|
|
52803
52825
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
52804
52826
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
52805
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";
|
|
52806
52830
|
//#endregion
|
|
52807
52831
|
//#region src/google-calendar-source.ts
|
|
52808
52832
|
function parseGoogleInstant(value) {
|
|
@@ -52905,6 +52929,148 @@ function createGoogleCalendarSource(deps) {
|
|
|
52905
52929
|
};
|
|
52906
52930
|
}
|
|
52907
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
|
|
52908
53074
|
//#region src/google-oauth.ts
|
|
52909
53075
|
/**
|
|
52910
53076
|
* Google OAuth2 authorization-code + refresh for Calendar API.
|
|
@@ -52914,7 +53080,7 @@ function buildAuthorizeUrl(cfg, state) {
|
|
|
52914
53080
|
u.searchParams.set("client_id", cfg.clientId);
|
|
52915
53081
|
u.searchParams.set("redirect_uri", cfg.redirectUri);
|
|
52916
53082
|
u.searchParams.set("response_type", "code");
|
|
52917
|
-
u.searchParams.set("scope",
|
|
53083
|
+
u.searchParams.set("scope", GOOGLE_OAUTH_SCOPES);
|
|
52918
53084
|
u.searchParams.set("access_type", "offline");
|
|
52919
53085
|
u.searchParams.set("prompt", "consent");
|
|
52920
53086
|
u.searchParams.set("state", state);
|
|
@@ -52984,7 +53150,7 @@ function pickOauthRedirectOrigin(book) {
|
|
|
52984
53150
|
//#endregion
|
|
52985
53151
|
//#region src/google-services.addon.ts
|
|
52986
53152
|
/**
|
|
52987
|
-
* Google Services — hub-only calendar-source
|
|
53153
|
+
* Google Services — hub-only calendar-source + Gmail smtp-provider (same OAuth2).
|
|
52988
53154
|
*/
|
|
52989
53155
|
var ADDON_ID = "google-services";
|
|
52990
53156
|
var CALLBACK_PATH = "/oauth/callback";
|
|
@@ -53008,17 +53174,23 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53008
53174
|
this.oauthHandle = this.state("oauth", OauthStateSchema, { ...DEFAULT_OAUTH_STATE });
|
|
53009
53175
|
this.oauth = await this.oauthHandle.get();
|
|
53010
53176
|
const api = createGoogleCalendarApi(async () => this.ensureAccessToken());
|
|
53011
|
-
const
|
|
53177
|
+
const calendarProvider = createGoogleCalendarSource({
|
|
53012
53178
|
logger: this.ctx.logger,
|
|
53013
53179
|
api,
|
|
53014
53180
|
connected: () => this.oauth.refreshToken.length > 0,
|
|
53015
53181
|
filterIds: () => this.parseFilter()
|
|
53016
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
|
+
});
|
|
53017
53189
|
const routeProvider = buildAddonRouteProvider(ADDON_ID, [{
|
|
53018
53190
|
method: "GET",
|
|
53019
53191
|
path: START_PATH,
|
|
53020
53192
|
access: "admin",
|
|
53021
|
-
description: "Begin Google Calendar
|
|
53193
|
+
description: "Begin Google OAuth (Calendar + Gmail send)",
|
|
53022
53194
|
handler: async (_req, reply) => this.handleStart(reply)
|
|
53023
53195
|
}, {
|
|
53024
53196
|
method: "GET",
|
|
@@ -53028,17 +53200,25 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53028
53200
|
description: "Google OAuth callback",
|
|
53029
53201
|
handler: async (req, reply) => this.handleCallback(req, reply)
|
|
53030
53202
|
}]);
|
|
53031
|
-
this.ctx.logger.info("Initialized — google calendar-source", { meta: {
|
|
53203
|
+
this.ctx.logger.info("Initialized — google calendar-source + gmail smtp", { meta: {
|
|
53032
53204
|
connected: this.oauth.refreshToken.length > 0,
|
|
53033
|
-
accountEmail: this.oauth.accountEmail || void 0
|
|
53205
|
+
accountEmail: this.oauth.accountEmail || void 0,
|
|
53206
|
+
gmailSendEnabled: this.config.gmailSendEnabled
|
|
53034
53207
|
} });
|
|
53035
|
-
return { providers: [
|
|
53036
|
-
|
|
53037
|
-
|
|
53038
|
-
|
|
53039
|
-
|
|
53040
|
-
|
|
53041
|
-
|
|
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
|
+
] };
|
|
53042
53222
|
}
|
|
53043
53223
|
async handleStart(reply) {
|
|
53044
53224
|
if (!this.config.clientId || !this.config.clientSecret) {
|
|
@@ -53084,15 +53264,33 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53084
53264
|
}, code);
|
|
53085
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.");
|
|
53086
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
|
+
};
|
|
53087
53272
|
await this.persistOauth();
|
|
53088
|
-
this.ctx.logger.info("google oauth connected", { meta: {
|
|
53089
|
-
|
|
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.");
|
|
53090
53278
|
} catch (e) {
|
|
53091
53279
|
const message = e instanceof Error ? e.message : String(e);
|
|
53092
53280
|
this.ctx.logger.warn("google oauth callback failed", { meta: { error: message } });
|
|
53093
53281
|
this.html(reply, 500, `OAuth failed: ${message}`);
|
|
53094
53282
|
}
|
|
53095
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
|
+
}
|
|
53096
53294
|
html(reply, code, body) {
|
|
53097
53295
|
reply.header("content-type", "text/html; charset=utf-8");
|
|
53098
53296
|
reply.status(code).send(`<!doctype html><html><body><p>${escapeHtml(body)}</p></body></html>`);
|
|
@@ -53126,39 +53324,89 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53126
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).");
|
|
53127
53325
|
return `${origin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
|
|
53128
53326
|
}
|
|
53129
|
-
|
|
53130
|
-
|
|
53131
|
-
|
|
53132
|
-
|
|
53133
|
-
|
|
53134
|
-
|
|
53135
|
-
|
|
53136
|
-
|
|
53137
|
-
|
|
53138
|
-
|
|
53139
|
-
|
|
53140
|
-
|
|
53141
|
-
|
|
53142
|
-
|
|
53143
|
-
|
|
53144
|
-
|
|
53145
|
-
|
|
53146
|
-
|
|
53147
|
-
|
|
53327
|
+
/**
|
|
53328
|
+
* Connectivity asks for settings scoped to the page capability, so Calendar
|
|
53329
|
+
* and Email do not each redraw the other's fields (same pattern as
|
|
53330
|
+
* mqtt-broker under `broker`). The addon page (no cap) keeps every section.
|
|
53331
|
+
*/
|
|
53332
|
+
globalSettingsSchema(cap) {
|
|
53333
|
+
const schema = this.fullSettingsSchema();
|
|
53334
|
+
if (cap === "calendar-source") return {
|
|
53335
|
+
...schema,
|
|
53336
|
+
sections: schema.sections.filter((s) => s.id === "google-oauth" || s.id === "google-calendar")
|
|
53337
|
+
};
|
|
53338
|
+
if (cap === "smtp-provider") return {
|
|
53339
|
+
...schema,
|
|
53340
|
+
sections: schema.sections.filter((s) => s.id === "google-oauth" || s.id === "gmail-send")
|
|
53341
|
+
};
|
|
53342
|
+
return schema;
|
|
53343
|
+
}
|
|
53344
|
+
fullSettingsSchema() {
|
|
53345
|
+
return this.schema({ sections: [
|
|
53346
|
+
{
|
|
53347
|
+
id: "google-oauth",
|
|
53348
|
+
title: "Google account",
|
|
53349
|
+
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).",
|
|
53350
|
+
columns: 1,
|
|
53351
|
+
fields: [
|
|
53352
|
+
{
|
|
53353
|
+
type: "text",
|
|
53354
|
+
key: "clientId",
|
|
53355
|
+
label: "Client ID"
|
|
53356
|
+
},
|
|
53357
|
+
{
|
|
53358
|
+
type: "password",
|
|
53359
|
+
key: "clientSecret",
|
|
53360
|
+
label: "Client secret",
|
|
53361
|
+
showToggle: true
|
|
53362
|
+
},
|
|
53363
|
+
{
|
|
53364
|
+
type: "info",
|
|
53365
|
+
key: "_connectInfo",
|
|
53366
|
+
label: "Connect",
|
|
53367
|
+
format: "html",
|
|
53368
|
+
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>.`
|
|
53369
|
+
}
|
|
53370
|
+
]
|
|
53371
|
+
},
|
|
53372
|
+
{
|
|
53373
|
+
id: "google-calendar",
|
|
53374
|
+
title: "Google Calendar",
|
|
53375
|
+
description: "Which calendars this hub exposes as calendar-source entries.",
|
|
53376
|
+
columns: 1,
|
|
53377
|
+
fields: [{
|
|
53148
53378
|
type: "text",
|
|
53149
53379
|
key: "calendarFilter",
|
|
53150
53380
|
label: "Calendar id filter (optional)",
|
|
53151
53381
|
description: "Comma-separated Google calendar ids to expose. Empty = all calendars the account can read."
|
|
53152
|
-
}
|
|
53153
|
-
|
|
53154
|
-
|
|
53155
|
-
|
|
53156
|
-
|
|
53157
|
-
|
|
53158
|
-
|
|
53159
|
-
|
|
53160
|
-
|
|
53161
|
-
|
|
53382
|
+
}]
|
|
53383
|
+
},
|
|
53384
|
+
{
|
|
53385
|
+
id: "gmail-send",
|
|
53386
|
+
title: "Gmail send",
|
|
53387
|
+
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.",
|
|
53388
|
+
columns: 1,
|
|
53389
|
+
fields: [
|
|
53390
|
+
{
|
|
53391
|
+
type: "boolean",
|
|
53392
|
+
key: "gmailSendEnabled",
|
|
53393
|
+
label: "Enable Gmail send",
|
|
53394
|
+
description: "When off, Calendar still works; this addon reports smtp-provider not ready."
|
|
53395
|
+
},
|
|
53396
|
+
{
|
|
53397
|
+
type: "text",
|
|
53398
|
+
key: "fromAddress",
|
|
53399
|
+
label: "From address",
|
|
53400
|
+
description: "Defaults to the connected Google account email when empty."
|
|
53401
|
+
},
|
|
53402
|
+
{
|
|
53403
|
+
type: "text",
|
|
53404
|
+
key: "fromName",
|
|
53405
|
+
label: "From display name (optional)"
|
|
53406
|
+
}
|
|
53407
|
+
]
|
|
53408
|
+
}
|
|
53409
|
+
] });
|
|
53162
53410
|
}
|
|
53163
53411
|
};
|
|
53164
53412
|
function escapeHtml(s) {
|
|
@@ -53166,11 +53414,15 @@ function escapeHtml(s) {
|
|
|
53166
53414
|
}
|
|
53167
53415
|
//#endregion
|
|
53168
53416
|
exports.DEFAULT_GOOGLE_CONFIG = DEFAULT_GOOGLE_CONFIG;
|
|
53417
|
+
exports.GOOGLE_GMAIL_SEND_SCOPE = GOOGLE_GMAIL_SEND_SCOPE;
|
|
53418
|
+
exports.GOOGLE_OAUTH_SCOPES = GOOGLE_OAUTH_SCOPES;
|
|
53169
53419
|
exports.GoogleServicesAddon = GoogleServicesAddon;
|
|
53170
53420
|
exports.default = GoogleServicesAddon;
|
|
53171
53421
|
exports.GoogleServicesConfigSchema = GoogleServicesConfigSchema;
|
|
53172
53422
|
exports.applyTokenResponse = applyTokenResponse;
|
|
53173
53423
|
exports.buildAuthorizeUrl = buildAuthorizeUrl;
|
|
53424
|
+
exports.buildRawMime = buildRawMime;
|
|
53425
|
+
exports.createGmailSmtpProvider = createGmailSmtpProvider;
|
|
53174
53426
|
exports.createGoogleCalendarApi = createGoogleCalendarApi;
|
|
53175
53427
|
exports.createGoogleCalendarSource = createGoogleCalendarSource;
|
|
53176
53428
|
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" })
|
|
@@ -39484,6 +39498,12 @@ var TrainingProviderDefaultsSchema = object({
|
|
|
39484
39498
|
/** Hours a FAILED job's cloud dataset is kept for debugging; 0 = delete at once. */
|
|
39485
39499
|
failedDatasetGraceHours: number().nonnegative()
|
|
39486
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();
|
|
39487
39507
|
/** One creatable provider type, for the Add-entry dialog. */
|
|
39488
39508
|
var TrainingProviderTypeSchema = object({
|
|
39489
39509
|
typeId: string(),
|
|
@@ -39491,7 +39511,13 @@ var TrainingProviderTypeSchema = object({
|
|
|
39491
39511
|
/** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
|
|
39492
39512
|
iconAsset: string(),
|
|
39493
39513
|
/** Owning `training-provider` addon — collection selector for createEntry. */
|
|
39494
|
-
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()
|
|
39495
39521
|
});
|
|
39496
39522
|
/** One configured account / workspace under a provider type. */
|
|
39497
39523
|
var TrainingProviderEntrySchema = object({
|
|
@@ -39523,24 +39549,11 @@ var TrainingProviderDescriptorSchema = object({
|
|
|
39523
39549
|
configured: boolean(),
|
|
39524
39550
|
defaults: TrainingProviderDefaultsSchema,
|
|
39525
39551
|
/**
|
|
39526
|
-
*
|
|
39527
|
-
*
|
|
39528
|
-
*
|
|
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).
|
|
39529
39555
|
*/
|
|
39530
|
-
|
|
39531
|
-
environment: string().optional(),
|
|
39532
|
-
hasTokenSecret: boolean().optional(),
|
|
39533
|
-
/**
|
|
39534
|
-
* Keychain view for the entry's token secret — for `SecretFieldControl` on
|
|
39535
|
-
* edit. Create has no view (`null` / omitted); the form uses a plain
|
|
39536
|
-
* `PasswordInput` instead.
|
|
39537
|
-
*/
|
|
39538
|
-
tokenSecretView: object({
|
|
39539
|
-
$secret: string().nullable(),
|
|
39540
|
-
v: literal(1),
|
|
39541
|
-
set: literal(true),
|
|
39542
|
-
inline: literal(true).optional()
|
|
39543
|
-
}).nullable().optional()
|
|
39556
|
+
formValues: record(string(), unknown()).optional()
|
|
39544
39557
|
});
|
|
39545
39558
|
/**
|
|
39546
39559
|
* Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
|
|
@@ -51742,6 +51755,7 @@ Object.freeze({
|
|
|
51742
51755
|
});
|
|
51743
51756
|
Object.freeze({
|
|
51744
51757
|
"broker": "broker",
|
|
51758
|
+
"calendar-source": "calendar",
|
|
51745
51759
|
"device-export": "device-export",
|
|
51746
51760
|
"llm": "ai",
|
|
51747
51761
|
"mesh-network": "mesh",
|
|
@@ -52786,7 +52800,12 @@ var GoogleServicesConfigSchema = object({
|
|
|
52786
52800
|
clientId: string().default(""),
|
|
52787
52801
|
clientSecret: string().default("").meta({ secret: true }),
|
|
52788
52802
|
/** Comma-separated Google calendar ids to expose; empty = all. */
|
|
52789
|
-
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("")
|
|
52790
52809
|
});
|
|
52791
52810
|
var DEFAULT_GOOGLE_CONFIG = GoogleServicesConfigSchema.parse({});
|
|
52792
52811
|
var DEFAULT_OAUTH_STATE = {
|
|
@@ -52796,9 +52815,14 @@ var DEFAULT_OAUTH_STATE = {
|
|
|
52796
52815
|
accountEmail: ""
|
|
52797
52816
|
};
|
|
52798
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}`;
|
|
52799
52821
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
52800
52822
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
52801
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";
|
|
52802
52826
|
//#endregion
|
|
52803
52827
|
//#region src/google-calendar-source.ts
|
|
52804
52828
|
function parseGoogleInstant(value) {
|
|
@@ -52901,6 +52925,148 @@ function createGoogleCalendarSource(deps) {
|
|
|
52901
52925
|
};
|
|
52902
52926
|
}
|
|
52903
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
|
|
52904
53070
|
//#region src/google-oauth.ts
|
|
52905
53071
|
/**
|
|
52906
53072
|
* Google OAuth2 authorization-code + refresh for Calendar API.
|
|
@@ -52910,7 +53076,7 @@ function buildAuthorizeUrl(cfg, state) {
|
|
|
52910
53076
|
u.searchParams.set("client_id", cfg.clientId);
|
|
52911
53077
|
u.searchParams.set("redirect_uri", cfg.redirectUri);
|
|
52912
53078
|
u.searchParams.set("response_type", "code");
|
|
52913
|
-
u.searchParams.set("scope",
|
|
53079
|
+
u.searchParams.set("scope", GOOGLE_OAUTH_SCOPES);
|
|
52914
53080
|
u.searchParams.set("access_type", "offline");
|
|
52915
53081
|
u.searchParams.set("prompt", "consent");
|
|
52916
53082
|
u.searchParams.set("state", state);
|
|
@@ -52980,7 +53146,7 @@ function pickOauthRedirectOrigin(book) {
|
|
|
52980
53146
|
//#endregion
|
|
52981
53147
|
//#region src/google-services.addon.ts
|
|
52982
53148
|
/**
|
|
52983
|
-
* Google Services — hub-only calendar-source
|
|
53149
|
+
* Google Services — hub-only calendar-source + Gmail smtp-provider (same OAuth2).
|
|
52984
53150
|
*/
|
|
52985
53151
|
var ADDON_ID = "google-services";
|
|
52986
53152
|
var CALLBACK_PATH = "/oauth/callback";
|
|
@@ -53004,17 +53170,23 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53004
53170
|
this.oauthHandle = this.state("oauth", OauthStateSchema, { ...DEFAULT_OAUTH_STATE });
|
|
53005
53171
|
this.oauth = await this.oauthHandle.get();
|
|
53006
53172
|
const api = createGoogleCalendarApi(async () => this.ensureAccessToken());
|
|
53007
|
-
const
|
|
53173
|
+
const calendarProvider = createGoogleCalendarSource({
|
|
53008
53174
|
logger: this.ctx.logger,
|
|
53009
53175
|
api,
|
|
53010
53176
|
connected: () => this.oauth.refreshToken.length > 0,
|
|
53011
53177
|
filterIds: () => this.parseFilter()
|
|
53012
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
|
+
});
|
|
53013
53185
|
const routeProvider = buildAddonRouteProvider(ADDON_ID, [{
|
|
53014
53186
|
method: "GET",
|
|
53015
53187
|
path: START_PATH,
|
|
53016
53188
|
access: "admin",
|
|
53017
|
-
description: "Begin Google Calendar
|
|
53189
|
+
description: "Begin Google OAuth (Calendar + Gmail send)",
|
|
53018
53190
|
handler: async (_req, reply) => this.handleStart(reply)
|
|
53019
53191
|
}, {
|
|
53020
53192
|
method: "GET",
|
|
@@ -53024,17 +53196,25 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53024
53196
|
description: "Google OAuth callback",
|
|
53025
53197
|
handler: async (req, reply) => this.handleCallback(req, reply)
|
|
53026
53198
|
}]);
|
|
53027
|
-
this.ctx.logger.info("Initialized — google calendar-source", { meta: {
|
|
53199
|
+
this.ctx.logger.info("Initialized — google calendar-source + gmail smtp", { meta: {
|
|
53028
53200
|
connected: this.oauth.refreshToken.length > 0,
|
|
53029
|
-
accountEmail: this.oauth.accountEmail || void 0
|
|
53201
|
+
accountEmail: this.oauth.accountEmail || void 0,
|
|
53202
|
+
gmailSendEnabled: this.config.gmailSendEnabled
|
|
53030
53203
|
} });
|
|
53031
|
-
return { providers: [
|
|
53032
|
-
|
|
53033
|
-
|
|
53034
|
-
|
|
53035
|
-
|
|
53036
|
-
|
|
53037
|
-
|
|
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
|
+
] };
|
|
53038
53218
|
}
|
|
53039
53219
|
async handleStart(reply) {
|
|
53040
53220
|
if (!this.config.clientId || !this.config.clientSecret) {
|
|
@@ -53080,15 +53260,33 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53080
53260
|
}, code);
|
|
53081
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.");
|
|
53082
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
|
+
};
|
|
53083
53268
|
await this.persistOauth();
|
|
53084
|
-
this.ctx.logger.info("google oauth connected", { meta: {
|
|
53085
|
-
|
|
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.");
|
|
53086
53274
|
} catch (e) {
|
|
53087
53275
|
const message = e instanceof Error ? e.message : String(e);
|
|
53088
53276
|
this.ctx.logger.warn("google oauth callback failed", { meta: { error: message } });
|
|
53089
53277
|
this.html(reply, 500, `OAuth failed: ${message}`);
|
|
53090
53278
|
}
|
|
53091
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
|
+
}
|
|
53092
53290
|
html(reply, code, body) {
|
|
53093
53291
|
reply.header("content-type", "text/html; charset=utf-8");
|
|
53094
53292
|
reply.status(code).send(`<!doctype html><html><body><p>${escapeHtml(body)}</p></body></html>`);
|
|
@@ -53122,43 +53320,93 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
53122
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).");
|
|
53123
53321
|
return `${origin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
|
|
53124
53322
|
}
|
|
53125
|
-
|
|
53126
|
-
|
|
53127
|
-
|
|
53128
|
-
|
|
53129
|
-
|
|
53130
|
-
|
|
53131
|
-
|
|
53132
|
-
|
|
53133
|
-
|
|
53134
|
-
|
|
53135
|
-
|
|
53136
|
-
|
|
53137
|
-
|
|
53138
|
-
|
|
53139
|
-
|
|
53140
|
-
|
|
53141
|
-
|
|
53142
|
-
|
|
53143
|
-
|
|
53323
|
+
/**
|
|
53324
|
+
* Connectivity asks for settings scoped to the page capability, so Calendar
|
|
53325
|
+
* and Email do not each redraw the other's fields (same pattern as
|
|
53326
|
+
* mqtt-broker under `broker`). The addon page (no cap) keeps every section.
|
|
53327
|
+
*/
|
|
53328
|
+
globalSettingsSchema(cap) {
|
|
53329
|
+
const schema = this.fullSettingsSchema();
|
|
53330
|
+
if (cap === "calendar-source") return {
|
|
53331
|
+
...schema,
|
|
53332
|
+
sections: schema.sections.filter((s) => s.id === "google-oauth" || s.id === "google-calendar")
|
|
53333
|
+
};
|
|
53334
|
+
if (cap === "smtp-provider") return {
|
|
53335
|
+
...schema,
|
|
53336
|
+
sections: schema.sections.filter((s) => s.id === "google-oauth" || s.id === "gmail-send")
|
|
53337
|
+
};
|
|
53338
|
+
return schema;
|
|
53339
|
+
}
|
|
53340
|
+
fullSettingsSchema() {
|
|
53341
|
+
return this.schema({ sections: [
|
|
53342
|
+
{
|
|
53343
|
+
id: "google-oauth",
|
|
53344
|
+
title: "Google account",
|
|
53345
|
+
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).",
|
|
53346
|
+
columns: 1,
|
|
53347
|
+
fields: [
|
|
53348
|
+
{
|
|
53349
|
+
type: "text",
|
|
53350
|
+
key: "clientId",
|
|
53351
|
+
label: "Client ID"
|
|
53352
|
+
},
|
|
53353
|
+
{
|
|
53354
|
+
type: "password",
|
|
53355
|
+
key: "clientSecret",
|
|
53356
|
+
label: "Client secret",
|
|
53357
|
+
showToggle: true
|
|
53358
|
+
},
|
|
53359
|
+
{
|
|
53360
|
+
type: "info",
|
|
53361
|
+
key: "_connectInfo",
|
|
53362
|
+
label: "Connect",
|
|
53363
|
+
format: "html",
|
|
53364
|
+
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>.`
|
|
53365
|
+
}
|
|
53366
|
+
]
|
|
53367
|
+
},
|
|
53368
|
+
{
|
|
53369
|
+
id: "google-calendar",
|
|
53370
|
+
title: "Google Calendar",
|
|
53371
|
+
description: "Which calendars this hub exposes as calendar-source entries.",
|
|
53372
|
+
columns: 1,
|
|
53373
|
+
fields: [{
|
|
53144
53374
|
type: "text",
|
|
53145
53375
|
key: "calendarFilter",
|
|
53146
53376
|
label: "Calendar id filter (optional)",
|
|
53147
53377
|
description: "Comma-separated Google calendar ids to expose. Empty = all calendars the account can read."
|
|
53148
|
-
}
|
|
53149
|
-
|
|
53150
|
-
|
|
53151
|
-
|
|
53152
|
-
|
|
53153
|
-
|
|
53154
|
-
|
|
53155
|
-
|
|
53156
|
-
|
|
53157
|
-
|
|
53378
|
+
}]
|
|
53379
|
+
},
|
|
53380
|
+
{
|
|
53381
|
+
id: "gmail-send",
|
|
53382
|
+
title: "Gmail send",
|
|
53383
|
+
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.",
|
|
53384
|
+
columns: 1,
|
|
53385
|
+
fields: [
|
|
53386
|
+
{
|
|
53387
|
+
type: "boolean",
|
|
53388
|
+
key: "gmailSendEnabled",
|
|
53389
|
+
label: "Enable Gmail send",
|
|
53390
|
+
description: "When off, Calendar still works; this addon reports smtp-provider not ready."
|
|
53391
|
+
},
|
|
53392
|
+
{
|
|
53393
|
+
type: "text",
|
|
53394
|
+
key: "fromAddress",
|
|
53395
|
+
label: "From address",
|
|
53396
|
+
description: "Defaults to the connected Google account email when empty."
|
|
53397
|
+
},
|
|
53398
|
+
{
|
|
53399
|
+
type: "text",
|
|
53400
|
+
key: "fromName",
|
|
53401
|
+
label: "From display name (optional)"
|
|
53402
|
+
}
|
|
53403
|
+
]
|
|
53404
|
+
}
|
|
53405
|
+
] });
|
|
53158
53406
|
}
|
|
53159
53407
|
};
|
|
53160
53408
|
function escapeHtml(s) {
|
|
53161
53409
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
53162
53410
|
}
|
|
53163
53411
|
//#endregion
|
|
53164
|
-
export { GoogleServicesAddon, GoogleServicesAddon as default, refreshAccessToken as a,
|
|
53412
|
+
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.4",
|
|
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
|
}
|