@camstack/addon-google-services 0.1.4 → 0.1.6

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,86 @@ 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
+ * Root-relative hrefs must survive `sanitizeInfoHtml` in ui-library.
53136
+ */
53137
+ /** Enable APIs library in Google Cloud Console. */
53138
+ var GOOGLE_CONSOLE_APIS_URL = "https://console.cloud.google.com/apis/library";
53139
+ /** Create / manage OAuth Web clients. */
53140
+ var GOOGLE_CONSOLE_CREDENTIALS_URL = "https://console.cloud.google.com/apis/credentials";
53141
+ function redirectUriDisplay(publicOrigin) {
53142
+ if (publicOrigin === null || publicOrigin.length === 0) return `<public-hub>${OAUTH_CALLBACK_SUFFIX}`;
53143
+ return `${publicOrigin.replace(/\/$/, "")}${OAUTH_CALLBACK_SUFFIX}`;
53144
+ }
53145
+ function consoleLinksHtml() {
53146
+ return [
53147
+ `<a href="${GOOGLE_CONSOLE_APIS_URL}">API library</a>`,
53148
+ ` · `,
53149
+ `<a href="${GOOGLE_CONSOLE_CREDENTIALS_URL}">Credentials</a>`
53150
+ ].join("");
53151
+ }
53152
+ function introHtml(cap, publicOrigin) {
53153
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
53154
+ const console = consoleLinksHtml();
53155
+ if (cap === "smtp-provider") return [
53156
+ "<ol>",
53157
+ `<li>Open Google Cloud Console (${console}) and enable the <strong>Gmail API</strong> (and Calendar API if you also use Calendars).</li>`,
53158
+ "<li>Under Credentials, create an OAuth client of type <strong>Web application</strong>.</li>",
53159
+ `<li>Add this authorized redirect URI (Network → <strong>Public endpoint</strong>, not Internal): <code>${redirect}</code></li>`,
53160
+ "<li>Paste Client ID and Client secret below, then <strong>Save</strong>.</li>",
53161
+ `<li>Open <a href="${OAUTH_START_PATH}">Connect Google</a> while signed in as admin (same browser tab as this hub).</li>`,
53162
+ "<li>Set From address (connected account or a verified send-as). Turn this integration off with the page <strong>Enable</strong> toggle when you do not want Gmail send — Calendar can stay connected.</li>",
53163
+ "</ol>"
53164
+ ].join("");
53165
+ if (cap === "calendar-source") return [
53166
+ "<ol>",
53167
+ `<li>Open Google Cloud Console (${console}) and enable the <strong>Google Calendar API</strong> (and Gmail API if you also use Email).</li>`,
53168
+ "<li>Under Credentials, create an OAuth client of type <strong>Web application</strong>.</li>",
53169
+ `<li>Add this authorized redirect URI (Network → <strong>Public endpoint</strong>, not Internal): <code>${redirect}</code></li>`,
53170
+ "<li>Paste Client ID and Client secret below, then <strong>Save</strong>.</li>",
53171
+ `<li>Open <a href="${OAUTH_START_PATH}">Connect Google</a> while signed in as admin (same browser tab as this hub).</li>`,
53172
+ "<li>Optionally restrict which calendars are exposed with the filter below.</li>",
53173
+ "</ol>"
53174
+ ].join("");
53175
+ return [
53176
+ "<ol>",
53177
+ `<li>Open Google Cloud Console (${console}) and enable <strong>Google Calendar API</strong> and <strong>Gmail API</strong>.</li>`,
53178
+ "<li>Create a <strong>Web application</strong> OAuth client under Credentials.</li>",
53179
+ `<li>Register redirect URI: <code>${redirect}</code> (Network → Public endpoint).</li>`,
53180
+ `<li>Paste Client ID / secret, Save, then <a href="${OAUTH_START_PATH}">Connect Google</a>.</li>`,
53181
+ "<li>Calendars and Email share one Google account; use each page’s <strong>Enable</strong> toggle to turn a capability off without revoking OAuth.</li>",
53182
+ "</ol>"
53183
+ ].join("");
53184
+ }
53185
+ function connectHtml(cap, publicOrigin) {
53186
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
53187
+ if (cap === "smtp-provider") return [
53188
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
53189
+ "Connect requests <code>gmail.send</code> (and Calendar read for the Calendars page). ",
53190
+ `Google must list exactly: <code>${redirect}</code>. `,
53191
+ "If an older Connect predates Gmail send, revoke CamStack in Google Account → Security and Connect again. ",
53192
+ `Console: ${consoleLinksHtml()}.`
53193
+ ].join("");
53194
+ if (cap === "calendar-source") return [
53195
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
53196
+ "Connect requests <code>calendar.readonly</code> (and Gmail send for the Email page). ",
53197
+ `Google must list exactly: <code>${redirect}</code>. `,
53198
+ `Console: ${consoleLinksHtml()}.`
53199
+ ].join("");
53200
+ return [
53201
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a>. `,
53202
+ `Redirect URI registered in Google Cloud must be <code>${redirect}</code>. `,
53203
+ `Console: ${consoleLinksHtml()}.`
53204
+ ].join("");
53205
+ }
53206
+ function escapeHtml$1(s) {
53207
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53208
+ }
53209
+ //#endregion
53141
53210
  //#region src/oauth-redirect-origin.ts
53142
53211
  /**
53143
53212
  * Google OAuth redirect origin — same role as hub OAuth2 / sign-in
@@ -53167,12 +53236,15 @@ var GoogleServicesAddon = class extends BaseAddon {
53167
53236
  oauth = { ...DEFAULT_OAUTH_STATE };
53168
53237
  /** CSRF state → issuedAt ms */
53169
53238
  pendingStates = /* @__PURE__ */ new Map();
53239
+ /** Cached Public-endpoint origin for settings copy (redirect URI display). */
53240
+ publicOrigin = null;
53170
53241
  constructor() {
53171
53242
  super({ ...DEFAULT_GOOGLE_CONFIG });
53172
53243
  }
53173
53244
  async onInitialize() {
53174
53245
  this.oauthHandle = this.state("oauth", OauthStateSchema, { ...DEFAULT_OAUTH_STATE });
53175
53246
  this.oauth = await this.oauthHandle.get();
53247
+ await this.refreshPublicOrigin();
53176
53248
  const api = createGoogleCalendarApi(async () => this.ensureAccessToken());
53177
53249
  const calendarProvider = createGoogleCalendarSource({
53178
53250
  logger: this.ctx.logger,
@@ -53203,7 +53275,7 @@ var GoogleServicesAddon = class extends BaseAddon {
53203
53275
  this.ctx.logger.info("Initialized — google calendar-source + gmail smtp", { meta: {
53204
53276
  connected: this.oauth.refreshToken.length > 0,
53205
53277
  accountEmail: this.oauth.accountEmail || void 0,
53206
- gmailSendEnabled: this.config.gmailSendEnabled
53278
+ publicOrigin: this.publicOrigin ?? void 0
53207
53279
  } });
53208
53280
  return { providers: [
53209
53281
  {
@@ -53320,9 +53392,17 @@ var GoogleServicesAddon = class extends BaseAddon {
53320
53392
  return new Set(raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0));
53321
53393
  }
53322
53394
  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}`;
53395
+ await this.refreshPublicOrigin();
53396
+ 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).");
53397
+ return `${this.publicOrigin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
53398
+ }
53399
+ async refreshPublicOrigin() {
53400
+ try {
53401
+ const book = await this.ctx.api.localNetwork.getAddresses.query({});
53402
+ this.publicOrigin = pickOauthRedirectOrigin(book);
53403
+ } catch {
53404
+ this.publicOrigin = null;
53405
+ }
53326
53406
  }
53327
53407
  /**
53328
53408
  * Connectivity asks for settings scoped to the page capability, so Calendar
@@ -53330,23 +53410,25 @@ var GoogleServicesAddon = class extends BaseAddon {
53330
53410
  * mqtt-broker under `broker`). The addon page (no cap) keeps every section.
53331
53411
  */
53332
53412
  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() {
53413
+ const scoped = asSettingsCap(cap);
53345
53414
  return this.schema({ sections: [
53415
+ {
53416
+ id: "setup-intro",
53417
+ title: "Setup",
53418
+ 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.",
53419
+ columns: 1,
53420
+ fields: [{
53421
+ type: "info",
53422
+ key: "_setupSteps",
53423
+ label: "Steps",
53424
+ format: "html",
53425
+ content: introHtml(scoped, this.publicOrigin)
53426
+ }]
53427
+ },
53346
53428
  {
53347
53429
  id: "google-oauth",
53348
53430
  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).",
53431
+ description: "OAuth client credentials (Web application) stored in the hub keychain.",
53350
53432
  columns: 1,
53351
53433
  fields: [
53352
53434
  {
@@ -53365,11 +53447,11 @@ var GoogleServicesAddon = class extends BaseAddon {
53365
53447
  key: "_connectInfo",
53366
53448
  label: "Connect",
53367
53449
  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>.`
53450
+ content: connectHtml(scoped, this.publicOrigin)
53369
53451
  }
53370
53452
  ]
53371
53453
  },
53372
- {
53454
+ ...scoped === "smtp-provider" ? [] : [{
53373
53455
  id: "google-calendar",
53374
53456
  title: "Google Calendar",
53375
53457
  description: "Which calendars this hub exposes as calendar-source entries.",
@@ -53380,52 +53462,56 @@ var GoogleServicesAddon = class extends BaseAddon {
53380
53462
  label: "Calendar id filter (optional)",
53381
53463
  description: "Comma-separated Google calendar ids to expose. Empty = all calendars the account can read."
53382
53464
  }]
53383
- },
53384
- {
53465
+ }],
53466
+ ...scoped === "calendar-source" ? [] : [{
53385
53467
  id: "gmail-send",
53386
53468
  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.",
53469
+ 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
53470
  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
- }
53471
+ fields: [{
53472
+ type: "text",
53473
+ key: "fromAddress",
53474
+ label: "From address",
53475
+ description: "Defaults to the connected Google account email when empty."
53476
+ }, {
53477
+ type: "text",
53478
+ key: "fromName",
53479
+ label: "From display name (optional)"
53480
+ }]
53481
+ }]
53409
53482
  ] });
53410
53483
  }
53484
+ async getGlobalSettings(overlay, cap, nodeId) {
53485
+ await this.refreshPublicOrigin();
53486
+ return super.getGlobalSettings(overlay, cap, nodeId);
53487
+ }
53411
53488
  };
53489
+ function asSettingsCap(cap) {
53490
+ if (cap === "calendar-source" || cap === "smtp-provider") return cap;
53491
+ }
53412
53492
  function escapeHtml(s) {
53413
53493
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53414
53494
  }
53415
53495
  //#endregion
53416
53496
  exports.DEFAULT_GOOGLE_CONFIG = DEFAULT_GOOGLE_CONFIG;
53497
+ exports.GOOGLE_CONSOLE_APIS_URL = GOOGLE_CONSOLE_APIS_URL;
53498
+ exports.GOOGLE_CONSOLE_CREDENTIALS_URL = GOOGLE_CONSOLE_CREDENTIALS_URL;
53417
53499
  exports.GOOGLE_GMAIL_SEND_SCOPE = GOOGLE_GMAIL_SEND_SCOPE;
53418
53500
  exports.GOOGLE_OAUTH_SCOPES = GOOGLE_OAUTH_SCOPES;
53419
53501
  exports.GoogleServicesAddon = GoogleServicesAddon;
53420
53502
  exports.default = GoogleServicesAddon;
53421
53503
  exports.GoogleServicesConfigSchema = GoogleServicesConfigSchema;
53504
+ exports.OAUTH_START_PATH = OAUTH_START_PATH;
53422
53505
  exports.applyTokenResponse = applyTokenResponse;
53423
53506
  exports.buildAuthorizeUrl = buildAuthorizeUrl;
53424
53507
  exports.buildRawMime = buildRawMime;
53508
+ exports.connectHtml = connectHtml;
53425
53509
  exports.createGmailSmtpProvider = createGmailSmtpProvider;
53426
53510
  exports.createGoogleCalendarApi = createGoogleCalendarApi;
53427
53511
  exports.createGoogleCalendarSource = createGoogleCalendarSource;
53428
53512
  exports.exchangeCode = exchangeCode;
53513
+ exports.introHtml = introHtml;
53429
53514
  exports.mapGoogleEvent = mapGoogleEvent;
53430
53515
  exports.needsRefresh = needsRefresh;
53516
+ exports.redirectUriDisplay = redirectUriDisplay;
53431
53517
  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,86 @@ 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
+ * Root-relative hrefs must survive `sanitizeInfoHtml` in ui-library.
53132
+ */
53133
+ /** Enable APIs library in Google Cloud Console. */
53134
+ var GOOGLE_CONSOLE_APIS_URL = "https://console.cloud.google.com/apis/library";
53135
+ /** Create / manage OAuth Web clients. */
53136
+ var GOOGLE_CONSOLE_CREDENTIALS_URL = "https://console.cloud.google.com/apis/credentials";
53137
+ function redirectUriDisplay(publicOrigin) {
53138
+ if (publicOrigin === null || publicOrigin.length === 0) return `<public-hub>${OAUTH_CALLBACK_SUFFIX}`;
53139
+ return `${publicOrigin.replace(/\/$/, "")}${OAUTH_CALLBACK_SUFFIX}`;
53140
+ }
53141
+ function consoleLinksHtml() {
53142
+ return [
53143
+ `<a href="${GOOGLE_CONSOLE_APIS_URL}">API library</a>`,
53144
+ ` · `,
53145
+ `<a href="${GOOGLE_CONSOLE_CREDENTIALS_URL}">Credentials</a>`
53146
+ ].join("");
53147
+ }
53148
+ function introHtml(cap, publicOrigin) {
53149
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
53150
+ const console = consoleLinksHtml();
53151
+ if (cap === "smtp-provider") return [
53152
+ "<ol>",
53153
+ `<li>Open Google Cloud Console (${console}) and enable the <strong>Gmail API</strong> (and Calendar API if you also use Calendars).</li>`,
53154
+ "<li>Under Credentials, create an OAuth client of type <strong>Web application</strong>.</li>",
53155
+ `<li>Add this authorized redirect URI (Network → <strong>Public endpoint</strong>, not Internal): <code>${redirect}</code></li>`,
53156
+ "<li>Paste Client ID and Client secret below, then <strong>Save</strong>.</li>",
53157
+ `<li>Open <a href="${OAUTH_START_PATH}">Connect Google</a> while signed in as admin (same browser tab as this hub).</li>`,
53158
+ "<li>Set From address (connected account or a verified send-as). Turn this integration off with the page <strong>Enable</strong> toggle when you do not want Gmail send — Calendar can stay connected.</li>",
53159
+ "</ol>"
53160
+ ].join("");
53161
+ if (cap === "calendar-source") return [
53162
+ "<ol>",
53163
+ `<li>Open Google Cloud Console (${console}) and enable the <strong>Google Calendar API</strong> (and Gmail API if you also use Email).</li>`,
53164
+ "<li>Under Credentials, create an OAuth client of type <strong>Web application</strong>.</li>",
53165
+ `<li>Add this authorized redirect URI (Network → <strong>Public endpoint</strong>, not Internal): <code>${redirect}</code></li>`,
53166
+ "<li>Paste Client ID and Client secret below, then <strong>Save</strong>.</li>",
53167
+ `<li>Open <a href="${OAUTH_START_PATH}">Connect Google</a> while signed in as admin (same browser tab as this hub).</li>`,
53168
+ "<li>Optionally restrict which calendars are exposed with the filter below.</li>",
53169
+ "</ol>"
53170
+ ].join("");
53171
+ return [
53172
+ "<ol>",
53173
+ `<li>Open Google Cloud Console (${console}) and enable <strong>Google Calendar API</strong> and <strong>Gmail API</strong>.</li>`,
53174
+ "<li>Create a <strong>Web application</strong> OAuth client under Credentials.</li>",
53175
+ `<li>Register redirect URI: <code>${redirect}</code> (Network → Public endpoint).</li>`,
53176
+ `<li>Paste Client ID / secret, Save, then <a href="${OAUTH_START_PATH}">Connect Google</a>.</li>`,
53177
+ "<li>Calendars and Email share one Google account; use each page’s <strong>Enable</strong> toggle to turn a capability off without revoking OAuth.</li>",
53178
+ "</ol>"
53179
+ ].join("");
53180
+ }
53181
+ function connectHtml(cap, publicOrigin) {
53182
+ const redirect = escapeHtml$1(redirectUriDisplay(publicOrigin));
53183
+ if (cap === "smtp-provider") return [
53184
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
53185
+ "Connect requests <code>gmail.send</code> (and Calendar read for the Calendars page). ",
53186
+ `Google must list exactly: <code>${redirect}</code>. `,
53187
+ "If an older Connect predates Gmail send, revoke CamStack in Google Account → Security and Connect again. ",
53188
+ `Console: ${consoleLinksHtml()}.`
53189
+ ].join("");
53190
+ if (cap === "calendar-source") return [
53191
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a> in this hub (admin session required). `,
53192
+ "Connect requests <code>calendar.readonly</code> (and Gmail send for the Email page). ",
53193
+ `Google must list exactly: <code>${redirect}</code>. `,
53194
+ `Console: ${consoleLinksHtml()}.`
53195
+ ].join("");
53196
+ return [
53197
+ `After Save, open <a href="${OAUTH_START_PATH}">Connect Google</a>. `,
53198
+ `Redirect URI registered in Google Cloud must be <code>${redirect}</code>. `,
53199
+ `Console: ${consoleLinksHtml()}.`
53200
+ ].join("");
53201
+ }
53202
+ function escapeHtml$1(s) {
53203
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53204
+ }
53205
+ //#endregion
53137
53206
  //#region src/oauth-redirect-origin.ts
53138
53207
  /**
53139
53208
  * Google OAuth redirect origin — same role as hub OAuth2 / sign-in
@@ -53163,12 +53232,15 @@ var GoogleServicesAddon = class extends BaseAddon {
53163
53232
  oauth = { ...DEFAULT_OAUTH_STATE };
53164
53233
  /** CSRF state → issuedAt ms */
53165
53234
  pendingStates = /* @__PURE__ */ new Map();
53235
+ /** Cached Public-endpoint origin for settings copy (redirect URI display). */
53236
+ publicOrigin = null;
53166
53237
  constructor() {
53167
53238
  super({ ...DEFAULT_GOOGLE_CONFIG });
53168
53239
  }
53169
53240
  async onInitialize() {
53170
53241
  this.oauthHandle = this.state("oauth", OauthStateSchema, { ...DEFAULT_OAUTH_STATE });
53171
53242
  this.oauth = await this.oauthHandle.get();
53243
+ await this.refreshPublicOrigin();
53172
53244
  const api = createGoogleCalendarApi(async () => this.ensureAccessToken());
53173
53245
  const calendarProvider = createGoogleCalendarSource({
53174
53246
  logger: this.ctx.logger,
@@ -53199,7 +53271,7 @@ var GoogleServicesAddon = class extends BaseAddon {
53199
53271
  this.ctx.logger.info("Initialized — google calendar-source + gmail smtp", { meta: {
53200
53272
  connected: this.oauth.refreshToken.length > 0,
53201
53273
  accountEmail: this.oauth.accountEmail || void 0,
53202
- gmailSendEnabled: this.config.gmailSendEnabled
53274
+ publicOrigin: this.publicOrigin ?? void 0
53203
53275
  } });
53204
53276
  return { providers: [
53205
53277
  {
@@ -53316,9 +53388,17 @@ var GoogleServicesAddon = class extends BaseAddon {
53316
53388
  return new Set(raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0));
53317
53389
  }
53318
53390
  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}`;
53391
+ await this.refreshPublicOrigin();
53392
+ 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).");
53393
+ return `${this.publicOrigin.replace(/\/$/, "")}/addon/${ADDON_ID}${CALLBACK_PATH}`;
53394
+ }
53395
+ async refreshPublicOrigin() {
53396
+ try {
53397
+ const book = await this.ctx.api.localNetwork.getAddresses.query({});
53398
+ this.publicOrigin = pickOauthRedirectOrigin(book);
53399
+ } catch {
53400
+ this.publicOrigin = null;
53401
+ }
53322
53402
  }
53323
53403
  /**
53324
53404
  * Connectivity asks for settings scoped to the page capability, so Calendar
@@ -53326,23 +53406,25 @@ var GoogleServicesAddon = class extends BaseAddon {
53326
53406
  * mqtt-broker under `broker`). The addon page (no cap) keeps every section.
53327
53407
  */
53328
53408
  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() {
53409
+ const scoped = asSettingsCap(cap);
53341
53410
  return this.schema({ sections: [
53411
+ {
53412
+ id: "setup-intro",
53413
+ title: "Setup",
53414
+ 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.",
53415
+ columns: 1,
53416
+ fields: [{
53417
+ type: "info",
53418
+ key: "_setupSteps",
53419
+ label: "Steps",
53420
+ format: "html",
53421
+ content: introHtml(scoped, this.publicOrigin)
53422
+ }]
53423
+ },
53342
53424
  {
53343
53425
  id: "google-oauth",
53344
53426
  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).",
53427
+ description: "OAuth client credentials (Web application) stored in the hub keychain.",
53346
53428
  columns: 1,
53347
53429
  fields: [
53348
53430
  {
@@ -53361,11 +53443,11 @@ var GoogleServicesAddon = class extends BaseAddon {
53361
53443
  key: "_connectInfo",
53362
53444
  label: "Connect",
53363
53445
  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>.`
53446
+ content: connectHtml(scoped, this.publicOrigin)
53365
53447
  }
53366
53448
  ]
53367
53449
  },
53368
- {
53450
+ ...scoped === "smtp-provider" ? [] : [{
53369
53451
  id: "google-calendar",
53370
53452
  title: "Google Calendar",
53371
53453
  description: "Which calendars this hub exposes as calendar-source entries.",
@@ -53376,37 +53458,35 @@ var GoogleServicesAddon = class extends BaseAddon {
53376
53458
  label: "Calendar id filter (optional)",
53377
53459
  description: "Comma-separated Google calendar ids to expose. Empty = all calendars the account can read."
53378
53460
  }]
53379
- },
53380
- {
53461
+ }],
53462
+ ...scoped === "calendar-source" ? [] : [{
53381
53463
  id: "gmail-send",
53382
53464
  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.",
53465
+ 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
53466
  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
- }
53467
+ fields: [{
53468
+ type: "text",
53469
+ key: "fromAddress",
53470
+ label: "From address",
53471
+ description: "Defaults to the connected Google account email when empty."
53472
+ }, {
53473
+ type: "text",
53474
+ key: "fromName",
53475
+ label: "From display name (optional)"
53476
+ }]
53477
+ }]
53405
53478
  ] });
53406
53479
  }
53480
+ async getGlobalSettings(overlay, cap, nodeId) {
53481
+ await this.refreshPublicOrigin();
53482
+ return super.getGlobalSettings(overlay, cap, nodeId);
53483
+ }
53407
53484
  };
53485
+ function asSettingsCap(cap) {
53486
+ if (cap === "calendar-source" || cap === "smtp-provider") return cap;
53487
+ }
53408
53488
  function escapeHtml(s) {
53409
53489
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53410
53490
  }
53411
53491
  //#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 };
53492
+ export { GoogleServicesAddon, GoogleServicesAddon as default, GOOGLE_GMAIL_SEND_SCOPE as _, redirectUriDisplay as a, OAUTH_START_PATH as b, exchangeCode as c, buildRawMime as d, createGmailSmtpProvider as f, DEFAULT_GOOGLE_CONFIG as g, mapGoogleEvent as h, introHtml as i, needsRefresh as l, createGoogleCalendarSource as m, GOOGLE_CONSOLE_CREDENTIALS_URL as n, applyTokenResponse as o, createGoogleCalendarApi as p, connectHtml as r, buildAuthorizeUrl as s, GOOGLE_CONSOLE_APIS_URL as t, refreshAccessToken as u, GOOGLE_OAUTH_SCOPES as v, GoogleServicesConfigSchema as y };
package/dist/index.js CHANGED
@@ -4,18 +4,24 @@ 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_CONSOLE_APIS_URL = require_google_services_addon.GOOGLE_CONSOLE_APIS_URL;
8
+ exports.GOOGLE_CONSOLE_CREDENTIALS_URL = require_google_services_addon.GOOGLE_CONSOLE_CREDENTIALS_URL;
7
9
  exports.GOOGLE_GMAIL_SEND_SCOPE = require_google_services_addon.GOOGLE_GMAIL_SEND_SCOPE;
8
10
  exports.GOOGLE_OAUTH_SCOPES = require_google_services_addon.GOOGLE_OAUTH_SCOPES;
9
11
  exports.GoogleServicesAddon = require_google_services_addon.GoogleServicesAddon;
10
12
  exports.GoogleServicesConfigSchema = require_google_services_addon.GoogleServicesConfigSchema;
13
+ exports.OAUTH_START_PATH = require_google_services_addon.OAUTH_START_PATH;
11
14
  exports.applyTokenResponse = require_google_services_addon.applyTokenResponse;
12
15
  exports.buildAuthorizeUrl = require_google_services_addon.buildAuthorizeUrl;
13
16
  exports.buildRawMime = require_google_services_addon.buildRawMime;
17
+ exports.connectHtml = require_google_services_addon.connectHtml;
14
18
  exports.createGmailSmtpProvider = require_google_services_addon.createGmailSmtpProvider;
15
19
  exports.createGoogleCalendarApi = require_google_services_addon.createGoogleCalendarApi;
16
20
  exports.createGoogleCalendarSource = require_google_services_addon.createGoogleCalendarSource;
17
21
  exports.default = require_google_services_addon.GoogleServicesAddon;
18
22
  exports.exchangeCode = require_google_services_addon.exchangeCode;
23
+ exports.introHtml = require_google_services_addon.introHtml;
19
24
  exports.mapGoogleEvent = require_google_services_addon.mapGoogleEvent;
20
25
  exports.needsRefresh = require_google_services_addon.needsRefresh;
26
+ exports.redirectUriDisplay = require_google_services_addon.redirectUriDisplay;
21
27
  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 GOOGLE_GMAIL_SEND_SCOPE, a as redirectUriDisplay, b as OAUTH_START_PATH, c as exchangeCode, d as buildRawMime, f as createGmailSmtpProvider, g as DEFAULT_GOOGLE_CONFIG, h as mapGoogleEvent, i as introHtml, l as needsRefresh, m as createGoogleCalendarSource, n as GOOGLE_CONSOLE_CREDENTIALS_URL, o as applyTokenResponse, p as createGoogleCalendarApi, r as connectHtml, s as buildAuthorizeUrl, t as GOOGLE_CONSOLE_APIS_URL, u as refreshAccessToken, v as GOOGLE_OAUTH_SCOPES, y as GoogleServicesConfigSchema } from "./google-services.addon.mjs";
2
+ export { DEFAULT_GOOGLE_CONFIG, GOOGLE_CONSOLE_APIS_URL, GOOGLE_CONSOLE_CREDENTIALS_URL, 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.6",
4
4
  "description": "Google Services — Google Calendar (calendar-source) and Gmail send (smtp-provider) via one OAuth2 account.",
5
5
  "keywords": [
6
6
  "camstack",