@sentry/junior 0.1.1 → 0.3.0

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.
@@ -1,304 +0,0 @@
1
- import {
2
- botConfig,
3
- escapeXml,
4
- generateAssistantReply,
5
- getOAuthProviderConfig,
6
- getSlackClient,
7
- getStateAdapter,
8
- getUserTokenStore,
9
- publishAppHomeView,
10
- resolveBaseUrl,
11
- truncateStatusText
12
- } from "./chunk-5LLCJPTH.js";
13
- import {
14
- logException,
15
- logInfo
16
- } from "./chunk-BBOVH5RF.js";
17
-
18
- // app/api/oauth/callback/[provider]/route.ts
19
- import { after } from "next/server";
20
- var runtime = "nodejs";
21
- function htmlErrorResponse(title, message, status) {
22
- const safeTitle = escapeXml(title);
23
- const html = `<!DOCTYPE html>
24
- <html>
25
- <head><title>${safeTitle}</title></head>
26
- <body style="font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
27
- <div style="text-align: center; max-width: 480px;">
28
- <h1>${safeTitle}</h1>
29
- <p>${message}</p>
30
- <p style="margin-top: 2rem; color: #666; font-size: 0.9em;">You can close this tab and return to Slack to try again.</p>
31
- </div>
32
- </body>
33
- </html>`;
34
- return new Response(html, {
35
- status,
36
- headers: { "Content-Type": "text/html; charset=utf-8" }
37
- });
38
- }
39
- async function postSlackMessage(channelId, threadTs, text) {
40
- try {
41
- await getSlackClient().chat.postMessage({ channel: channelId, thread_ts: threadTs, text });
42
- } catch {
43
- }
44
- }
45
- async function setAssistantStatus(channelId, threadTs, status) {
46
- try {
47
- await getSlackClient().assistant.threads.setStatus({ channel_id: channelId, thread_ts: threadTs, status });
48
- } catch {
49
- }
50
- }
51
- var STATUS_DEBOUNCE_MS = 1e3;
52
- function createDebouncedStatusPoster(channelId, threadTs) {
53
- let lastPostAt = 0;
54
- let currentStatus = "";
55
- let pendingStatus = null;
56
- let pendingTimer = null;
57
- let stopped = false;
58
- const flush = async () => {
59
- if (stopped || !pendingStatus) return;
60
- const status = pendingStatus;
61
- pendingStatus = null;
62
- pendingTimer = null;
63
- lastPostAt = Date.now();
64
- currentStatus = status;
65
- await setAssistantStatus(channelId, threadTs, status);
66
- };
67
- const post = async (status) => {
68
- if (stopped) return;
69
- const truncated = truncateStatusText(status);
70
- if (!truncated || truncated === currentStatus) return;
71
- const now = Date.now();
72
- const elapsed = now - lastPostAt;
73
- if (elapsed >= STATUS_DEBOUNCE_MS) {
74
- if (pendingTimer) {
75
- clearTimeout(pendingTimer);
76
- pendingTimer = null;
77
- }
78
- pendingStatus = null;
79
- lastPostAt = now;
80
- currentStatus = truncated;
81
- await setAssistantStatus(channelId, threadTs, truncated);
82
- return;
83
- }
84
- pendingStatus = truncated;
85
- if (!pendingTimer) {
86
- pendingTimer = setTimeout(() => {
87
- void flush();
88
- }, Math.max(1, STATUS_DEBOUNCE_MS - elapsed));
89
- }
90
- };
91
- post.stop = () => {
92
- stopped = true;
93
- if (pendingTimer) {
94
- clearTimeout(pendingTimer);
95
- pendingTimer = null;
96
- }
97
- pendingStatus = null;
98
- };
99
- return post;
100
- }
101
- function createReadOnlyConfigService(values) {
102
- const entries = Object.entries(values).map(([key, value]) => ({
103
- key,
104
- value,
105
- scope: "conversation",
106
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
107
- }));
108
- return {
109
- get: async (key) => entries.find((e) => e.key === key),
110
- set: async () => {
111
- throw new Error("Read-only configuration in resumed context");
112
- },
113
- unset: async () => false,
114
- list: async ({ prefix } = {}) => entries.filter((e) => !prefix || e.key.startsWith(prefix)),
115
- resolve: async (key) => values[key],
116
- resolveValues: async ({ keys, prefix } = {}) => {
117
- const filtered = {};
118
- for (const [key, value] of Object.entries(values)) {
119
- if (prefix && !key.startsWith(prefix)) continue;
120
- if (keys && !keys.includes(key)) continue;
121
- filtered[key] = value;
122
- }
123
- return filtered;
124
- }
125
- };
126
- }
127
- async function resumePendingMessage(stored) {
128
- if (!stored.pendingMessage || !stored.channelId || !stored.threadTs) return;
129
- const providerLabel = stored.provider.charAt(0).toUpperCase() + stored.provider.slice(1);
130
- await postSlackMessage(
131
- stored.channelId,
132
- stored.threadTs,
133
- `Your ${providerLabel} account is now connected. Processing your request...`
134
- );
135
- const postStatus = createDebouncedStatusPoster(stored.channelId, stored.threadTs);
136
- await setAssistantStatus(stored.channelId, stored.threadTs, "Thinking...");
137
- try {
138
- const reply = await generateAssistantReply(stored.pendingMessage, {
139
- assistant: { userName: botConfig.userName },
140
- requester: { userId: stored.userId },
141
- correlation: {
142
- channelId: stored.channelId,
143
- threadTs: stored.threadTs,
144
- requesterId: stored.userId
145
- },
146
- configuration: stored.configuration,
147
- channelConfiguration: stored.configuration ? createReadOnlyConfigService(stored.configuration) : void 0,
148
- onStatus: postStatus
149
- });
150
- postStatus.stop();
151
- if (reply.text) {
152
- await postSlackMessage(stored.channelId, stored.threadTs, reply.text);
153
- }
154
- logInfo(
155
- "oauth_callback_resume_complete",
156
- {},
157
- {
158
- "app.credential.provider": stored.provider,
159
- "app.ai.outcome": reply.diagnostics.outcome,
160
- "app.ai.tool_calls": reply.diagnostics.toolCalls.length
161
- },
162
- "Auto-resumed pending message after OAuth callback"
163
- );
164
- } catch (error) {
165
- postStatus.stop();
166
- logException(
167
- error,
168
- "oauth_callback_resume_failed",
169
- {},
170
- { "app.credential.provider": stored.provider },
171
- "Failed to auto-resume pending message after OAuth callback"
172
- );
173
- await postSlackMessage(
174
- stored.channelId,
175
- stored.threadTs,
176
- `I connected your account but hit an error processing your request. Please try \`${stored.pendingMessage}\` again.`
177
- );
178
- }
179
- }
180
- async function GET(request, context) {
181
- const { provider } = await context.params;
182
- const providerConfig = getOAuthProviderConfig(provider);
183
- if (!providerConfig) {
184
- return htmlErrorResponse("Unknown provider", "The OAuth provider in this link is not recognized.", 404);
185
- }
186
- const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1);
187
- const url = new URL(request.url);
188
- const errorParam = url.searchParams.get("error");
189
- const code = url.searchParams.get("code");
190
- const state = url.searchParams.get("state");
191
- if (errorParam) {
192
- if (state) {
193
- const cleanupAdapter = getStateAdapter();
194
- await cleanupAdapter.delete(`oauth-state:${state}`);
195
- }
196
- if (errorParam === "access_denied") {
197
- return htmlErrorResponse(
198
- "Authorization declined",
199
- `You declined the ${providerLabel} authorization request. Return to Slack and run the auth command again if you change your mind.`,
200
- 400
201
- );
202
- }
203
- return htmlErrorResponse(
204
- "Authorization failed",
205
- `${providerLabel} returned an error: ${escapeXml(errorParam)}. Return to Slack and try again.`,
206
- 400
207
- );
208
- }
209
- if (!code || !state) {
210
- return htmlErrorResponse("Invalid request", "This authorization link is missing required parameters.", 400);
211
- }
212
- const stateAdapter = getStateAdapter();
213
- const stateKey = `oauth-state:${state}`;
214
- const stored = await stateAdapter.get(stateKey);
215
- if (!stored) {
216
- return htmlErrorResponse(
217
- "Link expired",
218
- `This authorization link has expired (links are valid for 10 minutes). Return to Slack and ask to connect your ${providerLabel} account again, or retry your original command to get a new link.`,
219
- 400
220
- );
221
- }
222
- if (stored.provider !== provider) {
223
- return htmlErrorResponse("Provider mismatch", "This authorization link does not match the expected provider.", 400);
224
- }
225
- await stateAdapter.delete(stateKey);
226
- const clientId = process.env[providerConfig.clientIdEnv]?.trim();
227
- const clientSecret = process.env[providerConfig.clientSecretEnv]?.trim();
228
- if (!clientId || !clientSecret) {
229
- return htmlErrorResponse("Configuration error", "OAuth client credentials are not configured on the server.", 500);
230
- }
231
- const baseUrl = resolveBaseUrl();
232
- if (!baseUrl) {
233
- return htmlErrorResponse("Configuration error", "The server cannot determine its base URL.", 500);
234
- }
235
- const redirectUri = `${baseUrl}${providerConfig.callbackPath}`;
236
- let tokenResponse;
237
- try {
238
- tokenResponse = await fetch(providerConfig.tokenEndpoint, {
239
- method: "POST",
240
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
241
- body: new URLSearchParams({
242
- grant_type: "authorization_code",
243
- code,
244
- client_id: clientId,
245
- client_secret: clientSecret,
246
- redirect_uri: redirectUri
247
- })
248
- });
249
- } catch {
250
- return htmlErrorResponse("Connection failed", "Failed to exchange the authorization code. Please try again.", 500);
251
- }
252
- if (!tokenResponse.ok) {
253
- return htmlErrorResponse("Connection failed", "The token exchange with the provider failed. Please try again.", 500);
254
- }
255
- const tokenData = await tokenResponse.json();
256
- if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") {
257
- return htmlErrorResponse("Connection failed", "The provider returned an incomplete token response. Please try again.", 500);
258
- }
259
- const accessToken = tokenData.access_token;
260
- const refreshToken = tokenData.refresh_token;
261
- const expiresAt = Date.now() + tokenData.expires_in * 1e3;
262
- const userTokenStore = getUserTokenStore();
263
- await userTokenStore.set(stored.userId, provider, {
264
- accessToken,
265
- refreshToken,
266
- expiresAt
267
- });
268
- after(async () => {
269
- try {
270
- await publishAppHomeView(getSlackClient(), stored.userId, userTokenStore);
271
- } catch {
272
- }
273
- });
274
- if (stored.pendingMessage && stored.channelId && stored.threadTs) {
275
- after(() => resumePendingMessage(stored));
276
- } else if (stored.channelId && stored.threadTs) {
277
- after(async () => {
278
- await postSlackMessage(
279
- stored.channelId,
280
- stored.threadTs,
281
- `Your ${providerLabel} account is now connected. You can start using ${providerLabel} commands.`
282
- );
283
- });
284
- }
285
- const statusMessage = stored.pendingMessage ? "Your request is being processed in Slack." : "You can close this tab and return to Slack.";
286
- const html = `<!DOCTYPE html>
287
- <html>
288
- <head><title>${providerLabel} Connected</title></head>
289
- <body style="font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0;">
290
- <div style="text-align: center;">
291
- <h1>${providerLabel} account connected</h1>
292
- <p>${statusMessage}</p>
293
- </div>
294
- </body>
295
- </html>`;
296
- return new Response(html, {
297
- status: 200,
298
- headers: { "Content-Type": "text/html; charset=utf-8" }
299
- });
300
- }
301
- export {
302
- GET,
303
- runtime
304
- };