crewly 1.16.0 → 1.17.1
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/README.md +33 -1
- package/config/roles/orchestrator/prompt.md +20 -5
- package/config/skills/agent/core/calendar-create/SKILL.md +62 -0
- package/config/skills/agent/core/calendar-create/execute.sh +91 -0
- package/config/skills/agent/core/calendar-create/execute.test.sh +80 -0
- package/config/skills/agent/core/calendar-list/SKILL.md +58 -0
- package/config/skills/agent/core/calendar-list/execute.sh +88 -0
- package/config/skills/agent/core/calendar-list/execute.test.sh +71 -0
- package/config/skills/agent/core/gmail-read/SKILL.md +61 -0
- package/config/skills/agent/core/gmail-read/execute.sh +62 -0
- package/config/skills/agent/core/gmail-read/execute.test.sh +68 -0
- package/config/skills/agent/core/gmail-search/SKILL.md +64 -0
- package/config/skills/agent/core/gmail-search/execute.sh +70 -0
- package/config/skills/agent/core/gmail-search/execute.test.sh +67 -0
- package/config/skills/agent/core/gmail-send/SKILL.md +69 -0
- package/config/skills/agent/core/gmail-send/execute.sh +114 -0
- package/config/skills/agent/core/gmail-send/execute.test.sh +83 -0
- package/config/skills/orchestrator/list-missions/SKILL.md +47 -0
- package/config/skills/orchestrator/list-missions/execute.sh +34 -0
- package/config/skills/orchestrator/list-missions/execute.test.sh +33 -0
- package/config/slack-master-app-manifest.json +64 -0
- package/dist/backend/backend/src/constants.d.ts +125 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +125 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +3 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +100 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js +293 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts +26 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.routes.js +38 -0
- package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +209 -2
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +58 -8
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts +8 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js +25 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/backend/backend/src/services/core/crewly-home.utils.d.ts +49 -0
- package/dist/backend/backend/src/services/core/crewly-home.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/crewly-home.utils.js +86 -0
- package/dist/backend/backend/src/services/core/crewly-home.utils.js.map +1 -1
- package/dist/backend/backend/src/services/google/calendar.service.d.ts +98 -0
- package/dist/backend/backend/src/services/google/calendar.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/calendar.service.js +125 -0
- package/dist/backend/backend/src/services/google/calendar.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts +165 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/gmail.service.js +296 -0
- package/dist/backend/backend/src/services/google/gmail.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +44 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.js +104 -0
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +159 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +274 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/mission-context.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/mission-context.service.js +2 -1
- package/dist/backend/backend/src/services/memory/mission-context.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/project-memory.service.js +3 -2
- package/dist/backend/backend/src/services/memory/project-memory.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/session-memory.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/session-memory.service.js +2 -1
- package/dist/backend/backend/src/services/memory/session-memory.service.js.map +1 -1
- package/dist/backend/backend/src/services/project/task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project/task.service.js +2 -3
- package/dist/backend/backend/src/services/project/task.service.js.map +1 -1
- package/dist/backend/backend/src/services/project/ticket-editor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project/ticket-editor.service.js +2 -3
- package/dist/backend/backend/src/services/project/ticket-editor.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js +30 -0
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts +186 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js +409 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts +95 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +284 -4
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts +178 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js +392 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +76 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +234 -47
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.js +2 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-paths.d.ts +17 -0
- package/dist/backend/backend/src/services/v3/mission-paths.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-paths.js +21 -2
- package/dist/backend/backend/src/services/v3/mission-paths.js.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +4 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js +17 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts +116 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js +254 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/request.service.js +2 -1
- package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/task-projection.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/task-projection.service.js +2 -1
- package/dist/backend/backend/src/services/v3/task-projection.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/trigger-engine.service.js +2 -1
- package/dist/backend/backend/src/services/v3/trigger-engine.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js +2 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +6 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js +3 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +141 -2
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.d.ts +2 -0
- package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +125 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +125 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/cli/backend/src/services/core/crewly-home.utils.d.ts +49 -0
- package/dist/cli/backend/src/services/core/crewly-home.utils.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/crewly-home.utils.js +86 -0
- package/dist/cli/backend/src/services/core/crewly-home.utils.js.map +1 -1
- package/dist/cli/backend/src/services/memory/mission-context.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/mission-context.service.js +2 -1
- package/dist/cli/backend/src/services/memory/mission-context.service.js.map +1 -1
- package/dist/cli/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/project-memory.service.js +3 -2
- package/dist/cli/backend/src/services/memory/project-memory.service.js.map +1 -1
- package/dist/cli/backend/src/services/memory/session-memory.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/session-memory.service.js +2 -1
- package/dist/cli/backend/src/services/memory/session-memory.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js +30 -0
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +76 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +234 -47
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/escalation-router.service.js +2 -1
- package/dist/cli/backend/src/services/v3/escalation-router.service.js.map +1 -1
- package/dist/cli/backend/src/services/v3/mission-paths.d.ts +17 -0
- package/dist/cli/backend/src/services/v3/mission-paths.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/mission-paths.js +21 -2
- package/dist/cli/backend/src/services/v3/mission-paths.js.map +1 -1
- package/dist/cli/backend/src/services/v3/request.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/request.service.js +2 -1
- package/dist/cli/backend/src/services/v3/request.service.js.map +1 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.js +2 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +141 -2
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/mission.types.d.ts +2 -0
- package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -1
- package/frontend/dist/assets/{index-019817e2.js → index-c87332bf.js} +273 -273
- package/frontend/dist/assets/{index-a56613e7.css → index-f6b4230e.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail Service
|
|
3
|
+
*
|
|
4
|
+
* Search, read and send on the owner's mailbox through the Gmail REST API
|
|
5
|
+
* with a Cloud-minted access token. Messages are built locally as RFC 822
|
|
6
|
+
* and sent base64url-encoded; bodies are decoded here; attachments are
|
|
7
|
+
* listed but never downloaded.
|
|
8
|
+
*
|
|
9
|
+
* @module services/google/gmail.service
|
|
10
|
+
*/
|
|
11
|
+
import { type GoogleApiDeps } from './google-api.client.js';
|
|
12
|
+
/** One search hit (metadata only). */
|
|
13
|
+
export interface GmailSearchHit {
|
|
14
|
+
id: string;
|
|
15
|
+
threadId: string;
|
|
16
|
+
from: string;
|
|
17
|
+
to: string;
|
|
18
|
+
subject: string;
|
|
19
|
+
date: string;
|
|
20
|
+
snippet: string;
|
|
21
|
+
labelIds: string[];
|
|
22
|
+
}
|
|
23
|
+
/** An attachment reference — name/type/size only, never the bytes. */
|
|
24
|
+
export interface GmailAttachment {
|
|
25
|
+
filename: string;
|
|
26
|
+
mimeType: string;
|
|
27
|
+
size: number;
|
|
28
|
+
attachmentId: string;
|
|
29
|
+
}
|
|
30
|
+
/** A fully read message. */
|
|
31
|
+
export interface GmailMessage {
|
|
32
|
+
id: string;
|
|
33
|
+
threadId: string;
|
|
34
|
+
from: string;
|
|
35
|
+
to: string;
|
|
36
|
+
cc: string;
|
|
37
|
+
subject: string;
|
|
38
|
+
date: string;
|
|
39
|
+
/** RFC 822 Message-ID header (for replies) */
|
|
40
|
+
messageId: string;
|
|
41
|
+
snippet: string;
|
|
42
|
+
/** Decoded body text */
|
|
43
|
+
body: string;
|
|
44
|
+
/** Which part the body came from */
|
|
45
|
+
bodyType: 'text' | 'html' | 'none';
|
|
46
|
+
attachments: GmailAttachment[];
|
|
47
|
+
labelIds: string[];
|
|
48
|
+
}
|
|
49
|
+
/** Input to {@link GmailService.search}. */
|
|
50
|
+
export interface GmailSearchInput {
|
|
51
|
+
query: string;
|
|
52
|
+
max?: number;
|
|
53
|
+
}
|
|
54
|
+
/** Input to {@link GmailService.send}. */
|
|
55
|
+
export interface GmailSendInput {
|
|
56
|
+
to: string;
|
|
57
|
+
cc?: string;
|
|
58
|
+
subject: string;
|
|
59
|
+
text: string;
|
|
60
|
+
/** Gmail thread to append to */
|
|
61
|
+
threadId?: string;
|
|
62
|
+
/** RFC 822 Message-ID of the message being answered */
|
|
63
|
+
inReplyTo?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Result of {@link GmailService.send}. */
|
|
66
|
+
export interface GmailSendResult {
|
|
67
|
+
id: string;
|
|
68
|
+
threadId: string;
|
|
69
|
+
labelIds: string[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Standard base64url without padding — what `messages.send` expects in `raw`.
|
|
73
|
+
*
|
|
74
|
+
* @param input - Bytes or UTF-8 string
|
|
75
|
+
* @returns base64url string
|
|
76
|
+
*/
|
|
77
|
+
export declare function base64UrlEncode(input: string | Buffer): string;
|
|
78
|
+
/**
|
|
79
|
+
* Decode Gmail's base64url body data (tolerates standard base64 too).
|
|
80
|
+
*
|
|
81
|
+
* @param data - base64url string
|
|
82
|
+
* @returns UTF-8 text
|
|
83
|
+
*/
|
|
84
|
+
export declare function base64UrlDecode(data: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* RFC 2047 encoded-word for a header value that is not plain printable
|
|
87
|
+
* ASCII (a Chinese subject, an emoji); ASCII values are left untouched.
|
|
88
|
+
*
|
|
89
|
+
* @param value - Header value
|
|
90
|
+
* @returns The value as it should appear on the wire
|
|
91
|
+
*/
|
|
92
|
+
export declare function encodeHeaderValue(value: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Build the RFC 822 message for {@link GmailService.send}. The From header is
|
|
95
|
+
* left to Gmail (it fills in the authenticated user); the body is UTF-8
|
|
96
|
+
* text/plain, base64 transfer-encoded so any script is safe on the wire.
|
|
97
|
+
*
|
|
98
|
+
* @param input - Recipients, subject, body, optional reply threading
|
|
99
|
+
* @returns The raw message with CRLF line endings
|
|
100
|
+
* @throws GoogleWorkspaceError(400, validation) when `to` or `subject` is empty
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildRfc822(input: GmailSendInput): string;
|
|
103
|
+
/**
|
|
104
|
+
* Very small HTML → text: drop scripts/styles, turn block boundaries into
|
|
105
|
+
* newlines, strip tags, unescape the common entities, collapse blank runs.
|
|
106
|
+
* Only used when a message has no text/plain part.
|
|
107
|
+
*
|
|
108
|
+
* @param html - HTML source
|
|
109
|
+
* @returns Plain text
|
|
110
|
+
*/
|
|
111
|
+
export declare function stripHtml(html: string): string;
|
|
112
|
+
/**
|
|
113
|
+
* Gmail operations on the signed-in owner's mailbox.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const gmail = new GmailService({ tokens: GoogleWorkspaceTokenService.getInstance() });
|
|
118
|
+
* const hits = await gmail.search({ query: 'is:unread newer_than:1d' });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export declare class GmailService {
|
|
122
|
+
private readonly deps;
|
|
123
|
+
private readonly base;
|
|
124
|
+
/**
|
|
125
|
+
* @param deps - Token provider and optional fetch override
|
|
126
|
+
*/
|
|
127
|
+
constructor(deps: GoogleApiDeps);
|
|
128
|
+
/**
|
|
129
|
+
* Gmail search (`messages.list`) followed by one metadata `messages.get`
|
|
130
|
+
* per hit for From / To / Subject / Date and the snippet.
|
|
131
|
+
*
|
|
132
|
+
* @param input - Gmail query string and result cap
|
|
133
|
+
* @returns Hits in Gmail's order (newest first)
|
|
134
|
+
* @throws GoogleWorkspaceError on auth / Google failures
|
|
135
|
+
*/
|
|
136
|
+
search(input: GmailSearchInput): Promise<GmailSearchHit[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Full `messages.get`; body decoded (text/plain preferred, stripped
|
|
139
|
+
* text/html as fallback), attachments listed only.
|
|
140
|
+
*
|
|
141
|
+
* @param id - Gmail message id
|
|
142
|
+
* @returns The message
|
|
143
|
+
* @throws GoogleWorkspaceError — 404 when Gmail has no such message
|
|
144
|
+
*/
|
|
145
|
+
read(id: string): Promise<GmailMessage>;
|
|
146
|
+
/**
|
|
147
|
+
* `messages.send` with a locally built RFC 822 message.
|
|
148
|
+
*
|
|
149
|
+
* @param input - Recipients, subject, text and optional reply threading
|
|
150
|
+
* @returns The sent message's id / threadId / labels
|
|
151
|
+
* @throws GoogleWorkspaceError(400, validation) for missing to/subject; auth / Google failures
|
|
152
|
+
*/
|
|
153
|
+
send(input: GmailSendInput): Promise<GmailSendResult>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Coerce a caller-supplied result cap into [1, ceiling], defaulting when
|
|
157
|
+
* absent or not a number.
|
|
158
|
+
*
|
|
159
|
+
* @param value - Requested cap
|
|
160
|
+
* @param fallback - Default
|
|
161
|
+
* @param ceiling - Hard maximum
|
|
162
|
+
* @returns The cap to send to Google
|
|
163
|
+
*/
|
|
164
|
+
export declare function clampMax(value: number | undefined, fallback: number, ceiling: number): number;
|
|
165
|
+
//# sourceMappingURL=gmail.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmail.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail Service
|
|
3
|
+
*
|
|
4
|
+
* Search, read and send on the owner's mailbox through the Gmail REST API
|
|
5
|
+
* with a Cloud-minted access token. Messages are built locally as RFC 822
|
|
6
|
+
* and sent base64url-encoded; bodies are decoded here; attachments are
|
|
7
|
+
* listed but never downloaded.
|
|
8
|
+
*
|
|
9
|
+
* @module services/google/gmail.service
|
|
10
|
+
*/
|
|
11
|
+
import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
|
|
12
|
+
import { GoogleWorkspaceError } from './google-workspace-token.service.js';
|
|
13
|
+
import { buildGoogleUrl, googleRequest } from './google-api.client.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Encoding helpers (exported for the tests and the controller's dry-run)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Standard base64url without padding — what `messages.send` expects in `raw`.
|
|
19
|
+
*
|
|
20
|
+
* @param input - Bytes or UTF-8 string
|
|
21
|
+
* @returns base64url string
|
|
22
|
+
*/
|
|
23
|
+
export function base64UrlEncode(input) {
|
|
24
|
+
return Buffer.from(input).toString('base64url');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Decode Gmail's base64url body data (tolerates standard base64 too).
|
|
28
|
+
*
|
|
29
|
+
* @param data - base64url string
|
|
30
|
+
* @returns UTF-8 text
|
|
31
|
+
*/
|
|
32
|
+
export function base64UrlDecode(data) {
|
|
33
|
+
return Buffer.from(data, 'base64url').toString('utf8');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* RFC 2047 encoded-word for a header value that is not plain printable
|
|
37
|
+
* ASCII (a Chinese subject, an emoji); ASCII values are left untouched.
|
|
38
|
+
*
|
|
39
|
+
* @param value - Header value
|
|
40
|
+
* @returns The value as it should appear on the wire
|
|
41
|
+
*/
|
|
42
|
+
export function encodeHeaderValue(value) {
|
|
43
|
+
// eslint-disable-next-line no-control-regex
|
|
44
|
+
if (/^[\x20-\x7e]*$/.test(value))
|
|
45
|
+
return value;
|
|
46
|
+
return `=?UTF-8?B?${Buffer.from(value, 'utf8').toString('base64')}?=`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Wrap a base64 body at the RFC 2045 line width.
|
|
50
|
+
*
|
|
51
|
+
* @param base64 - Unwrapped base64
|
|
52
|
+
* @returns CRLF-wrapped base64
|
|
53
|
+
*/
|
|
54
|
+
function wrapBase64(base64) {
|
|
55
|
+
const width = GOOGLE_WORKSPACE_CONSTANTS.MIME_LINE_WIDTH;
|
|
56
|
+
const lines = [];
|
|
57
|
+
for (let i = 0; i < base64.length; i += width)
|
|
58
|
+
lines.push(base64.slice(i, i + width));
|
|
59
|
+
return lines.join('\r\n');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build the RFC 822 message for {@link GmailService.send}. The From header is
|
|
63
|
+
* left to Gmail (it fills in the authenticated user); the body is UTF-8
|
|
64
|
+
* text/plain, base64 transfer-encoded so any script is safe on the wire.
|
|
65
|
+
*
|
|
66
|
+
* @param input - Recipients, subject, body, optional reply threading
|
|
67
|
+
* @returns The raw message with CRLF line endings
|
|
68
|
+
* @throws GoogleWorkspaceError(400, validation) when `to` or `subject` is empty
|
|
69
|
+
*/
|
|
70
|
+
export function buildRfc822(input) {
|
|
71
|
+
const CODES = GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES;
|
|
72
|
+
if (!input.to?.trim())
|
|
73
|
+
throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"to" is required');
|
|
74
|
+
if (!input.subject?.trim())
|
|
75
|
+
throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"subject" is required');
|
|
76
|
+
const headers = [`To: ${input.to.trim()}`];
|
|
77
|
+
if (input.cc?.trim())
|
|
78
|
+
headers.push(`Cc: ${input.cc.trim()}`);
|
|
79
|
+
headers.push(`Subject: ${encodeHeaderValue(input.subject.trim())}`);
|
|
80
|
+
if (input.inReplyTo?.trim()) {
|
|
81
|
+
const ref = input.inReplyTo.trim();
|
|
82
|
+
headers.push(`In-Reply-To: ${ref}`, `References: ${ref}`);
|
|
83
|
+
}
|
|
84
|
+
headers.push('MIME-Version: 1.0', 'Content-Type: text/plain; charset="UTF-8"', 'Content-Transfer-Encoding: base64');
|
|
85
|
+
const body = wrapBase64(Buffer.from(input.text ?? '', 'utf8').toString('base64'));
|
|
86
|
+
return `${headers.join('\r\n')}\r\n\r\n${body}`;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Very small HTML → text: drop scripts/styles, turn block boundaries into
|
|
90
|
+
* newlines, strip tags, unescape the common entities, collapse blank runs.
|
|
91
|
+
* Only used when a message has no text/plain part.
|
|
92
|
+
*
|
|
93
|
+
* @param html - HTML source
|
|
94
|
+
* @returns Plain text
|
|
95
|
+
*/
|
|
96
|
+
export function stripHtml(html) {
|
|
97
|
+
return html
|
|
98
|
+
.replace(/<(script|style)[\s\S]*?<\/\1>/gi, '')
|
|
99
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
100
|
+
.replace(/<\/(p|div|tr|li|h[1-6]|blockquote)>/gi, '\n')
|
|
101
|
+
.replace(/<[^>]+>/g, '')
|
|
102
|
+
.replace(/ /gi, ' ')
|
|
103
|
+
.replace(/&/gi, '&')
|
|
104
|
+
.replace(/</gi, '<')
|
|
105
|
+
.replace(/>/gi, '>')
|
|
106
|
+
.replace(/"/gi, '"')
|
|
107
|
+
.replace(/'|'/gi, "'")
|
|
108
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
109
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
110
|
+
.trim();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Case-insensitive header lookup.
|
|
114
|
+
*
|
|
115
|
+
* @param headers - Gmail header list
|
|
116
|
+
* @param name - Header name
|
|
117
|
+
* @returns Value or empty string
|
|
118
|
+
*/
|
|
119
|
+
function header(headers, name) {
|
|
120
|
+
const lower = name.toLowerCase();
|
|
121
|
+
return headers?.find((h) => h.name.toLowerCase() === lower)?.value ?? '';
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Walk a MIME tree depth-first.
|
|
125
|
+
*
|
|
126
|
+
* @param part - Root part
|
|
127
|
+
* @param visit - Callback per part
|
|
128
|
+
*/
|
|
129
|
+
function walkParts(part, visit) {
|
|
130
|
+
if (!part)
|
|
131
|
+
return;
|
|
132
|
+
visit(part);
|
|
133
|
+
for (const child of part.parts ?? [])
|
|
134
|
+
walkParts(child, visit);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Pick the body: first text/plain part with inline data, else first text/html
|
|
138
|
+
* (stripped), else nothing.
|
|
139
|
+
*
|
|
140
|
+
* @param payload - Message payload
|
|
141
|
+
* @returns Body text and which kind it was
|
|
142
|
+
*/
|
|
143
|
+
function extractBody(payload) {
|
|
144
|
+
let plain;
|
|
145
|
+
let html;
|
|
146
|
+
walkParts(payload, (p) => {
|
|
147
|
+
if (p.filename || !p.body?.data)
|
|
148
|
+
return;
|
|
149
|
+
const mime = (p.mimeType ?? '').toLowerCase();
|
|
150
|
+
if (mime === 'text/plain' && plain === undefined)
|
|
151
|
+
plain = base64UrlDecode(p.body.data);
|
|
152
|
+
else if (mime === 'text/html' && html === undefined)
|
|
153
|
+
html = base64UrlDecode(p.body.data);
|
|
154
|
+
});
|
|
155
|
+
if (plain !== undefined)
|
|
156
|
+
return { body: plain, bodyType: 'text' };
|
|
157
|
+
if (html !== undefined)
|
|
158
|
+
return { body: stripHtml(html), bodyType: 'html' };
|
|
159
|
+
return { body: '', bodyType: 'none' };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Every part that carries a filename and an attachmentId.
|
|
163
|
+
*
|
|
164
|
+
* @param payload - Message payload
|
|
165
|
+
* @returns Attachment references
|
|
166
|
+
*/
|
|
167
|
+
function extractAttachments(payload) {
|
|
168
|
+
const out = [];
|
|
169
|
+
walkParts(payload, (p) => {
|
|
170
|
+
if (p.filename && p.body?.attachmentId) {
|
|
171
|
+
out.push({
|
|
172
|
+
filename: p.filename,
|
|
173
|
+
mimeType: p.mimeType ?? 'application/octet-stream',
|
|
174
|
+
size: p.body.size ?? 0,
|
|
175
|
+
attachmentId: p.body.attachmentId,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Service
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
/**
|
|
185
|
+
* Gmail operations on the signed-in owner's mailbox.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* const gmail = new GmailService({ tokens: GoogleWorkspaceTokenService.getInstance() });
|
|
190
|
+
* const hits = await gmail.search({ query: 'is:unread newer_than:1d' });
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
export class GmailService {
|
|
194
|
+
deps;
|
|
195
|
+
base = GOOGLE_WORKSPACE_CONSTANTS.GMAIL_API_BASE;
|
|
196
|
+
/**
|
|
197
|
+
* @param deps - Token provider and optional fetch override
|
|
198
|
+
*/
|
|
199
|
+
constructor(deps) {
|
|
200
|
+
this.deps = deps;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Gmail search (`messages.list`) followed by one metadata `messages.get`
|
|
204
|
+
* per hit for From / To / Subject / Date and the snippet.
|
|
205
|
+
*
|
|
206
|
+
* @param input - Gmail query string and result cap
|
|
207
|
+
* @returns Hits in Gmail's order (newest first)
|
|
208
|
+
* @throws GoogleWorkspaceError on auth / Google failures
|
|
209
|
+
*/
|
|
210
|
+
async search(input) {
|
|
211
|
+
const query = (input.query ?? '').trim();
|
|
212
|
+
const max = clampMax(input.max, GOOGLE_WORKSPACE_CONSTANTS.GMAIL_DEFAULT_MAX_RESULTS, GOOGLE_WORKSPACE_CONSTANTS.GMAIL_MAX_RESULTS_CEILING);
|
|
213
|
+
const list = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages`, { q: query, maxResults: max }));
|
|
214
|
+
const refs = list.messages ?? [];
|
|
215
|
+
if (refs.length === 0)
|
|
216
|
+
return [];
|
|
217
|
+
const messages = await Promise.all(refs.map((ref) => googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages/${encodeURIComponent(ref.id)}`, {
|
|
218
|
+
format: 'metadata',
|
|
219
|
+
metadataHeaders: [...GOOGLE_WORKSPACE_CONSTANTS.GMAIL_SEARCH_HEADERS],
|
|
220
|
+
}))));
|
|
221
|
+
return messages.map((m) => {
|
|
222
|
+
const h = m.payload?.headers;
|
|
223
|
+
return {
|
|
224
|
+
id: m.id,
|
|
225
|
+
threadId: m.threadId,
|
|
226
|
+
from: header(h, 'From'),
|
|
227
|
+
to: header(h, 'To'),
|
|
228
|
+
subject: header(h, 'Subject'),
|
|
229
|
+
date: header(h, 'Date'),
|
|
230
|
+
snippet: m.snippet ?? '',
|
|
231
|
+
labelIds: m.labelIds ?? [],
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Full `messages.get`; body decoded (text/plain preferred, stripped
|
|
237
|
+
* text/html as fallback), attachments listed only.
|
|
238
|
+
*
|
|
239
|
+
* @param id - Gmail message id
|
|
240
|
+
* @returns The message
|
|
241
|
+
* @throws GoogleWorkspaceError — 404 when Gmail has no such message
|
|
242
|
+
*/
|
|
243
|
+
async read(id) {
|
|
244
|
+
if (!id?.trim()) {
|
|
245
|
+
throw new GoogleWorkspaceError(400, GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES.VALIDATION, 'message id is required');
|
|
246
|
+
}
|
|
247
|
+
const m = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages/${encodeURIComponent(id.trim())}`, { format: 'full' }));
|
|
248
|
+
const h = m.payload?.headers;
|
|
249
|
+
const { body, bodyType } = extractBody(m.payload);
|
|
250
|
+
return {
|
|
251
|
+
id: m.id,
|
|
252
|
+
threadId: m.threadId,
|
|
253
|
+
from: header(h, 'From'),
|
|
254
|
+
to: header(h, 'To'),
|
|
255
|
+
cc: header(h, 'Cc'),
|
|
256
|
+
subject: header(h, 'Subject'),
|
|
257
|
+
date: header(h, 'Date'),
|
|
258
|
+
messageId: header(h, 'Message-ID'),
|
|
259
|
+
snippet: m.snippet ?? '',
|
|
260
|
+
body,
|
|
261
|
+
bodyType,
|
|
262
|
+
attachments: extractAttachments(m.payload),
|
|
263
|
+
labelIds: m.labelIds ?? [],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* `messages.send` with a locally built RFC 822 message.
|
|
268
|
+
*
|
|
269
|
+
* @param input - Recipients, subject, text and optional reply threading
|
|
270
|
+
* @returns The sent message's id / threadId / labels
|
|
271
|
+
* @throws GoogleWorkspaceError(400, validation) for missing to/subject; auth / Google failures
|
|
272
|
+
*/
|
|
273
|
+
async send(input) {
|
|
274
|
+
const raw = base64UrlEncode(buildRfc822(input));
|
|
275
|
+
const body = { raw };
|
|
276
|
+
if (input.threadId?.trim())
|
|
277
|
+
body.threadId = input.threadId.trim();
|
|
278
|
+
const sent = await googleRequest(this.deps, `${this.base}/messages/send`, { method: 'POST', body });
|
|
279
|
+
return { id: sent.id, threadId: sent.threadId, labelIds: sent.labelIds ?? [] };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Coerce a caller-supplied result cap into [1, ceiling], defaulting when
|
|
284
|
+
* absent or not a number.
|
|
285
|
+
*
|
|
286
|
+
* @param value - Requested cap
|
|
287
|
+
* @param fallback - Default
|
|
288
|
+
* @param ceiling - Hard maximum
|
|
289
|
+
* @returns The cap to send to Google
|
|
290
|
+
*/
|
|
291
|
+
export function clampMax(value, fallback, ceiling) {
|
|
292
|
+
if (value === undefined || !Number.isFinite(value))
|
|
293
|
+
return fallback;
|
|
294
|
+
return Math.min(Math.max(Math.floor(value), 1), ceiling);
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=gmail.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmail.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google API client — the one place Gmail and Calendar calls go through.
|
|
3
|
+
*
|
|
4
|
+
* Attaches the Cloud-minted access token, maps Google's HTTP failures to
|
|
5
|
+
* {@link GoogleWorkspaceError}, and drops the cached token on a 401 so the
|
|
6
|
+
* next call refreshes instead of failing the same way again.
|
|
7
|
+
*
|
|
8
|
+
* @module services/google/google-api.client
|
|
9
|
+
*/
|
|
10
|
+
/** The slice of GoogleWorkspaceTokenService the Google services need. */
|
|
11
|
+
export interface GoogleTokenProvider {
|
|
12
|
+
getAccessToken(): Promise<string>;
|
|
13
|
+
clearCache(): void;
|
|
14
|
+
}
|
|
15
|
+
/** Shared constructor dependencies for Gmail / Calendar services. */
|
|
16
|
+
export interface GoogleApiDeps {
|
|
17
|
+
tokens: GoogleTokenProvider;
|
|
18
|
+
fetchImpl?: typeof fetch;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build a URL with query parameters; `undefined`/empty values are skipped and
|
|
22
|
+
* array values are repeated (Gmail's `metadataHeaders=A&metadataHeaders=B`).
|
|
23
|
+
*
|
|
24
|
+
* @param base - Absolute URL without query
|
|
25
|
+
* @param params - Query parameters
|
|
26
|
+
* @returns The full URL string
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildGoogleUrl(base: string, params?: Record<string, string | number | string[] | undefined>): string;
|
|
29
|
+
/**
|
|
30
|
+
* Authenticated JSON call to a Google API.
|
|
31
|
+
*
|
|
32
|
+
* @param deps - Token provider + fetch
|
|
33
|
+
* @param url - Absolute URL (use {@link buildGoogleUrl})
|
|
34
|
+
* @param init - Method and optional JSON body
|
|
35
|
+
* @returns The parsed JSON response
|
|
36
|
+
* @throws GoogleWorkspaceError — token failures pass through; Google 401 →
|
|
37
|
+
* 401 google_error (cache dropped), 403/404/429 keep their status, other
|
|
38
|
+
* failures → 502 google_error, unreachable → 502 network
|
|
39
|
+
*/
|
|
40
|
+
export declare function googleRequest<T>(deps: GoogleApiDeps, url: string, init?: {
|
|
41
|
+
method?: 'GET' | 'POST';
|
|
42
|
+
body?: unknown;
|
|
43
|
+
}): Promise<T>;
|
|
44
|
+
//# sourceMappingURL=google-api.client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-api.client.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-api.client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAwBD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAM,GAAG,MAAM,CAWxH;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACrD,OAAO,CAAC,CAAC,CAAC,CAsCZ"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google API client — the one place Gmail and Calendar calls go through.
|
|
3
|
+
*
|
|
4
|
+
* Attaches the Cloud-minted access token, maps Google's HTTP failures to
|
|
5
|
+
* {@link GoogleWorkspaceError}, and drops the cached token on a 401 so the
|
|
6
|
+
* next call refreshes instead of failing the same way again.
|
|
7
|
+
*
|
|
8
|
+
* @module services/google/google-api.client
|
|
9
|
+
*/
|
|
10
|
+
import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
|
|
11
|
+
import { GoogleWorkspaceError } from './google-workspace-token.service.js';
|
|
12
|
+
/**
|
|
13
|
+
* Extract the human message from a Google error body, tolerating both the
|
|
14
|
+
* object and the plain-string shapes Google uses.
|
|
15
|
+
*
|
|
16
|
+
* @param text - Raw response body
|
|
17
|
+
* @returns Message or undefined
|
|
18
|
+
*/
|
|
19
|
+
function googleErrorMessage(text) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(text);
|
|
22
|
+
if (typeof parsed.error === 'string')
|
|
23
|
+
return parsed.error;
|
|
24
|
+
return parsed.error?.message;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return text ? text.slice(0, 200) : undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a URL with query parameters; `undefined`/empty values are skipped and
|
|
32
|
+
* array values are repeated (Gmail's `metadataHeaders=A&metadataHeaders=B`).
|
|
33
|
+
*
|
|
34
|
+
* @param base - Absolute URL without query
|
|
35
|
+
* @param params - Query parameters
|
|
36
|
+
* @returns The full URL string
|
|
37
|
+
*/
|
|
38
|
+
export function buildGoogleUrl(base, params = {}) {
|
|
39
|
+
const url = new URL(base);
|
|
40
|
+
for (const [key, value] of Object.entries(params)) {
|
|
41
|
+
if (value === undefined || value === '')
|
|
42
|
+
continue;
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
for (const v of value)
|
|
45
|
+
url.searchParams.append(key, v);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
url.searchParams.set(key, String(value));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return url.toString();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Authenticated JSON call to a Google API.
|
|
55
|
+
*
|
|
56
|
+
* @param deps - Token provider + fetch
|
|
57
|
+
* @param url - Absolute URL (use {@link buildGoogleUrl})
|
|
58
|
+
* @param init - Method and optional JSON body
|
|
59
|
+
* @returns The parsed JSON response
|
|
60
|
+
* @throws GoogleWorkspaceError — token failures pass through; Google 401 →
|
|
61
|
+
* 401 google_error (cache dropped), 403/404/429 keep their status, other
|
|
62
|
+
* failures → 502 google_error, unreachable → 502 network
|
|
63
|
+
*/
|
|
64
|
+
export async function googleRequest(deps, url, init = {}) {
|
|
65
|
+
const token = await deps.tokens.getAccessToken();
|
|
66
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
67
|
+
const CODES = GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES;
|
|
68
|
+
let res;
|
|
69
|
+
try {
|
|
70
|
+
res = await fetchImpl(url, {
|
|
71
|
+
method: init.method ?? 'GET',
|
|
72
|
+
headers: {
|
|
73
|
+
Authorization: `Bearer ${token}`,
|
|
74
|
+
Accept: 'application/json',
|
|
75
|
+
...(init.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
76
|
+
},
|
|
77
|
+
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
78
|
+
signal: AbortSignal.timeout(GOOGLE_WORKSPACE_CONSTANTS.REQUEST_TIMEOUT_MS),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
83
|
+
throw new GoogleWorkspaceError(502, CODES.NETWORK, `Google unreachable: ${message}`);
|
|
84
|
+
}
|
|
85
|
+
const text = await res.text();
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
const message = googleErrorMessage(text) ?? `Google request failed (${res.status})`;
|
|
88
|
+
if (res.status === 401) {
|
|
89
|
+
deps.tokens.clearCache();
|
|
90
|
+
throw new GoogleWorkspaceError(401, CODES.GOOGLE_ERROR, `Google rejected the access token: ${message}`);
|
|
91
|
+
}
|
|
92
|
+
const passthrough = res.status === 403 || res.status === 404 || res.status === 429;
|
|
93
|
+
throw new GoogleWorkspaceError(passthrough ? res.status : 502, CODES.GOOGLE_ERROR, message);
|
|
94
|
+
}
|
|
95
|
+
if (!text)
|
|
96
|
+
return {};
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(text);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new GoogleWorkspaceError(502, CODES.GOOGLE_ERROR, 'Google returned a non-JSON response.');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=google-api.client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-api.client.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-api.client.ts"],"names":[],"mappings":"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"}
|