@camstack/addon-google-services 0.1.3 → 0.1.5

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.
@@ -52805,8 +52805,6 @@ var GoogleServicesConfigSchema = object({
52805
52805
  clientSecret: string().default("").meta({ secret: true }),
52806
52806
  /** Comma-separated Google calendar ids to expose; empty = all. */
52807
52807
  calendarFilter: string().default(""),
52808
- /** Offer Gmail send via `smtp-provider` (same OAuth account). */
52809
- gmailSendEnabled: boolean().default(false),
52810
52808
  /** Default From for Gmail send — usually the connected account email. */
52811
52809
  fromAddress: string().default(""),
52812
52810
  fromName: string().default("")
@@ -52827,6 +52825,8 @@ var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
52827
52825
  var GOOGLE_CALENDAR_API = "https://www.googleapis.com/calendar/v3";
52828
52826
  var GOOGLE_GMAIL_API = "https://gmail.googleapis.com/gmail/v1";
52829
52827
  var GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
52828
+ var OAUTH_START_PATH = `/addon/google-services/oauth/start`;
52829
+ var OAUTH_CALLBACK_SUFFIX = `/addon/google-services/oauth/callback`;
52830
52830
  //#endregion
52831
52831
  //#region src/google-calendar-source.ts
52832
52832
  function parseGoogleInstant(value) {
@@ -52984,12 +52984,6 @@ function createGmailSmtpProvider(deps) {
52984
52984
  const status = () => {
52985
52985
  const cfg = deps.getConfig();
52986
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
52987
  if (!deps.connected()) return {
52994
52988
  ready: false,
52995
52989
  host: email,
@@ -53006,10 +53000,6 @@ function createGmailSmtpProvider(deps) {
53006
53000
  return {
53007
53001
  getStatus: async () => status(),
53008
53002
  verify: async () => {
53009
- if (!deps.getConfig().gmailSendEnabled) {
53010
- lastError = "Gmail send is disabled in Google Services settings.";
53011
- return status();
53012
- }
53013
53003
  if (!deps.connected()) {
53014
53004
  lastError = "Google account is not connected.";
53015
53005
  return status();
@@ -53032,7 +53022,6 @@ function createGmailSmtpProvider(deps) {
53032
53022
  },
53033
53023
  sendEmail: async (input) => {
53034
53024
  const cfg = deps.getConfig();
53035
- if (!cfg.gmailSendEnabled) throw new Error("Gmail send is disabled in Google Services settings.");
53036
53025
  if (!deps.connected()) throw new Error("Google account is not connected — open Connect Google.");
53037
53026
  const from = input.from?.trim() || buildFromHeader(cfg, deps.getAccountEmail());
53038
53027
  if (!from) throw new Error("Set From address in Google Services settings (or connect an account).");
@@ -53138,6 +53127,67 @@ function needsRefresh(state, nowMs = Date.now()) {
53138
53127
  return state.accessExpiresAt <= nowMs;
53139
53128
  }
53140
53129
  //#endregion
53130
+ //#region src/google-settings-copy.ts
53131
+ /**
53132
+ * Operator-facing setup copy for Connectivity → Calendars / Email.
53133
+ * Keep Connect as a same-origin relative link so the admin session cookie
53134
+ * rides along (an absolute Public URL from a LAN admin tab drops the cookie).
53135
+ */
53136
+ function redirectUriDisplay(publicOrigin) {
53137
+ if (publicOrigin === null || publicOrigin.length === 0) return `<public-hub>${OAUTH_CALLBACK_SUFFIX}`;
53138
+ return `${publicOrigin.replace(/\/$/, "")}${OAUTH_CALLBACK_SUFFIX}`;
53139
+ }
53140
+ function introHtml(cap, publicOrigin) {
53141
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
53142
+ if (cap === "smtp-provider") return [
53143
+ "<ol>",
53144
+ "<li>In Google Cloud Console enable the <b>Gmail API</b> (and Calendar API if you also use Calendars).</li>",
53145
+ "<li>Create an OAuth client of type <b>Web application</b>.</li>",
53146
+ `<li>Add this authorized redirect URI (Network → <b>Public endpoint</b>, not Internal): <code>${redirect}</code></li>`,
53147
+ "<li>Paste Client ID and Client secret below, then <b>Save</b>.</li>",
53148
+ `<li>Open <a href="${OAUTH_START_PATH}">Connect Google</a> while signed in as admin (same browser tab as this hub).</li>`,
53149
+ "<li>Set From address (connected account or a verified send-as). Turn this integration off with the page <b>Enable</b> toggle when you do not want Gmail send — Calendar can stay connected.</li>",
53150
+ "</ol>"
53151
+ ].join("");
53152
+ if (cap === "calendar-source") return [
53153
+ "<ol>",
53154
+ "<li>In Google Cloud Console enable the <b>Google Calendar API</b> (and Gmail API if you also use Email).</li>",
53155
+ "<li>Create an OAuth client of type <b>Web application</b>.</li>",
53156
+ `<li>Add this authorized redirect URI (Network → <b>Public endpoint</b>, not Internal): <code>${redirect}</code></li>`,
53157
+ "<li>Paste Client ID and Client secret below, then <b>Save</b>.</li>",
53158
+ `<li>Open <a href="${OAUTH_START_PATH}">Connect Google</a> while signed in as admin (same browser tab as this hub).</li>`,
53159
+ "<li>Optionally restrict which calendars are exposed with the filter below.</li>",
53160
+ "</ol>"
53161
+ ].join("");
53162
+ return [
53163
+ "<ol>",
53164
+ "<li>Enable <b>Google Calendar API</b> and <b>Gmail API</b> in Google Cloud Console.</li>",
53165
+ "<li>Create a <b>Web application</b> OAuth client.</li>",
53166
+ `<li>Register redirect URI: <code>${redirect}</code> (Network → Public endpoint).</li>`,
53167
+ "<li>Paste Client ID / secret, Save, then <a href=\"" + OAUTH_START_PATH + "\">Connect Google</a>.</li>",
53168
+ "<li>Calendars and Email share one Google account; use each page’s <b>Enable</b> toggle to turn a capability off without revoking OAuth.</li>",
53169
+ "</ol>"
53170
+ ].join("");
53171
+ }
53172
+ function connectHtml(cap, publicOrigin) {
53173
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
53174
+ if (cap === "smtp-provider") return [
53175
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
53176
+ "Connect requests <code>gmail.send</code> (and Calendar read for the Calendars page). ",
53177
+ `Google must list exactly: <code>${redirect}</code>. `,
53178
+ "If an older Connect predates Gmail send, revoke CamStack in Google Account → Security and Connect again."
53179
+ ].join("");
53180
+ if (cap === "calendar-source") return [
53181
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
53182
+ "Connect requests <code>calendar.readonly</code> (and Gmail send for the Email page). ",
53183
+ `Google must list exactly: <code>${redirect}</code>.`
53184
+ ].join("");
53185
+ return [`After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a>. `, `Redirect URI registered in Google Cloud must be <code>${redirect}</code>.`].join("");
53186
+ }
53187
+ function escapeHtml$1(s) {
53188
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53189
+ }
53190
+ //#endregion
53141
53191
  //#region src/oauth-redirect-origin.ts
53142
53192
  /**
53143
53193
  * Google OAuth redirect origin — same role as hub OAuth2 / sign-in
@@ -53167,12 +53217,15 @@ var GoogleServicesAddon = class extends BaseAddon {
53167
53217
  oauth = { ...DEFAULT_OAUTH_STATE };
53168
53218
  /** CSRF state → issuedAt ms */
53169
53219
  pendingStates = /* @__PURE__ */ new Map();
53220
+ /** Cached Public-endpoint origin for settings copy (redirect URI display). */
53221
+ publicOrigin = null;
53170
53222
  constructor() {
53171
53223
  super({ ...DEFAULT_GOOGLE_CONFIG });
53172
53224
  }
53173
53225
  async onInitialize() {
53174
53226
  this.oauthHandle = this.state("oauth", OauthStateSchema, { ...DEFAULT_OAUTH_STATE });
53175
53227
  this.oauth = await this.oauthHandle.get();
53228
+ await this.refreshPublicOrigin();
53176
53229
  const api = createGoogleCalendarApi(async () => this.ensureAccessToken());
53177
53230
  const calendarProvider = createGoogleCalendarSource({
53178
53231
  logger: this.ctx.logger,
@@ -53203,7 +53256,7 @@ var GoogleServicesAddon = class extends BaseAddon {
53203
53256
  this.ctx.logger.info("Initialized — google calendar-source + gmail smtp", { meta: {
53204
53257
  connected: this.oauth.refreshToken.length > 0,
53205
53258
  accountEmail: this.oauth.accountEmail || void 0,
53206
- gmailSendEnabled: this.config.gmailSendEnabled
53259
+ publicOrigin: this.publicOrigin ?? void 0
53207
53260
  } });
53208
53261
  return { providers: [
53209
53262
  {
@@ -53320,69 +53373,103 @@ var GoogleServicesAddon = class extends BaseAddon {
53320
53373
  return new Set(raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0));
53321
53374
  }
53322
53375
  async resolveRedirectUri() {
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).");
53325
- return `${origin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
53376
+ await this.refreshPublicOrigin();
53377
+ if (!this.publicOrigin) 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).");
53378
+ return `${this.publicOrigin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
53326
53379
  }
53327
- globalSettingsSchema() {
53328
- return this.schema({ sections: [{
53329
- id: "google-oauth",
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).",
53332
- columns: 1,
53333
- fields: [
53334
- {
53335
- type: "text",
53336
- key: "clientId",
53337
- label: "Client ID"
53338
- },
53339
- {
53340
- type: "password",
53341
- key: "clientSecret",
53342
- label: "Client secret",
53343
- showToggle: true
53344
- },
53345
- {
53380
+ async refreshPublicOrigin() {
53381
+ try {
53382
+ const book = await this.ctx.api.localNetwork.getAddresses.query({});
53383
+ this.publicOrigin = pickOauthRedirectOrigin(book);
53384
+ } catch {
53385
+ this.publicOrigin = null;
53386
+ }
53387
+ }
53388
+ /**
53389
+ * Connectivity asks for settings scoped to the page capability, so Calendar
53390
+ * and Email do not each redraw the other's fields (same pattern as
53391
+ * mqtt-broker under `broker`). The addon page (no cap) keeps every section.
53392
+ */
53393
+ globalSettingsSchema(cap) {
53394
+ const scoped = asSettingsCap(cap);
53395
+ return this.schema({ sections: [
53396
+ {
53397
+ id: "setup-intro",
53398
+ title: "Setup",
53399
+ description: scoped === "smtp-provider" ? "Connect a Google account for Gmail send (same OAuth as Calendars)." : scoped === "calendar-source" ? "Connect a Google account for Calendar (same OAuth as Email)." : "One Google account for Calendar and optional Gmail send.",
53400
+ columns: 1,
53401
+ fields: [{
53402
+ type: "info",
53403
+ key: "_setupSteps",
53404
+ label: "Steps",
53405
+ format: "html",
53406
+ content: introHtml(scoped, this.publicOrigin)
53407
+ }]
53408
+ },
53409
+ {
53410
+ id: "google-oauth",
53411
+ title: "Google account",
53412
+ description: "OAuth client credentials (Web application) stored in the hub keychain.",
53413
+ columns: 1,
53414
+ fields: [
53415
+ {
53416
+ type: "text",
53417
+ key: "clientId",
53418
+ label: "Client ID"
53419
+ },
53420
+ {
53421
+ type: "password",
53422
+ key: "clientSecret",
53423
+ label: "Client secret",
53424
+ showToggle: true
53425
+ },
53426
+ {
53427
+ type: "info",
53428
+ key: "_connectInfo",
53429
+ label: "Connect",
53430
+ format: "html",
53431
+ content: connectHtml(scoped, this.publicOrigin)
53432
+ }
53433
+ ]
53434
+ },
53435
+ ...scoped === "smtp-provider" ? [] : [{
53436
+ id: "google-calendar",
53437
+ title: "Google Calendar",
53438
+ description: "Which calendars this hub exposes as calendar-source entries.",
53439
+ columns: 1,
53440
+ fields: [{
53346
53441
  type: "text",
53347
53442
  key: "calendarFilter",
53348
53443
  label: "Calendar id filter (optional)",
53349
53444
  description: "Comma-separated Google calendar ids to expose. Empty = all calendars the account can read."
53350
- },
53351
- {
53352
- type: "info",
53353
- key: "_connectInfo",
53354
- label: "Connect",
53355
- format: "html",
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
- {
53445
+ }]
53446
+ }],
53447
+ ...scoped === "calendar-source" ? [] : [{
53448
+ id: "gmail-send",
53449
+ title: "Gmail send",
53450
+ description: "Delivery via the Gmail API. Disable this capability with the page Enable toggle — not a second switch here. From must be the connected account or a verified send-as.",
53451
+ columns: 1,
53452
+ fields: [{
53372
53453
  type: "text",
53373
53454
  key: "fromAddress",
53374
53455
  label: "From address",
53375
53456
  description: "Defaults to the connected Google account email when empty."
53376
- },
53377
- {
53457
+ }, {
53378
53458
  type: "text",
53379
53459
  key: "fromName",
53380
53460
  label: "From display name (optional)"
53381
- }
53382
- ]
53383
- }] });
53461
+ }]
53462
+ }]
53463
+ ] });
53464
+ }
53465
+ async getGlobalSettings(overlay, cap, nodeId) {
53466
+ await this.refreshPublicOrigin();
53467
+ return super.getGlobalSettings(overlay, cap, nodeId);
53384
53468
  }
53385
53469
  };
53470
+ function asSettingsCap(cap) {
53471
+ if (cap === "calendar-source" || cap === "smtp-provider") return cap;
53472
+ }
53386
53473
  function escapeHtml(s) {
53387
53474
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53388
53475
  }
@@ -53393,13 +53480,17 @@ exports.GOOGLE_OAUTH_SCOPES = GOOGLE_OAUTH_SCOPES;
53393
53480
  exports.GoogleServicesAddon = GoogleServicesAddon;
53394
53481
  exports.default = GoogleServicesAddon;
53395
53482
  exports.GoogleServicesConfigSchema = GoogleServicesConfigSchema;
53483
+ exports.OAUTH_START_PATH = OAUTH_START_PATH;
53396
53484
  exports.applyTokenResponse = applyTokenResponse;
53397
53485
  exports.buildAuthorizeUrl = buildAuthorizeUrl;
53398
53486
  exports.buildRawMime = buildRawMime;
53487
+ exports.connectHtml = connectHtml;
53399
53488
  exports.createGmailSmtpProvider = createGmailSmtpProvider;
53400
53489
  exports.createGoogleCalendarApi = createGoogleCalendarApi;
53401
53490
  exports.createGoogleCalendarSource = createGoogleCalendarSource;
53402
53491
  exports.exchangeCode = exchangeCode;
53492
+ exports.introHtml = introHtml;
53403
53493
  exports.mapGoogleEvent = mapGoogleEvent;
53404
53494
  exports.needsRefresh = needsRefresh;
53495
+ exports.redirectUriDisplay = redirectUriDisplay;
53405
53496
  exports.refreshAccessToken = refreshAccessToken;
@@ -52801,8 +52801,6 @@ var GoogleServicesConfigSchema = object({
52801
52801
  clientSecret: string().default("").meta({ secret: true }),
52802
52802
  /** Comma-separated Google calendar ids to expose; empty = all. */
52803
52803
  calendarFilter: string().default(""),
52804
- /** Offer Gmail send via `smtp-provider` (same OAuth account). */
52805
- gmailSendEnabled: boolean().default(false),
52806
52804
  /** Default From for Gmail send — usually the connected account email. */
52807
52805
  fromAddress: string().default(""),
52808
52806
  fromName: string().default("")
@@ -52823,6 +52821,8 @@ var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
52823
52821
  var GOOGLE_CALENDAR_API = "https://www.googleapis.com/calendar/v3";
52824
52822
  var GOOGLE_GMAIL_API = "https://gmail.googleapis.com/gmail/v1";
52825
52823
  var GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
52824
+ var OAUTH_START_PATH = `/addon/google-services/oauth/start`;
52825
+ var OAUTH_CALLBACK_SUFFIX = `/addon/google-services/oauth/callback`;
52826
52826
  //#endregion
52827
52827
  //#region src/google-calendar-source.ts
52828
52828
  function parseGoogleInstant(value) {
@@ -52980,12 +52980,6 @@ function createGmailSmtpProvider(deps) {
52980
52980
  const status = () => {
52981
52981
  const cfg = deps.getConfig();
52982
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
52983
  if (!deps.connected()) return {
52990
52984
  ready: false,
52991
52985
  host: email,
@@ -53002,10 +52996,6 @@ function createGmailSmtpProvider(deps) {
53002
52996
  return {
53003
52997
  getStatus: async () => status(),
53004
52998
  verify: async () => {
53005
- if (!deps.getConfig().gmailSendEnabled) {
53006
- lastError = "Gmail send is disabled in Google Services settings.";
53007
- return status();
53008
- }
53009
52999
  if (!deps.connected()) {
53010
53000
  lastError = "Google account is not connected.";
53011
53001
  return status();
@@ -53028,7 +53018,6 @@ function createGmailSmtpProvider(deps) {
53028
53018
  },
53029
53019
  sendEmail: async (input) => {
53030
53020
  const cfg = deps.getConfig();
53031
- if (!cfg.gmailSendEnabled) throw new Error("Gmail send is disabled in Google Services settings.");
53032
53021
  if (!deps.connected()) throw new Error("Google account is not connected — open Connect Google.");
53033
53022
  const from = input.from?.trim() || buildFromHeader(cfg, deps.getAccountEmail());
53034
53023
  if (!from) throw new Error("Set From address in Google Services settings (or connect an account).");
@@ -53134,6 +53123,67 @@ function needsRefresh(state, nowMs = Date.now()) {
53134
53123
  return state.accessExpiresAt <= nowMs;
53135
53124
  }
53136
53125
  //#endregion
53126
+ //#region src/google-settings-copy.ts
53127
+ /**
53128
+ * Operator-facing setup copy for Connectivity → Calendars / Email.
53129
+ * Keep Connect as a same-origin relative link so the admin session cookie
53130
+ * rides along (an absolute Public URL from a LAN admin tab drops the cookie).
53131
+ */
53132
+ function redirectUriDisplay(publicOrigin) {
53133
+ if (publicOrigin === null || publicOrigin.length === 0) return `<public-hub>${OAUTH_CALLBACK_SUFFIX}`;
53134
+ return `${publicOrigin.replace(/\/$/, "")}${OAUTH_CALLBACK_SUFFIX}`;
53135
+ }
53136
+ function introHtml(cap, publicOrigin) {
53137
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
53138
+ if (cap === "smtp-provider") return [
53139
+ "<ol>",
53140
+ "<li>In Google Cloud Console enable the <b>Gmail API</b> (and Calendar API if you also use Calendars).</li>",
53141
+ "<li>Create an OAuth client of type <b>Web application</b>.</li>",
53142
+ `<li>Add this authorized redirect URI (Network → <b>Public endpoint</b>, not Internal): <code>${redirect}</code></li>`,
53143
+ "<li>Paste Client ID and Client secret below, then <b>Save</b>.</li>",
53144
+ `<li>Open <a href="${OAUTH_START_PATH}">Connect Google</a> while signed in as admin (same browser tab as this hub).</li>`,
53145
+ "<li>Set From address (connected account or a verified send-as). Turn this integration off with the page <b>Enable</b> toggle when you do not want Gmail send — Calendar can stay connected.</li>",
53146
+ "</ol>"
53147
+ ].join("");
53148
+ if (cap === "calendar-source") return [
53149
+ "<ol>",
53150
+ "<li>In Google Cloud Console enable the <b>Google Calendar API</b> (and Gmail API if you also use Email).</li>",
53151
+ "<li>Create an OAuth client of type <b>Web application</b>.</li>",
53152
+ `<li>Add this authorized redirect URI (Network → <b>Public endpoint</b>, not Internal): <code>${redirect}</code></li>`,
53153
+ "<li>Paste Client ID and Client secret below, then <b>Save</b>.</li>",
53154
+ `<li>Open <a href="${OAUTH_START_PATH}">Connect Google</a> while signed in as admin (same browser tab as this hub).</li>`,
53155
+ "<li>Optionally restrict which calendars are exposed with the filter below.</li>",
53156
+ "</ol>"
53157
+ ].join("");
53158
+ return [
53159
+ "<ol>",
53160
+ "<li>Enable <b>Google Calendar API</b> and <b>Gmail API</b> in Google Cloud Console.</li>",
53161
+ "<li>Create a <b>Web application</b> OAuth client.</li>",
53162
+ `<li>Register redirect URI: <code>${redirect}</code> (Network → Public endpoint).</li>`,
53163
+ "<li>Paste Client ID / secret, Save, then <a href=\"" + OAUTH_START_PATH + "\">Connect Google</a>.</li>",
53164
+ "<li>Calendars and Email share one Google account; use each page’s <b>Enable</b> toggle to turn a capability off without revoking OAuth.</li>",
53165
+ "</ol>"
53166
+ ].join("");
53167
+ }
53168
+ function connectHtml(cap, publicOrigin) {
53169
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
53170
+ if (cap === "smtp-provider") return [
53171
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
53172
+ "Connect requests <code>gmail.send</code> (and Calendar read for the Calendars page). ",
53173
+ `Google must list exactly: <code>${redirect}</code>. `,
53174
+ "If an older Connect predates Gmail send, revoke CamStack in Google Account → Security and Connect again."
53175
+ ].join("");
53176
+ if (cap === "calendar-source") return [
53177
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
53178
+ "Connect requests <code>calendar.readonly</code> (and Gmail send for the Email page). ",
53179
+ `Google must list exactly: <code>${redirect}</code>.`
53180
+ ].join("");
53181
+ return [`After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a>. `, `Redirect URI registered in Google Cloud must be <code>${redirect}</code>.`].join("");
53182
+ }
53183
+ function escapeHtml$1(s) {
53184
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53185
+ }
53186
+ //#endregion
53137
53187
  //#region src/oauth-redirect-origin.ts
53138
53188
  /**
53139
53189
  * Google OAuth redirect origin — same role as hub OAuth2 / sign-in
@@ -53163,12 +53213,15 @@ var GoogleServicesAddon = class extends BaseAddon {
53163
53213
  oauth = { ...DEFAULT_OAUTH_STATE };
53164
53214
  /** CSRF state → issuedAt ms */
53165
53215
  pendingStates = /* @__PURE__ */ new Map();
53216
+ /** Cached Public-endpoint origin for settings copy (redirect URI display). */
53217
+ publicOrigin = null;
53166
53218
  constructor() {
53167
53219
  super({ ...DEFAULT_GOOGLE_CONFIG });
53168
53220
  }
53169
53221
  async onInitialize() {
53170
53222
  this.oauthHandle = this.state("oauth", OauthStateSchema, { ...DEFAULT_OAUTH_STATE });
53171
53223
  this.oauth = await this.oauthHandle.get();
53224
+ await this.refreshPublicOrigin();
53172
53225
  const api = createGoogleCalendarApi(async () => this.ensureAccessToken());
53173
53226
  const calendarProvider = createGoogleCalendarSource({
53174
53227
  logger: this.ctx.logger,
@@ -53199,7 +53252,7 @@ var GoogleServicesAddon = class extends BaseAddon {
53199
53252
  this.ctx.logger.info("Initialized — google calendar-source + gmail smtp", { meta: {
53200
53253
  connected: this.oauth.refreshToken.length > 0,
53201
53254
  accountEmail: this.oauth.accountEmail || void 0,
53202
- gmailSendEnabled: this.config.gmailSendEnabled
53255
+ publicOrigin: this.publicOrigin ?? void 0
53203
53256
  } });
53204
53257
  return { providers: [
53205
53258
  {
@@ -53316,71 +53369,105 @@ var GoogleServicesAddon = class extends BaseAddon {
53316
53369
  return new Set(raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0));
53317
53370
  }
53318
53371
  async resolveRedirectUri() {
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).");
53321
- return `${origin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
53372
+ await this.refreshPublicOrigin();
53373
+ if (!this.publicOrigin) 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).");
53374
+ return `${this.publicOrigin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
53322
53375
  }
53323
- globalSettingsSchema() {
53324
- return this.schema({ sections: [{
53325
- id: "google-oauth",
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).",
53328
- columns: 1,
53329
- fields: [
53330
- {
53331
- type: "text",
53332
- key: "clientId",
53333
- label: "Client ID"
53334
- },
53335
- {
53336
- type: "password",
53337
- key: "clientSecret",
53338
- label: "Client secret",
53339
- showToggle: true
53340
- },
53341
- {
53376
+ async refreshPublicOrigin() {
53377
+ try {
53378
+ const book = await this.ctx.api.localNetwork.getAddresses.query({});
53379
+ this.publicOrigin = pickOauthRedirectOrigin(book);
53380
+ } catch {
53381
+ this.publicOrigin = null;
53382
+ }
53383
+ }
53384
+ /**
53385
+ * Connectivity asks for settings scoped to the page capability, so Calendar
53386
+ * and Email do not each redraw the other's fields (same pattern as
53387
+ * mqtt-broker under `broker`). The addon page (no cap) keeps every section.
53388
+ */
53389
+ globalSettingsSchema(cap) {
53390
+ const scoped = asSettingsCap(cap);
53391
+ return this.schema({ sections: [
53392
+ {
53393
+ id: "setup-intro",
53394
+ title: "Setup",
53395
+ description: scoped === "smtp-provider" ? "Connect a Google account for Gmail send (same OAuth as Calendars)." : scoped === "calendar-source" ? "Connect a Google account for Calendar (same OAuth as Email)." : "One Google account for Calendar and optional Gmail send.",
53396
+ columns: 1,
53397
+ fields: [{
53398
+ type: "info",
53399
+ key: "_setupSteps",
53400
+ label: "Steps",
53401
+ format: "html",
53402
+ content: introHtml(scoped, this.publicOrigin)
53403
+ }]
53404
+ },
53405
+ {
53406
+ id: "google-oauth",
53407
+ title: "Google account",
53408
+ description: "OAuth client credentials (Web application) stored in the hub keychain.",
53409
+ columns: 1,
53410
+ fields: [
53411
+ {
53412
+ type: "text",
53413
+ key: "clientId",
53414
+ label: "Client ID"
53415
+ },
53416
+ {
53417
+ type: "password",
53418
+ key: "clientSecret",
53419
+ label: "Client secret",
53420
+ showToggle: true
53421
+ },
53422
+ {
53423
+ type: "info",
53424
+ key: "_connectInfo",
53425
+ label: "Connect",
53426
+ format: "html",
53427
+ content: connectHtml(scoped, this.publicOrigin)
53428
+ }
53429
+ ]
53430
+ },
53431
+ ...scoped === "smtp-provider" ? [] : [{
53432
+ id: "google-calendar",
53433
+ title: "Google Calendar",
53434
+ description: "Which calendars this hub exposes as calendar-source entries.",
53435
+ columns: 1,
53436
+ fields: [{
53342
53437
  type: "text",
53343
53438
  key: "calendarFilter",
53344
53439
  label: "Calendar id filter (optional)",
53345
53440
  description: "Comma-separated Google calendar ids to expose. Empty = all calendars the account can read."
53346
- },
53347
- {
53348
- type: "info",
53349
- key: "_connectInfo",
53350
- label: "Connect",
53351
- format: "html",
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
- {
53441
+ }]
53442
+ }],
53443
+ ...scoped === "calendar-source" ? [] : [{
53444
+ id: "gmail-send",
53445
+ title: "Gmail send",
53446
+ description: "Delivery via the Gmail API. Disable this capability with the page Enable toggle — not a second switch here. From must be the connected account or a verified send-as.",
53447
+ columns: 1,
53448
+ fields: [{
53368
53449
  type: "text",
53369
53450
  key: "fromAddress",
53370
53451
  label: "From address",
53371
53452
  description: "Defaults to the connected Google account email when empty."
53372
- },
53373
- {
53453
+ }, {
53374
53454
  type: "text",
53375
53455
  key: "fromName",
53376
53456
  label: "From display name (optional)"
53377
- }
53378
- ]
53379
- }] });
53457
+ }]
53458
+ }]
53459
+ ] });
53460
+ }
53461
+ async getGlobalSettings(overlay, cap, nodeId) {
53462
+ await this.refreshPublicOrigin();
53463
+ return super.getGlobalSettings(overlay, cap, nodeId);
53380
53464
  }
53381
53465
  };
53466
+ function asSettingsCap(cap) {
53467
+ if (cap === "calendar-source" || cap === "smtp-provider") return cap;
53468
+ }
53382
53469
  function escapeHtml(s) {
53383
53470
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53384
53471
  }
53385
53472
  //#endregion
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 };
53473
+ export { GoogleServicesAddon, GoogleServicesAddon as default, GoogleServicesConfigSchema as _, buildAuthorizeUrl as a, refreshAccessToken as c, createGoogleCalendarApi as d, createGoogleCalendarSource as f, GOOGLE_OAUTH_SCOPES as g, GOOGLE_GMAIL_SEND_SCOPE as h, applyTokenResponse as i, buildRawMime as l, DEFAULT_GOOGLE_CONFIG as m, introHtml as n, exchangeCode as o, mapGoogleEvent as p, redirectUriDisplay as r, needsRefresh as s, connectHtml as t, createGmailSmtpProvider as u, OAUTH_START_PATH as v };
package/dist/index.js CHANGED
@@ -8,14 +8,18 @@ exports.GOOGLE_GMAIL_SEND_SCOPE = require_google_services_addon.GOOGLE_GMAIL_SEN
8
8
  exports.GOOGLE_OAUTH_SCOPES = require_google_services_addon.GOOGLE_OAUTH_SCOPES;
9
9
  exports.GoogleServicesAddon = require_google_services_addon.GoogleServicesAddon;
10
10
  exports.GoogleServicesConfigSchema = require_google_services_addon.GoogleServicesConfigSchema;
11
+ exports.OAUTH_START_PATH = require_google_services_addon.OAUTH_START_PATH;
11
12
  exports.applyTokenResponse = require_google_services_addon.applyTokenResponse;
12
13
  exports.buildAuthorizeUrl = require_google_services_addon.buildAuthorizeUrl;
13
14
  exports.buildRawMime = require_google_services_addon.buildRawMime;
15
+ exports.connectHtml = require_google_services_addon.connectHtml;
14
16
  exports.createGmailSmtpProvider = require_google_services_addon.createGmailSmtpProvider;
15
17
  exports.createGoogleCalendarApi = require_google_services_addon.createGoogleCalendarApi;
16
18
  exports.createGoogleCalendarSource = require_google_services_addon.createGoogleCalendarSource;
17
19
  exports.default = require_google_services_addon.GoogleServicesAddon;
18
20
  exports.exchangeCode = require_google_services_addon.exchangeCode;
21
+ exports.introHtml = require_google_services_addon.introHtml;
19
22
  exports.mapGoogleEvent = require_google_services_addon.mapGoogleEvent;
20
23
  exports.needsRefresh = require_google_services_addon.needsRefresh;
24
+ exports.redirectUriDisplay = require_google_services_addon.redirectUriDisplay;
21
25
  exports.refreshAccessToken = require_google_services_addon.refreshAccessToken;
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
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 };
1
+ import { GoogleServicesAddon, _ as GoogleServicesConfigSchema, a as buildAuthorizeUrl, c as refreshAccessToken, d as createGoogleCalendarApi, f as createGoogleCalendarSource, g as GOOGLE_OAUTH_SCOPES, h as GOOGLE_GMAIL_SEND_SCOPE, i as applyTokenResponse, l as buildRawMime, m as DEFAULT_GOOGLE_CONFIG, n as introHtml, o as exchangeCode, p as mapGoogleEvent, r as redirectUriDisplay, s as needsRefresh, t as connectHtml, u as createGmailSmtpProvider, v as OAUTH_START_PATH } from "./google-services.addon.mjs";
2
+ export { DEFAULT_GOOGLE_CONFIG, GOOGLE_GMAIL_SEND_SCOPE, GOOGLE_OAUTH_SCOPES, GoogleServicesAddon, GoogleServicesAddon as default, GoogleServicesConfigSchema, OAUTH_START_PATH, applyTokenResponse, buildAuthorizeUrl, buildRawMime, connectHtml, createGmailSmtpProvider, createGoogleCalendarApi, createGoogleCalendarSource, exchangeCode, introHtml, mapGoogleEvent, needsRefresh, redirectUriDisplay, refreshAccessToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-google-services",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Google Services — Google Calendar (calendar-source) and Gmail send (smtp-provider) via one OAuth2 account.",
5
5
  "keywords": [
6
6
  "camstack",