@lynx-js/cef-webview 0.0.1-alpha.4
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 +71 -0
- package/index.cjs +23 -0
- package/lynx.lib.json +7 -0
- package/package.json +47 -0
- package/scripts/install.js +95 -0
- package/utils/download.js +72 -0
- package/utils/env-config.js +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @lynx-js/cef-webview
|
|
2
|
+
|
|
3
|
+
A x-webview element implementation based on Chromium Embedded Framework (CEF) for Lynxtron.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This library provides a CEF-based implementation of the x-webview element for Lynxtron applications. It allows you to embed Chromium-based web content within your Lynxtron app, providing a full-featured web browsing experience.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @lynx-js/cef-webview
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Enable the Lynxtron autolink plugin in your application build. AutoLink requires
|
|
18
|
+
`@lynx-js/cef-webview/node-api`, which loads the current platform's Node-API
|
|
19
|
+
addon so its static Lynx registrations run during startup. CEF itself is
|
|
20
|
+
initialized only when you call `initialize()`.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import cefWebview from '@lynx-js/cef-webview/node-api';
|
|
24
|
+
|
|
25
|
+
cefWebview.initialize();
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Once initialized, you can use the x-webview element in your Lynx templates:
|
|
29
|
+
|
|
30
|
+
```xml
|
|
31
|
+
<x-webview src="https://www.example.com" width="100%" height="500px"></x-webview>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Building
|
|
35
|
+
|
|
36
|
+
### Prerequisites
|
|
37
|
+
|
|
38
|
+
- Node.js >= 18
|
|
39
|
+
- CMake
|
|
40
|
+
- CEF SDK
|
|
41
|
+
|
|
42
|
+
### Build Steps
|
|
43
|
+
|
|
44
|
+
1. Clone the repository
|
|
45
|
+
2. Install dependencies:
|
|
46
|
+
```bash
|
|
47
|
+
npm install
|
|
48
|
+
```
|
|
49
|
+
3. Build the native addon:
|
|
50
|
+
```bash
|
|
51
|
+
npx cmake-js build
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Dependencies
|
|
55
|
+
|
|
56
|
+
- **Runtime Dependencies:**
|
|
57
|
+
- js-yaml
|
|
58
|
+
- plist
|
|
59
|
+
|
|
60
|
+
- **Development Dependencies:**
|
|
61
|
+
- node-addon-api
|
|
62
|
+
- cmake-js
|
|
63
|
+
- lynxtron
|
|
64
|
+
|
|
65
|
+
## Contributing
|
|
66
|
+
|
|
67
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
68
|
+
|
|
69
|
+
## Authors
|
|
70
|
+
|
|
71
|
+
Lynxtron Authors
|
package/index.cjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const platform = process.platform;
|
|
4
|
+
const arch = process.arch;
|
|
5
|
+
|
|
6
|
+
const nativeBinding = require(path.join(
|
|
7
|
+
__dirname,
|
|
8
|
+
'dist',
|
|
9
|
+
platform,
|
|
10
|
+
arch,
|
|
11
|
+
'cef_extension.node',
|
|
12
|
+
));
|
|
13
|
+
|
|
14
|
+
function initialize(options = {}) {
|
|
15
|
+
return nativeBinding.initialize(options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const cefWebview = {
|
|
19
|
+
initialize,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports = cefWebview;
|
|
23
|
+
module.exports.default = cefWebview;
|
package/lynx.lib.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lynx-js/cef-webview",
|
|
3
|
+
"author": "Lynxtron Authors",
|
|
4
|
+
"version": "0.0.1-alpha.4",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "a webview element based on cef",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/lynx-family/lynxtron.git"
|
|
10
|
+
},
|
|
11
|
+
"main": "index.cjs",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./index.cjs",
|
|
14
|
+
"./node-api": "./index.cjs",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node scripts/install.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"lynx",
|
|
22
|
+
"cef",
|
|
23
|
+
"webview"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"index.cjs",
|
|
27
|
+
"lynx.lib.json",
|
|
28
|
+
"README.md",
|
|
29
|
+
"dist/**/*",
|
|
30
|
+
"scripts/**/*",
|
|
31
|
+
"utils/**/*"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"js-yaml": "^4.1.1",
|
|
35
|
+
"plist": "^3.1.0",
|
|
36
|
+
"extract-zip": "2.0.1",
|
|
37
|
+
"node-fetch": "3.3.2"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"node-addon-api": "^7.0.0",
|
|
41
|
+
"cmake-js": "^7.4.0",
|
|
42
|
+
"@lynx-js/lynxtron": "*"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { downloadBinary } from '../utils/download.js';
|
|
5
|
+
import { BASE_URL, VERSION, ARCH, PLATFORM} from '../utils/env-config.js';
|
|
6
|
+
import extractZip from 'extract-zip';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
const CEF_WEBVIEW_PATH = path.join(__dirname, "..", "dist", PLATFORM, ARCH);
|
|
12
|
+
|
|
13
|
+
const hasDownloadCefWebview = () => {
|
|
14
|
+
return fs.existsSync(CEF_WEBVIEW_PATH);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// if cef-webview is already installed, exit.
|
|
18
|
+
if (hasDownloadCefWebview() && !process.env.npm_config_force_download) {
|
|
19
|
+
console.log("cef-webview is already installed");
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (process.env.npm_config_skip_cef_webview_download) {
|
|
24
|
+
console.log("skip cef-webview download by env");
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const base_url = BASE_URL;
|
|
29
|
+
|
|
30
|
+
let downloadUrl = ''
|
|
31
|
+
if (process.env.npm_config_custom_cef_webview_binary_url) {
|
|
32
|
+
console.log(`using custom cef-webview url: ${process.env.npm_config_custom_cef_webview_binary_url}`);
|
|
33
|
+
downloadUrl = process.env.npm_config_custom_cef_webview_binary_url;
|
|
34
|
+
} else {
|
|
35
|
+
if (!base_url) {
|
|
36
|
+
console.log("cef-webview base url is empty");
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
downloadUrl = `${base_url}/v${VERSION}/cef_webview-v${VERSION}-${PLATFORM}-${ARCH}.zip`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`downloading cef-webview from ${downloadUrl}`);
|
|
43
|
+
|
|
44
|
+
const PACKAGE_DIR_PATH = path.join(__dirname, "..", "dist", PLATFORM, ARCH);
|
|
45
|
+
const PACKAGE_PATH = path.join(PACKAGE_DIR_PATH, `${VERSION}.zip`);
|
|
46
|
+
if (fs.existsSync(PACKAGE_PATH)) {
|
|
47
|
+
fs.rmSync(path.join(__dirname, "..", "dist"), { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
fs.mkdirSync(PACKAGE_DIR_PATH, { recursive: true });
|
|
50
|
+
|
|
51
|
+
await downloadBinary(downloadUrl, PACKAGE_PATH, { timeoutMs: 120000 });
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(PACKAGE_PATH)) {
|
|
54
|
+
throw new Error("cef-webview download failed");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Begin extract zip file
|
|
58
|
+
console.log(`Begin extract zip file: ${PACKAGE_PATH}`);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Unzip file by extract-zip module
|
|
62
|
+
const TMP_DIR = path.join(CEF_WEBVIEW_PATH, '_tmp_extract');
|
|
63
|
+
if (!fs.existsSync(TMP_DIR)) {
|
|
64
|
+
fs.mkdirSync(TMP_DIR, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
await extractZip(PACKAGE_PATH, { dir: TMP_DIR });
|
|
67
|
+
const SRC_DIR = path.join(TMP_DIR, `cef_webview-v${VERSION}-${PLATFORM}-${ARCH}`);
|
|
68
|
+
const items = fs.readdirSync(SRC_DIR);
|
|
69
|
+
for (const item of items) {
|
|
70
|
+
const srcPath = path.join(SRC_DIR, item);
|
|
71
|
+
const destPath = path.join(CEF_WEBVIEW_PATH, item);
|
|
72
|
+
fs.renameSync(srcPath, destPath);
|
|
73
|
+
}
|
|
74
|
+
fs.rmSync(TMP_DIR, { recursive: true, force: true });
|
|
75
|
+
console.log('Unzip completed');
|
|
76
|
+
|
|
77
|
+
// Delete original zip file after unzip to release space
|
|
78
|
+
try {
|
|
79
|
+
fs.unlinkSync(PACKAGE_PATH);
|
|
80
|
+
console.log(`Deleted temporary zip file: ${PACKAGE_PATH}`);
|
|
81
|
+
} catch (deleteError) {
|
|
82
|
+
console.warn('Error deleting zip file:', deleteError);
|
|
83
|
+
// Here we don't throw an error because unzip is already successful, and delete failure does not affect the main function
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Verify unzip success
|
|
87
|
+
if (fs.existsSync(CEF_WEBVIEW_PATH)) {
|
|
88
|
+
console.log('Install success: cef-webview has been successfully downloaded and unzipped');
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error(`Unzip failed: Expected executable file ${CEF_WEBVIEW_PATH} not found`);
|
|
91
|
+
}
|
|
92
|
+
} catch (extractError) {
|
|
93
|
+
console.error('Error extracting zip file:', extractError);
|
|
94
|
+
throw new Error('Install failed: Error extracting zip file');
|
|
95
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fetch from 'node-fetch';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Download binary file (arrayBuffer version)
|
|
7
|
+
* @param {string} url - URL of the file to download
|
|
8
|
+
* @param {string} outputPath - Output file path
|
|
9
|
+
* @param {Object} options - Download options
|
|
10
|
+
* @param {number} options.timeoutMs - Download timeout in milliseconds
|
|
11
|
+
* @returns {Promise<void>}
|
|
12
|
+
*/
|
|
13
|
+
export async function downloadBinary(url, outputPath, options = {}) {
|
|
14
|
+
const { timeoutMs = 120000 } = options;
|
|
15
|
+
|
|
16
|
+
// Ensure output directory exists
|
|
17
|
+
const outputDir = path.dirname(outputPath);
|
|
18
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
19
|
+
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const signal = controller.signal;
|
|
22
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(url, { signal });
|
|
26
|
+
clearTimeout(timeoutId);
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Download failed: HTTP ${response.status} ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Get content length if available
|
|
33
|
+
const contentLength = response.headers.get('content-length');
|
|
34
|
+
const totalBytes = contentLength ? parseInt(contentLength, 10) : null;
|
|
35
|
+
|
|
36
|
+
// Read to memory
|
|
37
|
+
const ab = await response.arrayBuffer();
|
|
38
|
+
const buffer = Buffer.from(ab);
|
|
39
|
+
|
|
40
|
+
// Write to file (synchronous one-time write)
|
|
41
|
+
await fs.promises.writeFile(outputPath, buffer);
|
|
42
|
+
|
|
43
|
+
// Console output progress information
|
|
44
|
+
if (totalBytes) {
|
|
45
|
+
process.stdout.write(`\nDownload completed: ${formatBytes(buffer.length)} / ${formatBytes(totalBytes)}\n`);
|
|
46
|
+
} else {
|
|
47
|
+
process.stdout.write(`\nDownload completed: ${formatBytes(buffer.length)}\n`);
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
|
|
52
|
+
// Clean up partially downloaded file
|
|
53
|
+
try {
|
|
54
|
+
await fs.promises.unlink(outputPath);
|
|
55
|
+
} catch (_) {
|
|
56
|
+
// Ignore cleanup failure
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (error.name === 'AbortError') {
|
|
60
|
+
throw new Error(`Download timeout: Exceeded ${timeoutMs} milliseconds`);
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function formatBytes(bytes) {
|
|
67
|
+
if (bytes === 0) return '0 Bytes';
|
|
68
|
+
const k = 1024;
|
|
69
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
70
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
71
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
72
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export const BASE_URL = 'https://github.com/lynx-family/lynxtron/releases/download/';
|
|
10
|
+
|
|
11
|
+
const pckJson = JSON.parse(fs.readFileSync(path.join(__dirname, "..", 'package.json'), 'utf8'));
|
|
12
|
+
|
|
13
|
+
export const VERSION = pckJson.version;
|
|
14
|
+
|
|
15
|
+
export const ARCH = process.env.npm_config_arch || process.arch;
|
|
16
|
+
|
|
17
|
+
export const PLATFORM = process.env.npm_config_platform || os.platform();
|