@detiq/crawler 1.1.9 → 1.1.11

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 (83) hide show
  1. package/dist/accessibility-auditor.js +54 -74
  2. package/dist/ai-login.js +227 -249
  3. package/dist/auth-token-rotator.js +57 -67
  4. package/dist/auth.js +299 -336
  5. package/dist/browser-extension.js +104 -142
  6. package/dist/captcha-solver.js +98 -125
  7. package/dist/color-contrast.js +77 -89
  8. package/dist/content-chunker.js +126 -126
  9. package/dist/content-dedup.js +54 -71
  10. package/dist/cookie-consent.js +68 -83
  11. package/dist/cookie-jar.js +53 -60
  12. package/dist/crawler.js +4494 -5353
  13. package/dist/custom-js-injector.js +45 -61
  14. package/dist/dark-mode-capture.js +67 -81
  15. package/dist/dead-link-detector.js +47 -55
  16. package/dist/element-hash.js +8 -12
  17. package/dist/email-otp.js +154 -195
  18. package/dist/engine-router.js +87 -98
  19. package/dist/export/bulk-export.js +70 -55
  20. package/dist/export/vector-export.js +77 -105
  21. package/dist/extractors/advanced.js +249 -266
  22. package/dist/extractors/artifact-envelope.js +59 -58
  23. package/dist/failure-analysis-agent.js +37 -33
  24. package/dist/feature-flag-detector.js +51 -66
  25. package/dist/form-detector.js +55 -75
  26. package/dist/frontier.js +76 -101
  27. package/dist/gap-pass.js +68 -89
  28. package/dist/graphql-detector.js +62 -72
  29. package/dist/http-engine.js +282 -330
  30. package/dist/incremental-diff.js +54 -87
  31. package/dist/index.js +67 -28
  32. package/dist/input-fuzzer.js +93 -97
  33. package/dist/interactive-probe.js +715 -827
  34. package/dist/job-queue.js +2151 -2169
  35. package/dist/js-coverage.js +52 -56
  36. package/dist/js-router-detector.js +86 -117
  37. package/dist/language-detector.js +51 -63
  38. package/dist/link-classifier.js +32 -50
  39. package/dist/live-view.js +91 -108
  40. package/dist/locator-extractor.js +151 -187
  41. package/dist/logger.js +9 -7
  42. package/dist/markdown-extract.js +48 -61
  43. package/dist/native-crawler.js +780 -946
  44. package/dist/network-observer.js +135 -156
  45. package/dist/network-throttle.js +29 -40
  46. package/dist/openapi-generator.js +77 -92
  47. package/dist/pdf-extractor.js +72 -90
  48. package/dist/performance-metrics.js +80 -102
  49. package/dist/phash.js +125 -144
  50. package/dist/pii-scrubber.js +65 -68
  51. package/dist/playwright-recorder.js +70 -58
  52. package/dist/policies/crawl-safety.js +31 -34
  53. package/dist/profiles/domain-profile.js +130 -125
  54. package/dist/proxy.js +146 -172
  55. package/dist/rate-limiter.js +58 -65
  56. package/dist/redirect-tracker.js +60 -64
  57. package/dist/redis-frontier.js +116 -136
  58. package/dist/redis-queue.js +157 -144
  59. package/dist/response-cache.js +127 -140
  60. package/dist/responsive-detector.js +42 -46
  61. package/dist/retry-policy.js +46 -50
  62. package/dist/robots-txt.js +69 -83
  63. package/dist/screen-reader.js +45 -70
  64. package/dist/screenshot-masker.js +59 -77
  65. package/dist/seo-checker.js +88 -107
  66. package/dist/session-capture.js +192 -212
  67. package/dist/session-export.js +107 -133
  68. package/dist/sitemap-writer.js +38 -41
  69. package/dist/sitemap.js +141 -202
  70. package/dist/skyvern.js +123 -148
  71. package/dist/sqs-queue.js +42 -42
  72. package/dist/sso-token.js +74 -90
  73. package/dist/stealth/human-behavior.js +156 -173
  74. package/dist/telemetry.js +68 -82
  75. package/dist/test-executor.js +309 -335
  76. package/dist/tls-fingerprint.js +77 -88
  77. package/dist/url-pattern-completer.js +71 -90
  78. package/dist/visual-diff-verdict.js +23 -38
  79. package/dist/webhook-notifier.js +71 -69
  80. package/dist/websocket-detector.js +56 -68
  81. package/dist/worker.js +29 -38
  82. package/package.json +1 -1
  83. package/src/job-queue.ts +136 -0
package/dist/skyvern.js CHANGED
@@ -1,154 +1,129 @@
1
- /**
2
- * Skyvern Vision Fallback
3
- *
4
- * Skyvern combines computer vision + LLM reasoning to navigate sites that
5
- * defeat DOM-based approaches: CAPTCHAs, legacy apps, visual-only login flows,
6
- * government portals. It's the "break glass" option when Stagehand fails.
7
- *
8
- * Production setup (Phase 4+):
9
- * 1. Self-host: https://github.com/Skyvern-AI/skyvern
10
- * 2. Or use cloud: https://app.skyvern.com
11
- * 3. Set SKYVERN_API_URL + SKYVERN_API_KEY in .env
12
- *
13
- * Pricing note: vision tasks cost more per step than DOM-based Stagehand.
14
- * Use only when the fast path fails.
15
- */
16
- /**
17
- * Gap 5: Post-login navigation via Skyvern vision for apps where DOM analysis
18
- * cannot discover screens (canvas, WebGL, legacy Flash-replacement stacks).
19
- * Skyvern drives the browser via screenshot + LLM, returning visited page list.
20
- */
21
- export async function skyvernNavigate(startUrl, navigationGoals, tenantConfig, triggerReason) {
22
- const apiUrl = tenantConfig?.apiUrl || process.env.SKYVERN_API_URL;
23
- const apiKey = tenantConfig?.apiKey || process.env.SKYVERN_API_KEY;
24
- if (!apiUrl || !apiKey) {
25
- console.warn('[skyvern] skyvernNavigate: no API config — skipping post-login vision navigation');
26
- return { screenshotUrls: [], visitedUrls: [], status: 'failed' };
27
- }
28
- const visitedUrls = [];
29
- const screenshotUrls = [];
30
- const skyvernSteps = [];
31
- let lastTaskId;
32
- let totalDurationMs = 0;
33
- for (const goal of navigationGoals) {
34
- try {
35
- const taskStart = Date.now();
36
- const res = await fetch(`${apiUrl}/api/v1/tasks`, {
37
- method: 'POST',
38
- headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
39
- body: JSON.stringify({
40
- url: startUrl,
41
- navigation_goal: goal,
42
- ...(tenantConfig?.llmKey ? { llm_key: tenantConfig.llmKey } : process.env.SKYVERN_LLM_KEY ? { llm_key: process.env.SKYVERN_LLM_KEY } : {}),
43
- ...(tenantConfig?.llmApiKey ? { llm_api_key: tenantConfig.llmApiKey } : process.env.OPENAI_API_KEY ? { llm_api_key: process.env.OPENAI_API_KEY } : {}),
44
- }),
45
- });
46
- if (!res.ok)
47
- continue;
48
- const data = await res.json();
49
- const taskId = data.task_id;
50
- if (!taskId)
51
- continue;
52
- lastTaskId = taskId;
53
- // Poll for task completion (max 60s per goal)
54
- let done = false;
55
- for (let p = 0; p < 12 && !done; p++) {
56
- await new Promise((r) => setTimeout(r, 5000));
57
- const poll = await fetch(`${apiUrl}/api/v1/tasks/${taskId}`, {
58
- headers: { 'x-api-key': apiKey },
59
- }).catch(() => null);
60
- if (!poll?.ok)
61
- continue;
62
- const pollData = await poll.json();
63
- if (pollData.status === 'completed' || pollData.status === 'failed') {
64
- done = true;
65
- totalDurationMs += Date.now() - taskStart;
66
- if (pollData.status === 'completed') {
67
- if (pollData.output?.url)
68
- visitedUrls.push(pollData.output.url);
69
- if (pollData.output?.screenshot_url)
70
- screenshotUrls.push(pollData.output.screenshot_url);
71
- }
72
- // Fetch step-level detail from Skyvern
73
- try {
74
- const stepsRes = await fetch(`${apiUrl}/api/v1/tasks/${taskId}/steps`, {
75
- headers: { 'x-api-key': apiKey },
76
- }).catch(() => null);
77
- if (stepsRes?.ok) {
78
- const stepsData = await stepsRes.json();
79
- const stepsList = Array.isArray(stepsData) ? stepsData : (stepsData?.steps ?? []);
80
- for (const s of stepsList) {
81
- if (s.output?.screenshot_url)
82
- screenshotUrls.push(s.output.screenshot_url);
83
- skyvernSteps.push({
84
- order: s.step_order ?? s.order ?? 0,
85
- action_type: s.action_type ?? s.action ?? 'unknown',
86
- element_description: s.output?.element_description,
87
- reasoning: s.output?.llm_response?.action_description ?? s.output?.reasoning,
88
- success: s.status === 'completed',
89
- screenshot_url: s.output?.screenshot_url,
90
- error: s.status === 'failed' ? (s.output?.errors?.[0] ?? 'Step failed') : undefined,
91
- });
92
- }
93
- }
94
- }
95
- catch { /* non-fatal */ }
96
- }
1
+ async function skyvernNavigate(startUrl, navigationGoals, tenantConfig, triggerReason) {
2
+ const apiUrl = tenantConfig?.apiUrl || process.env.SKYVERN_API_URL;
3
+ const apiKey = tenantConfig?.apiKey || process.env.SKYVERN_API_KEY;
4
+ if (!apiUrl || !apiKey) {
5
+ console.warn("[skyvern] skyvernNavigate: no API config \u2014 skipping post-login vision navigation");
6
+ return { screenshotUrls: [], visitedUrls: [], status: "failed" };
7
+ }
8
+ const visitedUrls = [];
9
+ const screenshotUrls = [];
10
+ const skyvernSteps = [];
11
+ let lastTaskId;
12
+ let totalDurationMs = 0;
13
+ for (const goal of navigationGoals) {
14
+ try {
15
+ const taskStart = Date.now();
16
+ const res = await fetch(`${apiUrl}/api/v1/tasks`, {
17
+ method: "POST",
18
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
19
+ body: JSON.stringify({
20
+ url: startUrl,
21
+ navigation_goal: goal,
22
+ ...tenantConfig?.llmKey ? { llm_key: tenantConfig.llmKey } : process.env.SKYVERN_LLM_KEY ? { llm_key: process.env.SKYVERN_LLM_KEY } : {},
23
+ ...tenantConfig?.llmApiKey ? { llm_api_key: tenantConfig.llmApiKey } : process.env.OPENAI_API_KEY ? { llm_api_key: process.env.OPENAI_API_KEY } : {}
24
+ })
25
+ });
26
+ if (!res.ok) continue;
27
+ const data = await res.json();
28
+ const taskId = data.task_id;
29
+ if (!taskId) continue;
30
+ lastTaskId = taskId;
31
+ let done = false;
32
+ for (let p = 0; p < 12 && !done; p++) {
33
+ await new Promise((r) => setTimeout(r, 5e3));
34
+ const poll = await fetch(`${apiUrl}/api/v1/tasks/${taskId}`, {
35
+ headers: { "x-api-key": apiKey }
36
+ }).catch(() => null);
37
+ if (!poll?.ok) continue;
38
+ const pollData = await poll.json();
39
+ if (pollData.status === "completed" || pollData.status === "failed") {
40
+ done = true;
41
+ totalDurationMs += Date.now() - taskStart;
42
+ if (pollData.status === "completed") {
43
+ if (pollData.output?.url) visitedUrls.push(pollData.output.url);
44
+ if (pollData.output?.screenshot_url) screenshotUrls.push(pollData.output.screenshot_url);
45
+ }
46
+ try {
47
+ const stepsRes = await fetch(`${apiUrl}/api/v1/tasks/${taskId}/steps`, {
48
+ headers: { "x-api-key": apiKey }
49
+ }).catch(() => null);
50
+ if (stepsRes?.ok) {
51
+ const stepsData = await stepsRes.json();
52
+ const stepsList = Array.isArray(stepsData) ? stepsData : stepsData?.steps ?? [];
53
+ for (const s of stepsList) {
54
+ if (s.output?.screenshot_url) screenshotUrls.push(s.output.screenshot_url);
55
+ skyvernSteps.push({
56
+ order: s.step_order ?? s.order ?? 0,
57
+ action_type: s.action_type ?? s.action ?? "unknown",
58
+ element_description: s.output?.element_description,
59
+ reasoning: s.output?.llm_response?.action_description ?? s.output?.reasoning,
60
+ success: s.status === "completed",
61
+ screenshot_url: s.output?.screenshot_url,
62
+ error: s.status === "failed" ? s.output?.errors?.[0] ?? "Step failed" : void 0
63
+ });
64
+ }
97
65
  }
66
+ } catch {
67
+ }
98
68
  }
99
- catch (err) {
100
- console.warn(`[skyvern] Navigation goal failed: ${goal}`, err);
101
- }
69
+ }
70
+ } catch (err) {
71
+ console.warn(`[skyvern] Navigation goal failed: ${goal}`, err);
102
72
  }
103
- return {
104
- screenshotUrls,
105
- visitedUrls,
106
- status: visitedUrls.length > 0 ? 'success' : screenshotUrls.length > 0 ? 'partial' : 'failed',
107
- steps: skyvernSteps,
108
- taskId: lastTaskId,
109
- durationMs: totalDurationMs || undefined,
110
- triggerReason,
111
- };
73
+ }
74
+ return {
75
+ screenshotUrls,
76
+ visitedUrls,
77
+ status: visitedUrls.length > 0 ? "success" : screenshotUrls.length > 0 ? "partial" : "failed",
78
+ steps: skyvernSteps,
79
+ taskId: lastTaskId,
80
+ durationMs: totalDurationMs || void 0,
81
+ triggerReason
82
+ };
112
83
  }
113
- export async function skyvernFallback(task, tenantConfig) {
114
- const apiUrl = tenantConfig?.apiUrl || process.env.SKYVERN_API_URL;
115
- const apiKey = tenantConfig?.apiKey || process.env.SKYVERN_API_KEY;
116
- if (!apiUrl || !apiKey) {
117
- console.warn('[skyvern] Vision fallback disabled — set SKYVERN_API_URL + SKYVERN_API_KEY in .env to enable.', '\n[skyvern] Self-host: https://github.com/Skyvern-AI/skyvern');
118
- return 'failed';
119
- }
120
- try {
121
- const res = await fetch(`${apiUrl}/api/v1/tasks`, {
122
- method: 'POST',
123
- headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
124
- body: JSON.stringify({
125
- url: task.url,
126
- navigation_goal: task.taskDescription,
127
- ...(task.credentials && {
128
- navigation_payload: {
129
- username: task.credentials.username,
130
- password: task.credentials.password,
131
- ...(task.credentials.oneTimeCode && { otp: task.credentials.oneTimeCode }),
132
- },
133
- }),
134
- ...(tenantConfig?.llmKey ? { llm_key: tenantConfig.llmKey } : process.env.SKYVERN_LLM_KEY ? { llm_key: process.env.SKYVERN_LLM_KEY } : {}),
135
- ...(tenantConfig?.llmApiKey ? { llm_api_key: tenantConfig.llmApiKey } : process.env.OPENAI_API_KEY ? { llm_api_key: process.env.OPENAI_API_KEY } : {}),
136
- }),
137
- });
138
- if (!res.ok) {
139
- const body = await res.text().catch(() => '');
140
- console.error(`[skyvern] Task creation failed: ${res.status}`, body);
141
- return 'failed';
142
- }
143
- const data = await res.json();
144
- console.log(`[skyvern] Task queued: ${data.task_id}. Poll GET /api/v1/tasks/${data.task_id} for completion.`);
145
- // Full implementation: poll until status === "completed" | "failed".
146
- // For Phase 1 the login already happened in the browser context Skyvern manages,
147
- // so returning 'success' here means we can proceed in that browser session.
148
- return 'success';
149
- }
150
- catch (err) {
151
- console.error('[skyvern] Error:', err);
152
- return 'failed';
84
+ async function skyvernFallback(task, tenantConfig) {
85
+ const apiUrl = tenantConfig?.apiUrl || process.env.SKYVERN_API_URL;
86
+ const apiKey = tenantConfig?.apiKey || process.env.SKYVERN_API_KEY;
87
+ if (!apiUrl || !apiKey) {
88
+ console.warn(
89
+ "[skyvern] Vision fallback disabled \u2014 set SKYVERN_API_URL + SKYVERN_API_KEY in .env to enable.",
90
+ "\n[skyvern] Self-host: https://github.com/Skyvern-AI/skyvern"
91
+ );
92
+ return "failed";
93
+ }
94
+ try {
95
+ const res = await fetch(`${apiUrl}/api/v1/tasks`, {
96
+ method: "POST",
97
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
98
+ body: JSON.stringify({
99
+ url: task.url,
100
+ navigation_goal: task.taskDescription,
101
+ ...task.credentials && {
102
+ navigation_payload: {
103
+ username: task.credentials.username,
104
+ password: task.credentials.password,
105
+ ...task.credentials.oneTimeCode && { otp: task.credentials.oneTimeCode }
106
+ }
107
+ },
108
+ ...tenantConfig?.llmKey ? { llm_key: tenantConfig.llmKey } : process.env.SKYVERN_LLM_KEY ? { llm_key: process.env.SKYVERN_LLM_KEY } : {},
109
+ ...tenantConfig?.llmApiKey ? { llm_api_key: tenantConfig.llmApiKey } : process.env.OPENAI_API_KEY ? { llm_api_key: process.env.OPENAI_API_KEY } : {}
110
+ })
111
+ });
112
+ if (!res.ok) {
113
+ const body = await res.text().catch(() => "");
114
+ console.error(`[skyvern] Task creation failed: ${res.status}`, body);
115
+ return "failed";
153
116
  }
117
+ const data = await res.json();
118
+ console.log(`[skyvern] Task queued: ${data.task_id}. Poll GET /api/v1/tasks/${data.task_id} for completion.`);
119
+ return "success";
120
+ } catch (err) {
121
+ console.error("[skyvern] Error:", err);
122
+ return "failed";
123
+ }
154
124
  }
125
+ export {
126
+ skyvernFallback,
127
+ skyvernNavigate
128
+ };
129
+ //# sourceMappingURL=skyvern.js.map
package/dist/sqs-queue.js CHANGED
@@ -1,47 +1,47 @@
1
- import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
2
- const sqs = new SQSClient({ region: process.env.AWS_REGION ?? 'us-east-1' });
3
- export async function sendJobToSqs(jobId, tenantId, config) {
4
- const queueUrl = process.env.SQS_CRAWL_QUEUE_URL;
5
- if (!queueUrl)
6
- throw new Error('SQS_CRAWL_QUEUE_URL not configured');
7
- await sqs.send(new SendMessageCommand({
8
- QueueUrl: queueUrl,
9
- MessageBody: JSON.stringify({ jobId, tenantId, config }),
10
- }));
1
+ import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from "@aws-sdk/client-sqs";
2
+ const sqs = new SQSClient({ region: process.env.AWS_REGION ?? "us-east-1" });
3
+ async function sendJobToSqs(jobId, tenantId, config) {
4
+ const queueUrl = process.env.SQS_CRAWL_QUEUE_URL;
5
+ if (!queueUrl) throw new Error("SQS_CRAWL_QUEUE_URL not configured");
6
+ await sqs.send(new SendMessageCommand({
7
+ QueueUrl: queueUrl,
8
+ MessageBody: JSON.stringify({ jobId, tenantId, config })
9
+ }));
11
10
  }
12
- export async function startSqsWorker() {
13
- const queueUrl = process.env.SQS_CRAWL_QUEUE_URL;
14
- if (!queueUrl)
15
- throw new Error('SQS_CRAWL_QUEUE_URL not configured');
16
- const { processJob } = await import('./job-queue.js');
17
- console.log('[sqs-worker] starting, queue:', queueUrl);
18
- while (true) {
11
+ async function startSqsWorker() {
12
+ const queueUrl = process.env.SQS_CRAWL_QUEUE_URL;
13
+ if (!queueUrl) throw new Error("SQS_CRAWL_QUEUE_URL not configured");
14
+ const { processJob } = await import("./job-queue.js");
15
+ console.log("[sqs-worker] starting, queue:", queueUrl);
16
+ while (true) {
17
+ try {
18
+ const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({
19
+ QueueUrl: queueUrl,
20
+ WaitTimeSeconds: 20,
21
+ MaxNumberOfMessages: 2
22
+ }));
23
+ await Promise.all(Messages.map(async (msg) => {
24
+ let jobId = "?";
19
25
  try {
20
- const { Messages = [] } = await sqs.send(new ReceiveMessageCommand({
21
- QueueUrl: queueUrl,
22
- WaitTimeSeconds: 20,
23
- MaxNumberOfMessages: 2,
24
- }));
25
- await Promise.all(Messages.map(async (msg) => {
26
- let jobId = '?';
27
- try {
28
- const data = JSON.parse(msg.Body);
29
- jobId = data.jobId;
30
- await processJob(data.jobId, data.config);
31
- await sqs.send(new DeleteMessageCommand({
32
- QueueUrl: queueUrl,
33
- ReceiptHandle: msg.ReceiptHandle,
34
- }));
35
- }
36
- catch (err) {
37
- console.error(`[sqs-worker] job ${jobId} failed — leaving for SQS redrive:`, err.message);
38
- // Do NOT delete — SQS redrive policy + DLQ handles retry
39
- }
40
- }));
41
- }
42
- catch (err) {
43
- console.error('[sqs-worker] receive error:', err.message);
44
- await new Promise((r) => setTimeout(r, 2000));
26
+ const data = JSON.parse(msg.Body);
27
+ jobId = data.jobId;
28
+ await processJob(data.jobId, data.config);
29
+ await sqs.send(new DeleteMessageCommand({
30
+ QueueUrl: queueUrl,
31
+ ReceiptHandle: msg.ReceiptHandle
32
+ }));
33
+ } catch (err) {
34
+ console.error(`[sqs-worker] job ${jobId} failed \u2014 leaving for SQS redrive:`, err.message);
45
35
  }
36
+ }));
37
+ } catch (err) {
38
+ console.error("[sqs-worker] receive error:", err.message);
39
+ await new Promise((r) => setTimeout(r, 2e3));
46
40
  }
41
+ }
47
42
  }
43
+ export {
44
+ sendJobToSqs,
45
+ startSqsWorker
46
+ };
47
+ //# sourceMappingURL=sqs-queue.js.map
package/dist/sso-token.js CHANGED
@@ -1,92 +1,76 @@
1
- /**
2
- * SSO token retrieval for Okta and Azure AD.
3
- * Uses OAuth2 ROPC (Resource Owner Password Credentials) flow to get access tokens
4
- * without browser-based login.
5
- *
6
- * Note: ROPC is deprecated in OAuth 2.1 but still supported by Okta and Azure AD
7
- * for service accounts and automated testing scenarios.
8
- */
9
- /**
10
- * Get an access token using ROPC flow (username + password → token).
11
- * Returns the access token string.
12
- */
13
- export async function getSsoToken(config, username, password) {
14
- let tokenUrl;
15
- const params = {
16
- grant_type: 'password',
17
- username,
18
- password,
19
- scope: config.scope ?? 'openid profile',
20
- client_id: config.clientId,
21
- };
22
- if (config.clientSecret) {
23
- params.client_secret = config.clientSecret;
24
- }
25
- switch (config.provider) {
26
- case 'okta':
27
- tokenUrl = `https://${config.domain}/oauth2/v1/token`;
28
- break;
29
- case 'azure_ad':
30
- // domain here is the tenant ID (e.g. XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX or 'mycompany.onmicrosoft.com')
31
- tokenUrl = `https://login.microsoftonline.com/${config.domain}/oauth2/v2.0/token`;
32
- break;
33
- case 'generic_oidc':
34
- tokenUrl = config.domain.startsWith('http') ? config.domain : `https://${config.domain}/oauth2/token`;
35
- break;
36
- default:
37
- throw new Error(`Unknown SSO provider: ${config.provider}`);
38
- }
39
- const res = await fetch(tokenUrl, {
40
- method: 'POST',
41
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
42
- body: new URLSearchParams(params).toString(),
43
- });
44
- if (!res.ok) {
45
- const body = await res.text().catch(() => res.statusText);
46
- throw new Error(`SSO token request failed (${res.status}): ${body}`);
47
- }
48
- const data = await res.json();
49
- if (data.error)
50
- throw new Error(`SSO error: ${data.error} — ${data.error_description}`);
51
- if (!data.access_token)
52
- throw new Error('SSO response missing access_token');
53
- return data.access_token;
1
+ async function getSsoToken(config, username, password) {
2
+ let tokenUrl;
3
+ const params = {
4
+ grant_type: "password",
5
+ username,
6
+ password,
7
+ scope: config.scope ?? "openid profile",
8
+ client_id: config.clientId
9
+ };
10
+ if (config.clientSecret) {
11
+ params.client_secret = config.clientSecret;
12
+ }
13
+ switch (config.provider) {
14
+ case "okta":
15
+ tokenUrl = `https://${config.domain}/oauth2/v1/token`;
16
+ break;
17
+ case "azure_ad":
18
+ tokenUrl = `https://login.microsoftonline.com/${config.domain}/oauth2/v2.0/token`;
19
+ break;
20
+ case "generic_oidc":
21
+ tokenUrl = config.domain.startsWith("http") ? config.domain : `https://${config.domain}/oauth2/token`;
22
+ break;
23
+ default:
24
+ throw new Error(`Unknown SSO provider: ${config.provider}`);
25
+ }
26
+ const res = await fetch(tokenUrl, {
27
+ method: "POST",
28
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
29
+ body: new URLSearchParams(params).toString()
30
+ });
31
+ if (!res.ok) {
32
+ const body = await res.text().catch(() => res.statusText);
33
+ throw new Error(`SSO token request failed (${res.status}): ${body}`);
34
+ }
35
+ const data = await res.json();
36
+ if (data.error) throw new Error(`SSO error: ${data.error} \u2014 ${data.error_description}`);
37
+ if (!data.access_token) throw new Error("SSO response missing access_token");
38
+ return data.access_token;
54
39
  }
55
- /**
56
- * Get a client credentials token (no user — for service accounts).
57
- */
58
- export async function getSsoClientCredentialsToken(config) {
59
- if (!config.clientSecret)
60
- throw new Error('clientSecret required for client credentials flow');
61
- let tokenUrl;
62
- switch (config.provider) {
63
- case 'okta':
64
- tokenUrl = `https://${config.domain}/oauth2/v1/token`;
65
- break;
66
- case 'azure_ad':
67
- tokenUrl = `https://login.microsoftonline.com/${config.domain}/oauth2/v2.0/token`;
68
- break;
69
- default:
70
- tokenUrl = `https://${config.domain}/oauth2/token`;
71
- }
72
- const res = await fetch(tokenUrl, {
73
- method: 'POST',
74
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
75
- body: new URLSearchParams({
76
- grant_type: 'client_credentials',
77
- client_id: config.clientId,
78
- client_secret: config.clientSecret,
79
- scope: config.scope ?? 'openid profile',
80
- }).toString(),
81
- });
82
- if (!res.ok) {
83
- const body = await res.text().catch(() => res.statusText);
84
- throw new Error(`SSO client credentials failed (${res.status}): ${body}`);
85
- }
86
- const data = await res.json();
87
- if (data.error)
88
- throw new Error(`SSO error: ${data.error} — ${data.error_description}`);
89
- if (!data.access_token)
90
- throw new Error('SSO response missing access_token');
91
- return data.access_token;
40
+ async function getSsoClientCredentialsToken(config) {
41
+ if (!config.clientSecret) throw new Error("clientSecret required for client credentials flow");
42
+ let tokenUrl;
43
+ switch (config.provider) {
44
+ case "okta":
45
+ tokenUrl = `https://${config.domain}/oauth2/v1/token`;
46
+ break;
47
+ case "azure_ad":
48
+ tokenUrl = `https://login.microsoftonline.com/${config.domain}/oauth2/v2.0/token`;
49
+ break;
50
+ default:
51
+ tokenUrl = `https://${config.domain}/oauth2/token`;
52
+ }
53
+ const res = await fetch(tokenUrl, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
56
+ body: new URLSearchParams({
57
+ grant_type: "client_credentials",
58
+ client_id: config.clientId,
59
+ client_secret: config.clientSecret,
60
+ scope: config.scope ?? "openid profile"
61
+ }).toString()
62
+ });
63
+ if (!res.ok) {
64
+ const body = await res.text().catch(() => res.statusText);
65
+ throw new Error(`SSO client credentials failed (${res.status}): ${body}`);
66
+ }
67
+ const data = await res.json();
68
+ if (data.error) throw new Error(`SSO error: ${data.error} \u2014 ${data.error_description}`);
69
+ if (!data.access_token) throw new Error("SSO response missing access_token");
70
+ return data.access_token;
92
71
  }
72
+ export {
73
+ getSsoClientCredentialsToken,
74
+ getSsoToken
75
+ };
76
+ //# sourceMappingURL=sso-token.js.map