@asiyst/sdk 0.1.4 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -4
- package/dist/index.cjs +261 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -3
- package/dist/index.d.ts +44 -3
- package/dist/index.js +258 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -69,7 +69,7 @@ type AsiystEventName = keyof AsiystEventMap;
|
|
|
69
69
|
|
|
70
70
|
type AnchorPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
71
71
|
type AssistantMode = "guided" | "assist";
|
|
72
|
-
type ActionKind = "navigate" | "click" | "highlight" | "scroll" | "type" | "select" | "open-menu" | "open-modal" | "search" | "wait" | "explain" | "complete";
|
|
72
|
+
type ActionKind = "navigate" | "click" | "highlight" | "scroll" | "type" | "select" | "open" | "close" | "filter" | "open-menu" | "open-modal" | "search" | "wait" | "explain" | "complete";
|
|
73
73
|
type HighlightStyle = "spotlight" | "outline" | "glow" | "pointer" | "pulse" | "dim";
|
|
74
74
|
type ElementKind = "button" | "link" | "input" | "select" | "textarea" | "form" | "navigation" | "heading" | "menu" | "dialog" | "tab" | "card" | "search" | "section" | "other";
|
|
75
75
|
interface TargetRef {
|
|
@@ -102,6 +102,28 @@ interface BehaviorConfig {
|
|
|
102
102
|
voiceSpeed?: number;
|
|
103
103
|
speechBubble?: boolean;
|
|
104
104
|
}
|
|
105
|
+
interface AvatarRuntimeRule {
|
|
106
|
+
action?: string;
|
|
107
|
+
allowed?: boolean;
|
|
108
|
+
target?: string;
|
|
109
|
+
route?: string;
|
|
110
|
+
routes?: string[];
|
|
111
|
+
domain?: string;
|
|
112
|
+
domains?: string[];
|
|
113
|
+
enabled?: boolean;
|
|
114
|
+
sourceId?: string;
|
|
115
|
+
dataSourceId?: string;
|
|
116
|
+
}
|
|
117
|
+
interface AvatarDataSource {
|
|
118
|
+
id: string;
|
|
119
|
+
name?: string;
|
|
120
|
+
type?: string;
|
|
121
|
+
enabled?: boolean;
|
|
122
|
+
url?: string;
|
|
123
|
+
method?: string;
|
|
124
|
+
headers?: Record<string, string>;
|
|
125
|
+
credentials?: string;
|
|
126
|
+
}
|
|
105
127
|
interface ProjectConfig {
|
|
106
128
|
schemaVersion: number;
|
|
107
129
|
version: number;
|
|
@@ -116,6 +138,11 @@ interface ProjectConfig {
|
|
|
116
138
|
behavior: BehaviorConfig;
|
|
117
139
|
mode: AssistantMode;
|
|
118
140
|
allowedActions: ActionKind[];
|
|
141
|
+
allowedDomains: string[];
|
|
142
|
+
allowedRoutes: string[];
|
|
143
|
+
blockedRoutes: string[];
|
|
144
|
+
rules: AvatarRuntimeRule[];
|
|
145
|
+
dataSources: AvatarDataSource[];
|
|
119
146
|
elementSelectors: Record<string, string>;
|
|
120
147
|
}
|
|
121
148
|
interface Rect {
|
|
@@ -210,7 +237,7 @@ declare const Asiyst: {
|
|
|
210
237
|
off<K extends keyof AsiystEventMap>(event: K, handler: (payload: AsiystEventMap[K]) => void): void;
|
|
211
238
|
};
|
|
212
239
|
|
|
213
|
-
declare const SDK_VERSION
|
|
240
|
+
declare const SDK_VERSION: string;
|
|
214
241
|
|
|
215
242
|
declare class AsiystError extends Error {
|
|
216
243
|
readonly code: string;
|
|
@@ -299,4 +326,18 @@ declare function anchorToViewport(position: "bottom-right" | "bottom-left" | "to
|
|
|
299
326
|
y: number;
|
|
300
327
|
};
|
|
301
328
|
|
|
302
|
-
|
|
329
|
+
declare function validateWebsiteDomain(config: ProjectConfig, currentUrl: string): {
|
|
330
|
+
allowed: boolean;
|
|
331
|
+
reason?: string;
|
|
332
|
+
};
|
|
333
|
+
declare function canNavigateTo(config: ProjectConfig, targetUrl: string, currentUrl: string): {
|
|
334
|
+
allowed: boolean;
|
|
335
|
+
reason?: string;
|
|
336
|
+
};
|
|
337
|
+
declare function evaluateActionPermission(action: ActionKind, config: ProjectConfig, currentUrl: string, target?: string): {
|
|
338
|
+
allowed: boolean;
|
|
339
|
+
reason?: string;
|
|
340
|
+
};
|
|
341
|
+
declare function isActionAllowed(action: ActionKind, config: ProjectConfig, source: "developer" | "cloud" | "local", currentUrl?: string, target?: string): boolean;
|
|
342
|
+
|
|
343
|
+
export { type ActionKind, ActionNotAllowedError, Asiyst, AsiystError, type AsiystEventMap, type AsiystEventName, type AssistantMode, AuthenticationError, ConfigurationError, type HighlightStyle, type InitOptions, InitializationError, type MappedElement, NetworkError, type ProjectConfig, SDK_VERSION, type TargetInput, TargetNotFoundError, type TargetRef, type TaskDefinition, TaskExecutionError, TaskStatus, type TaskStep, type WebsiteMapSnapshot, type WorkflowDefinition, anchorToViewport, canNavigateTo, canTransition, computeAvatarDestination, evaluateActionPermission, fallbackConfig, isActionAllowed, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions, validateWebsiteDomain };
|
package/dist/index.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ type AsiystEventName = keyof AsiystEventMap;
|
|
|
69
69
|
|
|
70
70
|
type AnchorPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
71
71
|
type AssistantMode = "guided" | "assist";
|
|
72
|
-
type ActionKind = "navigate" | "click" | "highlight" | "scroll" | "type" | "select" | "open-menu" | "open-modal" | "search" | "wait" | "explain" | "complete";
|
|
72
|
+
type ActionKind = "navigate" | "click" | "highlight" | "scroll" | "type" | "select" | "open" | "close" | "filter" | "open-menu" | "open-modal" | "search" | "wait" | "explain" | "complete";
|
|
73
73
|
type HighlightStyle = "spotlight" | "outline" | "glow" | "pointer" | "pulse" | "dim";
|
|
74
74
|
type ElementKind = "button" | "link" | "input" | "select" | "textarea" | "form" | "navigation" | "heading" | "menu" | "dialog" | "tab" | "card" | "search" | "section" | "other";
|
|
75
75
|
interface TargetRef {
|
|
@@ -102,6 +102,28 @@ interface BehaviorConfig {
|
|
|
102
102
|
voiceSpeed?: number;
|
|
103
103
|
speechBubble?: boolean;
|
|
104
104
|
}
|
|
105
|
+
interface AvatarRuntimeRule {
|
|
106
|
+
action?: string;
|
|
107
|
+
allowed?: boolean;
|
|
108
|
+
target?: string;
|
|
109
|
+
route?: string;
|
|
110
|
+
routes?: string[];
|
|
111
|
+
domain?: string;
|
|
112
|
+
domains?: string[];
|
|
113
|
+
enabled?: boolean;
|
|
114
|
+
sourceId?: string;
|
|
115
|
+
dataSourceId?: string;
|
|
116
|
+
}
|
|
117
|
+
interface AvatarDataSource {
|
|
118
|
+
id: string;
|
|
119
|
+
name?: string;
|
|
120
|
+
type?: string;
|
|
121
|
+
enabled?: boolean;
|
|
122
|
+
url?: string;
|
|
123
|
+
method?: string;
|
|
124
|
+
headers?: Record<string, string>;
|
|
125
|
+
credentials?: string;
|
|
126
|
+
}
|
|
105
127
|
interface ProjectConfig {
|
|
106
128
|
schemaVersion: number;
|
|
107
129
|
version: number;
|
|
@@ -116,6 +138,11 @@ interface ProjectConfig {
|
|
|
116
138
|
behavior: BehaviorConfig;
|
|
117
139
|
mode: AssistantMode;
|
|
118
140
|
allowedActions: ActionKind[];
|
|
141
|
+
allowedDomains: string[];
|
|
142
|
+
allowedRoutes: string[];
|
|
143
|
+
blockedRoutes: string[];
|
|
144
|
+
rules: AvatarRuntimeRule[];
|
|
145
|
+
dataSources: AvatarDataSource[];
|
|
119
146
|
elementSelectors: Record<string, string>;
|
|
120
147
|
}
|
|
121
148
|
interface Rect {
|
|
@@ -210,7 +237,7 @@ declare const Asiyst: {
|
|
|
210
237
|
off<K extends keyof AsiystEventMap>(event: K, handler: (payload: AsiystEventMap[K]) => void): void;
|
|
211
238
|
};
|
|
212
239
|
|
|
213
|
-
declare const SDK_VERSION
|
|
240
|
+
declare const SDK_VERSION: string;
|
|
214
241
|
|
|
215
242
|
declare class AsiystError extends Error {
|
|
216
243
|
readonly code: string;
|
|
@@ -299,4 +326,18 @@ declare function anchorToViewport(position: "bottom-right" | "bottom-left" | "to
|
|
|
299
326
|
y: number;
|
|
300
327
|
};
|
|
301
328
|
|
|
302
|
-
|
|
329
|
+
declare function validateWebsiteDomain(config: ProjectConfig, currentUrl: string): {
|
|
330
|
+
allowed: boolean;
|
|
331
|
+
reason?: string;
|
|
332
|
+
};
|
|
333
|
+
declare function canNavigateTo(config: ProjectConfig, targetUrl: string, currentUrl: string): {
|
|
334
|
+
allowed: boolean;
|
|
335
|
+
reason?: string;
|
|
336
|
+
};
|
|
337
|
+
declare function evaluateActionPermission(action: ActionKind, config: ProjectConfig, currentUrl: string, target?: string): {
|
|
338
|
+
allowed: boolean;
|
|
339
|
+
reason?: string;
|
|
340
|
+
};
|
|
341
|
+
declare function isActionAllowed(action: ActionKind, config: ProjectConfig, source: "developer" | "cloud" | "local", currentUrl?: string, target?: string): boolean;
|
|
342
|
+
|
|
343
|
+
export { type ActionKind, ActionNotAllowedError, Asiyst, AsiystError, type AsiystEventMap, type AsiystEventName, type AssistantMode, AuthenticationError, ConfigurationError, type HighlightStyle, type InitOptions, InitializationError, type MappedElement, NetworkError, type ProjectConfig, SDK_VERSION, type TargetInput, TargetNotFoundError, type TargetRef, type TaskDefinition, TaskExecutionError, TaskStatus, type TaskStep, type WebsiteMapSnapshot, type WorkflowDefinition, anchorToViewport, canNavigateTo, canTransition, computeAvatarDestination, evaluateActionPermission, fallbackConfig, isActionAllowed, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions, validateWebsiteDomain };
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,9 @@ var ALL_ACTION_KINDS = [
|
|
|
57
57
|
"scroll",
|
|
58
58
|
"type",
|
|
59
59
|
"select",
|
|
60
|
+
"open",
|
|
61
|
+
"close",
|
|
62
|
+
"filter",
|
|
60
63
|
"open-menu",
|
|
61
64
|
"open-modal",
|
|
62
65
|
"search",
|
|
@@ -66,8 +69,8 @@ var ALL_ACTION_KINDS = [
|
|
|
66
69
|
];
|
|
67
70
|
|
|
68
71
|
// src/core/constants.ts
|
|
69
|
-
var SDK_VERSION = "0.1.
|
|
70
|
-
var DEFAULT_API_BASE_URL = "https://
|
|
72
|
+
var SDK_VERSION = "0.1.7" ;
|
|
73
|
+
var DEFAULT_API_BASE_URL = "https://nqhxpgsjofzqudyqkqib.supabase.co/functions/v1/api";
|
|
71
74
|
var CONFIG_SCHEMA_VERSION = 1;
|
|
72
75
|
var HOST_ELEMENT_ID = "asiyst-host";
|
|
73
76
|
var DATA_ATTR = "data-asiyst";
|
|
@@ -122,6 +125,19 @@ var ANCHORS = [
|
|
|
122
125
|
"top-right",
|
|
123
126
|
"top-left"
|
|
124
127
|
];
|
|
128
|
+
var PUBLIC_IDENTIFIER_PATTERN = /^(?=.{1,128}$)[A-Za-z0-9_][A-Za-z0-9_-]*$/;
|
|
129
|
+
function isValidPublicIdentifier(value) {
|
|
130
|
+
if (typeof value !== "string") return false;
|
|
131
|
+
const trimmed = value.trim();
|
|
132
|
+
if (!trimmed || trimmed.length > 128) return false;
|
|
133
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return PUBLIC_IDENTIFIER_PATTERN.test(trimmed);
|
|
137
|
+
}
|
|
138
|
+
function isValidProjectId(value) {
|
|
139
|
+
return isValidPublicIdentifier(value);
|
|
140
|
+
}
|
|
125
141
|
function isRecord(value) {
|
|
126
142
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
127
143
|
}
|
|
@@ -150,6 +166,88 @@ function parseAllowedActions(value, fallback) {
|
|
|
150
166
|
}
|
|
151
167
|
return next.length > 0 ? next : [...fallback];
|
|
152
168
|
}
|
|
169
|
+
function parseStringList(value) {
|
|
170
|
+
if (typeof value === "string") {
|
|
171
|
+
return value.split(",").map((item) => sanitizeText(item.trim(), 200)).filter(Boolean);
|
|
172
|
+
}
|
|
173
|
+
if (!Array.isArray(value)) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
return value.filter((item) => typeof item === "string").map((item) => sanitizeText(item.trim(), 200)).filter(Boolean);
|
|
177
|
+
}
|
|
178
|
+
function parseRule(value) {
|
|
179
|
+
if (!isRecord(value)) return null;
|
|
180
|
+
const action = typeof value.action === "string" ? sanitizeText(value.action, 64) : void 0;
|
|
181
|
+
if (action && !ALL_ACTION_KINDS.includes(action)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const routeList = parseStringList(value.routes ?? value.route);
|
|
185
|
+
const domainList = parseStringList(value.domains ?? value.domain);
|
|
186
|
+
const hasAllowedValue = typeof value.allowed === "boolean";
|
|
187
|
+
const hasAnySemanticField = hasAllowedValue || typeof value.target === "string" || typeof value.route === "string" || routeList.length > 0 || typeof value.domain === "string" || domainList.length > 0 || typeof value.enabled === "boolean" || typeof value.sourceId === "string" || typeof value.dataSourceId === "string";
|
|
188
|
+
if (!hasAnySemanticField) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const rule = {
|
|
192
|
+
action,
|
|
193
|
+
allowed: typeof value.allowed === "boolean" ? value.allowed : void 0,
|
|
194
|
+
target: typeof value.target === "string" ? sanitizeText(value.target, 200) : void 0,
|
|
195
|
+
route: typeof value.route === "string" ? sanitizeText(value.route, 200) : void 0,
|
|
196
|
+
routes: routeList,
|
|
197
|
+
domain: typeof value.domain === "string" ? sanitizeText(value.domain, 200) : void 0,
|
|
198
|
+
domains: domainList,
|
|
199
|
+
enabled: typeof value.enabled === "boolean" ? value.enabled : void 0,
|
|
200
|
+
sourceId: typeof value.sourceId === "string" ? sanitizeText(value.sourceId, 128) : void 0,
|
|
201
|
+
dataSourceId: typeof value.dataSourceId === "string" ? sanitizeText(value.dataSourceId, 128) : void 0
|
|
202
|
+
};
|
|
203
|
+
if (typeof value.allowed !== "undefined" && typeof value.allowed !== "boolean") {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return rule;
|
|
207
|
+
}
|
|
208
|
+
function parseRules(value) {
|
|
209
|
+
if (!Array.isArray(value)) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
const rules = [];
|
|
213
|
+
for (const entry of value) {
|
|
214
|
+
const rule = parseRule(entry);
|
|
215
|
+
if (rule) {
|
|
216
|
+
rules.push(rule);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return rules;
|
|
220
|
+
}
|
|
221
|
+
function parseDataSources(value) {
|
|
222
|
+
if (!Array.isArray(value)) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const next = [];
|
|
226
|
+
for (const entry of value) {
|
|
227
|
+
if (!isRecord(entry)) continue;
|
|
228
|
+
const id = typeof entry.id === "string" ? sanitizeText(entry.id, 128) : "";
|
|
229
|
+
if (!id) continue;
|
|
230
|
+
next.push({
|
|
231
|
+
id,
|
|
232
|
+
name: typeof entry.name === "string" ? sanitizeText(entry.name, 128) : void 0,
|
|
233
|
+
type: typeof entry.type === "string" ? sanitizeText(entry.type, 64) : void 0,
|
|
234
|
+
enabled: typeof entry.enabled === "boolean" ? entry.enabled : void 0,
|
|
235
|
+
url: typeof entry.url === "string" ? sanitizeText(entry.url, 400) : void 0,
|
|
236
|
+
method: typeof entry.method === "string" ? sanitizeText(entry.method, 20) : void 0,
|
|
237
|
+
headers: isRecord(entry.headers) ? (() => {
|
|
238
|
+
const safeHeaders = {};
|
|
239
|
+
for (const [key, value2] of Object.entries(entry.headers)) {
|
|
240
|
+
if (typeof value2 === "string") {
|
|
241
|
+
safeHeaders[sanitizeText(key, 64)] = sanitizeText(value2, 200);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return safeHeaders;
|
|
245
|
+
})() : void 0,
|
|
246
|
+
credentials: typeof entry.credentials === "string" ? "[redacted]" : void 0
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return next;
|
|
250
|
+
}
|
|
153
251
|
function parseTheme(value) {
|
|
154
252
|
if (!isRecord(value)) {
|
|
155
253
|
return {};
|
|
@@ -220,6 +318,11 @@ function fallbackConfig() {
|
|
|
220
318
|
},
|
|
221
319
|
mode: "guided",
|
|
222
320
|
allowedActions: ["navigate", "highlight", "scroll", "wait", "explain", "complete"],
|
|
321
|
+
allowedDomains: [],
|
|
322
|
+
allowedRoutes: [],
|
|
323
|
+
blockedRoutes: [],
|
|
324
|
+
rules: [],
|
|
325
|
+
dataSources: [],
|
|
223
326
|
elementSelectors: {}
|
|
224
327
|
};
|
|
225
328
|
}
|
|
@@ -243,21 +346,30 @@ function normalizeProjectConfig(raw) {
|
|
|
243
346
|
behavior: { ...base.behavior, ...parseBehavior(raw.behavior) },
|
|
244
347
|
mode: raw.mode === "assist" ? "assist" : "guided",
|
|
245
348
|
allowedActions: parseAllowedActions(raw.allowedActions, base.allowedActions),
|
|
349
|
+
allowedDomains: parseStringList(raw.allowedDomains ?? raw.allowed_domains),
|
|
350
|
+
allowedRoutes: parseStringList(raw.allowedRoutes ?? raw.allowed_routes),
|
|
351
|
+
blockedRoutes: parseStringList(raw.blockedRoutes ?? raw.blocked_routes),
|
|
352
|
+
rules: parseRules(raw.rules ?? raw.avatarRules),
|
|
353
|
+
dataSources: parseDataSources(raw.dataSources ?? raw.enabledDataSources),
|
|
246
354
|
elementSelectors: parseElementSelectors(raw.elementSelectors)
|
|
247
355
|
};
|
|
248
356
|
}
|
|
249
357
|
function validateInitOptions(options) {
|
|
250
|
-
|
|
251
|
-
|
|
358
|
+
const projectIdValue = typeof options.projectId === "string" ? options.projectId.trim() : "";
|
|
359
|
+
if (!projectIdValue) {
|
|
360
|
+
throw new ConfigurationError("Asiyst SDK: projectId is required.");
|
|
361
|
+
}
|
|
362
|
+
if (!isValidProjectId(projectIdValue)) {
|
|
363
|
+
throw new ConfigurationError("Asiyst SDK: projectId is invalid. Use a public Project ID.");
|
|
252
364
|
}
|
|
253
365
|
if (typeof options.publicKey !== "string" || !options.publicKey.trim()) {
|
|
254
366
|
throw new ConfigurationError("publicKey is required");
|
|
255
367
|
}
|
|
256
|
-
if (
|
|
368
|
+
if (projectIdValue.length > 128 || options.publicKey.trim().length > 256) {
|
|
257
369
|
throw new ConfigurationError("project credentials exceed allowed length");
|
|
258
370
|
}
|
|
259
371
|
return {
|
|
260
|
-
projectId:
|
|
372
|
+
projectId: projectIdValue,
|
|
261
373
|
publicKey: options.publicKey.trim()
|
|
262
374
|
};
|
|
263
375
|
}
|
|
@@ -980,7 +1092,17 @@ var TargetResolver = class {
|
|
|
980
1092
|
if (!best || bestScore < 2) {
|
|
981
1093
|
return null;
|
|
982
1094
|
}
|
|
983
|
-
return findElementByAsiystId(this.doc, best.id) ?? this.doc.querySelector(`[id="${best.id.replace(/^dom:/, "")}"]`);
|
|
1095
|
+
return findElementByAsiystId(this.doc, best.id) ?? this.doc.querySelector(`[id="${best.id.replace(/^dom:/, "")}"]`) ?? this.findMappedElement(best);
|
|
1096
|
+
}
|
|
1097
|
+
findMappedElement(mapped) {
|
|
1098
|
+
const candidates = Array.from(this.doc.querySelectorAll("a[href], button, input, select, textarea, form, nav, h1, h2, h3, [role]"));
|
|
1099
|
+
return candidates.find((element) => {
|
|
1100
|
+
if (!isElementVisible(element) || isDisabled(element)) return false;
|
|
1101
|
+
const text = normalize(`${element.getAttribute("aria-label") ?? ""} ${element.textContent ?? ""}`);
|
|
1102
|
+
const wanted = normalize(`${mapped.label} ${mapped.text}`);
|
|
1103
|
+
const role = normalize(element.getAttribute("role") ?? "");
|
|
1104
|
+
return wanted.length > 0 && (text.includes(wanted) || wanted.includes(text)) || mapped.role !== null && role === normalize(mapped.role);
|
|
1105
|
+
}) ?? null;
|
|
984
1106
|
}
|
|
985
1107
|
matchesMapped(item, element, ref) {
|
|
986
1108
|
if (ref.id && item.id === ref.id) {
|
|
@@ -1625,14 +1747,110 @@ var AvatarController = class {
|
|
|
1625
1747
|
};
|
|
1626
1748
|
|
|
1627
1749
|
// src/interaction/permissions.ts
|
|
1628
|
-
function
|
|
1750
|
+
function normalizeHost(host) {
|
|
1751
|
+
return host.replace(/^https?:\/\//i, "").replace(/\/$/, "").toLowerCase();
|
|
1752
|
+
}
|
|
1753
|
+
function matchesRoutePattern(pathname, pattern) {
|
|
1754
|
+
const candidate = pathname;
|
|
1755
|
+
const normalized = pattern.trim();
|
|
1756
|
+
if (!normalized) {
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
|
|
1760
|
+
return new RegExp(`^${escaped}$`, "i").test(candidate);
|
|
1761
|
+
}
|
|
1762
|
+
function isLikelyJavaScriptUrl(value) {
|
|
1763
|
+
const lower = value.trim().toLowerCase();
|
|
1764
|
+
return lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.includes("eval(") || lower.includes("new function") || lower.includes("document.cookie");
|
|
1765
|
+
}
|
|
1766
|
+
function validateWebsiteDomain(config, currentUrl) {
|
|
1767
|
+
const allowedDomains = config.allowedDomains.map((domain) => normalizeHost(domain));
|
|
1768
|
+
if (allowedDomains.length === 0) {
|
|
1769
|
+
return { allowed: true };
|
|
1770
|
+
}
|
|
1771
|
+
try {
|
|
1772
|
+
const parsed = new URL(currentUrl);
|
|
1773
|
+
const host = normalizeHost(parsed.host);
|
|
1774
|
+
const allowed = allowedDomains.some((domain) => host === domain || host.endsWith(`.${domain}`));
|
|
1775
|
+
if (!allowed) {
|
|
1776
|
+
return { allowed: false, reason: "DOMAIN_NOT_AUTHORIZED" };
|
|
1777
|
+
}
|
|
1778
|
+
return { allowed: true };
|
|
1779
|
+
} catch {
|
|
1780
|
+
return { allowed: false, reason: "DOMAIN_NOT_AUTHORIZED" };
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
function canNavigateTo(config, targetUrl, currentUrl) {
|
|
1784
|
+
if (!targetUrl || typeof targetUrl !== "string") {
|
|
1785
|
+
return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
|
|
1786
|
+
}
|
|
1787
|
+
if (isLikelyJavaScriptUrl(targetUrl)) {
|
|
1788
|
+
return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
|
|
1789
|
+
}
|
|
1790
|
+
let target;
|
|
1791
|
+
try {
|
|
1792
|
+
target = new URL(targetUrl, currentUrl);
|
|
1793
|
+
} catch {
|
|
1794
|
+
return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
|
|
1795
|
+
}
|
|
1796
|
+
if (!["http:", "https:"].includes(target.protocol)) {
|
|
1797
|
+
return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
|
|
1798
|
+
}
|
|
1799
|
+
const domainCheck = validateWebsiteDomain(config, currentUrl);
|
|
1800
|
+
if (!domainCheck.allowed) {
|
|
1801
|
+
return domainCheck;
|
|
1802
|
+
}
|
|
1803
|
+
const host = normalizeHost(target.host);
|
|
1804
|
+
const allowedDomains = config.allowedDomains.map((domain) => normalizeHost(domain));
|
|
1805
|
+
if (allowedDomains.length > 0 && !allowedDomains.some((domain) => host === domain || host.endsWith(`.${domain}`))) {
|
|
1806
|
+
return { allowed: false, reason: "DOMAIN_NOT_ALLOWED" };
|
|
1807
|
+
}
|
|
1808
|
+
const pathname = target.pathname || "/";
|
|
1809
|
+
if (config.blockedRoutes.some((route) => matchesRoutePattern(pathname, route))) {
|
|
1810
|
+
return { allowed: false, reason: "ROUTE_BLOCKED" };
|
|
1811
|
+
}
|
|
1812
|
+
if (config.allowedRoutes.length > 0 && !config.allowedRoutes.some((route) => matchesRoutePattern(pathname, route))) {
|
|
1813
|
+
return { allowed: false, reason: "ROUTE_NOT_PERMITTED" };
|
|
1814
|
+
}
|
|
1815
|
+
return { allowed: true };
|
|
1816
|
+
}
|
|
1817
|
+
function evaluateActionPermission(action, config, currentUrl, target) {
|
|
1818
|
+
const explicitRule = config.rules.find((rule) => rule.action === action && rule.enabled !== false);
|
|
1819
|
+
if (explicitRule && typeof explicitRule.allowed === "boolean") {
|
|
1820
|
+
if (!explicitRule.allowed) {
|
|
1821
|
+
return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
if (!config.allowedActions.includes(action)) {
|
|
1825
|
+
return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
|
|
1826
|
+
}
|
|
1827
|
+
if (action === "navigate") {
|
|
1828
|
+
if (!target) {
|
|
1829
|
+
return { allowed: true };
|
|
1830
|
+
}
|
|
1831
|
+
return canNavigateTo(config, target, currentUrl);
|
|
1832
|
+
}
|
|
1833
|
+
const allowedDomains = config.allowedDomains;
|
|
1834
|
+
if (allowedDomains.length > 0) {
|
|
1835
|
+
const domainCheck = validateWebsiteDomain(config, currentUrl);
|
|
1836
|
+
if (!domainCheck.allowed) {
|
|
1837
|
+
return domainCheck;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return { allowed: true };
|
|
1841
|
+
}
|
|
1842
|
+
function isActionAllowed(action, config, source, currentUrl, target) {
|
|
1629
1843
|
if (source === "developer") {
|
|
1630
1844
|
return true;
|
|
1631
1845
|
}
|
|
1632
|
-
|
|
1846
|
+
if (currentUrl) {
|
|
1847
|
+
return evaluateActionPermission(action, config, currentUrl, target).allowed;
|
|
1848
|
+
}
|
|
1849
|
+
return config.allowedActions.includes(action) && !config.rules.some((rule) => rule.action === action && rule.allowed === false);
|
|
1633
1850
|
}
|
|
1634
|
-
function assertActionAllowed(action, config, source) {
|
|
1635
|
-
|
|
1851
|
+
function assertActionAllowed(action, config, source, currentUrl, target) {
|
|
1852
|
+
const allowed = isActionAllowed(action, config, source, currentUrl, target);
|
|
1853
|
+
if (!allowed) {
|
|
1636
1854
|
throw new ActionNotAllowedError(`Action "${action}" is not permitted for this project`);
|
|
1637
1855
|
}
|
|
1638
1856
|
}
|
|
@@ -1662,7 +1880,12 @@ var InteractionEngine = class {
|
|
|
1662
1880
|
}
|
|
1663
1881
|
async execute(step, source) {
|
|
1664
1882
|
const config = this.getConfig();
|
|
1665
|
-
|
|
1883
|
+
const currentUrl = this.win.location.href;
|
|
1884
|
+
const target = step.url ?? (typeof step.target === "string" ? step.target : void 0);
|
|
1885
|
+
const allowed = this.evaluatePermission(step.action, config, currentUrl, target, source);
|
|
1886
|
+
if (!allowed.ok) {
|
|
1887
|
+
return { ok: false, waitedForUser: false, allowed: false, reason: allowed.reason ?? "ACTION_NOT_PERMITTED" };
|
|
1888
|
+
}
|
|
1666
1889
|
const wait = shouldWaitForUser(step.action, config.mode, step.waitForUser);
|
|
1667
1890
|
switch (step.action) {
|
|
1668
1891
|
case "explain":
|
|
@@ -1693,6 +1916,14 @@ var InteractionEngine = class {
|
|
|
1693
1916
|
throw new TaskExecutionError("Unsupported action");
|
|
1694
1917
|
}
|
|
1695
1918
|
}
|
|
1919
|
+
evaluatePermission(action, config, currentUrl, target, source) {
|
|
1920
|
+
try {
|
|
1921
|
+
assertActionAllowed(action, config, source, currentUrl, target);
|
|
1922
|
+
return { ok: true };
|
|
1923
|
+
} catch {
|
|
1924
|
+
return { ok: false, reason: "ACTION_NOT_PERMITTED" };
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1696
1927
|
async highlight(step, source, wait) {
|
|
1697
1928
|
if (!step.target) {
|
|
1698
1929
|
throw new TaskExecutionError("Highlight requires a target");
|
|
@@ -1876,6 +2107,11 @@ var TaskEngine = class {
|
|
|
1876
2107
|
}
|
|
1877
2108
|
this.move(TaskStatus.ActionStarted);
|
|
1878
2109
|
const result = await this.interaction.execute(step, source);
|
|
2110
|
+
if (!result.ok) {
|
|
2111
|
+
this.events.emit("asiyst:task:failed", { taskId: task.id, reason: result.reason ?? "ACTION_NOT_PERMITTED" });
|
|
2112
|
+
this.analytics.track("task_failed", { taskId: task.id });
|
|
2113
|
+
return this.finish(TaskStatus.Failed, result.reason ?? "ACTION_NOT_PERMITTED");
|
|
2114
|
+
}
|
|
1879
2115
|
if (result.waitedForUser && result.elementId) {
|
|
1880
2116
|
this.move(TaskStatus.WaitingForUser);
|
|
1881
2117
|
const clicked = await this.interaction.waitForElementClick(result.elementId, step.timeoutMs ?? 3e4);
|
|
@@ -2178,6 +2414,15 @@ var AsiystRuntime = class {
|
|
|
2178
2414
|
async start() {
|
|
2179
2415
|
this.events.emit("asiyst:initialized", { projectId: this.options.projectId });
|
|
2180
2416
|
const cfg = await isolateAsync(() => this.config.refresh(), this.config.get());
|
|
2417
|
+
const domainCheck = validateWebsiteDomain(cfg, window.location.href);
|
|
2418
|
+
if (!domainCheck.allowed) {
|
|
2419
|
+
this.connectionStatus = "disconnected";
|
|
2420
|
+
this.events.emit("asiyst:error", {
|
|
2421
|
+
code: "domain_not_authorized",
|
|
2422
|
+
message: "This website is not authorized for the current Asiyst project."
|
|
2423
|
+
});
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2181
2426
|
this.avatar.applyConfig(cfg);
|
|
2182
2427
|
this.avatar.show();
|
|
2183
2428
|
try {
|
|
@@ -2372,6 +2617,6 @@ var Asiyst = {
|
|
|
2372
2617
|
}
|
|
2373
2618
|
};
|
|
2374
2619
|
|
|
2375
|
-
export { ActionNotAllowedError, Asiyst, AsiystError, AuthenticationError, ConfigurationError, InitializationError, NetworkError, SDK_VERSION, TargetNotFoundError, TaskExecutionError, TaskStatus, anchorToViewport, canTransition, computeAvatarDestination, fallbackConfig, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions };
|
|
2620
|
+
export { ActionNotAllowedError, Asiyst, AsiystError, AuthenticationError, ConfigurationError, InitializationError, NetworkError, SDK_VERSION, TargetNotFoundError, TaskExecutionError, TaskStatus, anchorToViewport, canNavigateTo, canTransition, computeAvatarDestination, evaluateActionPermission, fallbackConfig, isActionAllowed, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions, validateWebsiteDomain };
|
|
2376
2621
|
//# sourceMappingURL=index.js.map
|
|
2377
2622
|
//# sourceMappingURL=index.js.map
|