@open-mercato/channel-gmail 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +95 -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,14 @@
1
+ const injectionTable = {
2
+ "profile:communication-channels:connect": [
3
+ {
4
+ widgetId: "channel_gmail.injection.connect",
5
+ priority: 120
6
+ }
7
+ ]
8
+ };
9
+ var injection_table_default = injectionTable;
10
+ export {
11
+ injection_table_default as default,
12
+ injectionTable
13
+ };
14
+ //# sourceMappingURL=injection-table.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_gmail/widgets/injection-table.ts"],
4
+ "sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\nexport const injectionTable: ModuleInjectionTable = {\n 'profile:communication-channels:connect': [\n {\n widgetId: 'channel_gmail.injection.connect',\n priority: 120,\n },\n ],\n}\n\nexport default injectionTable\n"],
5
+ "mappings": "AAEO,MAAM,iBAAuC;AAAA,EAClD,0CAA0C;AAAA,IACxC;AAAA,MACE,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,34 @@
1
+ /** @type {import('jest').Config} */
2
+ const base = require('../../jest.config.base.cjs')
3
+
4
+ module.exports = {
5
+ ...base,
6
+ testEnvironment: 'node',
7
+ watchman: false,
8
+ rootDir: '.',
9
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
10
+ moduleNameMapper: {
11
+ '^@open-mercato/channel-gmail/(.*)$': '<rootDir>/src/$1',
12
+ '^@open-mercato/core/(.*)$': '<rootDir>/../core/src/$1',
13
+ '^@open-mercato/shared/(.*)$': '<rootDir>/../shared/src/$1',
14
+ '^@open-mercato/queue/(.*)$': '<rootDir>/../queue/src/$1',
15
+ '^@open-mercato/ui/(.*)$': '<rootDir>/../ui/src/$1',
16
+ },
17
+ transform: {
18
+ '^.+\\.(t|j)sx?$': [
19
+ '<rootDir>/../../scripts/jest-mikroorm-transformer.cjs',
20
+ {
21
+ tsconfig: {
22
+ jsx: 'react-jsx',
23
+ rootDir: '.',
24
+ ignoreDeprecations: '6.0',
25
+ },
26
+ },
27
+ ],
28
+ },
29
+ transformIgnorePatterns: [
30
+ 'node_modules/(?!(@mikro-orm|kysely)/)',
31
+ ],
32
+ testMatch: ['<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)'],
33
+ passWithNoTests: true,
34
+ }
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@open-mercato/channel-gmail",
3
+ "version": "0.6.4",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "scripts": {
7
+ "build": "node build.mjs",
8
+ "watch": "node watch.mjs",
9
+ "test": "jest --config jest.config.cjs",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "exports": {
13
+ ".": "./dist/index.js",
14
+ "./*.ts": {
15
+ "types": "./src/*.ts",
16
+ "default": "./dist/*.js"
17
+ },
18
+ "./*.tsx": {
19
+ "types": "./src/*.tsx",
20
+ "default": "./dist/*.js"
21
+ },
22
+ "./*.json": "./src/*.json",
23
+ "./*": {
24
+ "types": [
25
+ "./src/*.ts",
26
+ "./src/*.tsx"
27
+ ],
28
+ "default": "./dist/*.js"
29
+ },
30
+ "./*/*.json": "./src/*/*.json",
31
+ "./*/*": {
32
+ "types": [
33
+ "./src/*/*.ts",
34
+ "./src/*/*.tsx"
35
+ ],
36
+ "default": "./dist/*/*.js"
37
+ },
38
+ "./*/*/*.json": "./src/*/*/*.json",
39
+ "./*/*/*": {
40
+ "types": [
41
+ "./src/*/*/*.ts",
42
+ "./src/*/*/*.tsx"
43
+ ],
44
+ "default": "./dist/*/*/*.js"
45
+ },
46
+ "./*/*/*/*.json": "./src/*/*/*/*.json",
47
+ "./*/*/*/*": {
48
+ "types": [
49
+ "./src/*/*/*/*.ts",
50
+ "./src/*/*/*/*.tsx"
51
+ ],
52
+ "default": "./dist/*/*/*/*.js"
53
+ },
54
+ "./*/*/*/*/*.json": "./src/*/*/*/*/*.json",
55
+ "./*/*/*/*/*": {
56
+ "types": [
57
+ "./src/*/*/*/*/*.ts",
58
+ "./src/*/*/*/*/*.tsx"
59
+ ],
60
+ "default": "./dist/*/*/*/*/*.js"
61
+ }
62
+ },
63
+ "dependencies": {
64
+ "@open-mercato/core": "0.6.4",
65
+ "@open-mercato/ui": "0.6.4",
66
+ "@types/mailparser": "^3.4.5",
67
+ "mailparser": "^3.7.1"
68
+ },
69
+ "peerDependencies": {
70
+ "@mikro-orm/postgresql": "^7.0.14",
71
+ "@open-mercato/shared": "0.6.4",
72
+ "react": "^19.0.0",
73
+ "react-dom": "^19.0.0"
74
+ },
75
+ "devDependencies": {
76
+ "@open-mercato/shared": "0.6.4",
77
+ "@types/jest": "^30.0.0",
78
+ "@types/react": "^19.2.17",
79
+ "@types/react-dom": "^19.2.3",
80
+ "esbuild": "^0.28.0",
81
+ "glob": "^13.0.6",
82
+ "jest": "^30.4.2",
83
+ "react": "19.2.7",
84
+ "react-dom": "19.2.7",
85
+ "ts-jest": "^29.4.11"
86
+ },
87
+ "publishConfig": {
88
+ "access": "public"
89
+ },
90
+ "repository": {
91
+ "type": "git",
92
+ "url": "https://github.com/open-mercato/open-mercato",
93
+ "directory": "packages/channel-gmail"
94
+ }
95
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { metadata } from './modules/channel_gmail/index'
@@ -0,0 +1,24 @@
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
+
5
+ /**
6
+ * TC-CHANNEL-EMAIL-006 — Gmail provider visible to the hub's OAuth router.
7
+ *
8
+ * After slice 3f registers the `gmail` adapter, the OAuth initiate route must
9
+ * accept `provider=gmail`. Without a real Google client_id we exercise routing
10
+ * only — the adapter is reachable when the route does NOT 404.
11
+ */
12
+ test.describe('TC-CHANNEL-EMAIL-006: Gmail OAuth router wiring', () => {
13
+ test('POST /oauth/gmail/initiate does not 404 on the provider', async ({ request }) => {
14
+ const token = await getAuthToken(request)
15
+ const response = await apiRequest(
16
+ request,
17
+ 'POST',
18
+ '/api/communication_channels/oauth/gmail/initiate',
19
+ { token, data: { redirectUri: 'https://example.com/cb' } },
20
+ )
21
+ expect(response.status(), 'route should not 5xx').toBeLessThan(500)
22
+ expect(response.status(), 'Gmail provider should be registered').not.toBe(404)
23
+ })
24
+ })
@@ -0,0 +1,23 @@
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
+
5
+ /**
6
+ * TC-CHANNEL-EMAIL-007 — Gmail webhook stays a no-op (Pub/Sub deferred to v2).
7
+ *
8
+ * The provider-generic webhook route exists at `/api/communication_channels/webhook/gmail`.
9
+ * Until Pub/Sub push is implemented, the adapter returns `eventType: 'other'` and the
10
+ * route must respond 2xx rather than 5xx-ing or 404-ing.
11
+ */
12
+ test.describe('TC-CHANNEL-EMAIL-007: Gmail webhook is a no-op for now', () => {
13
+ test('POST /api/communication_channels/webhook/gmail does not 5xx', async ({ request }) => {
14
+ const token = await getAuthToken(request)
15
+ const response = await apiRequest(
16
+ request,
17
+ 'POST',
18
+ '/api/communication_channels/webhook/gmail',
19
+ { token, data: { ping: true } },
20
+ )
21
+ expect(response.status()).toBeLessThan(500)
22
+ })
23
+ })
@@ -0,0 +1,23 @@
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
+
5
+ /**
6
+ * TC-CHANNEL-EMAIL-008 — Profile page renders with both IMAP and Gmail providers installed.
7
+ *
8
+ * Confirms `@open-mercato/channel-gmail` does not break the per-user profile page
9
+ * (`/backend/profile/communication-channels`) — the page must still render even
10
+ * when more than one provider package is installed.
11
+ */
12
+ test.describe('TC-CHANNEL-EMAIL-008: profile page with Gmail provider installed', () => {
13
+ test('GET /backend/profile/communication-channels does not 5xx', async ({ request }) => {
14
+ const token = await getAuthToken(request)
15
+ const response = await apiRequest(
16
+ request,
17
+ 'GET',
18
+ '/backend/profile/communication-channels',
19
+ { token },
20
+ )
21
+ expect(response.status(), 'profile page should not 5xx').toBeLessThan(500)
22
+ })
23
+ })
@@ -0,0 +1,39 @@
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
+
5
+ /**
6
+ * TC-CHANNEL-EMAIL-A01 — Gmail token refresh wiring (Spec A).
7
+ *
8
+ * Verifies that the OAuth refresh contract surface exists end-to-end:
9
+ * - The /me/channels route is reachable for an authenticated user.
10
+ * - The OAuth initiate route accepts `provider=gmail` (means the Gmail
11
+ * adapter is registered and discoverable).
12
+ *
13
+ * The full token-refresh roundtrip requires a real connected Gmail
14
+ * mailbox with stored OAuth client config in `integration_credentials`
15
+ * for `oauth_gmail`. That manual verification is captured in
16
+ * `.ai/qa/scenarios/TC-CHANNEL-EMAIL-A01-gmail-token-refresh.md`.
17
+ *
18
+ * The wiring itself (resolution of `oauth_gmail` -> `oauthClient` field
19
+ * on `RefreshCredentialsInput`) is thoroughly covered by unit tests:
20
+ * - packages/core/src/modules/communication_channels/lib/__tests__/credential-refresh.test.ts
21
+ * - packages/channel-gmail/src/modules/channel_gmail/lib/__tests__/adapter.test.ts
22
+ */
23
+ test.describe('TC-CHANNEL-EMAIL-A01: Gmail token refresh wiring', () => {
24
+ test('OAuth initiate route for gmail is registered (refresh contract is reachable)', async ({ request }) => {
25
+ const token = await getAuthToken(request)
26
+ const response = await apiRequest(
27
+ request,
28
+ 'POST',
29
+ '/api/communication_channels/oauth/gmail/initiate',
30
+ { token, data: { redirectUri: 'https://example.com/cb' } },
31
+ )
32
+ // Smoke test only: the Gmail adapter being registered is necessary for the
33
+ // refresh path to ever fire. A 4xx (missing oauth_gmail row) or 2xx
34
+ // (initiate succeeds) both prove the adapter is wired. A 404/5xx would
35
+ // indicate the route or provider is gone — block-level regression.
36
+ expect(response.status(), 'route should not 5xx').toBeLessThan(500)
37
+ expect(response.status(), 'Gmail provider should be registered').not.toBe(404)
38
+ })
39
+ })
@@ -0,0 +1,48 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { apiRequest } from '@open-mercato/core/helpers/integration/api'
3
+
4
+ /**
5
+ * TC-CHANNEL-EMAIL-C01 — Gmail Pub/Sub webhook authentication
6
+ *
7
+ * Verifies that `POST /api/communication_channels/webhooks/gmail` rejects
8
+ * unauthenticated / unsigned requests. The full happy-path (publish to
9
+ * Pub/Sub → webhook fires → history sync → message appears in CRM) is the
10
+ * manual QA scenario `TC-CHANNEL-EMAIL-C01-gmail-push-delivery.md` since it
11
+ * requires a real GCP project and connected Gmail mailbox.
12
+ *
13
+ * Unit-level coverage of the JWT verifier + envelope decoder lives in
14
+ * `packages/core/src/modules/communication_channels/lib/__tests__/gmail-pubsub-jwt.test.ts`.
15
+ */
16
+ test.describe('TC-CHANNEL-EMAIL-C01: Gmail Pub/Sub webhook', () => {
17
+ test('rejects requests missing the bearer JWT', async ({ request }) => {
18
+ const response = await apiRequest(
19
+ request,
20
+ 'POST',
21
+ '/api/communication_channels/webhooks/gmail',
22
+ // Webhook auth is the Pub/Sub JWT in `Authorization`, not the platform
23
+ // session token. `apiRequest` requires a token field; empty is fine —
24
+ // the route ignores Authorization unless the JWT validator can decode it.
25
+ { token: '', data: { message: { data: '', messageId: 'm1' } } },
26
+ )
27
+ // 401 when expectedAudience/expectedEmail are configured;
28
+ // 503 when env vars aren't set (acceptable for a smoke test against an
29
+ // unconfigured CI environment).
30
+ expect([401, 503]).toContain(response.status())
31
+ })
32
+
33
+ test('rejects malformed JSON body when JWT is absent', async ({ request }) => {
34
+ const response = await apiRequest(
35
+ request,
36
+ 'POST',
37
+ '/api/communication_channels/webhooks/gmail',
38
+ { token: '', data: 'not-json' as unknown as Record<string, unknown> },
39
+ )
40
+ // A request without a Pub/Sub JWT is rejected at the config/verification gate
41
+ // BEFORE the body is parsed, so the malformed body never reaches the decoder:
42
+ // 503 when the verifier env vars aren't set (the unconfigured CI case — same as
43
+ // the sibling test above) and 401 when they are. The body-decode 400 path is
44
+ // covered by the gmail-pubsub-jwt unit tests; here we only assert a controlled
45
+ // rejection, never an uncontrolled 5xx crash.
46
+ expect([401, 503]).toContain(response.status())
47
+ })
48
+ })
@@ -0,0 +1,6 @@
1
+ export 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
+
6
+ export default features
@@ -0,0 +1,21 @@
1
+ import { asValue } from 'awilix'
2
+ import type { AppContainer } from '@open-mercato/shared/lib/di/container'
3
+ import {
4
+ hasChannelAdapter,
5
+ registerChannelAdapter,
6
+ } from '@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton'
7
+ import { getGmailChannelAdapter } from './lib/adapter'
8
+ import { channelGmailHealthCheck } from './lib/health'
9
+
10
+ export function register(container: AppContainer): void {
11
+ if (!hasChannelAdapter('gmail')) {
12
+ registerChannelAdapter(getGmailChannelAdapter())
13
+ }
14
+ container.register({
15
+ channelGmailAdapter: asValue(getGmailChannelAdapter()),
16
+ // Registered under the exact service name declared in `integration.ts`
17
+ // (`healthCheck.service`). Without this, the hub's `container.resolve(...)`
18
+ // throws and the channel reports permanently 'unhealthy'.
19
+ channelGmailHealthCheck: asValue(channelGmailHealthCheck),
20
+ })
21
+ }
@@ -0,0 +1,6 @@
1
+ export const metadata = {
2
+ id: 'channel_gmail',
3
+ title: 'Gmail Email Channel',
4
+ description:
5
+ 'Connect per-user Gmail accounts via OAuth2. Outbound via gmail.users.messages.send; inbound via History API polling. Pairs with the Communications Hub.',
6
+ }
@@ -0,0 +1,67 @@
1
+ import { buildIntegrationDetailWidgetSpotId, type IntegrationBundle, type IntegrationDefinition } from '@open-mercato/shared/modules/integrations/types'
2
+
3
+ export const channelGmailDetailWidgetSpotId = buildIntegrationDetailWidgetSpotId('channel_gmail')
4
+
5
+ export const integration: IntegrationDefinition = {
6
+ id: 'channel_gmail',
7
+ title: 'Gmail',
8
+ description:
9
+ 'Connect per-user Gmail accounts via OAuth2. Outbound via gmail.users.messages.send; inbound via History API polling (5-min default).',
10
+ category: 'communication',
11
+ hub: 'communication_channels',
12
+ providerKey: 'gmail',
13
+ icon: 'gmail',
14
+ docsUrl: 'https://developers.google.com/gmail/api',
15
+ package: '@open-mercato/channel-gmail',
16
+ version: '0.1.0',
17
+ author: 'Open Mercato Team',
18
+ company: 'Open Mercato',
19
+ license: 'MIT',
20
+ tags: ['email', 'gmail', 'oauth2', 'polling', 'communication'],
21
+ detailPage: {
22
+ widgetSpotId: channelGmailDetailWidgetSpotId,
23
+ },
24
+ apiVersions: [
25
+ {
26
+ id: 'v1',
27
+ label: 'Gmail API v1',
28
+ status: 'stable',
29
+ default: true,
30
+ changelog: 'Gmail API v1 with History API incremental sync and OAuth2.',
31
+ },
32
+ ],
33
+ credentials: {
34
+ fields: [
35
+ {
36
+ key: 'clientId',
37
+ label: 'OAuth Client ID',
38
+ type: 'text',
39
+ required: true,
40
+ placeholder: '1234567890-abcdef.apps.googleusercontent.com',
41
+ helpText:
42
+ 'Google Cloud Console -> APIs & Services -> Credentials -> OAuth 2.0 Client ID. Configure Authorized Redirect URI to <yourdomain>/api/communication_channels/oauth/gmail/callback.',
43
+ },
44
+ {
45
+ key: 'clientSecret',
46
+ label: 'OAuth Client Secret',
47
+ type: 'secret',
48
+ required: true,
49
+ helpText: 'Paired with the Client ID above. Stored encrypted at rest.',
50
+ },
51
+ {
52
+ key: 'scopes',
53
+ label: 'OAuth Scopes (comma-separated)',
54
+ type: 'text',
55
+ required: false,
56
+ placeholder: 'https://www.googleapis.com/auth/gmail.modify,https://www.googleapis.com/auth/userinfo.email',
57
+ helpText:
58
+ 'Defaults to gmail.modify + userinfo.email which is enough for send + receive + label management. Leave blank to use defaults.',
59
+ },
60
+ ],
61
+ },
62
+ healthCheck: { service: 'channelGmailHealthCheck' },
63
+ }
64
+
65
+ export const integrations: IntegrationDefinition[] = [integration]
66
+ export const bundles: IntegrationBundle[] = []
67
+ export const bundle: IntegrationBundle | undefined = undefined