@open-mercato/channel-gmail 0.6.5-canary.4394.1.2cdbd10737

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.
Files changed (79) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/AGENTS.md +47 -0
  3. package/build.mjs +7 -0
  4. package/dist/index.js +5 -0
  5. package/dist/index.js.map +7 -0
  6. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-006.spec.js +17 -0
  7. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-006.spec.js.map +7 -0
  8. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-007.spec.js +16 -0
  9. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-007.spec.js.map +7 -0
  10. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-008.spec.js +16 -0
  11. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-008.spec.js.map +7 -0
  12. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-A01-token-refresh.spec.js +17 -0
  13. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-A01-token-refresh.spec.js.map +7 -0
  14. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-C01.spec.js +26 -0
  15. package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-C01.spec.js.map +7 -0
  16. package/dist/modules/channel_gmail/acl.js +10 -0
  17. package/dist/modules/channel_gmail/acl.js.map +7 -0
  18. package/dist/modules/channel_gmail/di.js +23 -0
  19. package/dist/modules/channel_gmail/di.js.map +7 -0
  20. package/dist/modules/channel_gmail/index.js +9 -0
  21. package/dist/modules/channel_gmail/index.js.map +7 -0
  22. package/dist/modules/channel_gmail/integration.js +69 -0
  23. package/dist/modules/channel_gmail/integration.js.map +7 -0
  24. package/dist/modules/channel_gmail/lib/adapter.js +542 -0
  25. package/dist/modules/channel_gmail/lib/adapter.js.map +7 -0
  26. package/dist/modules/channel_gmail/lib/capabilities.js +10 -0
  27. package/dist/modules/channel_gmail/lib/capabilities.js.map +7 -0
  28. package/dist/modules/channel_gmail/lib/convert-outbound.js +84 -0
  29. package/dist/modules/channel_gmail/lib/convert-outbound.js.map +7 -0
  30. package/dist/modules/channel_gmail/lib/credentials.js +48 -0
  31. package/dist/modules/channel_gmail/lib/credentials.js.map +7 -0
  32. package/dist/modules/channel_gmail/lib/gmail-client.js +160 -0
  33. package/dist/modules/channel_gmail/lib/gmail-client.js.map +7 -0
  34. package/dist/modules/channel_gmail/lib/health.js +10 -0
  35. package/dist/modules/channel_gmail/lib/health.js.map +7 -0
  36. package/dist/modules/channel_gmail/lib/normalize-inbound.js +28 -0
  37. package/dist/modules/channel_gmail/lib/normalize-inbound.js.map +7 -0
  38. package/dist/modules/channel_gmail/lib/oauth.js +77 -0
  39. package/dist/modules/channel_gmail/lib/oauth.js.map +7 -0
  40. package/dist/modules/channel_gmail/setup.js +25 -0
  41. package/dist/modules/channel_gmail/setup.js.map +7 -0
  42. package/dist/modules/channel_gmail/widgets/injection/connect/widget.client.js +24 -0
  43. package/dist/modules/channel_gmail/widgets/injection/connect/widget.client.js.map +7 -0
  44. package/dist/modules/channel_gmail/widgets/injection/connect/widget.js +17 -0
  45. package/dist/modules/channel_gmail/widgets/injection/connect/widget.js.map +7 -0
  46. package/dist/modules/channel_gmail/widgets/injection-table.js +14 -0
  47. package/dist/modules/channel_gmail/widgets/injection-table.js.map +7 -0
  48. package/jest.config.cjs +34 -0
  49. package/package.json +96 -0
  50. package/src/index.ts +1 -0
  51. package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-006.spec.ts +24 -0
  52. package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-007.spec.ts +23 -0
  53. package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-008.spec.ts +23 -0
  54. package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-A01-token-refresh.spec.ts +39 -0
  55. package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-C01.spec.ts +48 -0
  56. package/src/modules/channel_gmail/acl.ts +6 -0
  57. package/src/modules/channel_gmail/di.ts +21 -0
  58. package/src/modules/channel_gmail/index.ts +6 -0
  59. package/src/modules/channel_gmail/integration.ts +67 -0
  60. package/src/modules/channel_gmail/lib/__tests__/adapter.test.ts +838 -0
  61. package/src/modules/channel_gmail/lib/__tests__/convert-outbound.test.ts +128 -0
  62. package/src/modules/channel_gmail/lib/__tests__/credentials.test.ts +76 -0
  63. package/src/modules/channel_gmail/lib/__tests__/gmail-client.test.ts +209 -0
  64. package/src/modules/channel_gmail/lib/__tests__/normalize-inbound.test.ts +106 -0
  65. package/src/modules/channel_gmail/lib/__tests__/oauth.test.ts +148 -0
  66. package/src/modules/channel_gmail/lib/adapter.ts +734 -0
  67. package/src/modules/channel_gmail/lib/capabilities.ts +22 -0
  68. package/src/modules/channel_gmail/lib/convert-outbound.ts +136 -0
  69. package/src/modules/channel_gmail/lib/credentials.ts +90 -0
  70. package/src/modules/channel_gmail/lib/gmail-client.ts +305 -0
  71. package/src/modules/channel_gmail/lib/health.ts +14 -0
  72. package/src/modules/channel_gmail/lib/normalize-inbound.ts +57 -0
  73. package/src/modules/channel_gmail/lib/oauth.ts +128 -0
  74. package/src/modules/channel_gmail/setup.ts +36 -0
  75. package/src/modules/channel_gmail/widgets/injection/connect/widget.client.tsx +28 -0
  76. package/src/modules/channel_gmail/widgets/injection/connect/widget.ts +16 -0
  77. package/src/modules/channel_gmail/widgets/injection-table.ts +12 -0
  78. package/tsconfig.json +9 -0
  79. package/watch.mjs +7 -0
@@ -0,0 +1,2 @@
1
+ [build:channel-gmail] found 22 entry points
2
+ [build:channel-gmail] built successfully
package/AGENTS.md ADDED
@@ -0,0 +1,47 @@
1
+ # `@open-mercato/channel-gmail` — Agent Guidelines
2
+
3
+ Gmail email channel provider for the Communications Hub (`communication_channels`). Connects per-user Gmail accounts via OAuth2. Outbound uses `gmail.users.messages.send`; inbound uses the History API (polling by default, optional Pub/Sub push).
4
+
5
+ - **Package**: `@open-mercato/channel-gmail` ⇒ **module id**: `channel_gmail`
6
+ - **Provider key**: `gmail` (registered in the hub's channel adapter registry)
7
+ - This is an integration provider package — keep all Gmail-specific logic here. Do NOT add it to `packages/core`.
8
+
9
+ ## Key Files (`src/modules/channel_gmail/`)
10
+
11
+ | File | Purpose |
12
+ |------|---------|
13
+ | `integration.ts` | `IntegrationDefinition` (credentials fields, `healthCheck.service`, detail widget spot) |
14
+ | `di.ts` | `register(container)` — registers the adapter AND `channelGmailHealthCheck` under the exact `healthCheck.service` name |
15
+ | `setup.ts` | Registers the adapter at import time; declares `defaultRoleFeatures` |
16
+ | `acl.ts` | `channel_gmail.view`, `channel_gmail.configure` |
17
+ | `lib/adapter.ts` | `GmailChannelAdapter` — implements the `ChannelAdapter` contract |
18
+ | `lib/credentials.ts` | Zod schemas: OAuth client config + per-user tokens + channel sync state |
19
+ | `lib/oauth.ts` | `GoogleOAuthClient` (authorize URL, code exchange, refresh, userinfo) |
20
+ | `lib/gmail-client.ts` | `GmailApiClient` (history.list, messages.list/get/send, watch/stop) |
21
+ | `lib/health.ts` | `channelGmailHealthCheck` liveness probe |
22
+ | `lib/convert-outbound.ts` / `lib/normalize-inbound.ts` | RFC2822 outbound build / inbound MIME normalization |
23
+ | `lib/capabilities.ts` | `ChannelCapabilities` (`realtimePush: false`, `deleteMessage: true`) |
24
+
25
+ ## Adapter Contract
26
+
27
+ `lib/adapter.ts` implements `ChannelAdapter` from `@open-mercato/core/modules/communication_channels/lib/adapter`. Key methods: `sendMessage`, `normalizeInbound`, `convertOutbound`, `buildOAuthAuthorizeUrl`, `exchangeOAuthCode`, `refreshCredentials`, `fetchHistory`, `registerPush`/`unregisterPush`/`applyPushNotification`, `deleteMessage`, `resolveContact`.
28
+
29
+ - Tenant OAuth client config (`{ clientId, clientSecret, scopes? }`) lives on `IntegrationCredentials` for provider `gmail`; per-user tokens live on `CommunicationChannel.credentials`.
30
+ - `fetchHistory` is cursor-driven via `channelState.historyId`. The terminal `historyId` MUST only advance over messages actually normalized; a transient (non-404/410) fetch failure pins the cursor and re-fetches next tick (see the L3 fix in `fetchAndNormalize`).
31
+ - The clients are swappable via `setGmailApiClient` / `setGoogleOAuthClient` (test-only hooks).
32
+
33
+ ## Health Check
34
+
35
+ `lib/health.ts` exports `channelGmailHealthCheck`, registered in `di.ts` under the name declared in `integration.ts` (`healthCheck.service`). The hub passes the tenant-scoped OAuth client config (no access token at this layer), so the probe validates the client config against `gmailClientCredentialsSchema` rather than calling the API. Missing/invalid config ⇒ `unhealthy`.
36
+
37
+ ## Env Vars
38
+
39
+ | Var | Purpose |
40
+ |-----|---------|
41
+ | `OM_GMAIL_PUBSUB_TOPIC` | Fully-qualified Pub/Sub topic for `registerPush` (optional; polling works without it) |
42
+
43
+ ## After Changes
44
+
45
+ - Run `yarn generate` after adding/modifying module files (DI, setup, acl, integration).
46
+ - Run unit tests: `yarn test` (jest specs live under `lib/__tests__/`).
47
+ - If you change the `healthCheck.service` name, update both `integration.ts` AND `di.ts` so the hub can resolve it.
package/build.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import { dirname } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { buildPackage } from '../../scripts/build-package.mjs'
4
+
5
+ const packageDir = dirname(fileURLToPath(import.meta.url))
6
+
7
+ await buildPackage(packageDir, { name: 'channel-gmail' })
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { metadata } from "./modules/channel_gmail/index.js";
2
+ export {
3
+ metadata
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["export { metadata } from './modules/channel_gmail/index'\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { getAuthToken } from "@open-mercato/core/helpers/integration/authFixtures";
3
+ import { apiRequest } from "@open-mercato/core/helpers/integration/api";
4
+ test.describe("TC-CHANNEL-EMAIL-006: Gmail OAuth router wiring", () => {
5
+ test("POST /oauth/gmail/initiate does not 404 on the provider", async ({ request }) => {
6
+ const token = await getAuthToken(request);
7
+ const response = await apiRequest(
8
+ request,
9
+ "POST",
10
+ "/api/communication_channels/oauth/gmail/initiate",
11
+ { token, data: { redirectUri: "https://example.com/cb" } }
12
+ );
13
+ expect(response.status(), "route should not 5xx").toBeLessThan(500);
14
+ expect(response.status(), "Gmail provider should be registered").not.toBe(404);
15
+ });
16
+ });
17
+ //# sourceMappingURL=TC-CHANNEL-EMAIL-006.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-006.spec.ts"],
4
+ "sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/helpers/integration/authFixtures'\nimport { apiRequest } from '@open-mercato/core/helpers/integration/api'\n\n/**\n * TC-CHANNEL-EMAIL-006 \u2014 Gmail provider visible to the hub's OAuth router.\n *\n * After slice 3f registers the `gmail` adapter, the OAuth initiate route must\n * accept `provider=gmail`. Without a real Google client_id we exercise routing\n * only \u2014 the adapter is reachable when the route does NOT 404.\n */\ntest.describe('TC-CHANNEL-EMAIL-006: Gmail OAuth router wiring', () => {\n test('POST /oauth/gmail/initiate does not 404 on the provider', async ({ request }) => {\n const token = await getAuthToken(request)\n const response = await apiRequest(\n request,\n 'POST',\n '/api/communication_channels/oauth/gmail/initiate',\n { token, data: { redirectUri: 'https://example.com/cb' } },\n )\n expect(response.status(), 'route should not 5xx').toBeLessThan(500)\n expect(response.status(), 'Gmail provider should be registered').not.toBe(404)\n })\n})\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAS3B,KAAK,SAAS,mDAAmD,MAAM;AACrE,OAAK,2DAA2D,OAAO,EAAE,QAAQ,MAAM;AACrF,UAAM,QAAQ,MAAM,aAAa,OAAO;AACxC,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,OAAO,MAAM,EAAE,aAAa,yBAAyB,EAAE;AAAA,IAC3D;AACA,WAAO,SAAS,OAAO,GAAG,sBAAsB,EAAE,aAAa,GAAG;AAClE,WAAO,SAAS,OAAO,GAAG,qCAAqC,EAAE,IAAI,KAAK,GAAG;AAAA,EAC/E,CAAC;AACH,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { getAuthToken } from "@open-mercato/core/helpers/integration/authFixtures";
3
+ import { apiRequest } from "@open-mercato/core/helpers/integration/api";
4
+ test.describe("TC-CHANNEL-EMAIL-007: Gmail webhook is a no-op for now", () => {
5
+ test("POST /api/communication_channels/webhook/gmail does not 5xx", async ({ request }) => {
6
+ const token = await getAuthToken(request);
7
+ const response = await apiRequest(
8
+ request,
9
+ "POST",
10
+ "/api/communication_channels/webhook/gmail",
11
+ { token, data: { ping: true } }
12
+ );
13
+ expect(response.status()).toBeLessThan(500);
14
+ });
15
+ });
16
+ //# sourceMappingURL=TC-CHANNEL-EMAIL-007.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-007.spec.ts"],
4
+ "sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/helpers/integration/authFixtures'\nimport { apiRequest } from '@open-mercato/core/helpers/integration/api'\n\n/**\n * TC-CHANNEL-EMAIL-007 \u2014 Gmail webhook stays a no-op (Pub/Sub deferred to v2).\n *\n * The provider-generic webhook route exists at `/api/communication_channels/webhook/gmail`.\n * Until Pub/Sub push is implemented, the adapter returns `eventType: 'other'` and the\n * route must respond 2xx rather than 5xx-ing or 404-ing.\n */\ntest.describe('TC-CHANNEL-EMAIL-007: Gmail webhook is a no-op for now', () => {\n test('POST /api/communication_channels/webhook/gmail does not 5xx', async ({ request }) => {\n const token = await getAuthToken(request)\n const response = await apiRequest(\n request,\n 'POST',\n '/api/communication_channels/webhook/gmail',\n { token, data: { ping: true } },\n )\n expect(response.status()).toBeLessThan(500)\n })\n})\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAS3B,KAAK,SAAS,0DAA0D,MAAM;AAC5E,OAAK,+DAA+D,OAAO,EAAE,QAAQ,MAAM;AACzF,UAAM,QAAQ,MAAM,aAAa,OAAO;AACxC,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,OAAO,MAAM,EAAE,MAAM,KAAK,EAAE;AAAA,IAChC;AACA,WAAO,SAAS,OAAO,CAAC,EAAE,aAAa,GAAG;AAAA,EAC5C,CAAC;AACH,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { getAuthToken } from "@open-mercato/core/helpers/integration/authFixtures";
3
+ import { apiRequest } from "@open-mercato/core/helpers/integration/api";
4
+ test.describe("TC-CHANNEL-EMAIL-008: profile page with Gmail provider installed", () => {
5
+ test("GET /backend/profile/communication-channels does not 5xx", async ({ request }) => {
6
+ const token = await getAuthToken(request);
7
+ const response = await apiRequest(
8
+ request,
9
+ "GET",
10
+ "/backend/profile/communication-channels",
11
+ { token }
12
+ );
13
+ expect(response.status(), "profile page should not 5xx").toBeLessThan(500);
14
+ });
15
+ });
16
+ //# sourceMappingURL=TC-CHANNEL-EMAIL-008.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-008.spec.ts"],
4
+ "sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/helpers/integration/authFixtures'\nimport { apiRequest } from '@open-mercato/core/helpers/integration/api'\n\n/**\n * TC-CHANNEL-EMAIL-008 \u2014 Profile page renders with both IMAP and Gmail providers installed.\n *\n * Confirms `@open-mercato/channel-gmail` does not break the per-user profile page\n * (`/backend/profile/communication-channels`) \u2014 the page must still render even\n * when more than one provider package is installed.\n */\ntest.describe('TC-CHANNEL-EMAIL-008: profile page with Gmail provider installed', () => {\n test('GET /backend/profile/communication-channels does not 5xx', async ({ request }) => {\n const token = await getAuthToken(request)\n const response = await apiRequest(\n request,\n 'GET',\n '/backend/profile/communication-channels',\n { token },\n )\n expect(response.status(), 'profile page should not 5xx').toBeLessThan(500)\n })\n})\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAS3B,KAAK,SAAS,oEAAoE,MAAM;AACtF,OAAK,4DAA4D,OAAO,EAAE,QAAQ,MAAM;AACtF,UAAM,QAAQ,MAAM,aAAa,OAAO;AACxC,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,MAAM;AAAA,IACV;AACA,WAAO,SAAS,OAAO,GAAG,6BAA6B,EAAE,aAAa,GAAG;AAAA,EAC3E,CAAC;AACH,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { getAuthToken } from "@open-mercato/core/helpers/integration/authFixtures";
3
+ import { apiRequest } from "@open-mercato/core/helpers/integration/api";
4
+ test.describe("TC-CHANNEL-EMAIL-A01: Gmail token refresh wiring", () => {
5
+ test("OAuth initiate route for gmail is registered (refresh contract is reachable)", async ({ request }) => {
6
+ const token = await getAuthToken(request);
7
+ const response = await apiRequest(
8
+ request,
9
+ "POST",
10
+ "/api/communication_channels/oauth/gmail/initiate",
11
+ { token, data: { redirectUri: "https://example.com/cb" } }
12
+ );
13
+ expect(response.status(), "route should not 5xx").toBeLessThan(500);
14
+ expect(response.status(), "Gmail provider should be registered").not.toBe(404);
15
+ });
16
+ });
17
+ //# sourceMappingURL=TC-CHANNEL-EMAIL-A01-token-refresh.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-A01-token-refresh.spec.ts"],
4
+ "sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/helpers/integration/authFixtures'\nimport { apiRequest } from '@open-mercato/core/helpers/integration/api'\n\n/**\n * TC-CHANNEL-EMAIL-A01 \u2014 Gmail token refresh wiring (Spec A).\n *\n * Verifies that the OAuth refresh contract surface exists end-to-end:\n * - The /me/channels route is reachable for an authenticated user.\n * - The OAuth initiate route accepts `provider=gmail` (means the Gmail\n * adapter is registered and discoverable).\n *\n * The full token-refresh roundtrip requires a real connected Gmail\n * mailbox with stored OAuth client config in `integration_credentials`\n * for `oauth_gmail`. That manual verification is captured in\n * `.ai/qa/scenarios/TC-CHANNEL-EMAIL-A01-gmail-token-refresh.md`.\n *\n * The wiring itself (resolution of `oauth_gmail` -> `oauthClient` field\n * on `RefreshCredentialsInput`) is thoroughly covered by unit tests:\n * - packages/core/src/modules/communication_channels/lib/__tests__/credential-refresh.test.ts\n * - packages/channel-gmail/src/modules/channel_gmail/lib/__tests__/adapter.test.ts\n */\ntest.describe('TC-CHANNEL-EMAIL-A01: Gmail token refresh wiring', () => {\n test('OAuth initiate route for gmail is registered (refresh contract is reachable)', async ({ request }) => {\n const token = await getAuthToken(request)\n const response = await apiRequest(\n request,\n 'POST',\n '/api/communication_channels/oauth/gmail/initiate',\n { token, data: { redirectUri: 'https://example.com/cb' } },\n )\n // Smoke test only: the Gmail adapter being registered is necessary for the\n // refresh path to ever fire. A 4xx (missing oauth_gmail row) or 2xx\n // (initiate succeeds) both prove the adapter is wired. A 404/5xx would\n // indicate the route or provider is gone \u2014 block-level regression.\n expect(response.status(), 'route should not 5xx').toBeLessThan(500)\n expect(response.status(), 'Gmail provider should be registered').not.toBe(404)\n })\n})\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAoB3B,KAAK,SAAS,oDAAoD,MAAM;AACtE,OAAK,gFAAgF,OAAO,EAAE,QAAQ,MAAM;AAC1G,UAAM,QAAQ,MAAM,aAAa,OAAO;AACxC,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,OAAO,MAAM,EAAE,aAAa,yBAAyB,EAAE;AAAA,IAC3D;AAKA,WAAO,SAAS,OAAO,GAAG,sBAAsB,EAAE,aAAa,GAAG;AAClE,WAAO,SAAS,OAAO,GAAG,qCAAqC,EAAE,IAAI,KAAK,GAAG;AAAA,EAC/E,CAAC;AACH,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,26 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { apiRequest } from "@open-mercato/core/helpers/integration/api";
3
+ test.describe("TC-CHANNEL-EMAIL-C01: Gmail Pub/Sub webhook", () => {
4
+ test("rejects requests missing the bearer JWT", async ({ request }) => {
5
+ const response = await apiRequest(
6
+ request,
7
+ "POST",
8
+ "/api/communication_channels/webhooks/gmail",
9
+ // Webhook auth is the Pub/Sub JWT in `Authorization`, not the platform
10
+ // session token. `apiRequest` requires a token field; empty is fine —
11
+ // the route ignores Authorization unless the JWT validator can decode it.
12
+ { token: "", data: { message: { data: "", messageId: "m1" } } }
13
+ );
14
+ expect([401, 503]).toContain(response.status());
15
+ });
16
+ test("rejects malformed JSON body when JWT is absent", async ({ request }) => {
17
+ const response = await apiRequest(
18
+ request,
19
+ "POST",
20
+ "/api/communication_channels/webhooks/gmail",
21
+ { token: "", data: "not-json" }
22
+ );
23
+ expect([401, 503]).toContain(response.status());
24
+ });
25
+ });
26
+ //# sourceMappingURL=TC-CHANNEL-EMAIL-C01.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-C01.spec.ts"],
4
+ "sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { apiRequest } from '@open-mercato/core/helpers/integration/api'\n\n/**\n * TC-CHANNEL-EMAIL-C01 \u2014 Gmail Pub/Sub webhook authentication\n *\n * Verifies that `POST /api/communication_channels/webhooks/gmail` rejects\n * unauthenticated / unsigned requests. The full happy-path (publish to\n * Pub/Sub \u2192 webhook fires \u2192 history sync \u2192 message appears in CRM) is the\n * manual QA scenario `TC-CHANNEL-EMAIL-C01-gmail-push-delivery.md` since it\n * requires a real GCP project and connected Gmail mailbox.\n *\n * Unit-level coverage of the JWT verifier + envelope decoder lives in\n * `packages/core/src/modules/communication_channels/lib/__tests__/gmail-pubsub-jwt.test.ts`.\n */\ntest.describe('TC-CHANNEL-EMAIL-C01: Gmail Pub/Sub webhook', () => {\n test('rejects requests missing the bearer JWT', async ({ request }) => {\n const response = await apiRequest(\n request,\n 'POST',\n '/api/communication_channels/webhooks/gmail',\n // Webhook auth is the Pub/Sub JWT in `Authorization`, not the platform\n // session token. `apiRequest` requires a token field; empty is fine \u2014\n // the route ignores Authorization unless the JWT validator can decode it.\n { token: '', data: { message: { data: '', messageId: 'm1' } } },\n )\n // 401 when expectedAudience/expectedEmail are configured;\n // 503 when env vars aren't set (acceptable for a smoke test against an\n // unconfigured CI environment).\n expect([401, 503]).toContain(response.status())\n })\n\n test('rejects malformed JSON body when JWT is absent', async ({ request }) => {\n const response = await apiRequest(\n request,\n 'POST',\n '/api/communication_channels/webhooks/gmail',\n { token: '', data: 'not-json' as unknown as Record<string, unknown> },\n )\n // A request without a Pub/Sub JWT is rejected at the config/verification gate\n // BEFORE the body is parsed, so the malformed body never reaches the decoder:\n // 503 when the verifier env vars aren't set (the unconfigured CI case \u2014 same as\n // the sibling test above) and 401 when they are. The body-decode 400 path is\n // covered by the gmail-pubsub-jwt unit tests; here we only assert a controlled\n // rejection, never an uncontrolled 5xx crash.\n expect([401, 503]).toContain(response.status())\n })\n})\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,kBAAkB;AAc3B,KAAK,SAAS,+CAA+C,MAAM;AACjE,OAAK,2CAA2C,OAAO,EAAE,QAAQ,MAAM;AACrE,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA;AAAA;AAAA;AAAA,MAIA,EAAE,OAAO,IAAI,MAAM,EAAE,SAAS,EAAE,MAAM,IAAI,WAAW,KAAK,EAAE,EAAE;AAAA,IAChE;AAIA,WAAO,CAAC,KAAK,GAAG,CAAC,EAAE,UAAU,SAAS,OAAO,CAAC;AAAA,EAChD,CAAC;AAED,OAAK,kDAAkD,OAAO,EAAE,QAAQ,MAAM;AAC5E,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,OAAO,IAAI,MAAM,WAAiD;AAAA,IACtE;AAOA,WAAO,CAAC,KAAK,GAAG,CAAC,EAAE,UAAU,SAAS,OAAO,CAAC;AAAA,EAChD,CAAC;AACH,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,10 @@
1
+ const features = [
2
+ { id: "channel_gmail.view", title: "View Gmail channel configuration", module: "channel_gmail" },
3
+ { id: "channel_gmail.configure", title: "Configure Gmail OAuth defaults", module: "channel_gmail" }
4
+ ];
5
+ var acl_default = features;
6
+ export {
7
+ acl_default as default,
8
+ features
9
+ };
10
+ //# sourceMappingURL=acl.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_gmail/acl.ts"],
4
+ "sourcesContent": ["export const features = [\n { id: 'channel_gmail.view', title: 'View Gmail channel configuration', module: 'channel_gmail' },\n { id: 'channel_gmail.configure', title: 'Configure Gmail OAuth defaults', module: 'channel_gmail' },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,sBAAsB,OAAO,oCAAoC,QAAQ,gBAAgB;AAAA,EAC/F,EAAE,IAAI,2BAA2B,OAAO,kCAAkC,QAAQ,gBAAgB;AACpG;AAEA,IAAO,cAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ import { asValue } from "awilix";
2
+ import {
3
+ hasChannelAdapter,
4
+ registerChannelAdapter
5
+ } from "@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton";
6
+ import { getGmailChannelAdapter } from "./lib/adapter.js";
7
+ import { channelGmailHealthCheck } from "./lib/health.js";
8
+ function register(container) {
9
+ if (!hasChannelAdapter("gmail")) {
10
+ registerChannelAdapter(getGmailChannelAdapter());
11
+ }
12
+ container.register({
13
+ channelGmailAdapter: asValue(getGmailChannelAdapter()),
14
+ // Registered under the exact service name declared in `integration.ts`
15
+ // (`healthCheck.service`). Without this, the hub's `container.resolve(...)`
16
+ // throws and the channel reports permanently 'unhealthy'.
17
+ channelGmailHealthCheck: asValue(channelGmailHealthCheck)
18
+ });
19
+ }
20
+ export {
21
+ register
22
+ };
23
+ //# sourceMappingURL=di.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_gmail/di.ts"],
4
+ "sourcesContent": ["import { asValue } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n hasChannelAdapter,\n registerChannelAdapter,\n} from '@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton'\nimport { getGmailChannelAdapter } from './lib/adapter'\nimport { channelGmailHealthCheck } from './lib/health'\n\nexport function register(container: AppContainer): void {\n if (!hasChannelAdapter('gmail')) {\n registerChannelAdapter(getGmailChannelAdapter())\n }\n container.register({\n channelGmailAdapter: asValue(getGmailChannelAdapter()),\n // Registered under the exact service name declared in `integration.ts`\n // (`healthCheck.service`). Without this, the hub's `container.resolve(...)`\n // throws and the channel reports permanently 'unhealthy'.\n channelGmailHealthCheck: asValue(channelGmailHealthCheck),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,eAAe;AAExB;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AAEjC,SAAS,SAAS,WAA+B;AACtD,MAAI,CAAC,kBAAkB,OAAO,GAAG;AAC/B,2BAAuB,uBAAuB,CAAC;AAAA,EACjD;AACA,YAAU,SAAS;AAAA,IACjB,qBAAqB,QAAQ,uBAAuB,CAAC;AAAA;AAAA;AAAA;AAAA,IAIrD,yBAAyB,QAAQ,uBAAuB;AAAA,EAC1D,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,9 @@
1
+ const metadata = {
2
+ id: "channel_gmail",
3
+ title: "Gmail Email Channel",
4
+ description: "Connect per-user Gmail accounts via OAuth2. Outbound via gmail.users.messages.send; inbound via History API polling. Pairs with the Communications Hub."
5
+ };
6
+ export {
7
+ metadata
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_gmail/index.ts"],
4
+ "sourcesContent": ["export const metadata = {\n id: 'channel_gmail',\n title: 'Gmail Email Channel',\n description:\n 'Connect per-user Gmail accounts via OAuth2. Outbound via gmail.users.messages.send; inbound via History API polling. Pairs with the Communications Hub.',\n}\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,aACE;AACJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,69 @@
1
+ import { buildIntegrationDetailWidgetSpotId } from "@open-mercato/shared/modules/integrations/types";
2
+ const channelGmailDetailWidgetSpotId = buildIntegrationDetailWidgetSpotId("channel_gmail");
3
+ const integration = {
4
+ id: "channel_gmail",
5
+ title: "Gmail",
6
+ description: "Connect per-user Gmail accounts via OAuth2. Outbound via gmail.users.messages.send; inbound via History API polling (5-min default).",
7
+ category: "communication",
8
+ hub: "communication_channels",
9
+ providerKey: "gmail",
10
+ icon: "gmail",
11
+ docsUrl: "https://developers.google.com/gmail/api",
12
+ package: "@open-mercato/channel-gmail",
13
+ version: "0.1.0",
14
+ author: "Open Mercato Team",
15
+ company: "Open Mercato",
16
+ license: "MIT",
17
+ tags: ["email", "gmail", "oauth2", "polling", "communication"],
18
+ detailPage: {
19
+ widgetSpotId: channelGmailDetailWidgetSpotId
20
+ },
21
+ apiVersions: [
22
+ {
23
+ id: "v1",
24
+ label: "Gmail API v1",
25
+ status: "stable",
26
+ default: true,
27
+ changelog: "Gmail API v1 with History API incremental sync and OAuth2."
28
+ }
29
+ ],
30
+ credentials: {
31
+ fields: [
32
+ {
33
+ key: "clientId",
34
+ label: "OAuth Client ID",
35
+ type: "text",
36
+ required: true,
37
+ placeholder: "1234567890-abcdef.apps.googleusercontent.com",
38
+ helpText: "Google Cloud Console -> APIs & Services -> Credentials -> OAuth 2.0 Client ID. Configure Authorized Redirect URI to <yourdomain>/api/communication_channels/oauth/gmail/callback."
39
+ },
40
+ {
41
+ key: "clientSecret",
42
+ label: "OAuth Client Secret",
43
+ type: "secret",
44
+ required: true,
45
+ helpText: "Paired with the Client ID above. Stored encrypted at rest."
46
+ },
47
+ {
48
+ key: "scopes",
49
+ label: "OAuth Scopes (comma-separated)",
50
+ type: "text",
51
+ required: false,
52
+ placeholder: "https://www.googleapis.com/auth/gmail.modify,https://www.googleapis.com/auth/userinfo.email",
53
+ helpText: "Defaults to gmail.modify + userinfo.email which is enough for send + receive + label management. Leave blank to use defaults."
54
+ }
55
+ ]
56
+ },
57
+ healthCheck: { service: "channelGmailHealthCheck" }
58
+ };
59
+ const integrations = [integration];
60
+ const bundles = [];
61
+ const bundle = void 0;
62
+ export {
63
+ bundle,
64
+ bundles,
65
+ channelGmailDetailWidgetSpotId,
66
+ integration,
67
+ integrations
68
+ };
69
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_gmail/integration.ts"],
4
+ "sourcesContent": ["import { buildIntegrationDetailWidgetSpotId, type IntegrationBundle, type IntegrationDefinition } from '@open-mercato/shared/modules/integrations/types'\n\nexport const channelGmailDetailWidgetSpotId = buildIntegrationDetailWidgetSpotId('channel_gmail')\n\nexport const integration: IntegrationDefinition = {\n id: 'channel_gmail',\n title: 'Gmail',\n description:\n 'Connect per-user Gmail accounts via OAuth2. Outbound via gmail.users.messages.send; inbound via History API polling (5-min default).',\n category: 'communication',\n hub: 'communication_channels',\n providerKey: 'gmail',\n icon: 'gmail',\n docsUrl: 'https://developers.google.com/gmail/api',\n package: '@open-mercato/channel-gmail',\n version: '0.1.0',\n author: 'Open Mercato Team',\n company: 'Open Mercato',\n license: 'MIT',\n tags: ['email', 'gmail', 'oauth2', 'polling', 'communication'],\n detailPage: {\n widgetSpotId: channelGmailDetailWidgetSpotId,\n },\n apiVersions: [\n {\n id: 'v1',\n label: 'Gmail API v1',\n status: 'stable',\n default: true,\n changelog: 'Gmail API v1 with History API incremental sync and OAuth2.',\n },\n ],\n credentials: {\n fields: [\n {\n key: 'clientId',\n label: 'OAuth Client ID',\n type: 'text',\n required: true,\n placeholder: '1234567890-abcdef.apps.googleusercontent.com',\n helpText:\n 'Google Cloud Console -> APIs & Services -> Credentials -> OAuth 2.0 Client ID. Configure Authorized Redirect URI to <yourdomain>/api/communication_channels/oauth/gmail/callback.',\n },\n {\n key: 'clientSecret',\n label: 'OAuth Client Secret',\n type: 'secret',\n required: true,\n helpText: 'Paired with the Client ID above. Stored encrypted at rest.',\n },\n {\n key: 'scopes',\n label: 'OAuth Scopes (comma-separated)',\n type: 'text',\n required: false,\n placeholder: 'https://www.googleapis.com/auth/gmail.modify,https://www.googleapis.com/auth/userinfo.email',\n helpText:\n 'Defaults to gmail.modify + userinfo.email which is enough for send + receive + label management. Leave blank to use defaults.',\n },\n ],\n },\n healthCheck: { service: 'channelGmailHealthCheck' },\n}\n\nexport const integrations: IntegrationDefinition[] = [integration]\nexport const bundles: IntegrationBundle[] = []\nexport const bundle: IntegrationBundle | undefined = undefined\n"],
5
+ "mappings": "AAAA,SAAS,0CAA8F;AAEhG,MAAM,iCAAiC,mCAAmC,eAAe;AAEzF,MAAM,cAAqC;AAAA,EAChD,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,aACE;AAAA,EACF,UAAU;AAAA,EACV,KAAK;AAAA,EACL,aAAa;AAAA,EACb,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM,CAAC,SAAS,SAAS,UAAU,WAAW,eAAe;AAAA,EAC7D,YAAY;AAAA,IACV,cAAc;AAAA,EAChB;AAAA,EACA,aAAa;AAAA,IACX;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,QAAQ;AAAA,MACN;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,QACb,UACE;AAAA,MACJ;AAAA,MACA;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,QACV,UAAU;AAAA,MACZ;AAAA,MACA;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,QACb,UACE;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAAA,EACA,aAAa,EAAE,SAAS,0BAA0B;AACpD;AAEO,MAAM,eAAwC,CAAC,WAAW;AAC1D,MAAM,UAA+B,CAAC;AACtC,MAAM,SAAwC;",
6
+ "names": []
7
+ }