@camstack/addon-google-services 0.1.4 → 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,9 +53373,17 @@ 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}`;
53379
+ }
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
+ }
53326
53387
  }
53327
53388
  /**
53328
53389
  * Connectivity asks for settings scoped to the page capability, so Calendar
@@ -53330,23 +53391,25 @@ var GoogleServicesAddon = class extends BaseAddon {
53330
53391
  * mqtt-broker under `broker`). The addon page (no cap) keeps every section.
53331
53392
  */
53332
53393
  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() {
53394
+ const scoped = asSettingsCap(cap);
53345
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
+ },
53346
53409
  {
53347
53410
  id: "google-oauth",
53348
53411
  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).",
53412
+ description: "OAuth client credentials (Web application) stored in the hub keychain.",
53350
53413
  columns: 1,
53351
53414
  fields: [
53352
53415
  {
@@ -53365,11 +53428,11 @@ var GoogleServicesAddon = class extends BaseAddon {
53365
53428
  key: "_connectInfo",
53366
53429
  label: "Connect",
53367
53430
  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>.`
53431
+ content: connectHtml(scoped, this.publicOrigin)
53369
53432
  }
53370
53433
  ]
53371
53434
  },
53372
- {
53435
+ ...scoped === "smtp-provider" ? [] : [{
53373
53436
  id: "google-calendar",
53374
53437
  title: "Google Calendar",
53375
53438
  description: "Which calendars this hub exposes as calendar-source entries.",
@@ -53380,35 +53443,33 @@ var GoogleServicesAddon = class extends BaseAddon {
53380
53443
  label: "Calendar id filter (optional)",
53381
53444
  description: "Comma-separated Google calendar ids to expose. Empty = all calendars the account can read."
53382
53445
  }]
53383
- },
53384
- {
53446
+ }],
53447
+ ...scoped === "calendar-source" ? [] : [{
53385
53448
  id: "gmail-send",
53386
53449
  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.",
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.",
53388
53451
  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
- }
53452
+ fields: [{
53453
+ type: "text",
53454
+ key: "fromAddress",
53455
+ label: "From address",
53456
+ description: "Defaults to the connected Google account email when empty."
53457
+ }, {
53458
+ type: "text",
53459
+ key: "fromName",
53460
+ label: "From display name (optional)"
53461
+ }]
53462
+ }]
53409
53463
  ] });
53410
53464
  }
53465
+ async getGlobalSettings(overlay, cap, nodeId) {
53466
+ await this.refreshPublicOrigin();
53467
+ return super.getGlobalSettings(overlay, cap, nodeId);
53468
+ }
53411
53469
  };
53470
+ function asSettingsCap(cap) {
53471
+ if (cap === "calendar-source" || cap === "smtp-provider") return cap;
53472
+ }
53412
53473
  function escapeHtml(s) {
53413
53474
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53414
53475
  }
@@ -53419,13 +53480,17 @@ exports.GOOGLE_OAUTH_SCOPES = GOOGLE_OAUTH_SCOPES;
53419
53480
  exports.GoogleServicesAddon = GoogleServicesAddon;
53420
53481
  exports.default = GoogleServicesAddon;
53421
53482
  exports.GoogleServicesConfigSchema = GoogleServicesConfigSchema;
53483
+ exports.OAUTH_START_PATH = OAUTH_START_PATH;
53422
53484
  exports.applyTokenResponse = applyTokenResponse;
53423
53485
  exports.buildAuthorizeUrl = buildAuthorizeUrl;
53424
53486
  exports.buildRawMime = buildRawMime;
53487
+ exports.connectHtml = connectHtml;
53425
53488
  exports.createGmailSmtpProvider = createGmailSmtpProvider;
53426
53489
  exports.createGoogleCalendarApi = createGoogleCalendarApi;
53427
53490
  exports.createGoogleCalendarSource = createGoogleCalendarSource;
53428
53491
  exports.exchangeCode = exchangeCode;
53492
+ exports.introHtml = introHtml;
53429
53493
  exports.mapGoogleEvent = mapGoogleEvent;
53430
53494
  exports.needsRefresh = needsRefresh;
53495
+ exports.redirectUriDisplay = redirectUriDisplay;
53431
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,9 +53369,17 @@ 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}`;
53375
+ }
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
+ }
53322
53383
  }
53323
53384
  /**
53324
53385
  * Connectivity asks for settings scoped to the page capability, so Calendar
@@ -53326,23 +53387,25 @@ var GoogleServicesAddon = class extends BaseAddon {
53326
53387
  * mqtt-broker under `broker`). The addon page (no cap) keeps every section.
53327
53388
  */
53328
53389
  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() {
53390
+ const scoped = asSettingsCap(cap);
53341
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
+ },
53342
53405
  {
53343
53406
  id: "google-oauth",
53344
53407
  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).",
53408
+ description: "OAuth client credentials (Web application) stored in the hub keychain.",
53346
53409
  columns: 1,
53347
53410
  fields: [
53348
53411
  {
@@ -53361,11 +53424,11 @@ var GoogleServicesAddon = class extends BaseAddon {
53361
53424
  key: "_connectInfo",
53362
53425
  label: "Connect",
53363
53426
  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>.`
53427
+ content: connectHtml(scoped, this.publicOrigin)
53365
53428
  }
53366
53429
  ]
53367
53430
  },
53368
- {
53431
+ ...scoped === "smtp-provider" ? [] : [{
53369
53432
  id: "google-calendar",
53370
53433
  title: "Google Calendar",
53371
53434
  description: "Which calendars this hub exposes as calendar-source entries.",
@@ -53376,37 +53439,35 @@ var GoogleServicesAddon = class extends BaseAddon {
53376
53439
  label: "Calendar id filter (optional)",
53377
53440
  description: "Comma-separated Google calendar ids to expose. Empty = all calendars the account can read."
53378
53441
  }]
53379
- },
53380
- {
53442
+ }],
53443
+ ...scoped === "calendar-source" ? [] : [{
53381
53444
  id: "gmail-send",
53382
53445
  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.",
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.",
53384
53447
  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
- }
53448
+ fields: [{
53449
+ type: "text",
53450
+ key: "fromAddress",
53451
+ label: "From address",
53452
+ description: "Defaults to the connected Google account email when empty."
53453
+ }, {
53454
+ type: "text",
53455
+ key: "fromName",
53456
+ label: "From display name (optional)"
53457
+ }]
53458
+ }]
53405
53459
  ] });
53406
53460
  }
53461
+ async getGlobalSettings(overlay, cap, nodeId) {
53462
+ await this.refreshPublicOrigin();
53463
+ return super.getGlobalSettings(overlay, cap, nodeId);
53464
+ }
53407
53465
  };
53466
+ function asSettingsCap(cap) {
53467
+ if (cap === "calendar-source" || cap === "smtp-provider") return cap;
53468
+ }
53408
53469
  function escapeHtml(s) {
53409
53470
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53410
53471
  }
53411
53472
  //#endregion
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 };
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.4",
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",