n8n-nodes-nvk-browser 1.0.78 → 1.0.79

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.
@@ -95,29 +95,48 @@ class CreateProfile {
95
95
  const extensionsInput = this.getNodeParameter('extensions', i) || '';
96
96
  // Parse extensions
97
97
  const extensions = ExtensionHandler_1.ExtensionHandler.parseExtensions(extensionsInput);
98
- // Validate extensions lấy resolved paths
99
- const extensionPaths = [];
98
+ // Tạo profile trước (để profilePath)
99
+ const profile = profileManager.createProfile(profileName, proxy, note, extensions, [] // extensionPaths sẽ được set sau
100
+ );
101
+ const profilePath = profileManager.getProfilePath(profile.id);
102
+ // Validate và install extensions vào profile
103
+ const extensionInfos = [];
100
104
  if (extensions.length > 0) {
101
- console.log(`[CreateProfile] Validating ${extensions.length} extension(s)...`);
105
+ console.log(`[CreateProfile] Validating and installing ${extensions.length} extension(s)...`);
102
106
  for (const ext of extensions) {
103
107
  try {
104
- const validation = await ExtensionHandler_1.ExtensionHandler.validateExtension(ext, resolvedProfilesDir);
105
- if (validation.valid && validation.path) {
106
- extensionPaths.push(validation.path);
107
- console.log(`[CreateProfile] Extension validated: ${ext} -> ${validation.path}`);
108
+ // Validate extension lấy info
109
+ const validation = await ExtensionHandler_1.ExtensionHandler.validateAndGetExtensionInfo(ext, resolvedProfilesDir);
110
+ if (validation.valid && validation.path && validation.extensionId) {
111
+ // Install extension vào profile Extensions directory
112
+ const extensionInfo = await ExtensionHandler_1.ExtensionHandler.installExtensionToProfile(validation.path, profilePath, ext);
113
+ extensionInfos.push({
114
+ extensionId: extensionInfo.extensionId,
115
+ version: extensionInfo.version,
116
+ path: extensionInfo.path,
117
+ });
118
+ console.log(`[CreateProfile] ✓ Extension installed: ${ext} -> ${extensionInfo.extensionId} v${extensionInfo.version}`);
108
119
  }
109
120
  else {
110
121
  console.error(`[CreateProfile] ✗ Extension validation failed: ${ext} - ${validation.error || 'Unknown error'}`);
111
122
  }
112
123
  }
113
124
  catch (error) {
114
- console.error(`[CreateProfile] ✗ Error validating extension "${ext}":`, error);
125
+ console.error(`[CreateProfile] ✗ Error installing extension "${ext}":`, error);
126
+ }
127
+ }
128
+ console.log(`[CreateProfile] Successfully installed ${extensionInfos.length}/${extensions.length} extension(s)`);
129
+ // Lưu extension configuration vào Preferences
130
+ if (extensionInfos.length > 0) {
131
+ try {
132
+ profileManager.saveExtensionsToPreferences(profilePath, extensionInfos);
133
+ console.log(`[CreateProfile] ✓ Extension configurations saved to Preferences`);
134
+ }
135
+ catch (error) {
136
+ console.error(`[CreateProfile] ✗ Error saving extension configurations:`, error);
115
137
  }
116
138
  }
117
- console.log(`[CreateProfile] Successfully validated ${extensionPaths.length}/${extensions.length} extension(s)`);
118
139
  }
119
- // Tạo profile với cả extensionPaths đã resolve
120
- const profile = profileManager.createProfile(profileName, proxy, note, extensions, extensionPaths);
121
140
  // Lưu proxy authentication vào profile nếu có
122
141
  // QUAN TRỌNG: Phải gọi SAU khi createProfile và setProfileName để Preferences file đã được tạo
123
142
  // Nhưng phải đảm bảo không bị ghi đè
@@ -31,7 +31,6 @@ const path = __importStar(require("path"));
31
31
  const fs = __importStar(require("fs"));
32
32
  const puppeteer_core_1 = __importDefault(require("puppeteer-core"));
33
33
  const ProxyHandler_1 = require("./ProxyHandler");
34
- const ExtensionHandler_1 = require("./ExtensionHandler");
35
34
  const ProfileManager_1 = require("./ProfileManager");
36
35
  class BrowserManager {
37
36
  constructor(browserPath, profilesDir) {
@@ -91,43 +90,25 @@ class BrowserManager {
91
90
  '--disable-dev-shm-usage',
92
91
  '--no-sandbox',
93
92
  '--disable-setuid-sandbox',
93
+ '--enable-extensions', // Cho phép extensions
94
94
  ];
95
95
  // Proxy configuration - KHÔNG bao gồm credentials trong URL
96
96
  if (proxyConfig) {
97
97
  args.push(...ProxyHandler_1.ProxyHandler.getChromeProxyArgs(proxyConfig));
98
98
  }
99
99
  // Extension configuration
100
- // Sử dụng extensionPaths đã resolve thay validate lại
101
- if (profile.extensionPaths && profile.extensionPaths.length > 0) {
102
- // Kiểm tra paths còn tồn tại không
103
- const validPaths = [];
104
- for (const extPath of profile.extensionPaths) {
105
- const manifestPath = path.join(extPath, 'manifest.json');
106
- if (fs.existsSync(manifestPath)) {
107
- validPaths.push(extPath);
108
- }
109
- else {
110
- console.warn(`[BrowserManager] Extension path no longer valid (missing manifest.json): ${extPath}`);
111
- }
112
- }
113
- if (validPaths.length > 0) {
114
- args.push(`--load-extension=${validPaths.join(',')}`);
115
- console.log(`[BrowserManager] Loading ${validPaths.length} extension(s) from cached paths`);
116
- }
117
- else {
118
- console.warn(`[BrowserManager] No valid extension paths found, skipping extensions`);
119
- }
120
- }
121
- else if (profile.extensions && profile.extensions.length > 0) {
122
- // Fallback: Nếu không có extensionPaths, validate lại (cho backward compatibility)
123
- console.log(`[BrowserManager] No extensionPaths found, validating extensions on-the-fly (legacy mode)`);
124
- try {
125
- const extensionArgs = await ExtensionHandler_1.ExtensionHandler.getExtensionArgs(profile.extensions, this.profilesDir);
126
- args.push(...extensionArgs);
127
- }
128
- catch (error) {
129
- console.error(`[BrowserManager] Error loading extensions:`, error);
130
- // Continue without extensions rather than failing completely
100
+ // Extensions đã được install vào profile/Default/Extensions/ nên không cần --load-extension
101
+ // Chrome sẽ tự động load từ Preferences và Extensions directory
102
+ const extensionsDir = path.join(profilePath, 'Default', 'Extensions');
103
+ if (fs.existsSync(extensionsDir)) {
104
+ const extensionDirs = fs.readdirSync(extensionsDir).filter((dir) => {
105
+ const dirPath = path.join(extensionsDir, dir);
106
+ return fs.statSync(dirPath).isDirectory();
107
+ });
108
+ if (extensionDirs.length > 0) {
109
+ console.log(`[BrowserManager] Found ${extensionDirs.length} extension(s) installed in profile Extensions directory`);
110
+ // Extensions sẽ được load tự động từ Preferences và Extensions directory
111
+ // Không cần --load-extension vì đã có trong profile
131
112
  }
132
113
  }
133
114
  // Window configuration
@@ -3,6 +3,14 @@ interface ExtensionValidationResult {
3
3
  path?: string;
4
4
  error?: string;
5
5
  type?: 'store-id' | 'url' | 'local-path';
6
+ extensionId?: string;
7
+ version?: string;
8
+ }
9
+ interface ExtensionInfo {
10
+ extensionId: string;
11
+ version: string;
12
+ path: string;
13
+ manifest: any;
6
14
  }
7
15
  export declare class ExtensionHandler {
8
16
  private static readonly CACHE_DIR_NAME;
@@ -62,6 +70,40 @@ export declare class ExtensionHandler {
62
70
  * Download và extract extension từ URL
63
71
  */
64
72
  private static downloadAndExtractExtensionFromUrl;
73
+ /**
74
+ * Đọc và parse manifest.json từ extension path
75
+ */
76
+ private static readManifest;
77
+ /**
78
+ * Lấy extension ID từ manifest.json
79
+ * Nếu là Chrome Store ID thì dùng ID đó, nếu không thì generate từ manifest key
80
+ */
81
+ private static getExtensionId;
82
+ /**
83
+ * Lấy version từ manifest.json
84
+ */
85
+ private static getExtensionVersion;
86
+ /**
87
+ * Copy extension vào profile Extensions directory
88
+ * Structure: {profile}/Default/Extensions/{extension-id}/{version}/
89
+ */
90
+ static installExtensionToProfile(extensionPath: string, profilePath: string, originalExtensionInput?: string): Promise<ExtensionInfo>;
91
+ /**
92
+ * Copy directory recursively
93
+ */
94
+ private static copyDirectory;
95
+ /**
96
+ * Tạo verified_contents.json structure
97
+ */
98
+ private static createVerifiedContents;
99
+ /**
100
+ * Validate extension và lấy thông tin chi tiết
101
+ */
102
+ static validateAndGetExtensionInfo(extension: string, workspacePath?: string): Promise<ExtensionValidationResult & {
103
+ extensionId?: string;
104
+ version?: string;
105
+ manifest?: any;
106
+ }>;
65
107
  /**
66
108
  * Get Chrome arguments để load extensions
67
109
  */
@@ -509,6 +509,198 @@ class ExtensionHandler {
509
509
  throw error;
510
510
  }
511
511
  }
512
+ /**
513
+ * Đọc và parse manifest.json từ extension path
514
+ */
515
+ static readManifest(extensionPath) {
516
+ const manifestPath = path.join(extensionPath, 'manifest.json');
517
+ if (!fs.existsSync(manifestPath)) {
518
+ throw new Error('manifest.json not found');
519
+ }
520
+ const content = fs.readFileSync(manifestPath, 'utf-8');
521
+ return JSON.parse(content);
522
+ }
523
+ /**
524
+ * Lấy extension ID từ manifest.json
525
+ * Nếu là Chrome Store ID thì dùng ID đó, nếu không thì generate từ manifest key
526
+ */
527
+ static getExtensionId(manifest, originalExtensionInput) {
528
+ // Nếu input là Chrome Store ID (32 chars), dùng nó
529
+ if (originalExtensionInput && /^[a-z0-9]{32}$/i.test(originalExtensionInput)) {
530
+ return originalExtensionInput.toLowerCase();
531
+ }
532
+ // Nếu manifest có key (public key), generate ID từ key theo cách Chrome làm
533
+ if (manifest.key) {
534
+ try {
535
+ // Chrome extension ID được generate từ public key PEM
536
+ // Format: Base32 encoding của first 128 bits (16 bytes) của SHA256(public key)
537
+ let keyData = manifest.key;
538
+ let keyString = '';
539
+ // Nếu key là string (PEM format hoặc base64)
540
+ if (typeof keyData === 'string') {
541
+ keyString = keyData;
542
+ }
543
+ else if (Array.isArray(keyData) || Buffer.isBuffer(keyData)) {
544
+ keyString = Buffer.from(keyData).toString('base64');
545
+ }
546
+ if (keyString) {
547
+ // Parse PEM nếu có
548
+ if (keyString.includes('-----BEGIN')) {
549
+ // Extract base64 content từ PEM
550
+ const pemMatch = keyString.match(/-----BEGIN[^-]+-----([\s\S]*)-----END[^-]+-----/);
551
+ if (pemMatch) {
552
+ keyString = pemMatch[1].replace(/\s/g, '');
553
+ }
554
+ }
555
+ // SHA256 hash của public key
556
+ const hash = crypto.createHash('sha256').update(keyString).digest();
557
+ // Base32 encode first 16 bytes (128 bits)
558
+ const base32Chars = 'abcdefghijklmnopqrstuvwxyz234567';
559
+ let extensionId = '';
560
+ let bits = 0;
561
+ let value = 0;
562
+ for (let i = 0; i < 16; i++) {
563
+ value = (value << 8) | hash[i];
564
+ bits += 8;
565
+ while (bits >= 5) {
566
+ extensionId += base32Chars[(value >>> (bits - 5)) & 31];
567
+ bits -= 5;
568
+ }
569
+ }
570
+ if (bits > 0) {
571
+ extensionId += base32Chars[(value << (5 - bits)) & 31];
572
+ }
573
+ // Chrome extension ID luôn là 32 ký tự
574
+ return extensionId.substring(0, 32).toLowerCase();
575
+ }
576
+ }
577
+ catch (error) {
578
+ console.warn(`[ExtensionHandler] Could not generate ID from key:`, error);
579
+ }
580
+ }
581
+ // Fallback: Generate ID từ manifest (name + path hash) - luôn tạo 32 ký tự
582
+ const name = manifest.name || 'extension';
583
+ const version = manifest.version || '1.0.0';
584
+ // Dùng SHA256 và lấy 32 ký tự đầu tiên (hex)
585
+ const hash = crypto
586
+ .createHash('sha256')
587
+ .update(`${name}${version}${Date.now()}`)
588
+ .digest('hex')
589
+ .substring(0, 32);
590
+ return hash;
591
+ }
592
+ /**
593
+ * Lấy version từ manifest.json
594
+ */
595
+ static getExtensionVersion(manifest) {
596
+ return manifest.version || '1.0.0';
597
+ }
598
+ /**
599
+ * Copy extension vào profile Extensions directory
600
+ * Structure: {profile}/Default/Extensions/{extension-id}/{version}/
601
+ */
602
+ static async installExtensionToProfile(extensionPath, profilePath, originalExtensionInput) {
603
+ console.log(`[ExtensionHandler] Installing extension to profile: ${extensionPath} -> ${profilePath}`);
604
+ // Đọc manifest
605
+ const manifest = this.readManifest(extensionPath);
606
+ const extensionId = this.getExtensionId(manifest, originalExtensionInput);
607
+ const version = this.getExtensionVersion(manifest);
608
+ // Tạo Extensions directory structure
609
+ const extensionsDir = path.join(profilePath, 'Default', 'Extensions');
610
+ if (!fs.existsSync(extensionsDir)) {
611
+ fs.mkdirSync(extensionsDir, { recursive: true });
612
+ }
613
+ const extensionIdDir = path.join(extensionsDir, extensionId);
614
+ if (!fs.existsSync(extensionIdDir)) {
615
+ fs.mkdirSync(extensionIdDir, { recursive: true });
616
+ }
617
+ // Version directory (format: {version}_0)
618
+ const versionDir = path.join(extensionIdDir, `${version}_0`);
619
+ if (fs.existsSync(versionDir)) {
620
+ // Nếu đã tồn tại, xóa và copy lại
621
+ fs.rmSync(versionDir, { recursive: true, force: true });
622
+ }
623
+ fs.mkdirSync(versionDir, { recursive: true });
624
+ // Copy tất cả files từ extensionPath vào versionDir
625
+ this.copyDirectory(extensionPath, versionDir);
626
+ // Tạo _metadata directory và verified_contents.json
627
+ const metadataDir = path.join(versionDir, '_metadata');
628
+ fs.mkdirSync(metadataDir, { recursive: true });
629
+ // Tạo verified_contents.json (simplified version)
630
+ const verifiedContents = this.createVerifiedContents(extensionId, version, versionDir);
631
+ const verifiedContentsPath = path.join(metadataDir, 'verified_contents.json');
632
+ fs.writeFileSync(verifiedContentsPath, JSON.stringify(verifiedContents, null, 2), 'utf-8');
633
+ console.log(`[ExtensionHandler] ✓ Extension installed: ${extensionId} v${version} -> ${versionDir}`);
634
+ return {
635
+ extensionId,
636
+ version,
637
+ path: versionDir,
638
+ manifest,
639
+ };
640
+ }
641
+ /**
642
+ * Copy directory recursively
643
+ */
644
+ static copyDirectory(src, dest) {
645
+ if (!fs.existsSync(src)) {
646
+ throw new Error(`Source directory does not exist: ${src}`);
647
+ }
648
+ const entries = fs.readdirSync(src, { withFileTypes: true });
649
+ for (const entry of entries) {
650
+ const srcPath = path.join(src, entry.name);
651
+ const destPath = path.join(dest, entry.name);
652
+ if (entry.isDirectory()) {
653
+ // Skip _metadata if copying from another profile
654
+ if (entry.name === '_metadata' && fs.existsSync(destPath)) {
655
+ continue;
656
+ }
657
+ fs.mkdirSync(destPath, { recursive: true });
658
+ this.copyDirectory(srcPath, destPath);
659
+ }
660
+ else {
661
+ fs.copyFileSync(srcPath, destPath);
662
+ }
663
+ }
664
+ }
665
+ /**
666
+ * Tạo verified_contents.json structure
667
+ */
668
+ static createVerifiedContents(extensionId, version, extensionPath) {
669
+ // Simplified verified_contents - Chrome sẽ tự verify khi cần
670
+ // Đây là format cơ bản, Chrome có thể tự generate lại khi cần
671
+ return [
672
+ {
673
+ description: 'treehash per file',
674
+ signed_content: {
675
+ payload: '',
676
+ signatures: [],
677
+ },
678
+ },
679
+ ];
680
+ }
681
+ /**
682
+ * Validate extension và lấy thông tin chi tiết
683
+ */
684
+ static async validateAndGetExtensionInfo(extension, workspacePath) {
685
+ const validation = await this.validateExtension(extension, workspacePath);
686
+ if (validation.valid && validation.path) {
687
+ try {
688
+ const manifest = this.readManifest(validation.path);
689
+ const extensionId = this.getExtensionId(manifest, extension);
690
+ const version = this.getExtensionVersion(manifest);
691
+ return {
692
+ ...validation,
693
+ extensionId,
694
+ version,
695
+ };
696
+ }
697
+ catch (error) {
698
+ console.warn(`[ExtensionHandler] Could not read extension info:`, error);
699
+ return validation;
700
+ }
701
+ }
702
+ return validation;
703
+ }
512
704
  /**
513
705
  * Get Chrome arguments để load extensions
514
706
  */
@@ -29,4 +29,13 @@ export declare class ProfileManager {
29
29
  * Đảm bảo file tồn tại và có cấu trúc gologin đầy đủ
30
30
  */
31
31
  private initializePreferencesFile;
32
+ /**
33
+ * Lưu extension configuration vào Preferences file
34
+ */
35
+ saveExtensionsToPreferences(profilePath: string, extensions: Array<{
36
+ extensionId: string;
37
+ version: string;
38
+ path: string;
39
+ manifest?: any;
40
+ }>): void;
32
41
  }
@@ -643,5 +643,88 @@ class ProfileManager {
643
643
  // Ignore errors - initialization is optional
644
644
  }
645
645
  }
646
+ /**
647
+ * Lưu extension configuration vào Preferences file
648
+ */
649
+ saveExtensionsToPreferences(profilePath, extensions) {
650
+ try {
651
+ const prefsPath = path.join(profilePath, 'Default', 'Preferences');
652
+ let prefs = {};
653
+ if (fs.existsSync(prefsPath)) {
654
+ try {
655
+ const prefsContent = fs.readFileSync(prefsPath, 'utf-8');
656
+ prefs = JSON.parse(prefsContent);
657
+ }
658
+ catch (error) {
659
+ console.error(`[ProfileManager] Error reading Preferences:`, error);
660
+ return;
661
+ }
662
+ }
663
+ // Khởi tạo extensions structure
664
+ if (!prefs.extensions) {
665
+ prefs.extensions = {};
666
+ }
667
+ if (!prefs.extensions.settings) {
668
+ prefs.extensions.settings = {};
669
+ }
670
+ if (!prefs.extensions.preferences) {
671
+ prefs.extensions.preferences = {};
672
+ }
673
+ // Lưu từng extension
674
+ const extensionIds = [];
675
+ for (const ext of extensions) {
676
+ const extensionId = ext.extensionId;
677
+ extensionIds.push(extensionId);
678
+ // Đọc manifest nếu chưa có
679
+ let manifest = ext.manifest;
680
+ if (!manifest) {
681
+ try {
682
+ const manifestPath = path.join(ext.path, 'manifest.json');
683
+ if (fs.existsSync(manifestPath)) {
684
+ const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
685
+ manifest = JSON.parse(manifestContent);
686
+ }
687
+ }
688
+ catch (error) {
689
+ console.warn(`[ProfileManager] Could not read manifest for ${extensionId}:`, error);
690
+ }
691
+ }
692
+ // Tính relative path từ Default directory
693
+ const relativePath = path.relative(path.join(profilePath, 'Default'), ext.path).replace(/\\/g, '/');
694
+ // Cấu hình extension settings
695
+ prefs.extensions.settings[extensionId] = {
696
+ location: 1,
697
+ creation_flags: 1,
698
+ path: relativePath,
699
+ state: 1,
700
+ incognito: 0,
701
+ manifest: manifest || {},
702
+ granted_permissions: manifest?.permissions || [],
703
+ withheld_permissions: [],
704
+ disable_reasons: 0,
705
+ installation_timestamp: Date.now(),
706
+ update_url: '',
707
+ };
708
+ console.log(`[ProfileManager] ✓ Configured extension ${extensionId} in Preferences`);
709
+ }
710
+ // Lưu danh sách extension IDs
711
+ prefs.extensions.preferences = {
712
+ ...prefs.extensions.preferences,
713
+ extensions: {
714
+ ...(prefs.extensions.preferences.extensions || {}),
715
+ ui: {
716
+ developer_mode: false,
717
+ },
718
+ },
719
+ };
720
+ // Lưu lại Preferences
721
+ fs.writeFileSync(prefsPath, JSON.stringify(prefs, null, 2), 'utf-8');
722
+ console.log(`[ProfileManager] ✓ Saved ${extensionIds.length} extension(s) to Preferences`);
723
+ }
724
+ catch (error) {
725
+ console.error(`[ProfileManager] Error saving extensions to Preferences:`, error);
726
+ throw error;
727
+ }
728
+ }
646
729
  }
647
730
  exports.ProfileManager = ProfileManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-nvk-browser",
3
- "version": "1.0.78",
3
+ "version": "1.0.79",
4
4
  "description": "n8n nodes for managing Chrome browser profiles and page interactions with Puppeteer automation",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",