@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.
- package/.turbo/turbo-build.log +2 -0
- package/AGENTS.md +47 -0
- package/build.mjs +7 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-006.spec.js +17 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-006.spec.js.map +7 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-007.spec.js +16 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-007.spec.js.map +7 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-008.spec.js +16 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-008.spec.js.map +7 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-A01-token-refresh.spec.js +17 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-A01-token-refresh.spec.js.map +7 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-C01.spec.js +26 -0
- package/dist/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-C01.spec.js.map +7 -0
- package/dist/modules/channel_gmail/acl.js +10 -0
- package/dist/modules/channel_gmail/acl.js.map +7 -0
- package/dist/modules/channel_gmail/di.js +23 -0
- package/dist/modules/channel_gmail/di.js.map +7 -0
- package/dist/modules/channel_gmail/index.js +9 -0
- package/dist/modules/channel_gmail/index.js.map +7 -0
- package/dist/modules/channel_gmail/integration.js +69 -0
- package/dist/modules/channel_gmail/integration.js.map +7 -0
- package/dist/modules/channel_gmail/lib/adapter.js +542 -0
- package/dist/modules/channel_gmail/lib/adapter.js.map +7 -0
- package/dist/modules/channel_gmail/lib/capabilities.js +10 -0
- package/dist/modules/channel_gmail/lib/capabilities.js.map +7 -0
- package/dist/modules/channel_gmail/lib/convert-outbound.js +84 -0
- package/dist/modules/channel_gmail/lib/convert-outbound.js.map +7 -0
- package/dist/modules/channel_gmail/lib/credentials.js +48 -0
- package/dist/modules/channel_gmail/lib/credentials.js.map +7 -0
- package/dist/modules/channel_gmail/lib/gmail-client.js +160 -0
- package/dist/modules/channel_gmail/lib/gmail-client.js.map +7 -0
- package/dist/modules/channel_gmail/lib/health.js +10 -0
- package/dist/modules/channel_gmail/lib/health.js.map +7 -0
- package/dist/modules/channel_gmail/lib/normalize-inbound.js +28 -0
- package/dist/modules/channel_gmail/lib/normalize-inbound.js.map +7 -0
- package/dist/modules/channel_gmail/lib/oauth.js +77 -0
- package/dist/modules/channel_gmail/lib/oauth.js.map +7 -0
- package/dist/modules/channel_gmail/setup.js +25 -0
- package/dist/modules/channel_gmail/setup.js.map +7 -0
- package/dist/modules/channel_gmail/widgets/injection/connect/widget.client.js +24 -0
- package/dist/modules/channel_gmail/widgets/injection/connect/widget.client.js.map +7 -0
- package/dist/modules/channel_gmail/widgets/injection/connect/widget.js +17 -0
- package/dist/modules/channel_gmail/widgets/injection/connect/widget.js.map +7 -0
- package/dist/modules/channel_gmail/widgets/injection-table.js +14 -0
- package/dist/modules/channel_gmail/widgets/injection-table.js.map +7 -0
- package/jest.config.cjs +34 -0
- package/package.json +95 -0
- package/src/index.ts +1 -0
- package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-006.spec.ts +24 -0
- package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-007.spec.ts +23 -0
- package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-008.spec.ts +23 -0
- package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-A01-token-refresh.spec.ts +39 -0
- package/src/modules/channel_gmail/__integration__/TC-CHANNEL-EMAIL-C01.spec.ts +48 -0
- package/src/modules/channel_gmail/acl.ts +6 -0
- package/src/modules/channel_gmail/di.ts +21 -0
- package/src/modules/channel_gmail/index.ts +6 -0
- package/src/modules/channel_gmail/integration.ts +67 -0
- package/src/modules/channel_gmail/lib/__tests__/adapter.test.ts +838 -0
- package/src/modules/channel_gmail/lib/__tests__/convert-outbound.test.ts +128 -0
- package/src/modules/channel_gmail/lib/__tests__/credentials.test.ts +76 -0
- package/src/modules/channel_gmail/lib/__tests__/gmail-client.test.ts +209 -0
- package/src/modules/channel_gmail/lib/__tests__/normalize-inbound.test.ts +106 -0
- package/src/modules/channel_gmail/lib/__tests__/oauth.test.ts +148 -0
- package/src/modules/channel_gmail/lib/adapter.ts +734 -0
- package/src/modules/channel_gmail/lib/capabilities.ts +22 -0
- package/src/modules/channel_gmail/lib/convert-outbound.ts +136 -0
- package/src/modules/channel_gmail/lib/credentials.ts +90 -0
- package/src/modules/channel_gmail/lib/gmail-client.ts +305 -0
- package/src/modules/channel_gmail/lib/health.ts +14 -0
- package/src/modules/channel_gmail/lib/normalize-inbound.ts +57 -0
- package/src/modules/channel_gmail/lib/oauth.ts +128 -0
- package/src/modules/channel_gmail/setup.ts +36 -0
- package/src/modules/channel_gmail/widgets/injection/connect/widget.client.tsx +28 -0
- package/src/modules/channel_gmail/widgets/injection/connect/widget.ts +16 -0
- package/src/modules/channel_gmail/widgets/injection-table.ts +12 -0
- package/tsconfig.json +9 -0
- 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
|
+
}
|
package/jest.config.cjs
ADDED
|
@@ -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,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,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
|