@predicatelabs/sdk 0.99.9

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 (302) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +252 -0
  3. package/dist/actions.d.ts +185 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +1120 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/agent-runtime.d.ts +352 -0
  8. package/dist/agent-runtime.d.ts.map +1 -0
  9. package/dist/agent-runtime.js +1170 -0
  10. package/dist/agent-runtime.js.map +1 -0
  11. package/dist/agent.d.ts +164 -0
  12. package/dist/agent.d.ts.map +1 -0
  13. package/dist/agent.js +408 -0
  14. package/dist/agent.js.map +1 -0
  15. package/dist/asserts/expect.d.ts +159 -0
  16. package/dist/asserts/expect.d.ts.map +1 -0
  17. package/dist/asserts/expect.js +547 -0
  18. package/dist/asserts/expect.js.map +1 -0
  19. package/dist/asserts/index.d.ts +58 -0
  20. package/dist/asserts/index.d.ts.map +1 -0
  21. package/dist/asserts/index.js +70 -0
  22. package/dist/asserts/index.js.map +1 -0
  23. package/dist/asserts/query.d.ts +199 -0
  24. package/dist/asserts/query.d.ts.map +1 -0
  25. package/dist/asserts/query.js +288 -0
  26. package/dist/asserts/query.js.map +1 -0
  27. package/dist/backends/actions.d.ts +119 -0
  28. package/dist/backends/actions.d.ts.map +1 -0
  29. package/dist/backends/actions.js +291 -0
  30. package/dist/backends/actions.js.map +1 -0
  31. package/dist/backends/browser-use-adapter.d.ts +131 -0
  32. package/dist/backends/browser-use-adapter.d.ts.map +1 -0
  33. package/dist/backends/browser-use-adapter.js +219 -0
  34. package/dist/backends/browser-use-adapter.js.map +1 -0
  35. package/dist/backends/cdp-backend.d.ts +66 -0
  36. package/dist/backends/cdp-backend.d.ts.map +1 -0
  37. package/dist/backends/cdp-backend.js +273 -0
  38. package/dist/backends/cdp-backend.js.map +1 -0
  39. package/dist/backends/index.d.ts +80 -0
  40. package/dist/backends/index.d.ts.map +1 -0
  41. package/dist/backends/index.js +101 -0
  42. package/dist/backends/index.js.map +1 -0
  43. package/dist/backends/protocol.d.ts +156 -0
  44. package/dist/backends/protocol.d.ts.map +1 -0
  45. package/dist/backends/protocol.js +16 -0
  46. package/dist/backends/protocol.js.map +1 -0
  47. package/dist/backends/sentience-context.d.ts +143 -0
  48. package/dist/backends/sentience-context.d.ts.map +1 -0
  49. package/dist/backends/sentience-context.js +359 -0
  50. package/dist/backends/sentience-context.js.map +1 -0
  51. package/dist/backends/snapshot.d.ts +188 -0
  52. package/dist/backends/snapshot.d.ts.map +1 -0
  53. package/dist/backends/snapshot.js +360 -0
  54. package/dist/backends/snapshot.js.map +1 -0
  55. package/dist/browser.d.ts +154 -0
  56. package/dist/browser.d.ts.map +1 -0
  57. package/dist/browser.js +920 -0
  58. package/dist/browser.js.map +1 -0
  59. package/dist/canonicalization.d.ts +126 -0
  60. package/dist/canonicalization.d.ts.map +1 -0
  61. package/dist/canonicalization.js +161 -0
  62. package/dist/canonicalization.js.map +1 -0
  63. package/dist/captcha/strategies.d.ts +12 -0
  64. package/dist/captcha/strategies.d.ts.map +1 -0
  65. package/dist/captcha/strategies.js +43 -0
  66. package/dist/captcha/strategies.js.map +1 -0
  67. package/dist/captcha/types.d.ts +45 -0
  68. package/dist/captcha/types.d.ts.map +1 -0
  69. package/dist/captcha/types.js +12 -0
  70. package/dist/captcha/types.js.map +1 -0
  71. package/dist/cli.d.ts +5 -0
  72. package/dist/cli.d.ts.map +1 -0
  73. package/dist/cli.js +422 -0
  74. package/dist/cli.js.map +1 -0
  75. package/dist/conversational-agent.d.ts +123 -0
  76. package/dist/conversational-agent.d.ts.map +1 -0
  77. package/dist/conversational-agent.js +341 -0
  78. package/dist/conversational-agent.js.map +1 -0
  79. package/dist/cursor-policy.d.ts +41 -0
  80. package/dist/cursor-policy.d.ts.map +1 -0
  81. package/dist/cursor-policy.js +81 -0
  82. package/dist/cursor-policy.js.map +1 -0
  83. package/dist/debugger.d.ts +28 -0
  84. package/dist/debugger.d.ts.map +1 -0
  85. package/dist/debugger.js +107 -0
  86. package/dist/debugger.js.map +1 -0
  87. package/dist/expect.d.ts +16 -0
  88. package/dist/expect.d.ts.map +1 -0
  89. package/dist/expect.js +67 -0
  90. package/dist/expect.js.map +1 -0
  91. package/dist/failure-artifacts.d.ts +95 -0
  92. package/dist/failure-artifacts.d.ts.map +1 -0
  93. package/dist/failure-artifacts.js +805 -0
  94. package/dist/failure-artifacts.js.map +1 -0
  95. package/dist/generator.d.ts +16 -0
  96. package/dist/generator.d.ts.map +1 -0
  97. package/dist/generator.js +205 -0
  98. package/dist/generator.js.map +1 -0
  99. package/dist/index.d.ts +37 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +160 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/inspector.d.ts +13 -0
  104. package/dist/inspector.d.ts.map +1 -0
  105. package/dist/inspector.js +153 -0
  106. package/dist/inspector.js.map +1 -0
  107. package/dist/llm-provider.d.ts +144 -0
  108. package/dist/llm-provider.d.ts.map +1 -0
  109. package/dist/llm-provider.js +460 -0
  110. package/dist/llm-provider.js.map +1 -0
  111. package/dist/ordinal.d.ts +90 -0
  112. package/dist/ordinal.d.ts.map +1 -0
  113. package/dist/ordinal.js +249 -0
  114. package/dist/ordinal.js.map +1 -0
  115. package/dist/overlay.d.ts +63 -0
  116. package/dist/overlay.d.ts.map +1 -0
  117. package/dist/overlay.js +102 -0
  118. package/dist/overlay.js.map +1 -0
  119. package/dist/protocols/browser-protocol.d.ts +79 -0
  120. package/dist/protocols/browser-protocol.d.ts.map +1 -0
  121. package/dist/protocols/browser-protocol.js +9 -0
  122. package/dist/protocols/browser-protocol.js.map +1 -0
  123. package/dist/query.d.ts +66 -0
  124. package/dist/query.d.ts.map +1 -0
  125. package/dist/query.js +482 -0
  126. package/dist/query.js.map +1 -0
  127. package/dist/read.d.ts +47 -0
  128. package/dist/read.d.ts.map +1 -0
  129. package/dist/read.js +128 -0
  130. package/dist/read.js.map +1 -0
  131. package/dist/recorder.d.ts +44 -0
  132. package/dist/recorder.d.ts.map +1 -0
  133. package/dist/recorder.js +262 -0
  134. package/dist/recorder.js.map +1 -0
  135. package/dist/runtime-agent.d.ts +72 -0
  136. package/dist/runtime-agent.d.ts.map +1 -0
  137. package/dist/runtime-agent.js +357 -0
  138. package/dist/runtime-agent.js.map +1 -0
  139. package/dist/screenshot.d.ts +17 -0
  140. package/dist/screenshot.d.ts.map +1 -0
  141. package/dist/screenshot.js +40 -0
  142. package/dist/screenshot.js.map +1 -0
  143. package/dist/snapshot-diff.d.ts +23 -0
  144. package/dist/snapshot-diff.d.ts.map +1 -0
  145. package/dist/snapshot-diff.js +119 -0
  146. package/dist/snapshot-diff.js.map +1 -0
  147. package/dist/snapshot.d.ts +47 -0
  148. package/dist/snapshot.d.ts.map +1 -0
  149. package/dist/snapshot.js +358 -0
  150. package/dist/snapshot.js.map +1 -0
  151. package/dist/textSearch.d.ts +64 -0
  152. package/dist/textSearch.d.ts.map +1 -0
  153. package/dist/textSearch.js +113 -0
  154. package/dist/textSearch.js.map +1 -0
  155. package/dist/tools/context.d.ts +18 -0
  156. package/dist/tools/context.d.ts.map +1 -0
  157. package/dist/tools/context.js +40 -0
  158. package/dist/tools/context.js.map +1 -0
  159. package/dist/tools/defaults.d.ts +5 -0
  160. package/dist/tools/defaults.d.ts.map +1 -0
  161. package/dist/tools/defaults.js +368 -0
  162. package/dist/tools/defaults.js.map +1 -0
  163. package/dist/tools/filesystem.d.ts +12 -0
  164. package/dist/tools/filesystem.d.ts.map +1 -0
  165. package/dist/tools/filesystem.js +137 -0
  166. package/dist/tools/filesystem.js.map +1 -0
  167. package/dist/tools/index.d.ts +5 -0
  168. package/dist/tools/index.d.ts.map +1 -0
  169. package/dist/tools/index.js +15 -0
  170. package/dist/tools/index.js.map +1 -0
  171. package/dist/tools/registry.d.ts +38 -0
  172. package/dist/tools/registry.d.ts.map +1 -0
  173. package/dist/tools/registry.js +100 -0
  174. package/dist/tools/registry.js.map +1 -0
  175. package/dist/tracing/cloud-sink.d.ts +189 -0
  176. package/dist/tracing/cloud-sink.d.ts.map +1 -0
  177. package/dist/tracing/cloud-sink.js +1067 -0
  178. package/dist/tracing/cloud-sink.js.map +1 -0
  179. package/dist/tracing/index-schema.d.ts +231 -0
  180. package/dist/tracing/index-schema.d.ts.map +1 -0
  181. package/dist/tracing/index-schema.js +235 -0
  182. package/dist/tracing/index-schema.js.map +1 -0
  183. package/dist/tracing/index.d.ts +12 -0
  184. package/dist/tracing/index.d.ts.map +1 -0
  185. package/dist/tracing/index.js +28 -0
  186. package/dist/tracing/index.js.map +1 -0
  187. package/dist/tracing/indexer.d.ts +20 -0
  188. package/dist/tracing/indexer.d.ts.map +1 -0
  189. package/dist/tracing/indexer.js +347 -0
  190. package/dist/tracing/indexer.js.map +1 -0
  191. package/dist/tracing/jsonl-sink.d.ts +51 -0
  192. package/dist/tracing/jsonl-sink.d.ts.map +1 -0
  193. package/dist/tracing/jsonl-sink.js +329 -0
  194. package/dist/tracing/jsonl-sink.js.map +1 -0
  195. package/dist/tracing/sink.d.ts +25 -0
  196. package/dist/tracing/sink.d.ts.map +1 -0
  197. package/dist/tracing/sink.js +15 -0
  198. package/dist/tracing/sink.js.map +1 -0
  199. package/dist/tracing/tracer-factory.d.ts +102 -0
  200. package/dist/tracing/tracer-factory.d.ts.map +1 -0
  201. package/dist/tracing/tracer-factory.js +375 -0
  202. package/dist/tracing/tracer-factory.js.map +1 -0
  203. package/dist/tracing/tracer.d.ts +140 -0
  204. package/dist/tracing/tracer.d.ts.map +1 -0
  205. package/dist/tracing/tracer.js +336 -0
  206. package/dist/tracing/tracer.js.map +1 -0
  207. package/dist/tracing/types.d.ts +203 -0
  208. package/dist/tracing/types.d.ts.map +1 -0
  209. package/dist/tracing/types.js +8 -0
  210. package/dist/tracing/types.js.map +1 -0
  211. package/dist/types.d.ts +422 -0
  212. package/dist/types.d.ts.map +1 -0
  213. package/dist/types.js +6 -0
  214. package/dist/types.js.map +1 -0
  215. package/dist/utils/action-executor.d.ts +25 -0
  216. package/dist/utils/action-executor.d.ts.map +1 -0
  217. package/dist/utils/action-executor.js +121 -0
  218. package/dist/utils/action-executor.js.map +1 -0
  219. package/dist/utils/browser-evaluator.d.ts +76 -0
  220. package/dist/utils/browser-evaluator.d.ts.map +1 -0
  221. package/dist/utils/browser-evaluator.js +130 -0
  222. package/dist/utils/browser-evaluator.js.map +1 -0
  223. package/dist/utils/browser.d.ts +30 -0
  224. package/dist/utils/browser.d.ts.map +1 -0
  225. package/dist/utils/browser.js +75 -0
  226. package/dist/utils/browser.js.map +1 -0
  227. package/dist/utils/element-filter.d.ts +76 -0
  228. package/dist/utils/element-filter.d.ts.map +1 -0
  229. package/dist/utils/element-filter.js +195 -0
  230. package/dist/utils/element-filter.js.map +1 -0
  231. package/dist/utils/grid-utils.d.ts +37 -0
  232. package/dist/utils/grid-utils.d.ts.map +1 -0
  233. package/dist/utils/grid-utils.js +283 -0
  234. package/dist/utils/grid-utils.js.map +1 -0
  235. package/dist/utils/llm-interaction-handler.d.ts +41 -0
  236. package/dist/utils/llm-interaction-handler.d.ts.map +1 -0
  237. package/dist/utils/llm-interaction-handler.js +171 -0
  238. package/dist/utils/llm-interaction-handler.js.map +1 -0
  239. package/dist/utils/llm-response-builder.d.ts +56 -0
  240. package/dist/utils/llm-response-builder.d.ts.map +1 -0
  241. package/dist/utils/llm-response-builder.js +130 -0
  242. package/dist/utils/llm-response-builder.js.map +1 -0
  243. package/dist/utils/selector-utils.d.ts +12 -0
  244. package/dist/utils/selector-utils.d.ts.map +1 -0
  245. package/dist/utils/selector-utils.js +32 -0
  246. package/dist/utils/selector-utils.js.map +1 -0
  247. package/dist/utils/snapshot-event-builder.d.ts +28 -0
  248. package/dist/utils/snapshot-event-builder.d.ts.map +1 -0
  249. package/dist/utils/snapshot-event-builder.js +88 -0
  250. package/dist/utils/snapshot-event-builder.js.map +1 -0
  251. package/dist/utils/snapshot-processor.d.ts +27 -0
  252. package/dist/utils/snapshot-processor.d.ts.map +1 -0
  253. package/dist/utils/snapshot-processor.js +47 -0
  254. package/dist/utils/snapshot-processor.js.map +1 -0
  255. package/dist/utils/trace-event-builder.d.ts +122 -0
  256. package/dist/utils/trace-event-builder.d.ts.map +1 -0
  257. package/dist/utils/trace-event-builder.js +365 -0
  258. package/dist/utils/trace-event-builder.js.map +1 -0
  259. package/dist/utils/trace-file-manager.d.ts +70 -0
  260. package/dist/utils/trace-file-manager.d.ts.map +1 -0
  261. package/dist/utils/trace-file-manager.js +194 -0
  262. package/dist/utils/trace-file-manager.js.map +1 -0
  263. package/dist/utils/zod.d.ts +5 -0
  264. package/dist/utils/zod.d.ts.map +1 -0
  265. package/dist/utils/zod.js +80 -0
  266. package/dist/utils/zod.js.map +1 -0
  267. package/dist/utils.d.ts +8 -0
  268. package/dist/utils.d.ts.map +1 -0
  269. package/dist/utils.js +13 -0
  270. package/dist/utils.js.map +1 -0
  271. package/dist/verification.d.ts +194 -0
  272. package/dist/verification.d.ts.map +1 -0
  273. package/dist/verification.js +530 -0
  274. package/dist/verification.js.map +1 -0
  275. package/dist/vision-executor.d.ts +18 -0
  276. package/dist/vision-executor.d.ts.map +1 -0
  277. package/dist/vision-executor.js +60 -0
  278. package/dist/vision-executor.js.map +1 -0
  279. package/dist/visual-agent.d.ts +120 -0
  280. package/dist/visual-agent.d.ts.map +1 -0
  281. package/dist/visual-agent.js +796 -0
  282. package/dist/visual-agent.js.map +1 -0
  283. package/dist/wait.d.ts +35 -0
  284. package/dist/wait.d.ts.map +1 -0
  285. package/dist/wait.js +76 -0
  286. package/dist/wait.js.map +1 -0
  287. package/package.json +94 -0
  288. package/spec/README.md +72 -0
  289. package/spec/SNAPSHOT_V1.md +208 -0
  290. package/spec/sdk-types.md +259 -0
  291. package/spec/snapshot.schema.json +148 -0
  292. package/src/extension/background.js +104 -0
  293. package/src/extension/content.js +162 -0
  294. package/src/extension/injected_api.js +1399 -0
  295. package/src/extension/manifest.json +36 -0
  296. package/src/extension/pkg/README.md +1340 -0
  297. package/src/extension/pkg/package.json +15 -0
  298. package/src/extension/pkg/sentience_core.d.ts +51 -0
  299. package/src/extension/pkg/sentience_core.js +371 -0
  300. package/src/extension/pkg/sentience_core_bg.wasm +0 -0
  301. package/src/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
  302. package/src/extension/release.json +116 -0
@@ -0,0 +1,920 @@
1
+ "use strict";
2
+ /**
3
+ * Playwright browser harness with extension loading
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.PredicateBrowser = exports.SentienceBrowser = void 0;
40
+ exports.normalizeDomain = normalizeDomain;
41
+ exports.domainMatches = domainMatches;
42
+ exports.extractHost = extractHost;
43
+ exports.isDomainAllowed = isDomainAllowed;
44
+ const playwright_1 = require("playwright");
45
+ const path = __importStar(require("path"));
46
+ const fs = __importStar(require("fs"));
47
+ const os = __importStar(require("os"));
48
+ const url_1 = require("url");
49
+ const snapshot_1 = require("./snapshot");
50
+ function normalizeDomain(domain) {
51
+ const raw = domain.trim();
52
+ let host = raw;
53
+ if (raw.includes('://')) {
54
+ try {
55
+ host = new url_1.URL(raw).hostname || '';
56
+ }
57
+ catch {
58
+ host = raw;
59
+ }
60
+ }
61
+ else {
62
+ host = raw.split('/', 1)[0];
63
+ }
64
+ host = host.split(':', 1)[0];
65
+ return host.trim().toLowerCase().replace(/^\./, '');
66
+ }
67
+ function domainMatches(host, pattern) {
68
+ const hostNorm = normalizeDomain(host);
69
+ let pat = normalizeDomain(pattern);
70
+ if (pat.startsWith('*.')) {
71
+ pat = pat.slice(2);
72
+ }
73
+ return hostNorm === pat || hostNorm.endsWith(`.${pat}`);
74
+ }
75
+ function extractHost(url) {
76
+ let raw = url.trim();
77
+ if (!raw.includes('://')) {
78
+ raw = `https://${raw}`;
79
+ }
80
+ try {
81
+ const parsed = new url_1.URL(raw);
82
+ return parsed.hostname || null;
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ function isDomainAllowed(host, allowed, prohibited) {
89
+ if (!host)
90
+ return false;
91
+ if (prohibited && prohibited.length > 0) {
92
+ for (const pattern of prohibited) {
93
+ if (domainMatches(host, pattern)) {
94
+ return false;
95
+ }
96
+ }
97
+ }
98
+ if (allowed && allowed.length > 0) {
99
+ return allowed.some(pattern => domainMatches(host, pattern));
100
+ }
101
+ return true;
102
+ }
103
+ class SentienceBrowser {
104
+ /**
105
+ * Create a new SentienceBrowser instance
106
+ *
107
+ * @param apiKey - Optional API key for server-side processing (Pro/Enterprise tiers)
108
+ * @param apiUrl - Optional API URL (defaults to https://api.sentienceapi.com if apiKey provided)
109
+ * @param headless - Whether to run in headless mode (defaults to true in CI, false locally)
110
+ * @param proxy - Optional proxy server URL (e.g., 'http://user:pass@proxy.example.com:8080')
111
+ * @param userDataDir - Optional path to user data directory for persistent sessions
112
+ * @param storageState - Optional storage state to inject (cookies + localStorage)
113
+ * @param recordVideoDir - Optional directory path to save video recordings
114
+ * @param recordVideoSize - Optional video resolution as object with 'width' and 'height' keys
115
+ * @param viewport - Optional viewport size as object with 'width' and 'height' keys
116
+ * @param deviceScaleFactor - Optional device scale factor to emulate high-DPI (Retina) screens.
117
+ * Examples: 1.0 (default, standard DPI), 2.0 (Retina/high-DPI, like MacBook Pro), 3.0 (very high DPI)
118
+ * If undefined, defaults to 1.0 (standard DPI).
119
+ * @param allowedDomains - Optional list of allowed domains for navigation.
120
+ * @param prohibitedDomains - Optional list of prohibited domains for navigation.
121
+ * @param keepAlive - Keep browser alive after close() (no teardown).
122
+ */
123
+ constructor(apiKey, apiUrl, headless, proxy, userDataDir, storageState, recordVideoDir, recordVideoSize, viewport, deviceScaleFactor, allowedDomains, prohibitedDomains, keepAlive = false, permissionPolicy) {
124
+ this.context = null;
125
+ this.browser = null;
126
+ this.page = null;
127
+ this.extensionPath = null;
128
+ this.userDataDir = null;
129
+ this._apiKey = apiKey;
130
+ // Determine headless mode
131
+ if (headless === undefined) {
132
+ // Default to true in CI, false locally
133
+ const ci = process.env.CI?.toLowerCase();
134
+ this.headless = ci === 'true' || ci === '1' || ci === 'yes';
135
+ }
136
+ else {
137
+ this.headless = headless;
138
+ }
139
+ // Configure API URL
140
+ if (apiKey) {
141
+ this._apiUrl = apiUrl || 'https://api.sentienceapi.com';
142
+ }
143
+ else {
144
+ this._apiUrl = undefined;
145
+ }
146
+ // Support proxy from parameter or environment variable
147
+ // Only use env var if it's a valid non-empty string
148
+ const envProxy = process.env.SENTIENCE_PROXY;
149
+ this._proxy = proxy || (envProxy && envProxy.trim() ? envProxy : undefined);
150
+ // Auth injection support
151
+ this._userDataDir = userDataDir;
152
+ this._storageState = storageState;
153
+ // Video recording support
154
+ this._recordVideoDir = recordVideoDir;
155
+ this._recordVideoSize = recordVideoSize || { width: 1280, height: 800 };
156
+ // Viewport configuration
157
+ this._viewport = viewport || { width: 1280, height: 800 };
158
+ // Device scale factor for high-DPI emulation
159
+ this._deviceScaleFactor = deviceScaleFactor;
160
+ this._allowedDomains = allowedDomains;
161
+ this._prohibitedDomains = prohibitedDomains;
162
+ this._keepAlive = keepAlive;
163
+ this._permissionPolicy = permissionPolicy;
164
+ }
165
+ async applyPermissionPolicy(context, policy) {
166
+ const defaultPolicy = policy.default ?? 'clear';
167
+ if (defaultPolicy === 'clear' || defaultPolicy === 'deny') {
168
+ await context.clearPermissions();
169
+ }
170
+ if (policy.geolocation) {
171
+ await context.setGeolocation(policy.geolocation);
172
+ }
173
+ if (policy.autoGrant && policy.autoGrant.length > 0) {
174
+ const options = policy.origin ? { origin: policy.origin } : undefined;
175
+ await context.grantPermissions(policy.autoGrant, options);
176
+ }
177
+ }
178
+ async start() {
179
+ // 1. Resolve Extension Path
180
+ // Handle: src/extension (local dev), dist/extension (prod), or ../sentience-chrome (monorepo)
181
+ let extensionSource = '';
182
+ const candidates = [
183
+ // Production / Installed Package
184
+ path.resolve(__dirname, '../extension'),
185
+ path.resolve(__dirname, 'extension'),
186
+ // Local Monorepo Dev
187
+ path.resolve(__dirname, '../../sentience-chrome'),
188
+ path.resolve(__dirname, '../../../sentience-chrome'),
189
+ // CI Artifact
190
+ path.resolve(process.cwd(), 'extension'),
191
+ ];
192
+ for (const loc of candidates) {
193
+ if (fs.existsSync(path.join(loc, 'manifest.json'))) {
194
+ extensionSource = loc;
195
+ break;
196
+ }
197
+ }
198
+ if (!extensionSource) {
199
+ throw new Error(`Sentience extension not found. Checked:\n${candidates.map(c => `- ${c}`).join('\n')}\n` +
200
+ 'Ensure the extension is built/downloaded.');
201
+ }
202
+ // 2. Setup User Data Directory
203
+ if (this._userDataDir) {
204
+ // Use provided directory for persistent sessions
205
+ this.userDataDir = this._userDataDir;
206
+ if (!fs.existsSync(this.userDataDir)) {
207
+ fs.mkdirSync(this.userDataDir, { recursive: true });
208
+ }
209
+ }
210
+ else {
211
+ // Create temp directory (ephemeral, existing behavior)
212
+ this.userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sentience-ts-'));
213
+ }
214
+ this.extensionPath = path.join(this.userDataDir, 'extension');
215
+ // Copy extension to temp dir
216
+ this._copyRecursive(extensionSource, this.extensionPath);
217
+ // 3. Build Args
218
+ const args = [
219
+ `--disable-extensions-except=${this.extensionPath}`,
220
+ `--load-extension=${this.extensionPath}`,
221
+ '--disable-blink-features=AutomationControlled',
222
+ '--no-sandbox',
223
+ '--disable-infobars',
224
+ ];
225
+ // CRITICAL: Headless Extensions Support
226
+ // headless: true -> NO extensions.
227
+ // headless: false + args: '--headless=new' -> YES extensions.
228
+ if (this.headless) {
229
+ args.push('--headless=new');
230
+ }
231
+ // CRITICAL: WebRTC leak protection for datacenter usage with proxies
232
+ // Prevents WebRTC from leaking the real IP address even when using proxies
233
+ if (this._proxy) {
234
+ args.push('--disable-features=WebRtcHideLocalIpsWithMdns');
235
+ args.push('--force-webrtc-ip-handling-policy=disable_non_proxied_udp');
236
+ }
237
+ // 4. Parse proxy configuration
238
+ const proxyConfig = this.parseProxy(this._proxy);
239
+ // 5. Setup video recording directory if requested
240
+ if (this._recordVideoDir) {
241
+ if (!fs.existsSync(this._recordVideoDir)) {
242
+ fs.mkdirSync(this._recordVideoDir, { recursive: true });
243
+ }
244
+ console.log(`🎥 [Sentience] Recording video to: ${this._recordVideoDir}`);
245
+ console.log(` Resolution: ${this._recordVideoSize.width}x${this._recordVideoSize.height}`);
246
+ }
247
+ // 6. Launch Browser
248
+ const launchOptions = {
249
+ headless: false, // Must be false here, handled via args above
250
+ args: args,
251
+ viewport: this._viewport,
252
+ // Clean User-Agent to avoid "HeadlessChrome" detection
253
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
254
+ proxy: proxyConfig, // Pass proxy configuration
255
+ // CRITICAL: Ignore HTTPS errors when using proxy (proxies often use self-signed certs)
256
+ ignoreHTTPSErrors: proxyConfig !== undefined,
257
+ };
258
+ // Add device scale factor if configured
259
+ if (this._deviceScaleFactor !== undefined) {
260
+ launchOptions.deviceScaleFactor = this._deviceScaleFactor;
261
+ }
262
+ // Add video recording if configured
263
+ if (this._recordVideoDir) {
264
+ launchOptions.recordVideo = {
265
+ dir: this._recordVideoDir,
266
+ size: this._recordVideoSize,
267
+ };
268
+ }
269
+ this.context = await playwright_1.chromium.launchPersistentContext(this.userDataDir, launchOptions);
270
+ if (this._permissionPolicy) {
271
+ await this.applyPermissionPolicy(this.context, this._permissionPolicy);
272
+ }
273
+ this.page = this.context.pages()[0] || (await this.context.newPage());
274
+ // Inject storage state if provided (must be after context creation)
275
+ if (this._storageState) {
276
+ await this.injectStorageState(this._storageState);
277
+ }
278
+ // Apply context-level stealth patches (runs on every new page)
279
+ await this.context.addInitScript(() => {
280
+ // Early webdriver hiding - runs before any page script
281
+ // Use multiple strategies to completely hide webdriver
282
+ // Strategy 1: Try to delete it first
283
+ try {
284
+ delete navigator.webdriver;
285
+ }
286
+ catch {
287
+ // Property might not be deletable
288
+ }
289
+ // Strategy 2: Redefine to return undefined and hide from enumeration
290
+ Object.defineProperty(navigator, 'webdriver', {
291
+ get: () => undefined,
292
+ configurable: true,
293
+ enumerable: false,
294
+ writable: false,
295
+ });
296
+ // Strategy 3: Override 'in' operator check
297
+ const originalHasOwnProperty = Object.prototype.hasOwnProperty;
298
+ Object.prototype.hasOwnProperty = function (prop) {
299
+ if (this === navigator && (prop === 'webdriver' || prop === 'Webdriver')) {
300
+ return false;
301
+ }
302
+ return originalHasOwnProperty.call(this, prop);
303
+ };
304
+ });
305
+ // 5. Apply Comprehensive Stealth Patches
306
+ // Use both CDP (earlier) and addInitScript (backup) for maximum coverage
307
+ // Strategy A: Use CDP to inject at the earliest possible moment
308
+ const client = await this.page.context().newCDPSession(this.page);
309
+ await client.send('Page.addScriptToEvaluateOnNewDocument', {
310
+ source: `
311
+ // Aggressive webdriver hiding - must run before ANY page script
312
+ Object.defineProperty(navigator, 'webdriver', {
313
+ get: () => undefined,
314
+ configurable: true,
315
+ enumerable: false
316
+ });
317
+
318
+ // Override Object.getOwnPropertyDescriptor
319
+ const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
320
+ Object.getOwnPropertyDescriptor = function(obj, prop) {
321
+ if (obj === navigator && (prop === 'webdriver' || prop === 'Webdriver')) {
322
+ return undefined;
323
+ }
324
+ return originalGetOwnPropertyDescriptor(obj, prop);
325
+ };
326
+
327
+ // Override Object.keys
328
+ const originalKeys = Object.keys;
329
+ Object.keys = function(obj) {
330
+ const keys = originalKeys(obj);
331
+ if (obj === navigator) {
332
+ return keys.filter(k => k !== 'webdriver' && k !== 'Webdriver');
333
+ }
334
+ return keys;
335
+ };
336
+
337
+ // Override Object.getOwnPropertyNames
338
+ const originalGetOwnPropertyNames = Object.getOwnPropertyNames;
339
+ Object.getOwnPropertyNames = function(obj) {
340
+ const names = originalGetOwnPropertyNames(obj);
341
+ if (obj === navigator) {
342
+ return names.filter(n => n !== 'webdriver' && n !== 'Webdriver');
343
+ }
344
+ return names;
345
+ };
346
+
347
+ // Override 'in' operator check
348
+ const originalHasOwnProperty = Object.prototype.hasOwnProperty;
349
+ Object.prototype.hasOwnProperty = function(prop) {
350
+ if (this === navigator && (prop === 'webdriver' || prop === 'Webdriver')) {
351
+ return false;
352
+ }
353
+ return originalHasOwnProperty.call(this, prop);
354
+ };
355
+ `,
356
+ });
357
+ // Strategy B: Also use addInitScript as backup (runs after CDP but before page scripts)
358
+ await this.page.addInitScript(() => {
359
+ // 1. Hide navigator.webdriver (comprehensive approach for advanced detection)
360
+ // Advanced detection checks for property descriptor, so we need multiple strategies
361
+ try {
362
+ // Strategy 1: Try to delete the property
363
+ delete navigator.webdriver;
364
+ }
365
+ catch {
366
+ // Property might not be deletable, continue with redefine
367
+ }
368
+ // Strategy 2: Redefine to return undefined (better than false)
369
+ // Also set enumerable: false to hide from Object.keys() checks
370
+ Object.defineProperty(navigator, 'webdriver', {
371
+ get: () => undefined,
372
+ configurable: true,
373
+ enumerable: false,
374
+ });
375
+ // Strategy 3: Override Object.getOwnPropertyDescriptor only for navigator.webdriver
376
+ // This prevents advanced detection that checks the property descriptor
377
+ const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
378
+ Object.getOwnPropertyDescriptor = function (obj, prop) {
379
+ if (obj === navigator && (prop === 'webdriver' || prop === 'Webdriver')) {
380
+ return undefined;
381
+ }
382
+ return originalGetOwnPropertyDescriptor(obj, prop);
383
+ };
384
+ // Strategy 4: Hide from Object.keys() and Object.getOwnPropertyNames()
385
+ const originalKeys = Object.keys;
386
+ Object.keys = function (obj) {
387
+ const keys = originalKeys(obj);
388
+ if (obj === navigator) {
389
+ return keys.filter(k => k !== 'webdriver' && k !== 'Webdriver');
390
+ }
391
+ return keys;
392
+ };
393
+ // Strategy 5: Hide from Object.getOwnPropertyNames()
394
+ const originalGetOwnPropertyNames = Object.getOwnPropertyNames;
395
+ Object.getOwnPropertyNames = function (obj) {
396
+ const names = originalGetOwnPropertyNames(obj);
397
+ if (obj === navigator) {
398
+ return names.filter(n => n !== 'webdriver' && n !== 'Webdriver');
399
+ }
400
+ return names;
401
+ };
402
+ // Strategy 6: Override hasOwnProperty to hide from 'in' operator checks
403
+ const originalHasOwnProperty = Object.prototype.hasOwnProperty;
404
+ Object.prototype.hasOwnProperty = function (prop) {
405
+ if (this === navigator && (prop === 'webdriver' || prop === 'Webdriver')) {
406
+ return false;
407
+ }
408
+ return originalHasOwnProperty.call(this, prop);
409
+ };
410
+ // 2. Inject window.chrome object (required for Chrome detection)
411
+ if (typeof window.chrome === 'undefined') {
412
+ window.chrome = {
413
+ runtime: {},
414
+ loadTimes: function () { },
415
+ csi: function () { },
416
+ app: {},
417
+ };
418
+ }
419
+ // 3. Patch navigator.plugins (should have length > 0)
420
+ // Only patch if plugins array is empty (headless mode issue)
421
+ const originalPlugins = navigator.plugins;
422
+ if (originalPlugins.length === 0) {
423
+ // Create a PluginArray-like object with minimal plugins
424
+ const fakePlugins = [
425
+ {
426
+ name: 'Chrome PDF Plugin',
427
+ filename: 'internal-pdf-viewer',
428
+ description: 'Portable Document Format',
429
+ length: 1,
430
+ item: function () {
431
+ return null;
432
+ },
433
+ namedItem: function () {
434
+ return null;
435
+ },
436
+ },
437
+ {
438
+ name: 'Chrome PDF Viewer',
439
+ filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
440
+ description: '',
441
+ length: 0,
442
+ item: function () {
443
+ return null;
444
+ },
445
+ namedItem: function () {
446
+ return null;
447
+ },
448
+ },
449
+ {
450
+ name: 'Native Client',
451
+ filename: 'internal-nacl-plugin',
452
+ description: '',
453
+ length: 0,
454
+ item: function () {
455
+ return null;
456
+ },
457
+ namedItem: function () {
458
+ return null;
459
+ },
460
+ },
461
+ ];
462
+ // Create PluginArray-like object (array-like but not a real array)
463
+ // This needs to behave like the real PluginArray for detection to pass
464
+ const pluginArray = {};
465
+ fakePlugins.forEach((plugin, index) => {
466
+ Object.defineProperty(pluginArray, index.toString(), {
467
+ value: plugin,
468
+ enumerable: true,
469
+ configurable: true,
470
+ });
471
+ });
472
+ Object.defineProperty(pluginArray, 'length', {
473
+ value: fakePlugins.length,
474
+ enumerable: false,
475
+ configurable: false,
476
+ });
477
+ pluginArray.item = function (index) {
478
+ return this[index] || null;
479
+ };
480
+ pluginArray.namedItem = function (name) {
481
+ for (let i = 0; i < this.length; i++) {
482
+ if (this[i] && this[i].name === name)
483
+ return this[i];
484
+ }
485
+ return null;
486
+ };
487
+ // Make it iterable (for for...of loops)
488
+ pluginArray[Symbol.iterator] = function* () {
489
+ for (let i = 0; i < this.length; i++) {
490
+ yield this[i];
491
+ }
492
+ };
493
+ // Make it array-like for Array.from() and spread
494
+ Object.setPrototypeOf(pluginArray, Object.create(null));
495
+ Object.defineProperty(navigator, 'plugins', {
496
+ get: () => pluginArray,
497
+ configurable: true,
498
+ enumerable: true,
499
+ });
500
+ }
501
+ // 4. Ensure navigator.languages exists and has values
502
+ if (!navigator.languages || navigator.languages.length === 0) {
503
+ Object.defineProperty(navigator, 'languages', {
504
+ get: () => ['en-US', 'en'],
505
+ configurable: true,
506
+ });
507
+ }
508
+ // 5. Patch permissions API (should exist)
509
+ if (!navigator.permissions) {
510
+ navigator.permissions = {
511
+ query: (_parameters) => {
512
+ return { state: 'granted', onchange: null };
513
+ },
514
+ };
515
+ }
516
+ });
517
+ // Inject API Key if present
518
+ if (this._apiKey) {
519
+ await this.page.addInitScript(key => {
520
+ window.__SENTIENCE_API_KEY__ = key;
521
+ }, this._apiKey);
522
+ }
523
+ // Wait for extension background pages to spin up
524
+ await new Promise(r => setTimeout(r, 500));
525
+ }
526
+ async goto(url) {
527
+ const page = this.getPage();
528
+ if (!page) {
529
+ throw new Error('Browser not started. Call start() first.');
530
+ }
531
+ const host = extractHost(url);
532
+ if (!isDomainAllowed(host, this._allowedDomains, this._prohibitedDomains)) {
533
+ throw new Error(`domain not allowed: ${host}`);
534
+ }
535
+ await page.goto(url, { waitUntil: 'domcontentloaded' });
536
+ if (!(await this.waitForExtension(page, 15000))) {
537
+ // Gather Debug Info
538
+ const diag = await page
539
+ .evaluate(() => ({
540
+ sentience_global: typeof window.sentience !== 'undefined',
541
+ wasm_ready: window.sentience && window.sentience._wasmModule !== null,
542
+ ext_id: document.documentElement.dataset.sentienceExtensionId || 'not set',
543
+ url: window.location.href,
544
+ }))
545
+ .catch(e => ({ error: String(e) }));
546
+ throw new Error('Extension failed to load after navigation.\n' +
547
+ `Path: ${this.extensionPath}\n` +
548
+ `Diagnostics: ${JSON.stringify(diag, null, 2)}`);
549
+ }
550
+ }
551
+ async waitForExtension(page, timeoutMs) {
552
+ const start = Date.now();
553
+ while (Date.now() - start < timeoutMs) {
554
+ try {
555
+ const ready = await page.evaluate(() => {
556
+ // Check for API AND Wasm Module (set by injected_api.js)
557
+ const s = window.sentience;
558
+ return s && s._wasmModule !== null; // Strict check for null (it's initialized as null)
559
+ });
560
+ if (ready)
561
+ return true;
562
+ }
563
+ catch {
564
+ // Context invalid errors expected during navigation
565
+ }
566
+ await new Promise(r => setTimeout(r, 100));
567
+ }
568
+ return false;
569
+ }
570
+ getPage() {
571
+ return this.page;
572
+ }
573
+ // Helper for recursive copy (fs.cp is Node 16.7+)
574
+ _copyRecursive(src, dest) {
575
+ if (fs.statSync(src).isDirectory()) {
576
+ if (!fs.existsSync(dest))
577
+ fs.mkdirSync(dest);
578
+ fs.readdirSync(src).forEach(child => {
579
+ this._copyRecursive(path.join(src, child), path.join(dest, child));
580
+ });
581
+ }
582
+ else {
583
+ fs.copyFileSync(src, dest);
584
+ }
585
+ }
586
+ // Expose API configuration (read-only)
587
+ getApiKey() {
588
+ return this._apiKey;
589
+ }
590
+ getApiUrl() {
591
+ return this._apiUrl;
592
+ }
593
+ /**
594
+ * Take a snapshot of the current page
595
+ * Implements IBrowser interface
596
+ */
597
+ async snapshot(options) {
598
+ return (0, snapshot_1.snapshot)(this, options);
599
+ }
600
+ /**
601
+ * Parse proxy connection string into Playwright format.
602
+ *
603
+ * @param proxyString - Standard format "http://username:password@host:port"
604
+ * or "socks5://user:pass@host:port"
605
+ * @returns Playwright proxy object or undefined if invalid
606
+ */
607
+ parseProxy(proxyString) {
608
+ if (!proxyString || !proxyString.trim()) {
609
+ return undefined;
610
+ }
611
+ try {
612
+ const parsed = new url_1.URL(proxyString);
613
+ // Validate scheme
614
+ const validSchemes = ['http:', 'https:', 'socks5:'];
615
+ if (!validSchemes.includes(parsed.protocol)) {
616
+ throw new Error(`Unsupported proxy scheme: ${parsed.protocol}`);
617
+ }
618
+ // Validate host and port
619
+ if (!parsed.hostname || !parsed.port) {
620
+ throw new Error('Proxy URL must include hostname and port');
621
+ }
622
+ // Build Playwright proxy object
623
+ const proxyConfig = {
624
+ server: `${parsed.protocol}//${parsed.hostname}:${parsed.port}`,
625
+ };
626
+ // Add credentials if present
627
+ if (parsed.username && parsed.password) {
628
+ proxyConfig.username = parsed.username;
629
+ proxyConfig.password = parsed.password;
630
+ }
631
+ return proxyConfig;
632
+ }
633
+ catch (e) {
634
+ console.warn(`⚠️ [Sentience] Invalid proxy configuration: ${e.message}`);
635
+ console.warn(' Expected format: http://username:password@host:port');
636
+ return undefined;
637
+ }
638
+ }
639
+ /**
640
+ * Inject storage state (cookies + localStorage) into browser context.
641
+ *
642
+ * @param storageState - Path to JSON file, StorageState object, or plain object
643
+ */
644
+ async injectStorageState(storageState) {
645
+ // Load storage state
646
+ let state;
647
+ if (typeof storageState === 'string') {
648
+ // Load from file
649
+ const content = fs.readFileSync(storageState, 'utf-8');
650
+ state = JSON.parse(content);
651
+ }
652
+ else if (typeof storageState === 'object' && storageState !== null) {
653
+ // Already an object (StorageState or plain object)
654
+ state = storageState;
655
+ }
656
+ else {
657
+ throw new Error(`Invalid storageState type: ${typeof storageState}. ` +
658
+ 'Expected string (file path), StorageState, or object.');
659
+ }
660
+ // Inject cookies (works globally)
661
+ if (state.cookies && Array.isArray(state.cookies) && state.cookies.length > 0) {
662
+ // Convert to Playwright cookie format
663
+ const playwrightCookies = state.cookies.map(cookie => {
664
+ const playwrightCookie = {
665
+ name: cookie.name,
666
+ value: cookie.value,
667
+ domain: cookie.domain,
668
+ path: cookie.path || '/',
669
+ };
670
+ if (cookie.expires !== undefined) {
671
+ playwrightCookie.expires = cookie.expires;
672
+ }
673
+ if (cookie.httpOnly !== undefined) {
674
+ playwrightCookie.httpOnly = cookie.httpOnly;
675
+ }
676
+ if (cookie.secure !== undefined) {
677
+ playwrightCookie.secure = cookie.secure;
678
+ }
679
+ if (cookie.sameSite !== undefined) {
680
+ playwrightCookie.sameSite = cookie.sameSite;
681
+ }
682
+ return playwrightCookie;
683
+ });
684
+ await this.context.addCookies(playwrightCookies);
685
+ console.log(`✅ [Sentience] Injected ${state.cookies.length} cookie(s)`);
686
+ }
687
+ // Inject LocalStorage (requires navigation to each domain)
688
+ if (state.origins && Array.isArray(state.origins)) {
689
+ for (const originData of state.origins) {
690
+ const origin = originData.origin;
691
+ if (!origin) {
692
+ continue;
693
+ }
694
+ try {
695
+ // Navigate to origin
696
+ await this.page.goto(origin, { waitUntil: 'domcontentloaded', timeout: 10000 });
697
+ // Inject localStorage
698
+ if (originData.localStorage && Array.isArray(originData.localStorage)) {
699
+ // Convert to dict format for JavaScript
700
+ const localStorageDict = {};
701
+ for (const item of originData.localStorage) {
702
+ localStorageDict[item.name] = item.value;
703
+ }
704
+ await this.page.evaluate((localStorageData) => {
705
+ for (const [key, value] of Object.entries(localStorageData)) {
706
+ localStorage.setItem(key, value);
707
+ }
708
+ }, localStorageDict);
709
+ console.log(`✅ [Sentience] Injected ${originData.localStorage.length} localStorage item(s) for ${origin}`);
710
+ }
711
+ }
712
+ catch (error) {
713
+ console.warn(`⚠️ [Sentience] Failed to inject localStorage for ${origin}: ${error.message}`);
714
+ }
715
+ }
716
+ }
717
+ }
718
+ /**
719
+ * Get the browser context (for utilities like saveStorageState)
720
+ */
721
+ getContext() {
722
+ return this.context;
723
+ }
724
+ /**
725
+ * Create SentienceBrowser from an existing Playwright BrowserContext.
726
+ *
727
+ * This allows you to use Sentience SDK with a browser context you've already created,
728
+ * giving you more control over browser initialization.
729
+ *
730
+ * @param context - Existing Playwright BrowserContext
731
+ * @param apiKey - Optional API key for server-side processing
732
+ * @param apiUrl - Optional API URL (defaults to https://api.sentienceapi.com if apiKey provided)
733
+ * @returns SentienceBrowser instance configured to use the existing context
734
+ *
735
+ * @example
736
+ * ```typescript
737
+ * import { chromium } from 'playwright';
738
+ * import { SentienceBrowser } from '@sentience/sdk';
739
+ *
740
+ * const context = await chromium.launchPersistentContext(...);
741
+ * const browser = SentienceBrowser.fromExisting(context);
742
+ * await browser.getPage().goto('https://example.com');
743
+ * ```
744
+ */
745
+ static async fromExisting(context, apiKey, apiUrl) {
746
+ const instance = new SentienceBrowser(apiKey, apiUrl);
747
+ instance.context = context;
748
+ const pages = context.pages();
749
+ instance.page = pages.length > 0 ? pages[0] : await context.newPage();
750
+ // Wait for extension to be ready (if extension is loaded)
751
+ // Note: In TypeScript, we can't easily apply stealth here without the page
752
+ // The user should ensure stealth is applied to their context if needed
753
+ return instance;
754
+ }
755
+ /**
756
+ * Create SentienceBrowser from an existing Playwright Page.
757
+ *
758
+ * This allows you to use Sentience SDK with a page you've already created,
759
+ * giving you more control over browser initialization.
760
+ *
761
+ * @param page - Existing Playwright Page
762
+ * @param apiKey - Optional API key for server-side processing
763
+ * @param apiUrl - Optional API URL (defaults to https://api.sentienceapi.com if apiKey provided)
764
+ * @returns SentienceBrowser instance configured to use the existing page
765
+ *
766
+ * @example
767
+ * ```typescript
768
+ * import { chromium } from 'playwright';
769
+ * import { SentienceBrowser } from '@sentience/sdk';
770
+ *
771
+ * const browserInstance = await chromium.launch();
772
+ * const context = await browserInstance.newContext();
773
+ * const page = await context.newPage();
774
+ * await page.goto('https://example.com');
775
+ *
776
+ * const browser = SentienceBrowser.fromPage(page);
777
+ * ```
778
+ */
779
+ static fromPage(page, apiKey, apiUrl) {
780
+ const instance = new SentienceBrowser(apiKey, apiUrl);
781
+ instance.page = page;
782
+ instance.context = page.context();
783
+ // Wait for extension to be ready (if extension is loaded)
784
+ // Note: In TypeScript, we can't easily apply stealth here without the page
785
+ // The user should ensure stealth is applied to their context if needed
786
+ return instance;
787
+ }
788
+ async close(outputPath) {
789
+ if (this._keepAlive) {
790
+ return null;
791
+ }
792
+ let tempVideoPath = null;
793
+ // Get video path before closing (if recording was enabled)
794
+ // Note: Playwright saves videos when pages/context close, but we can get the
795
+ // expected path before closing. The actual file will be available after close.
796
+ if (this._recordVideoDir) {
797
+ try {
798
+ // Try to get video path from the first page
799
+ if (this.page) {
800
+ const video = this.page.video();
801
+ if (video) {
802
+ tempVideoPath = await video.path();
803
+ }
804
+ }
805
+ // If that fails, check all pages in the context (before closing)
806
+ if (!tempVideoPath && this.context) {
807
+ const pages = this.context.pages();
808
+ for (const page of pages) {
809
+ try {
810
+ const video = page.video();
811
+ if (video) {
812
+ tempVideoPath = await video.path();
813
+ break;
814
+ }
815
+ }
816
+ catch {
817
+ // Continue to next page
818
+ }
819
+ }
820
+ }
821
+ }
822
+ catch {
823
+ // Video path might not be available until after close
824
+ // We'll use fallback mechanism below
825
+ }
826
+ }
827
+ const cleanup = [];
828
+ // Close context first (this also closes the browser for persistent contexts)
829
+ // This triggers video file finalization
830
+ if (this.context) {
831
+ cleanup.push(this.context.close().catch(() => {
832
+ // Ignore errors during cleanup
833
+ }));
834
+ this.context = null;
835
+ }
836
+ // Close browser if it exists (for non-persistent contexts)
837
+ if (this.browser) {
838
+ cleanup.push(this.browser.close().catch(() => {
839
+ // Ignore errors during cleanup
840
+ }));
841
+ this.browser = null;
842
+ }
843
+ // Wait for all cleanup to complete
844
+ await Promise.all(cleanup);
845
+ // Clean up extension directory
846
+ if (this.extensionPath && fs.existsSync(this.extensionPath)) {
847
+ try {
848
+ fs.rmSync(this.extensionPath, { recursive: true, force: true });
849
+ }
850
+ catch {
851
+ // Ignore cleanup errors
852
+ }
853
+ this.extensionPath = null;
854
+ }
855
+ // After context closes, verify video file exists if we have a path
856
+ let finalPath = tempVideoPath;
857
+ if (tempVideoPath && fs.existsSync(tempVideoPath)) {
858
+ // Video file exists, proceed with rename if needed
859
+ }
860
+ else if (this._recordVideoDir && fs.existsSync(this._recordVideoDir)) {
861
+ // Fallback: If we couldn't get the path but recording was enabled,
862
+ // check the directory for video files
863
+ try {
864
+ const videoFiles = fs
865
+ .readdirSync(this._recordVideoDir)
866
+ .filter(f => f.endsWith('.webm'))
867
+ .map(f => ({
868
+ path: path.join(this._recordVideoDir, f),
869
+ mtime: fs.statSync(path.join(this._recordVideoDir, f)).mtime.getTime(),
870
+ }))
871
+ .sort((a, b) => b.mtime - a.mtime); // Most recent first
872
+ if (videoFiles.length > 0) {
873
+ finalPath = videoFiles[0].path;
874
+ }
875
+ }
876
+ catch {
877
+ // Ignore errors when scanning directory
878
+ }
879
+ }
880
+ // Rename/move video if output_path is specified
881
+ if (finalPath && outputPath && fs.existsSync(finalPath)) {
882
+ try {
883
+ // Ensure parent directory exists
884
+ const outputDir = path.dirname(outputPath);
885
+ if (!fs.existsSync(outputDir)) {
886
+ fs.mkdirSync(outputDir, { recursive: true });
887
+ }
888
+ fs.renameSync(finalPath, outputPath);
889
+ finalPath = outputPath;
890
+ }
891
+ catch (error) {
892
+ console.warn(`Failed to rename video file: ${error.message}`);
893
+ // Return original path if rename fails
894
+ }
895
+ }
896
+ // Clean up user data directory (only if it's a temp directory)
897
+ // If user provided a custom userDataDir, we don't delete it (persistent sessions)
898
+ if (this.userDataDir && fs.existsSync(this.userDataDir)) {
899
+ // Only delete if it's a temp directory (starts with os.tmpdir())
900
+ const isTempDir = this.userDataDir.startsWith(os.tmpdir());
901
+ if (isTempDir) {
902
+ try {
903
+ fs.rmSync(this.userDataDir, { recursive: true, force: true });
904
+ }
905
+ catch {
906
+ // Ignore cleanup errors
907
+ }
908
+ }
909
+ this.userDataDir = null;
910
+ }
911
+ return finalPath;
912
+ }
913
+ }
914
+ exports.SentienceBrowser = SentienceBrowser;
915
+ /**
916
+ * Predicate rebrand alias for SentienceBrowser.
917
+ * Kept as a runtime alias to avoid breaking existing integrations.
918
+ */
919
+ exports.PredicateBrowser = SentienceBrowser;
920
+ //# sourceMappingURL=browser.js.map