@hira-core/sdk 1.0.3 → 1.0.5

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 CHANGED
@@ -1,243 +1,349 @@
1
- # @hira-core/sdk
2
-
3
- SDK for building **Hira** automation flows with TypeScript. Provides type-safe base classes, browser utilities, and logger for running flows inside the Hira Agent sandbox.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @hira-core/sdk
9
- # or
10
- pnpm add @hira-core/sdk
11
- ```
12
-
13
- > **Peer dependency:** Requires `puppeteer-core` to be available at runtime (provided by the Hira Agent).
14
-
15
- ---
16
-
17
- ## Quick Start
18
-
19
- ### 1. Define your Flow Config
20
-
21
- ```typescript
22
- import {
23
- AntidetectBaseFlow,
24
- AntidetectProvider,
25
- IFlowConfig,
26
- IScriptContext,
27
- BrowserUtils,
28
- FlowLogger,
29
- } from "@hira-core/sdk";
30
-
31
- const config = {
32
- globalInput: [
33
- {
34
- key: "targetUrl",
35
- label: "Target URL",
36
- type: "text" as const,
37
- required: true,
38
- },
39
- {
40
- key: "delay",
41
- label: "Delay (ms)",
42
- type: "number" as const,
43
- defaultValue: 2000,
44
- },
45
- ],
46
- profileInput: [
47
- { key: "username", label: "Username", type: "text" as const },
48
- { key: "password", label: "Password", type: "text" as const },
49
- ],
50
- } as const satisfies IFlowConfig;
51
-
52
- type MyConfig = typeof config;
53
- ```
54
-
55
- ### 2. Implement your Flow class
56
-
57
- ```typescript
58
- export class MyFlow extends AntidetectBaseFlow<MyConfig> {
59
- constructor() {
60
- super(AntidetectProvider.GPM, new FlowLogger(MyFlow.name), config);
61
- }
62
-
63
- async script(context: IScriptContext<MyConfig>): Promise<void> {
64
- const { page, logger, globalInput, profileInput } = context;
65
- const utils = new BrowserUtils(context);
66
-
67
- await utils.goto(globalInput.targetUrl);
68
- logger.info(`Logging in as: ${profileInput.username}`);
69
-
70
- await utils.type("#username", profileInput.username);
71
- await utils.type("#password", profileInput.password);
72
- await utils.click("#login-btn");
73
-
74
- const title = await page.title();
75
- logger.success(`✅ Done — page: ${title}`);
76
- }
77
- }
78
-
79
- export default MyFlow;
80
- ```
81
-
82
- ### 3. Run locally (for development)
83
-
84
- ```typescript
85
- import MyFlow from "./index";
86
-
87
- const flow = new MyFlow();
88
-
89
- await flow.run(
90
- flow.createRunParams({
91
- antidetect: {
92
- name: "GPM",
93
- profileSettings: [
94
- flow.createProfileSetting("ProfileA", {
95
- username: "user1",
96
- password: "pass1",
97
- }),
98
- ],
99
- },
100
- execution: { concurrency: 2, maxRetries: 1 },
101
- globalInput: { targetUrl: "https://example.com", delay: 2000 },
102
- window: { width: 1280, height: 720, scale: 1 },
103
- }),
104
- );
105
- ```
106
-
107
- ---
108
-
109
- ## API Reference
110
-
111
- ### `AntidetectBaseFlow<TConfig>`
112
-
113
- Abstract base class for browser automation flows.
114
-
115
- | Method / Property | Description |
116
- | ----------------------------------- | ------------------------------------------------ |
117
- | `abstract script(context)` | Your automation logic — implement this |
118
- | `run(params)` | Start the flow (called by Hira Agent) |
119
- | `createRunParams(params)` | Type-safe helper to build run params |
120
- | `createProfileSetting(name, data?)` | Type-safe helper to build profile settings |
121
- | `flowConfig` | The declared config schema (embedded in `.hira`) |
122
-
123
- ### `AntidetectProvider`
124
-
125
- ```typescript
126
- enum AntidetectProvider {
127
- GPM = "gpm", // GPM Login antidetect browser
128
- HIDEMIUM = "hidemium", // coming soon
129
- GENLOGIN = "genlogin", // coming soon
130
- }
131
- ```
132
-
133
- ### `IScriptContext<TConfig>`
134
-
135
- Injected into `script()` by the runtime:
136
-
137
- | Field | Type | Description |
138
- | -------------- | ---------------------------- | -------------------------------------------- |
139
- | `browser` | `Browser` | Puppeteer browser instance |
140
- | `page` | `Page` | Active page |
141
- | `profile` | `IGpmProfile` | Current GPM profile info |
142
- | `index` | `number` | Profile index in the batch |
143
- | `globalInput` | `InferGlobalInput<TConfig>` | Typed global inputs |
144
- | `profileInput` | `InferProfileInput<TConfig>` | Typed per-profile inputs |
145
- | `logger` | `ILogger` | Bound logger (auto-tagged with profile name) |
146
-
147
- ### `BrowserUtils`
148
-
149
- Helper class for common browser actions. All methods auto-log and respect the abort signal.
150
-
151
- ```typescript
152
- const utils = new BrowserUtils(context);
153
- ```
154
-
155
- | Method | Description |
156
- | ------------------------------------------ | ----------------------------- |
157
- | `utils.goto(url)` | Navigate to URL |
158
- | `utils.click(selector)` | Wait + scroll + click |
159
- | `utils.type(selector, text)` | Wait + clear + type |
160
- | `utils.getText(selector)` | Get text content |
161
- | `utils.exists(selector, timeout?)` | Check element exists |
162
- | `utils.waitForElement(selector, timeout?)` | Wait for element |
163
- | `utils.waitForNavigation()` | Wait for page navigation |
164
- | `utils.screenshot(path?)` | Take screenshot |
165
- | `utils.switchToPopup(matcher)` | Switch to popup tab |
166
- | `utils.switchToTabIndex(index)` | Switch to tab by index |
167
- | `utils.closeCurrentTab()` | Close current tab |
168
- | `utils.closeOtherTabs()` | Close all other tabs |
169
- | `utils.sleep(ms)` | Delay (respects abort signal) |
170
- | `utils.logGlobalInput()` | Log all global inputs |
171
- | `utils.logProfileInput()` | Log all profile inputs |
172
-
173
- ### `FlowLogger`
174
-
175
- Logger that works both in standalone mode (console) and inside Hira Agent worker (postMessage).
176
-
177
- ```typescript
178
- const logger = new FlowLogger("MyFlow");
179
-
180
- logger.info("message");
181
- logger.success("done");
182
- logger.warn("warning");
183
- logger.error("error");
184
- logger.debug("debug");
185
- ```
186
-
187
- ### `IFlowConfig`
188
-
189
- Schema declaration for your flow's inputs:
190
-
191
- ```typescript
192
- interface IFlowConfig {
193
- globalInput: readonly IInputField[]; // shared across all profiles
194
- profileInput: readonly IInputField[]; // per-profile data
195
- }
196
-
197
- interface IInputField {
198
- key: string;
199
- label: string;
200
- type: "text" | "number" | "boolean" | "select" | "textarea";
201
- required?: boolean;
202
- defaultValue?: string | number | boolean;
203
- placeholder?: string;
204
- options?: { label: string; value: string | number }[];
205
- }
206
- ```
207
-
208
- ---
209
-
210
- ## Type Inference
211
-
212
- The SDK automatically infers TypeScript types from your config at compile time:
213
-
214
- ```typescript
215
- const config = {
216
- globalInput: [
217
- { key: "url", type: "text" as const },
218
- { key: "count", type: "number" as const },
219
- ],
220
- profileInput: [{ key: "username", type: "text" as const }],
221
- } as const satisfies IFlowConfig;
222
-
223
- // Inside script():
224
- // globalInput.url → string ✅
225
- // globalInput.count → number ✅
226
- // profileInput.username string ✅
227
- ```
228
-
229
- ---
230
-
231
- ## Building & Publishing a Flow
232
-
233
- Use [`@hira-core/cli`](https://www.npmjs.com/package/@hira-core/cli) to package your flow:
234
-
235
- ```bash
236
- npx @hira-core/cli build
237
- ```
238
-
239
- ---
240
-
241
- ## License
242
-
243
- ISC
1
+ # @hira-core/sdk
2
+
3
+ SDK for building **Hira** automation flows with TypeScript. Provides type-safe base classes, browser utilities, and logger for running flows inside the Hira Agent sandbox.
4
+
5
+ 📖 **[Full Documentation](https://hira-sdk.vercel.app/)** — Guides, API reference, and examples.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @hira-core/sdk
11
+ # or
12
+ pnpm add @hira-core/sdk
13
+ ```
14
+
15
+ > **Peer dependency:** Requires `puppeteer-core` to be available at runtime (provided by the Hira Agent).
16
+
17
+ ---
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Define your Flow Config
22
+
23
+ ```typescript
24
+ import {
25
+ AntidetectBaseFlow,
26
+ AntidetectProvider,
27
+ defineFlowConfig,
28
+ IScriptContext,
29
+ BrowserUtils,
30
+ FlowLogger,
31
+ } from "@hira-core/sdk";
32
+
33
+ const config = defineFlowConfig({
34
+ globalInput: [
35
+ {
36
+ key: "targetUrl",
37
+ label: "Target URL",
38
+ type: "text" as const,
39
+ required: true,
40
+ },
41
+ {
42
+ key: "delay",
43
+ label: "Delay (ms)",
44
+ type: "number" as const,
45
+ defaultValue: 2000,
46
+ },
47
+ ],
48
+ profileInput: [
49
+ { key: "username", label: "Username", type: "text" as const },
50
+ { key: "password", label: "Password", type: "text" as const },
51
+ ],
52
+ output: [
53
+ { index: 0, key: "isLoggedIn", label: "Login Status" },
54
+ { index: 1, key: "pageTitle", label: "Page Title" },
55
+ ],
56
+ });
57
+
58
+ type MyConfig = typeof config;
59
+ ```
60
+
61
+ > **`defineFlowConfig()`** validates for **duplicate keys, labels, and output indices** at compile time — providing instant TS errors if any field is duplicated.
62
+
63
+ ### 2. Implement your Flow class
64
+
65
+ ```typescript
66
+ export class MyFlow extends AntidetectBaseFlow<MyConfig> {
67
+ constructor() {
68
+ super(AntidetectProvider.GPM, new FlowLogger(MyFlow.name), config);
69
+ }
70
+
71
+ async script(context: IScriptContext<MyConfig>): Promise<void> {
72
+ const { page, logger, globalInput, profileInput, output } = context;
73
+ const utils = new BrowserUtils(context);
74
+
75
+ await utils.goto(globalInput.targetUrl);
76
+ logger.info(`Logging in as: ${profileInput.username}`);
77
+
78
+ await utils.type("#username", profileInput.username);
79
+ await utils.type("#password", profileInput.password);
80
+ await utils.click("#login-btn");
81
+
82
+ const title = await page.title();
83
+
84
+ // Write typed output (validated against config.output keys)
85
+ await utils.writeOutput("isLoggedIn", true);
86
+ await utils.writeOutput("pageTitle", title);
87
+
88
+ logger.success(`✅ Done — page: ${title}`);
89
+ }
90
+ }
91
+
92
+ export default MyFlow;
93
+ ```
94
+
95
+ ### 3. Run locally (for development)
96
+
97
+ ```typescript
98
+ import MyFlow from "./index";
99
+
100
+ const flow = new MyFlow();
101
+
102
+ await flow.run(
103
+ flow.createRunParams({
104
+ antidetect: {
105
+ profileSettings: [
106
+ flow.createProfileSetting("ProfileA", {
107
+ username: "user1",
108
+ password: "pass1",
109
+ }),
110
+ ],
111
+ },
112
+ execution: { concurrency: 2, maxRetries: 1 },
113
+ globalInput: { targetUrl: "https://example.com", delay: 2000 },
114
+ window: { width: 1280, height: 720, scale: 1 },
115
+ }),
116
+ );
117
+ ```
118
+
119
+ ---
120
+
121
+ ## API Reference
122
+
123
+ ### `AntidetectBaseFlow<TConfig>`
124
+
125
+ Abstract base class for browser automation flows.
126
+
127
+ | Method / Property | Description |
128
+ | ----------------------------------- | ------------------------------------------------ |
129
+ | `abstract script(context)` | Your automation logic — implement this |
130
+ | `run(params)` | Start the flow (called by Hira Agent) |
131
+ | `createRunParams(params)` | Type-safe helper to build run params |
132
+ | `createProfileSetting(name, data?)` | Type-safe helper to build profile settings |
133
+ | `flowConfig` | The declared config schema (embedded in `.hira`) |
134
+
135
+ ### `AntidetectProvider`
136
+
137
+ ```typescript
138
+ enum AntidetectProvider {
139
+ GPM = "gpm", // GPM Login antidetect browser
140
+ HIDEMIUM = "hidemium", // Hidemium antidetect browser
141
+ GENLOGIN = "genlogin", // coming soon
142
+ }
143
+ ```
144
+
145
+ ### `IScriptContext<TConfig>`
146
+
147
+ Injected into `script()` by the runtime:
148
+
149
+ | Field | Type | Description |
150
+ | -------------- | ---------------------------- | ---------------------------------------------------- |
151
+ | `browser` | `Browser` | Puppeteer browser instance |
152
+ | `page` | `Page` | Active page |
153
+ | `profile` | `IAntidetectProfile` | Current profile info (unified across providers) |
154
+ | `index` | `number` | Profile index in the batch |
155
+ | `globalInput` | `InferGlobalInput<TConfig>` | Typed global inputs |
156
+ | `profileInput` | `InferProfileInput<TConfig>` | Typed per-profile inputs |
157
+ | `output` | `InferOutput<TConfig>` | Current output values (pre-populated from last run) |
158
+ | `logger` | `ILogger` | Bound logger (auto-tagged with profile name) |
159
+
160
+ ### `IAntidetectProfile`
161
+
162
+ Unified profile interface works across all providers (GPM, Hidemium, etc.):
163
+
164
+ ```typescript
165
+ interface IAntidetectProfile {
166
+ id: string; // Unique ID
167
+ name: string; // Human-readable name
168
+ provider: "gpm" | "hidemium" | "genlogin";
169
+ raw_proxy?: string; // Proxy string
170
+ browser_type: "chromium" | "firefox";
171
+ browser_version: string;
172
+ group_id?: string;
173
+ profile_path: string;
174
+ note: string;
175
+ created_at: string; // ISO 8601
176
+ }
177
+ ```
178
+
179
+ > **Note:** `IGpmProfile` is deprecated — use `IAntidetectProfile` instead.
180
+
181
+ ### `BrowserUtils`
182
+
183
+ Helper class for common browser actions. All methods auto-log and respect the abort signal.
184
+
185
+ ```typescript
186
+ const utils = new BrowserUtils(context);
187
+ ```
188
+
189
+ #### Navigation & Interaction
190
+
191
+ | Method | Description |
192
+ | ------------------------------------------ | ----------------------------- |
193
+ | `utils.goto(url)` | Navigate to URL |
194
+ | `utils.click(selector)` | Wait + scroll + click |
195
+ | `utils.type(selector, text)` | Wait + clear + type |
196
+ | `utils.getText(selector)` | Get text content |
197
+ | `utils.exists(selector, timeout?)` | Check element exists |
198
+ | `utils.waitForElement(selector, timeout?)` | Wait for element |
199
+ | `utils.waitForNavigation()` | Wait for page navigation |
200
+ | `utils.screenshot(path?)` | Take screenshot |
201
+ | `utils.sleep(ms)` | Delay (respects abort signal) |
202
+
203
+ #### Tab Management
204
+
205
+ | Method | Description |
206
+ | --------------------------------- | ------------------------ |
207
+ | `utils.switchToPopup(matcher)` | Switch to popup tab |
208
+ | `utils.switchToTabIndex(index)` | Switch to tab by index |
209
+ | `utils.closeCurrentTab()` | Close current tab |
210
+ | `utils.closeOtherTabs()` | Close all other tabs |
211
+
212
+ #### Output & Data
213
+
214
+ | Method | Description |
215
+ | ------------------------------------ | ------------------------------------------------ |
216
+ | `utils.writeOutput(key, value)` | Write output value (type-safe against config) |
217
+ | `utils.writeProfileInput(key, val)` | Update profile input value for next run |
218
+ | `utils.logGlobalInput()` | Log all global inputs |
219
+ | `utils.logProfileInput()` | Log all profile inputs |
220
+
221
+ ### `FlowLogger`
222
+
223
+ Logger that works both in standalone mode (console) and inside Hira Agent worker (postMessage).
224
+
225
+ ```typescript
226
+ const logger = new FlowLogger("MyFlow");
227
+
228
+ logger.info("message");
229
+ logger.success("done");
230
+ logger.warn("warning");
231
+ logger.error("error");
232
+ logger.debug("debug");
233
+ ```
234
+
235
+ ### `IFlowConfig`
236
+
237
+ Schema declaration for your flow's inputs and outputs:
238
+
239
+ ```typescript
240
+ interface IFlowConfig {
241
+ globalInput: readonly IInputField[]; // shared across all profiles
242
+ profileInput: readonly IInputField[]; // per-profile data
243
+ output?: readonly IOutputField[]; // output fields per profile
244
+ }
245
+
246
+ interface IInputField {
247
+ key: string;
248
+ label: string;
249
+ type: "text" | "number" | "boolean" | "select" | "textarea";
250
+ required?: boolean;
251
+ defaultValue?: string | number | boolean;
252
+ placeholder?: string;
253
+ options?: { label: string; value: string | number }[];
254
+ }
255
+
256
+ interface IOutputField {
257
+ index: number; // display order
258
+ key: string; // unique key for writeOutput()
259
+ label: string; // human-readable label
260
+ }
261
+ ```
262
+
263
+ ### `defineFlowConfig()`
264
+
265
+ Helper function that validates your config at compile time — catches duplicate keys, labels, indices, and option values:
266
+
267
+ ```typescript
268
+ // ✅ OK
269
+ const config = defineFlowConfig({
270
+ globalInput: [
271
+ { key: "url", label: "URL", type: "text" },
272
+ ],
273
+ profileInput: [
274
+ { key: "user", label: "User", type: "text" },
275
+ ],
276
+ output: [
277
+ { index: 0, key: "status", label: "Status" },
278
+ { index: 1, key: "title", label: "Title" },
279
+ ],
280
+ });
281
+
282
+ // ❌ TS Error — duplicate key "url" in globalInput
283
+ const bad = defineFlowConfig({
284
+ globalInput: [
285
+ { key: "url", label: "URL 1", type: "text" },
286
+ { key: "url", label: "URL 2", type: "text" }, // Error!
287
+ ],
288
+ profileInput: [],
289
+ });
290
+ ```
291
+
292
+ ### `ExcelStorage`
293
+
294
+ Read profile data from Excel files and write output back:
295
+
296
+ ```typescript
297
+ import { ExcelStorage } from "@hira-core/sdk";
298
+
299
+ const storage = new ExcelStorage("data.xlsx");
300
+
301
+ // Read rows from a sheet
302
+ const rows = await storage.readRows("Sheet1");
303
+
304
+ // Write output rows
305
+ await storage.writeRows("Output", outputData);
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Type Inference
311
+
312
+ The SDK automatically infers TypeScript types from your config at compile time:
313
+
314
+ ```typescript
315
+ const config = defineFlowConfig({
316
+ globalInput: [
317
+ { key: "url", type: "text" as const, label: "URL" },
318
+ { key: "count", type: "number" as const, label: "Count" },
319
+ ],
320
+ profileInput: [
321
+ { key: "username", type: "text" as const, label: "Username" },
322
+ ],
323
+ output: [
324
+ { index: 0, key: "isLoggedIn", label: "Logged In" },
325
+ ],
326
+ });
327
+
328
+ // Inside script():
329
+ // globalInput.url → string ✅
330
+ // globalInput.count → number ✅
331
+ // profileInput.username → string ✅
332
+ // output.isLoggedIn → ProfileOutputValue | null ✅
333
+ ```
334
+
335
+ ---
336
+
337
+ ## Building & Publishing a Flow
338
+
339
+ Use [`@hira-core/cli`](https://www.npmjs.com/package/@hira-core/cli) to package your flow:
340
+
341
+ ```bash
342
+ npx @hira-core/cli build
343
+ ```
344
+
345
+ ---
346
+
347
+ ## License
348
+
349
+ ISC
package/dist/index.d.ts CHANGED
@@ -541,9 +541,12 @@ interface IHidemiumProfile {
541
541
  created_at: string;
542
542
  }
543
543
  interface IHidemiumStartResult {
544
+ status: boolean;
544
545
  remote_port: number;
545
- web_socket: string;
546
+ web_socket?: string;
546
547
  profile_path: string;
548
+ execute_path: string;
549
+ profile_name?: string;
547
550
  uuid: string;
548
551
  }
549
552
  interface IHidemiumListParams {
@@ -572,7 +575,13 @@ declare class HidemiumService {
572
575
  deleteProfiles(uuids: string[]): Promise<boolean>;
573
576
  updateProxy(uuid: string, proxy: string): Promise<boolean>;
574
577
  checkHealth(): Promise<boolean>;
575
- connectToBrowser(wsUrl: string, maxRetries?: number, delayMs?: number): Promise<Browser>;
578
+ /**
579
+ * Kết nối browser — thử theo thứ tự:
580
+ * 1. web_socket (nếu API trả)
581
+ * 2. browserURL via remote_port (nếu API trả)
582
+ * 3. Fallback: quét default port 9222 (Hidemium mặc định)
583
+ */
584
+ connectToBrowser(startResult: IHidemiumStartResult, maxRetries?: number, delayMs?: number): Promise<Browser>;
576
585
  }
577
586
 
578
587
  declare class HidemiumStandaloneAdapter implements IBrowserAdapter {
package/dist/index.js CHANGED
@@ -12535,6 +12535,26 @@ var WindowManager = class {
12535
12535
  const y = Math.floor(row * itemHeight);
12536
12536
  return { x, y };
12537
12537
  }
12538
+ /**
12539
+ * Tính vị trí cửa sổ cho Hidemium (--force-device-scale-factor)
12540
+ * Khác GPM: scale ảnh hưởng kích thước thực tế của window
12541
+ * → dùng effectiveWidth/Height cho cả grid lẫn vị trí
12542
+ */
12543
+ static calculatePositionScaled(index, screen, itemWidth, itemHeight, scale = 1) {
12544
+ const screenWidth = screen.width;
12545
+ const screenHeight = screen.height;
12546
+ const scaledWidth = Math.floor((itemWidth > 0 ? itemWidth : 800) * scale);
12547
+ const scaledHeight = Math.floor((itemHeight > 0 ? itemHeight : 600) * scale);
12548
+ const cols = Math.floor(screenWidth / scaledWidth) || 1;
12549
+ const rows = Math.floor(screenHeight / scaledHeight) || 1;
12550
+ const itemsPerScreen = cols * rows;
12551
+ const visualIndex = index % itemsPerScreen;
12552
+ const row = Math.floor(visualIndex / cols);
12553
+ const col = visualIndex % cols;
12554
+ const x = col * scaledWidth;
12555
+ const y = row * scaledHeight;
12556
+ return { x, y, scaledWidth, scaledHeight };
12557
+ }
12538
12558
  };
12539
12559
 
12540
12560
  // ../../node_modules/.pnpm/axios@1.13.4/node_modules/axios/lib/helpers/bind.js
@@ -16480,7 +16500,7 @@ var HidemiumService = class {
16480
16500
  }
16481
16501
  return false;
16482
16502
  }
16483
- async getProfiles(params, isLocal = true) {
16503
+ async getProfiles(params, isLocal) {
16484
16504
  var _a, _b;
16485
16505
  const response = await this.api.post(
16486
16506
  "/v1/browser/list",
@@ -16490,7 +16510,8 @@ var HidemiumService = class {
16490
16510
  search: (params == null ? void 0 : params.search) || "",
16491
16511
  orderName: (params == null ? void 0 : params.orderName) || 0
16492
16512
  },
16493
- { params: { is_local: isLocal } }
16513
+ // Chỉ truyền is_local nếu caller chỉ định — mặc định lấy tất cả
16514
+ isLocal !== void 0 ? { params: { is_local: isLocal } } : void 0
16494
16515
  );
16495
16516
  return ((_b = (_a = response.data) == null ? void 0 : _a.data) == null ? void 0 : _b.content) || [];
16496
16517
  }
@@ -16520,24 +16541,49 @@ var HidemiumService = class {
16520
16541
  return false;
16521
16542
  }
16522
16543
  }
16523
- async connectToBrowser(wsUrl, maxRetries = 5, delayMs = 1e3) {
16544
+ /**
16545
+ * Kết nối browser — thử theo thứ tự:
16546
+ * 1. web_socket (nếu API trả)
16547
+ * 2. browserURL via remote_port (nếu API trả)
16548
+ * 3. Fallback: quét default port 9222 (Hidemium mặc định)
16549
+ */
16550
+ async connectToBrowser(startResult, maxRetries = 5, delayMs = 1e3) {
16524
16551
  let lastError;
16525
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
16526
- try {
16527
- const browser = await import_puppeteer_core2.default.connect({
16528
- browserWSEndpoint: wsUrl,
16529
- defaultViewport: null
16530
- });
16531
- return browser;
16532
- } catch (error) {
16533
- lastError = error;
16534
- if (attempt < maxRetries) {
16535
- await new Promise((r) => setTimeout(r, delayMs));
16552
+ if (startResult.web_socket) {
16553
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
16554
+ try {
16555
+ const browser = await import_puppeteer_core2.default.connect({
16556
+ browserWSEndpoint: startResult.web_socket,
16557
+ defaultViewport: null
16558
+ });
16559
+ return browser;
16560
+ } catch (error) {
16561
+ lastError = error;
16562
+ if (attempt < maxRetries) {
16563
+ await new Promise((r) => setTimeout(r, delayMs));
16564
+ }
16565
+ }
16566
+ }
16567
+ }
16568
+ if (startResult.remote_port) {
16569
+ const browserURL = `http://127.0.0.1:${startResult.remote_port}`;
16570
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
16571
+ try {
16572
+ const browser = await import_puppeteer_core2.default.connect({
16573
+ browserURL,
16574
+ defaultViewport: null
16575
+ });
16576
+ return browser;
16577
+ } catch (error) {
16578
+ lastError = error;
16579
+ if (attempt < maxRetries) {
16580
+ await new Promise((r) => setTimeout(r, delayMs));
16581
+ }
16536
16582
  }
16537
16583
  }
16538
16584
  }
16539
16585
  throw new Error(
16540
- `Failed to connect to browser at ${wsUrl} after ${maxRetries} attempts. Last error: ${lastError == null ? void 0 : lastError.message}`
16586
+ `Failed to connect to Hidemium browser after ${maxRetries} attempts. Last error: ${lastError == null ? void 0 : lastError.message}`
16541
16587
  );
16542
16588
  }
16543
16589
  };
@@ -16599,7 +16645,16 @@ var HidemiumStandaloneAdapter = class {
16599
16645
  windowConfig.height,
16600
16646
  windowConfig.scale
16601
16647
  );
16602
- const command = `--window-position=${x},${y} --window-size=${windowConfig.width},${windowConfig.height}`;
16648
+ const command = [
16649
+ `--window-position=${x},${y}`,
16650
+ `--window-size=${windowConfig.width},${windowConfig.height}`,
16651
+ `--force-device-scale-factor=${windowConfig.scale}`
16652
+ ].join(" ");
16653
+ try {
16654
+ await this.service.stopProfile(targetProfile.uuid);
16655
+ await new Promise((r) => setTimeout(r, 2e3));
16656
+ } catch {
16657
+ }
16603
16658
  const startResult = await this.service.startProfile(
16604
16659
  targetProfile.uuid,
16605
16660
  command
@@ -16607,9 +16662,12 @@ var HidemiumStandaloneAdapter = class {
16607
16662
  if (!startResult) {
16608
16663
  throw new Error(`Failed to start profile: ${profileName}`);
16609
16664
  }
16610
- const browser = await this.service.connectToBrowser(
16611
- startResult.web_socket
16612
- );
16665
+ if (!startResult.web_socket && !startResult.remote_port) {
16666
+ throw new Error(
16667
+ `Hidemium openProfile did not return web_socket or remote_port for "${profileName}". Please ensure the profile is not already opened in another session.`
16668
+ );
16669
+ }
16670
+ const browser = await this.service.connectToBrowser(startResult);
16613
16671
  const pages = await browser.pages();
16614
16672
  const page = pages.length > 0 ? pages[0] : await browser.newPage();
16615
16673
  this.openedProfiles.set(profileName, targetProfile.uuid);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hira-core/sdk",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "SDK for building Hira automation flows with TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,6 +25,11 @@
25
25
  "puppeteer"
26
26
  ],
27
27
  "author": "tttKiet",
28
+ "homepage": "https://hira-sdk.vercel.app/",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/tttKiet/hira-automation"
32
+ },
28
33
  "license": "ISC",
29
34
  "engines": {
30
35
  "node": ">=18.0.0"