@jait/gateway 0.1.498 → 0.1.500

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 (111) hide show
  1. package/dist/db/migrations.d.ts.map +1 -1
  2. package/dist/db/migrations.js +22 -0
  3. package/dist/db/migrations.js.map +1 -1
  4. package/dist/db/schema.d.ts +178 -0
  5. package/dist/db/schema.d.ts.map +1 -1
  6. package/dist/db/schema.js +15 -0
  7. package/dist/db/schema.js.map +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +5 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/routes/chat.d.ts.map +1 -1
  12. package/dist/routes/chat.js +77 -7
  13. package/dist/routes/chat.js.map +1 -1
  14. package/dist/routes/email.d.ts +5 -0
  15. package/dist/routes/email.d.ts.map +1 -0
  16. package/dist/routes/email.js +202 -0
  17. package/dist/routes/email.js.map +1 -0
  18. package/dist/server.d.ts +1 -0
  19. package/dist/server.d.ts.map +1 -1
  20. package/dist/server.js +4 -0
  21. package/dist/server.js.map +1 -1
  22. package/dist/services/email/gmail.d.ts +16 -0
  23. package/dist/services/email/gmail.d.ts.map +1 -0
  24. package/dist/services/email/gmail.js +170 -0
  25. package/dist/services/email/gmail.js.map +1 -0
  26. package/dist/services/email/index.d.ts +3 -0
  27. package/dist/services/email/index.d.ts.map +1 -0
  28. package/dist/services/email/index.js +2 -0
  29. package/dist/services/email/index.js.map +1 -0
  30. package/dist/services/email/oauth.d.ts +32 -0
  31. package/dist/services/email/oauth.d.ts.map +1 -0
  32. package/dist/services/email/oauth.js +105 -0
  33. package/dist/services/email/oauth.js.map +1 -0
  34. package/dist/services/email/outlook.d.ts +15 -0
  35. package/dist/services/email/outlook.d.ts.map +1 -0
  36. package/dist/services/email/outlook.js +142 -0
  37. package/dist/services/email/outlook.js.map +1 -0
  38. package/dist/services/email/service.d.ts +50 -0
  39. package/dist/services/email/service.d.ts.map +1 -0
  40. package/dist/services/email/service.js +277 -0
  41. package/dist/services/email/service.js.map +1 -0
  42. package/dist/services/email/types.d.ts +82 -0
  43. package/dist/services/email/types.d.ts.map +1 -0
  44. package/dist/services/email/types.js +3 -0
  45. package/dist/services/email/types.js.map +1 -0
  46. package/dist/tools/email-tools.d.ts +52 -0
  47. package/dist/tools/email-tools.d.ts.map +1 -0
  48. package/dist/tools/email-tools.js +237 -0
  49. package/dist/tools/email-tools.js.map +1 -0
  50. package/dist/tools/index.d.ts +2 -0
  51. package/dist/tools/index.d.ts.map +1 -1
  52. package/dist/tools/index.js +8 -0
  53. package/dist/tools/index.js.map +1 -1
  54. package/package.json +2 -2
  55. package/web-dist/assets/{_basePickBy-1ypMBiW_.js → _basePickBy-weRnpeSP.js} +1 -1
  56. package/web-dist/assets/{_baseUniq-DvjcbfPq.js → _baseUniq-DtnLSF0I.js} +1 -1
  57. package/web-dist/assets/{arc-ErevYm19.js → arc-Cp1p0WjX.js} +1 -1
  58. package/web-dist/assets/{architectureDiagram-2XIMDMQ5-D_WVp0kY.js → architectureDiagram-2XIMDMQ5-DumDXBPY.js} +1 -1
  59. package/web-dist/assets/{blockDiagram-WCTKOSBZ-Bo6-xom-.js → blockDiagram-WCTKOSBZ-BUrXNnVe.js} +1 -1
  60. package/web-dist/assets/{c4Diagram-IC4MRINW-D9M8EKu_.js → c4Diagram-IC4MRINW-CoZYcV11.js} +1 -1
  61. package/web-dist/assets/channel-BsJsRGpT.js +1 -0
  62. package/web-dist/assets/{chunk-4BX2VUAB-WH1KAVAb.js → chunk-4BX2VUAB-B5TBucTO.js} +1 -1
  63. package/web-dist/assets/{chunk-55IACEB6-D2a2qrrp.js → chunk-55IACEB6-BdxI7eBH.js} +1 -1
  64. package/web-dist/assets/{chunk-FMBD7UC4-28DcGMIY.js → chunk-FMBD7UC4-Df6Ely3V.js} +1 -1
  65. package/web-dist/assets/{chunk-JSJVCQXG-C1uNGWZs.js → chunk-JSJVCQXG-DB9qqYGs.js} +1 -1
  66. package/web-dist/assets/{chunk-KX2RTZJC-V1qtcDEn.js → chunk-KX2RTZJC-DTIB6ktS.js} +1 -1
  67. package/web-dist/assets/{chunk-NQ4KR5QH-cug-v0rY.js → chunk-NQ4KR5QH-CkOSBUJF.js} +1 -1
  68. package/web-dist/assets/{chunk-QZHKN3VN-CM17QePX.js → chunk-QZHKN3VN-D66ocnl8.js} +1 -1
  69. package/web-dist/assets/{chunk-WL4C6EOR-DlYbqSTG.js → chunk-WL4C6EOR-NZ8dMhF_.js} +1 -1
  70. package/web-dist/assets/classDiagram-VBA2DB6C-CdqG1M_H.js +1 -0
  71. package/web-dist/assets/classDiagram-v2-RAHNMMFH-CdqG1M_H.js +1 -0
  72. package/web-dist/assets/clone-CSA1mxoh.js +1 -0
  73. package/web-dist/assets/{cose-bilkent-S5V4N54A-CVsGoDQL.js → cose-bilkent-S5V4N54A-Pp1KUBze.js} +1 -1
  74. package/web-dist/assets/{dagre-KLK3FWXG-DLQZVyW4.js → dagre-KLK3FWXG-wcukCjRT.js} +1 -1
  75. package/web-dist/assets/{diagram-E7M64L7V-BF0y78lG.js → diagram-E7M64L7V-C854JMtC.js} +1 -1
  76. package/web-dist/assets/{diagram-IFDJBPK2-MuDqalfZ.js → diagram-IFDJBPK2-Zv4LIgxw.js} +1 -1
  77. package/web-dist/assets/{diagram-P4PSJMXO-e36wc7XG.js → diagram-P4PSJMXO-HQUcIOnd.js} +1 -1
  78. package/web-dist/assets/{erDiagram-INFDFZHY-B_i7tPHh.js → erDiagram-INFDFZHY-C7S_j2RD.js} +1 -1
  79. package/web-dist/assets/{flowDiagram-PKNHOUZH-fPB07Diy.js → flowDiagram-PKNHOUZH-COcPLgSs.js} +1 -1
  80. package/web-dist/assets/{ganttDiagram-A5KZAMGK-q8EsM9Gb.js → ganttDiagram-A5KZAMGK-BqN7h9XV.js} +1 -1
  81. package/web-dist/assets/{gitGraphDiagram-K3NZZRJ6-BiAyblPL.js → gitGraphDiagram-K3NZZRJ6-ClqoYt2g.js} +1 -1
  82. package/web-dist/assets/{graph-DnNeq8po.js → graph-CFne_DNF.js} +1 -1
  83. package/web-dist/assets/index-BU0ZMPd5.css +32 -0
  84. package/web-dist/assets/{index-CzRne9la.js → index-CFPawU2E.js} +395 -373
  85. package/web-dist/assets/{index-BbmkMmHZ.js → index-CI4OPVvL.js} +1 -1
  86. package/web-dist/assets/{index-Bk_qadgU.js → index-CknzVVIn.js} +1 -1
  87. package/web-dist/assets/{infoDiagram-LFFYTUFH-C3TzSSln.js → infoDiagram-LFFYTUFH-DRIMKzUI.js} +1 -1
  88. package/web-dist/assets/{ishikawaDiagram-PHBUUO56-szGd8_KC.js → ishikawaDiagram-PHBUUO56-pNuJu2fx.js} +1 -1
  89. package/web-dist/assets/{journeyDiagram-4ABVD52K-ByTvobTa.js → journeyDiagram-4ABVD52K-DxXnVHBc.js} +1 -1
  90. package/web-dist/assets/{kanban-definition-K7BYSVSG-DoUUzwjw.js → kanban-definition-K7BYSVSG-ftpLe57e.js} +1 -1
  91. package/web-dist/assets/{layout-BzJ0RrG1.js → layout-BMI7FMq0.js} +1 -1
  92. package/web-dist/assets/{linear-B5XYxmuh.js → linear-Bo0YW6SK.js} +1 -1
  93. package/web-dist/assets/{mindmap-definition-YRQLILUH-FaHbvuby.js → mindmap-definition-YRQLILUH-B0nvMWNM.js} +1 -1
  94. package/web-dist/assets/{pieDiagram-SKSYHLDU-F0mNDNRX.js → pieDiagram-SKSYHLDU-DBZdzgOM.js} +1 -1
  95. package/web-dist/assets/{quadrantDiagram-337W2JSQ-CSy2sGI7.js → quadrantDiagram-337W2JSQ-B55i0l4Q.js} +1 -1
  96. package/web-dist/assets/{requirementDiagram-Z7DCOOCP-iXp1hJSy.js → requirementDiagram-Z7DCOOCP-DBypfiAR.js} +1 -1
  97. package/web-dist/assets/{sankeyDiagram-WA2Y5GQK-BUFT8Ncy.js → sankeyDiagram-WA2Y5GQK-BwR1KHwm.js} +1 -1
  98. package/web-dist/assets/{sequenceDiagram-2WXFIKYE-iWc1z5r5.js → sequenceDiagram-2WXFIKYE-Dvo9wvBR.js} +1 -1
  99. package/web-dist/assets/{stateDiagram-RAJIS63D-Dmla9JkM.js → stateDiagram-RAJIS63D-CWyL3FoR.js} +1 -1
  100. package/web-dist/assets/stateDiagram-v2-FVOUBMTO-BfO0Smfp.js +1 -0
  101. package/web-dist/assets/{timeline-definition-YZTLITO2-4XUMaeyN.js → timeline-definition-YZTLITO2-yII9647R.js} +1 -1
  102. package/web-dist/assets/{treemap-KZPCXAKY-BiXISVos.js → treemap-KZPCXAKY-BwFUa0xO.js} +1 -1
  103. package/web-dist/assets/{vennDiagram-LZ73GAT5-BHzH1mWp.js → vennDiagram-LZ73GAT5-CIPC_MOH.js} +1 -1
  104. package/web-dist/assets/{xychartDiagram-JWTSCODW-C1rAefFy.js → xychartDiagram-JWTSCODW-D8ZGjW-3.js} +1 -1
  105. package/web-dist/index.html +2 -2
  106. package/web-dist/assets/channel-CzVoFm04.js +0 -1
  107. package/web-dist/assets/classDiagram-VBA2DB6C-CkMkzWkJ.js +0 -1
  108. package/web-dist/assets/classDiagram-v2-RAHNMMFH-CkMkzWkJ.js +0 -1
  109. package/web-dist/assets/clone-BrtlKZXp.js +0 -1
  110. package/web-dist/assets/index-C-Ol-PwZ.css +0 -32
  111. package/web-dist/assets/stateDiagram-v2-FVOUBMTO-DrmN0_X9.js +0 -1
@@ -0,0 +1,32 @@
1
+ import type { EmailProvider, EmailTokens } from "./types.js";
2
+ export interface OAuthClientCredentials {
3
+ clientId: string;
4
+ clientSecret: string;
5
+ }
6
+ export type ClientCredentialResolver = (provider: EmailProvider) => OAuthClientCredentials | null;
7
+ export declare function envClientCredentials(provider: EmailProvider): OAuthClientCredentials | null;
8
+ /** PKCE verifier/challenge pair (S256). */
9
+ export declare function createPkce(): {
10
+ verifier: string;
11
+ challenge: string;
12
+ };
13
+ export declare function buildAuthorizeUrl(params: {
14
+ provider: EmailProvider;
15
+ credentials: OAuthClientCredentials;
16
+ redirectUri: string;
17
+ state: string;
18
+ codeChallenge: string;
19
+ }): string;
20
+ export declare function exchangeCode(params: {
21
+ provider: EmailProvider;
22
+ credentials: OAuthClientCredentials;
23
+ redirectUri: string;
24
+ code: string;
25
+ codeVerifier: string;
26
+ }): Promise<EmailTokens>;
27
+ export declare function refreshTokens(params: {
28
+ provider: EmailProvider;
29
+ credentials: OAuthClientCredentials;
30
+ refreshToken: string;
31
+ }): Promise<EmailTokens>;
32
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/services/email/oauth.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAkC7D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,wBAAwB,GAAG,CACrC,QAAQ,EAAE,aAAa,KACpB,sBAAsB,GAAG,IAAI,CAAC;AAEnC,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,sBAAsB,GAAG,IAAI,CAM3F;AAED,2CAA2C;AAC3C,wBAAgB,UAAU,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,QAAQ,EAAE,aAAa,CAAC;IACxB,WAAW,EAAE,sBAAsB,CAAC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,CAcT;AA6BD,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,QAAQ,EAAE,aAAa,CAAC;IACxB,WAAW,EAAE,sBAAsB,CAAC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,WAAW,CAAC,CAUvB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,aAAa,CAAC;IACxB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,WAAW,CAAC,CAQvB"}
@@ -0,0 +1,105 @@
1
+ // OAuth2 (authorization-code) helpers for Gmail and Outlook.
2
+ //
3
+ // Client credentials are resolved (in priority order) from:
4
+ // 1. Per-user secrets (type "email-oauth-app", keys "<provider>-client-id" /
5
+ // "<provider>-client-secret") — lets the user configure apps from the UI.
6
+ // 2. Gateway environment variables.
7
+ //
8
+ // Gmail: GMAIL_OAUTH_CLIENT_ID / GMAIL_OAUTH_CLIENT_SECRET
9
+ // Outlook: OUTLOOK_OAUTH_CLIENT_ID / OUTLOOK_OAUTH_CLIENT_SECRET
10
+ import { createHash, randomBytes } from "node:crypto";
11
+ const PROVIDERS = {
12
+ gmail: {
13
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
14
+ tokenUrl: "https://oauth2.googleapis.com/token",
15
+ scopes: [
16
+ "https://www.googleapis.com/auth/gmail.modify",
17
+ "https://www.googleapis.com/auth/gmail.send",
18
+ ],
19
+ // offline + consent are required to reliably receive a refresh token.
20
+ authorizeParams: { access_type: "offline", prompt: "consent" },
21
+ },
22
+ outlook: {
23
+ authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
24
+ tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
25
+ scopes: [
26
+ "offline_access",
27
+ "https://graph.microsoft.com/Mail.ReadWrite",
28
+ "https://graph.microsoft.com/Mail.Send",
29
+ "https://graph.microsoft.com/User.Read",
30
+ ],
31
+ authorizeParams: { response_mode: "query" },
32
+ },
33
+ };
34
+ export function envClientCredentials(provider) {
35
+ const prefix = provider === "gmail" ? "GMAIL" : "OUTLOOK";
36
+ const clientId = process.env[`${prefix}_OAUTH_CLIENT_ID`]?.trim();
37
+ const clientSecret = process.env[`${prefix}_OAUTH_CLIENT_SECRET`]?.trim();
38
+ if (!clientId || !clientSecret)
39
+ return null;
40
+ return { clientId, clientSecret };
41
+ }
42
+ /** PKCE verifier/challenge pair (S256). */
43
+ export function createPkce() {
44
+ const verifier = randomBytes(32).toString("base64url");
45
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
46
+ return { verifier, challenge };
47
+ }
48
+ export function buildAuthorizeUrl(params) {
49
+ const cfg = PROVIDERS[params.provider];
50
+ const url = new URL(cfg.authorizeUrl);
51
+ url.searchParams.set("client_id", params.credentials.clientId);
52
+ url.searchParams.set("redirect_uri", params.redirectUri);
53
+ url.searchParams.set("response_type", "code");
54
+ url.searchParams.set("scope", cfg.scopes.join(" "));
55
+ url.searchParams.set("state", params.state);
56
+ url.searchParams.set("code_challenge", params.codeChallenge);
57
+ url.searchParams.set("code_challenge_method", "S256");
58
+ for (const [k, v] of Object.entries(cfg.authorizeParams)) {
59
+ url.searchParams.set(k, v);
60
+ }
61
+ return url.toString();
62
+ }
63
+ function tokensFromResponse(data, fallbackRefresh) {
64
+ const expiresIn = typeof data["expires_in"] === "number" ? data["expires_in"] : 3600;
65
+ return {
66
+ accessToken: String(data["access_token"] ?? ""),
67
+ refreshToken: data["refresh_token"] ?? fallbackRefresh ?? null,
68
+ expiresAt: Date.now() + expiresIn * 1000,
69
+ scope: data["scope"],
70
+ };
71
+ }
72
+ async function postToken(provider, body) {
73
+ const res = await fetch(PROVIDERS[provider].tokenUrl, {
74
+ method: "POST",
75
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
76
+ body: new URLSearchParams(body).toString(),
77
+ });
78
+ const data = (await res.json().catch(() => ({})));
79
+ if (!res.ok) {
80
+ const detail = data["error_description"] ?? data["error"] ?? `HTTP ${res.status}`;
81
+ throw new Error(`OAuth token request failed: ${detail}`);
82
+ }
83
+ return data;
84
+ }
85
+ export async function exchangeCode(params) {
86
+ const data = await postToken(params.provider, {
87
+ client_id: params.credentials.clientId,
88
+ client_secret: params.credentials.clientSecret,
89
+ code: params.code,
90
+ redirect_uri: params.redirectUri,
91
+ grant_type: "authorization_code",
92
+ code_verifier: params.codeVerifier,
93
+ });
94
+ return tokensFromResponse(data);
95
+ }
96
+ export async function refreshTokens(params) {
97
+ const data = await postToken(params.provider, {
98
+ client_id: params.credentials.clientId,
99
+ client_secret: params.credentials.clientSecret,
100
+ refresh_token: params.refreshToken,
101
+ grant_type: "refresh_token",
102
+ });
103
+ return tokensFromResponse(data, params.refreshToken);
104
+ }
105
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/services/email/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { EmailFolder, EmailMessageFull, EmailMessageSummary, ListMessagesOptions, MailboxClient, SendMessageInput } from "./types.js";
2
+ export declare class OutlookClient implements MailboxClient {
3
+ private gfetch;
4
+ private summarize;
5
+ listMessages(accessToken: string, opts: ListMessagesOptions): Promise<EmailMessageSummary[]>;
6
+ getMessage(accessToken: string, id: string): Promise<EmailMessageFull>;
7
+ sendMessage(accessToken: string, input: SendMessageInput): Promise<{
8
+ id: string;
9
+ }>;
10
+ tagMessage(accessToken: string, id: string, add: string[], remove: string[]): Promise<void>;
11
+ deleteMessage(accessToken: string, id: string): Promise<void>;
12
+ listFolders(accessToken: string): Promise<EmailFolder[]>;
13
+ getProfileEmail(accessToken: string): Promise<string>;
14
+ }
15
+ //# sourceMappingURL=outlook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outlook.d.ts","sourceRoot":"","sources":["../../../src/services/email/outlook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAuCpB,qBAAa,aAAc,YAAW,aAAa;YACnC,MAAM;IAmBpB,OAAO,CAAC,SAAS;IAeX,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAsB5F,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAYtE,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IA8BlF,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAW3F,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7D,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAOxD,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAO5D"}
@@ -0,0 +1,142 @@
1
+ // Outlook mailbox client (Microsoft Graph v1.0).
2
+ const BASE = "https://graph.microsoft.com/v1.0";
3
+ function fmtRecipient(r) {
4
+ const ea = r?.emailAddress;
5
+ if (!ea)
6
+ return "";
7
+ return ea.name ? `${ea.name} <${ea.address ?? ""}>` : ea.address ?? "";
8
+ }
9
+ function fmtRecipients(list) {
10
+ return (list ?? []).map(fmtRecipient).filter(Boolean).join(", ");
11
+ }
12
+ function toGraphRecipients(value) {
13
+ if (!value)
14
+ return [];
15
+ return value
16
+ .split(/[,;]/)
17
+ .map((s) => s.trim())
18
+ .filter(Boolean)
19
+ .map((address) => ({ emailAddress: { address } }));
20
+ }
21
+ export class OutlookClient {
22
+ async gfetch(accessToken, path, init) {
23
+ const res = await fetch(`${BASE}${path}`, {
24
+ ...init,
25
+ headers: {
26
+ Authorization: `Bearer ${accessToken}`,
27
+ ...(init?.body ? { "Content-Type": "application/json" } : {}),
28
+ ...(init?.headers ?? {}),
29
+ },
30
+ });
31
+ if (res.status === 204)
32
+ return {};
33
+ const text = await res.text();
34
+ const data = text ? JSON.parse(text) : {};
35
+ if (!res.ok) {
36
+ const msg = data?.error?.message ?? `HTTP ${res.status}`;
37
+ throw new Error(`Microsoft Graph error: ${msg}`);
38
+ }
39
+ return data;
40
+ }
41
+ summarize(m) {
42
+ return {
43
+ id: m.id,
44
+ threadId: m.conversationId,
45
+ from: fmtRecipient(m.from),
46
+ to: fmtRecipients(m.toRecipients),
47
+ subject: m.subject ?? "",
48
+ snippet: m.bodyPreview ?? "",
49
+ date: m.receivedDateTime ?? "",
50
+ unread: m.isRead === false,
51
+ labels: m.categories ?? [],
52
+ hasAttachments: m.hasAttachments ?? false,
53
+ };
54
+ }
55
+ async listMessages(accessToken, opts) {
56
+ const limit = Math.min(Math.max(opts.limit ?? 25, 1), 50);
57
+ const folder = opts.folder && opts.folder.toLowerCase() !== "all" ? opts.folder : "inbox";
58
+ const params = new URLSearchParams({
59
+ $top: String(limit),
60
+ $select: "id,conversationId,subject,bodyPreview,receivedDateTime,isRead,hasAttachments,categories,from,toRecipients",
61
+ });
62
+ let path;
63
+ if (opts.query) {
64
+ // $search cannot be combined with $orderby.
65
+ params.set("$search", `"${opts.query.replace(/"/g, "")}"`);
66
+ path = `/me/mailFolders/${folder}/messages?${params.toString()}`;
67
+ }
68
+ else {
69
+ params.set("$orderby", "receivedDateTime desc");
70
+ path = `/me/mailFolders/${folder}/messages?${params.toString()}`;
71
+ }
72
+ const data = (await this.gfetch(accessToken, path, {
73
+ headers: { ConsistencyLevel: "eventual" },
74
+ }));
75
+ return (data.value ?? []).map((m) => this.summarize(m));
76
+ }
77
+ async getMessage(accessToken, id) {
78
+ const m = (await this.gfetch(accessToken, `/me/messages/${id}`));
79
+ const summary = this.summarize(m);
80
+ const isHtml = (m.body?.contentType ?? "").toLowerCase() === "html";
81
+ return {
82
+ ...summary,
83
+ cc: fmtRecipients(m.ccRecipients),
84
+ bodyText: isHtml ? "" : m.body?.content ?? "",
85
+ bodyHtml: isHtml ? m.body?.content ?? "" : "",
86
+ };
87
+ }
88
+ async sendMessage(accessToken, input) {
89
+ if (input.replyToMessageId) {
90
+ await this.gfetch(accessToken, `/me/messages/${input.replyToMessageId}/reply`, {
91
+ method: "POST",
92
+ body: JSON.stringify({
93
+ message: {
94
+ toRecipients: input.to ? toGraphRecipients(input.to) : undefined,
95
+ ccRecipients: toGraphRecipients(input.cc),
96
+ },
97
+ comment: input.body,
98
+ }),
99
+ });
100
+ return { id: input.replyToMessageId };
101
+ }
102
+ await this.gfetch(accessToken, "/me/sendMail", {
103
+ method: "POST",
104
+ body: JSON.stringify({
105
+ message: {
106
+ subject: input.subject,
107
+ body: { contentType: input.html ? "HTML" : "Text", content: input.body },
108
+ toRecipients: toGraphRecipients(input.to),
109
+ ccRecipients: toGraphRecipients(input.cc),
110
+ bccRecipients: toGraphRecipients(input.bcc),
111
+ },
112
+ saveToSentItems: true,
113
+ }),
114
+ });
115
+ return { id: "" };
116
+ }
117
+ async tagMessage(accessToken, id, add, remove) {
118
+ const m = (await this.gfetch(accessToken, `/me/messages/${id}?$select=categories`));
119
+ const current = new Set(m.categories ?? []);
120
+ for (const c of add)
121
+ current.add(c);
122
+ for (const c of remove)
123
+ current.delete(c);
124
+ await this.gfetch(accessToken, `/me/messages/${id}`, {
125
+ method: "PATCH",
126
+ body: JSON.stringify({ categories: [...current] }),
127
+ });
128
+ }
129
+ async deleteMessage(accessToken, id) {
130
+ // DELETE moves the message to Deleted Items.
131
+ await this.gfetch(accessToken, `/me/messages/${id}`, { method: "DELETE" });
132
+ }
133
+ async listFolders(accessToken) {
134
+ const data = (await this.gfetch(accessToken, "/me/mailFolders?$top=50&$select=id,displayName,unreadItemCount"));
135
+ return (data.value ?? []).map((f) => ({ id: f.id, name: f.displayName, unreadCount: f.unreadItemCount }));
136
+ }
137
+ async getProfileEmail(accessToken) {
138
+ const data = (await this.gfetch(accessToken, "/me?$select=mail,userPrincipalName"));
139
+ return data.mail ?? data.userPrincipalName ?? "";
140
+ }
141
+ }
142
+ //# sourceMappingURL=outlook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outlook.js","sourceRoot":"","sources":["../../../src/services/email/outlook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { JaitDB } from "../../db/connection.js";
2
+ import type { UserSecretService } from "../user-secrets.js";
3
+ import { type OAuthClientCredentials } from "./oauth.js";
4
+ import type { EmailAccount, EmailFolder, EmailMessageFull, EmailMessageSummary, EmailProvider, ListMessagesOptions, SendMessageInput } from "./types.js";
5
+ export declare class EmailService {
6
+ private readonly db;
7
+ private readonly secrets;
8
+ private readonly clients;
9
+ private readonly pending;
10
+ constructor(db: JaitDB, secrets: UserSecretService);
11
+ /** Resolve OAuth app credentials: per-user secret first, then env. */
12
+ resolveCredentials(provider: EmailProvider, userId: string | null): OAuthClientCredentials | null;
13
+ /** Which providers are ready to connect (have OAuth app credentials). */
14
+ configuredProviders(userId: string | null): Record<EmailProvider, boolean>;
15
+ saveAppCredentials(userId: string | null, provider: EmailProvider, creds: OAuthClientCredentials): void;
16
+ startConnect(params: {
17
+ userId: string | null;
18
+ provider: EmailProvider;
19
+ redirectUri: string;
20
+ }): {
21
+ authUrl: string;
22
+ };
23
+ completeConnect(params: {
24
+ code: string;
25
+ state: string;
26
+ }): Promise<EmailAccount>;
27
+ listAccounts(userId: string | null): EmailAccount[];
28
+ getAccount(userId: string | null, accountId: string): EmailAccount | null;
29
+ /** Resolve an account by id, or fall back to the user's first account. */
30
+ resolveAccount(userId: string | null, accountId?: string): EmailAccount;
31
+ disconnect(userId: string | null, accountId: string): boolean;
32
+ private upsertAccount;
33
+ private storeTokens;
34
+ private loadTokens;
35
+ private findTokenSecretId;
36
+ private validAccessToken;
37
+ listMessages(userId: string | null, accountId: string | undefined, opts: ListMessagesOptions): Promise<{
38
+ account: EmailAccount;
39
+ messages: EmailMessageSummary[];
40
+ }>;
41
+ getMessage(userId: string | null, accountId: string | undefined, id: string): Promise<EmailMessageFull>;
42
+ sendMessage(userId: string | null, accountId: string | undefined, input: SendMessageInput): Promise<{
43
+ id: string;
44
+ }>;
45
+ tagMessage(userId: string | null, accountId: string | undefined, id: string, add: string[], remove: string[]): Promise<void>;
46
+ deleteMessage(userId: string | null, accountId: string | undefined, id: string): Promise<void>;
47
+ listFolders(userId: string | null, accountId: string | undefined): Promise<EmailFolder[]>;
48
+ private gcPending;
49
+ }
50
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/services/email/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,277 @@
1
+ // EmailService — orchestrates connected mailboxes: OAuth connect flow,
2
+ // account persistence, token storage/refresh, and provider-agnostic ops.
3
+ import { randomBytes } from "node:crypto";
4
+ import { and, desc, eq, isNull } from "drizzle-orm";
5
+ import { emailAccounts } from "../../db/schema.js";
6
+ import { uuidv7 } from "../../db/uuidv7.js";
7
+ import { GmailClient } from "./gmail.js";
8
+ import { OutlookClient } from "./outlook.js";
9
+ import { buildAuthorizeUrl, createPkce, envClientCredentials, exchangeCode, refreshTokens, } from "./oauth.js";
10
+ const TOKEN_SECRET_TYPE = "email-account";
11
+ const APP_SECRET_TYPE = "email-oauth-app";
12
+ const PROVIDERS = ["gmail", "outlook"];
13
+ export class EmailService {
14
+ db;
15
+ secrets;
16
+ clients = {
17
+ gmail: new GmailClient(),
18
+ outlook: new OutlookClient(),
19
+ };
20
+ pending = new Map();
21
+ constructor(db, secrets) {
22
+ this.db = db;
23
+ this.secrets = secrets;
24
+ }
25
+ // ── OAuth client credentials ───────────────────────────────────────
26
+ /** Resolve OAuth app credentials: per-user secret first, then env. */
27
+ resolveCredentials(provider, userId) {
28
+ const clientId = this.secrets.getValue(userId, APP_SECRET_TYPE, `${provider}-client-id`);
29
+ const clientSecret = this.secrets.getValue(userId, APP_SECRET_TYPE, `${provider}-client-secret`);
30
+ if (clientId && clientSecret)
31
+ return { clientId, clientSecret };
32
+ return envClientCredentials(provider);
33
+ }
34
+ /** Which providers are ready to connect (have OAuth app credentials). */
35
+ configuredProviders(userId) {
36
+ return {
37
+ gmail: !!this.resolveCredentials("gmail", userId),
38
+ outlook: !!this.resolveCredentials("outlook", userId),
39
+ };
40
+ }
41
+ saveAppCredentials(userId, provider, creds) {
42
+ this.secrets.save({
43
+ userId,
44
+ type: APP_SECRET_TYPE,
45
+ key: `${provider}-client-id`,
46
+ label: `${provider} OAuth client id`,
47
+ value: creds.clientId,
48
+ });
49
+ this.secrets.save({
50
+ userId,
51
+ type: APP_SECRET_TYPE,
52
+ key: `${provider}-client-secret`,
53
+ label: `${provider} OAuth client secret`,
54
+ value: creds.clientSecret,
55
+ });
56
+ }
57
+ // ── Connect flow ───────────────────────────────────────────────────
58
+ startConnect(params) {
59
+ if (!PROVIDERS.includes(params.provider)) {
60
+ throw new Error(`Unsupported email provider: ${params.provider}`);
61
+ }
62
+ const credentials = this.resolveCredentials(params.provider, params.userId);
63
+ if (!credentials) {
64
+ throw new Error(`${params.provider} is not configured. Add its OAuth client id/secret (env ` +
65
+ `${params.provider === "gmail" ? "GMAIL" : "OUTLOOK"}_OAUTH_CLIENT_ID/SECRET or in Email settings).`);
66
+ }
67
+ const state = randomBytes(24).toString("base64url");
68
+ const { verifier, challenge } = createPkce();
69
+ this.pending.set(state, {
70
+ userId: params.userId,
71
+ provider: params.provider,
72
+ codeVerifier: verifier,
73
+ redirectUri: params.redirectUri,
74
+ createdAt: Date.now(),
75
+ });
76
+ this.gcPending();
77
+ const authUrl = buildAuthorizeUrl({
78
+ provider: params.provider,
79
+ credentials,
80
+ redirectUri: params.redirectUri,
81
+ state,
82
+ codeChallenge: challenge,
83
+ });
84
+ return { authUrl };
85
+ }
86
+ async completeConnect(params) {
87
+ const pending = this.pending.get(params.state);
88
+ if (!pending)
89
+ throw new Error("OAuth state is invalid or expired. Please retry connecting.");
90
+ this.pending.delete(params.state);
91
+ const credentials = this.resolveCredentials(pending.provider, pending.userId);
92
+ if (!credentials)
93
+ throw new Error(`${pending.provider} OAuth credentials are no longer configured.`);
94
+ const tokens = await exchangeCode({
95
+ provider: pending.provider,
96
+ credentials,
97
+ redirectUri: pending.redirectUri,
98
+ code: params.code,
99
+ codeVerifier: pending.codeVerifier,
100
+ });
101
+ const email = await this.clients[pending.provider].getProfileEmail(tokens.accessToken);
102
+ if (!email)
103
+ throw new Error("Could not read the account email address from the provider.");
104
+ const account = this.upsertAccount(pending.userId, pending.provider, email);
105
+ this.storeTokens(account.id, pending.userId, tokens);
106
+ return account;
107
+ }
108
+ // ── Account persistence ────────────────────────────────────────────
109
+ listAccounts(userId) {
110
+ return this.db
111
+ .select()
112
+ .from(emailAccounts)
113
+ .where(userId ? eq(emailAccounts.userId, userId) : isNull(emailAccounts.userId))
114
+ .orderBy(desc(emailAccounts.updatedAt))
115
+ .all()
116
+ .map(rowToAccount);
117
+ }
118
+ getAccount(userId, accountId) {
119
+ const row = this.db
120
+ .select()
121
+ .from(emailAccounts)
122
+ .where(and(eq(emailAccounts.id, accountId), userId ? eq(emailAccounts.userId, userId) : isNull(emailAccounts.userId)))
123
+ .get();
124
+ return row ? rowToAccount(row) : null;
125
+ }
126
+ /** Resolve an account by id, or fall back to the user's first account. */
127
+ resolveAccount(userId, accountId) {
128
+ if (accountId) {
129
+ const acct = this.getAccount(userId, accountId);
130
+ if (!acct)
131
+ throw new Error(`Email account ${accountId} not found.`);
132
+ return acct;
133
+ }
134
+ const accounts = this.listAccounts(userId);
135
+ const first = accounts[0];
136
+ if (!first)
137
+ throw new Error("No email account connected. Connect Gmail or Outlook first.");
138
+ return first;
139
+ }
140
+ disconnect(userId, accountId) {
141
+ const acct = this.getAccount(userId, accountId);
142
+ if (!acct)
143
+ return false;
144
+ this.db.delete(emailAccounts).where(eq(emailAccounts.id, accountId)).run();
145
+ const secretId = this.findTokenSecretId(userId, accountId);
146
+ if (secretId)
147
+ this.secrets.delete(secretId, userId);
148
+ return true;
149
+ }
150
+ upsertAccount(userId, provider, email) {
151
+ const now = new Date().toISOString();
152
+ const existing = this.db
153
+ .select()
154
+ .from(emailAccounts)
155
+ .where(and(userId ? eq(emailAccounts.userId, userId) : isNull(emailAccounts.userId), eq(emailAccounts.provider, provider), eq(emailAccounts.email, email)))
156
+ .get();
157
+ if (existing) {
158
+ this.db
159
+ .update(emailAccounts)
160
+ .set({ status: "connected", error: null, updatedAt: now })
161
+ .where(eq(emailAccounts.id, existing.id))
162
+ .run();
163
+ return rowToAccount({ ...existing, status: "connected", error: null, updatedAt: now });
164
+ }
165
+ const id = uuidv7();
166
+ const row = {
167
+ id,
168
+ userId: userId ?? null,
169
+ provider,
170
+ email,
171
+ displayName: email,
172
+ status: "connected",
173
+ error: null,
174
+ createdAt: now,
175
+ updatedAt: now,
176
+ };
177
+ this.db.insert(emailAccounts).values(row).run();
178
+ return rowToAccount(row);
179
+ }
180
+ // ── Token storage / refresh ────────────────────────────────────────
181
+ storeTokens(accountId, userId, tokens) {
182
+ this.secrets.save({
183
+ userId,
184
+ type: TOKEN_SECRET_TYPE,
185
+ key: accountId,
186
+ label: `Email tokens (${accountId})`,
187
+ value: JSON.stringify(tokens),
188
+ });
189
+ }
190
+ loadTokens(accountId, userId) {
191
+ const raw = this.secrets.getValue(userId, TOKEN_SECRET_TYPE, accountId);
192
+ if (!raw)
193
+ return null;
194
+ try {
195
+ return JSON.parse(raw);
196
+ }
197
+ catch {
198
+ return null;
199
+ }
200
+ }
201
+ findTokenSecretId(userId, accountId) {
202
+ const list = this.secrets.list(userId, TOKEN_SECRET_TYPE);
203
+ return list.find((s) => s.key === accountId)?.id ?? null;
204
+ }
205
+ async validAccessToken(account) {
206
+ const tokens = this.loadTokens(account.id, account.userId);
207
+ if (!tokens)
208
+ throw new Error("No stored credentials for this account. Reconnect it.");
209
+ if (tokens.expiresAt - Date.now() > 60_000)
210
+ return tokens.accessToken;
211
+ if (!tokens.refreshToken)
212
+ throw new Error("Access token expired and no refresh token is available. Reconnect.");
213
+ const credentials = this.resolveCredentials(account.provider, account.userId);
214
+ if (!credentials)
215
+ throw new Error(`${account.provider} OAuth credentials are not configured.`);
216
+ const refreshed = await refreshTokens({
217
+ provider: account.provider,
218
+ credentials,
219
+ refreshToken: tokens.refreshToken,
220
+ });
221
+ this.storeTokens(account.id, account.userId, refreshed);
222
+ return refreshed.accessToken;
223
+ }
224
+ // ── Provider-agnostic operations ───────────────────────────────────
225
+ async listMessages(userId, accountId, opts) {
226
+ const account = this.resolveAccount(userId, accountId);
227
+ const token = await this.validAccessToken(account);
228
+ const messages = await this.clients[account.provider].listMessages(token, opts);
229
+ return { account, messages };
230
+ }
231
+ async getMessage(userId, accountId, id) {
232
+ const account = this.resolveAccount(userId, accountId);
233
+ const token = await this.validAccessToken(account);
234
+ return this.clients[account.provider].getMessage(token, id);
235
+ }
236
+ async sendMessage(userId, accountId, input) {
237
+ const account = this.resolveAccount(userId, accountId);
238
+ const token = await this.validAccessToken(account);
239
+ return this.clients[account.provider].sendMessage(token, input);
240
+ }
241
+ async tagMessage(userId, accountId, id, add, remove) {
242
+ const account = this.resolveAccount(userId, accountId);
243
+ const token = await this.validAccessToken(account);
244
+ await this.clients[account.provider].tagMessage(token, id, add, remove);
245
+ }
246
+ async deleteMessage(userId, accountId, id) {
247
+ const account = this.resolveAccount(userId, accountId);
248
+ const token = await this.validAccessToken(account);
249
+ await this.clients[account.provider].deleteMessage(token, id);
250
+ }
251
+ async listFolders(userId, accountId) {
252
+ const account = this.resolveAccount(userId, accountId);
253
+ const token = await this.validAccessToken(account);
254
+ return this.clients[account.provider].listFolders(token);
255
+ }
256
+ gcPending() {
257
+ const cutoff = Date.now() - 10 * 60_000;
258
+ for (const [state, p] of this.pending) {
259
+ if (p.createdAt < cutoff)
260
+ this.pending.delete(state);
261
+ }
262
+ }
263
+ }
264
+ function rowToAccount(row) {
265
+ return {
266
+ id: row.id,
267
+ userId: row.userId,
268
+ provider: row.provider,
269
+ email: row.email,
270
+ displayName: row.displayName,
271
+ status: row.status,
272
+ error: row.error,
273
+ createdAt: row.createdAt,
274
+ updatedAt: row.updatedAt,
275
+ };
276
+ }
277
+ //# sourceMappingURL=service.js.map