@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.
- package/dist/accessibility-auditor.js +54 -74
- package/dist/ai-login.js +227 -249
- package/dist/auth-token-rotator.js +57 -67
- package/dist/auth.js +299 -336
- package/dist/browser-extension.js +104 -142
- package/dist/captcha-solver.js +98 -125
- package/dist/color-contrast.js +77 -89
- package/dist/content-chunker.js +126 -126
- package/dist/content-dedup.js +54 -71
- package/dist/cookie-consent.js +68 -83
- package/dist/cookie-jar.js +53 -60
- package/dist/crawler.js +4494 -5353
- package/dist/custom-js-injector.js +45 -61
- package/dist/dark-mode-capture.js +67 -81
- package/dist/dead-link-detector.js +47 -55
- package/dist/element-hash.js +8 -12
- package/dist/email-otp.js +154 -195
- package/dist/engine-router.js +87 -98
- package/dist/export/bulk-export.js +70 -55
- package/dist/export/vector-export.js +77 -105
- package/dist/extractors/advanced.js +249 -266
- package/dist/extractors/artifact-envelope.js +59 -58
- package/dist/failure-analysis-agent.js +37 -33
- package/dist/feature-flag-detector.js +51 -66
- package/dist/form-detector.js +55 -75
- package/dist/frontier.js +76 -101
- package/dist/gap-pass.js +68 -89
- package/dist/graphql-detector.js +62 -72
- package/dist/http-engine.js +282 -330
- package/dist/incremental-diff.js +54 -87
- package/dist/index.js +67 -28
- package/dist/input-fuzzer.js +93 -97
- package/dist/interactive-probe.js +715 -827
- package/dist/job-queue.js +2151 -2169
- package/dist/js-coverage.js +52 -56
- package/dist/js-router-detector.js +86 -117
- package/dist/language-detector.js +51 -63
- package/dist/link-classifier.js +32 -50
- package/dist/live-view.js +91 -108
- package/dist/locator-extractor.js +151 -187
- package/dist/logger.js +9 -7
- package/dist/markdown-extract.js +48 -61
- package/dist/native-crawler.js +780 -946
- package/dist/network-observer.js +135 -156
- package/dist/network-throttle.js +29 -40
- package/dist/openapi-generator.js +77 -92
- package/dist/pdf-extractor.js +72 -90
- package/dist/performance-metrics.js +80 -102
- package/dist/phash.js +125 -144
- package/dist/pii-scrubber.js +65 -68
- package/dist/playwright-recorder.js +70 -58
- package/dist/policies/crawl-safety.js +31 -34
- package/dist/profiles/domain-profile.js +130 -125
- package/dist/proxy.js +146 -172
- package/dist/rate-limiter.js +58 -65
- package/dist/redirect-tracker.js +60 -64
- package/dist/redis-frontier.js +116 -136
- package/dist/redis-queue.js +157 -144
- package/dist/response-cache.js +127 -140
- package/dist/responsive-detector.js +42 -46
- package/dist/retry-policy.js +46 -50
- package/dist/robots-txt.js +69 -83
- package/dist/screen-reader.js +45 -70
- package/dist/screenshot-masker.js +59 -77
- package/dist/seo-checker.js +88 -107
- package/dist/session-capture.js +192 -212
- package/dist/session-export.js +107 -133
- package/dist/sitemap-writer.js +38 -41
- package/dist/sitemap.js +141 -202
- package/dist/skyvern.js +123 -148
- package/dist/sqs-queue.js +42 -42
- package/dist/sso-token.js +74 -90
- package/dist/stealth/human-behavior.js +156 -173
- package/dist/telemetry.js +68 -82
- package/dist/test-executor.js +309 -335
- package/dist/tls-fingerprint.js +77 -88
- package/dist/url-pattern-completer.js +71 -90
- package/dist/visual-diff-verdict.js +23 -38
- package/dist/webhook-notifier.js +71 -69
- package/dist/websocket-detector.js +56 -68
- package/dist/worker.js +29 -38
- package/package.json +1 -1
- package/src/job-queue.ts +136 -0
package/dist/skyvern.js
CHANGED
|
@@ -1,154 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.warn(`[skyvern] Navigation goal failed: ${goal}`, err);
|
|
102
72
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
2
|
-
const sqs = new SQSClient({ region: process.env.AWS_REGION ??
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|