@lynx-js/lynxtron 0.0.1 → 0.0.3
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 +80 -0
- package/apis/api/app.d.ts +1848 -0
- package/apis/api/asar.d.ts +124 -0
- package/apis/api/base-window.d.ts +1712 -0
- package/apis/api/clipboard.d.ts +54 -0
- package/apis/api/command-line.d.ts +46 -0
- package/apis/api/context-bridge.d.ts +8 -0
- package/apis/api/devtool.d.ts +34 -0
- package/apis/api/dialog.d.ts +633 -0
- package/apis/api/dock.d.ts +88 -0
- package/apis/api/environment.d.ts +9 -0
- package/apis/api/event.d.ts +8 -0
- package/apis/api/jump-list-item.d.ts +83 -0
- package/apis/api/lynx-library.d.ts +7 -0
- package/apis/api/lynx-template-bundle.d.ts +32 -0
- package/apis/api/lynx-template-data.d.ts +10 -0
- package/apis/api/lynx-update-meta.d.ts +16 -0
- package/apis/api/lynx-window.d.ts +405 -0
- package/apis/api/menu.d.ts +96 -0
- package/apis/api/native-image.d.ts +268 -0
- package/apis/api/notification-response.d.ts +26 -0
- package/apis/api/notification.d.ts +242 -0
- package/apis/api/power-monitor.d.ts +121 -0
- package/apis/api/process-metric.d.ts +93 -0
- package/apis/api/protocol.d.ts +54 -0
- package/apis/api/screen.d.ts +222 -0
- package/apis/api/shell.d.ts +124 -0
- package/apis/api/task.d.ts +39 -0
- package/apis/api/touch-bar.d.ts +206 -0
- package/apis/api/tray.d.ts +44 -0
- package/apis/api/utility-process.d.ts +73 -0
- package/apis/lynx.d.ts +15 -0
- package/apis/lynxtron.d.ts +39 -0
- package/apis/structures/point.d.ts +10 -0
- package/apis/structures/rectangle.d.ts +22 -0
- package/apis/structures/size.d.ts +8 -0
- package/apis/web-host.d.ts +24 -0
- package/cli.js +28 -0
- package/context-bridge.js +6 -0
- package/fuses-cli.js +143 -0
- package/fuses.js +299 -0
- package/install.js +127 -0
- package/lynx.js +1 -0
- package/lynxtron.js +43 -0
- package/lynxtron_bin.js +10 -0
- package/native-paths.cjs +38 -0
- package/package.json +71 -4
- package/utils/download.js +72 -0
- package/utils/env-config.js +35 -0
- package/web-host/index.js +110 -0
- package/web-worker.js +12 -0
package/fuses.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import lynxtronBinaryPath from './lynxtron_bin.js';
|
|
5
|
+
|
|
6
|
+
const FUSE_SENTINEL = Buffer.from('dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX', 'ascii');
|
|
7
|
+
const FUSE_VERSION = 1;
|
|
8
|
+
const MAX_SUPPORTED_SENTINELS = 2;
|
|
9
|
+
const ENABLED_FUSE_VALUE = '1';
|
|
10
|
+
const DISABLED_FUSE_VALUE = '0';
|
|
11
|
+
|
|
12
|
+
const FUSE_SCHEMA = [
|
|
13
|
+
{ key: 'runAsNode', defaultValue: true },
|
|
14
|
+
{ key: 'nodeOptions', defaultValue: true },
|
|
15
|
+
{ key: 'nodeCliInspect', defaultValue: true },
|
|
16
|
+
{ key: 'embeddedAsarIntegrityValidation', defaultValue: false },
|
|
17
|
+
{ key: 'onlyLoadAppFromAsar', defaultValue: false },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export const FuseVersion = Object.freeze({
|
|
21
|
+
V1: FUSE_VERSION,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const FuseV1Options = Object.freeze({
|
|
25
|
+
RunAsNode: 'runAsNode',
|
|
26
|
+
EnableNodeOptionsEnvironmentVariable: 'nodeOptions',
|
|
27
|
+
EnableNodeCliInspectArguments: 'nodeCliInspect',
|
|
28
|
+
EnableEmbeddedAsarIntegrityValidation: 'embeddedAsarIntegrityValidation',
|
|
29
|
+
OnlyLoadAppFromAsar: 'onlyLoadAppFromAsar',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function resolveTarget(target) {
|
|
33
|
+
if (target == null) {
|
|
34
|
+
return { binary: lynxtronBinaryPath };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof target === 'string') {
|
|
38
|
+
const normalizedTarget = target.toLowerCase();
|
|
39
|
+
if (normalizedTarget.endsWith('.app')) {
|
|
40
|
+
return { app: target };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (normalizedTarget.endsWith('.exe')) {
|
|
44
|
+
return { binary: target };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { app: target };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof target !== 'object') {
|
|
51
|
+
throw new TypeError('target must be a path string or an options object');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return target;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function pathExists(candidatePath) {
|
|
58
|
+
try {
|
|
59
|
+
await fs.access(candidatePath);
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function resolveAppBinaryPath(appPath) {
|
|
67
|
+
const resolvedAppPath = path.resolve(appPath);
|
|
68
|
+
const lowerCaseAppPath = resolvedAppPath.toLowerCase();
|
|
69
|
+
|
|
70
|
+
if (lowerCaseAppPath.endsWith('.app')) {
|
|
71
|
+
const frameworkBinaryPath = path.join(
|
|
72
|
+
resolvedAppPath,
|
|
73
|
+
'Contents',
|
|
74
|
+
'Frameworks',
|
|
75
|
+
'Lynxtron Framework.framework',
|
|
76
|
+
'Lynxtron Framework'
|
|
77
|
+
);
|
|
78
|
+
if (await pathExists(frameworkBinaryPath)) {
|
|
79
|
+
return frameworkBinaryPath;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return path.join(resolvedAppPath, 'Contents', 'MacOS', 'Lynxtron');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (lowerCaseAppPath.endsWith('.exe')) {
|
|
86
|
+
return resolvedAppPath;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const macFrameworkBinaryPath = path.join(
|
|
90
|
+
resolvedAppPath,
|
|
91
|
+
'Contents',
|
|
92
|
+
'Frameworks',
|
|
93
|
+
'Lynxtron Framework.framework',
|
|
94
|
+
'Lynxtron Framework'
|
|
95
|
+
);
|
|
96
|
+
if (await pathExists(macFrameworkBinaryPath)) {
|
|
97
|
+
return macFrameworkBinaryPath;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const macBinaryPath = path.join(
|
|
101
|
+
resolvedAppPath,
|
|
102
|
+
'Contents',
|
|
103
|
+
'MacOS',
|
|
104
|
+
'Lynxtron'
|
|
105
|
+
);
|
|
106
|
+
if (await pathExists(macBinaryPath)) {
|
|
107
|
+
return macBinaryPath;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const candidate of ['lynxtron.dll', 'Lynxtron.dll']) {
|
|
111
|
+
const windowsBinaryPath = path.join(resolvedAppPath, candidate);
|
|
112
|
+
if (await pathExists(windowsBinaryPath)) {
|
|
113
|
+
return windowsBinaryPath;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const candidate of ['lynxtron.exe', 'Lynxtron.exe']) {
|
|
118
|
+
const windowsExecutablePath = path.join(resolvedAppPath, candidate);
|
|
119
|
+
if (await pathExists(windowsExecutablePath)) {
|
|
120
|
+
return windowsExecutablePath;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Unable to resolve a Lynxtron binary from app path: ${resolvedAppPath}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function resolveBinaryPath(target) {
|
|
130
|
+
if (target.binary && target.app) {
|
|
131
|
+
throw new Error('Provide either "binary" or "app", not both');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (target.binary) {
|
|
135
|
+
return path.resolve(target.binary);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (target.app) {
|
|
139
|
+
return resolveAppBinaryPath(target.app);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return path.resolve(lynxtronBinaryPath);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function findSentinelOffsets(buffer) {
|
|
146
|
+
const offsets = [];
|
|
147
|
+
let searchFrom = 0;
|
|
148
|
+
|
|
149
|
+
while (searchFrom < buffer.length) {
|
|
150
|
+
const offset = buffer.indexOf(FUSE_SENTINEL, searchFrom);
|
|
151
|
+
if (offset === -1) {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
offsets.push(offset);
|
|
156
|
+
searchFrom = offset + FUSE_SENTINEL.length;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (offsets.length === 0) {
|
|
160
|
+
throw new Error('Unable to locate a Lynxtron fuse wire in the target binary');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (offsets.length > MAX_SUPPORTED_SENTINELS) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Unsupported Lynxtron binary: found ${offsets.length} fuse sentinels`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return offsets;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getWireRecord(buffer, sentinelOffset) {
|
|
173
|
+
const versionOffset = sentinelOffset + FUSE_SENTINEL.length;
|
|
174
|
+
const wireLengthOffset = versionOffset + 1;
|
|
175
|
+
const wireOffset = wireLengthOffset + 1;
|
|
176
|
+
const version = buffer[versionOffset];
|
|
177
|
+
const wireLength = buffer[wireLengthOffset];
|
|
178
|
+
|
|
179
|
+
if (version !== FUSE_VERSION) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Unsupported Lynxtron fuse version ${version}; expected ${FUSE_VERSION}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (wireLength !== FUSE_SCHEMA.length) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Unsupported Lynxtron fuse wire length ${wireLength}; expected ${FUSE_SCHEMA.length}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
wireOffset,
|
|
193
|
+
wireLength,
|
|
194
|
+
wire: buffer.subarray(wireOffset, wireOffset + wireLength),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readWire(buffer) {
|
|
199
|
+
const records = findSentinelOffsets(buffer).map((offset) =>
|
|
200
|
+
getWireRecord(buffer, offset)
|
|
201
|
+
);
|
|
202
|
+
const referenceWire = Buffer.from(records[0].wire);
|
|
203
|
+
|
|
204
|
+
for (const record of records.slice(1)) {
|
|
205
|
+
if (!Buffer.from(record.wire).equals(referenceWire)) {
|
|
206
|
+
throw new Error('Fuse wires differ across binary slices');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { records, wire: referenceWire };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function decodeWire(wire) {
|
|
214
|
+
const values = { version: FuseVersion.V1 };
|
|
215
|
+
|
|
216
|
+
for (const [index, fuse] of FUSE_SCHEMA.entries()) {
|
|
217
|
+
const rawValue = String.fromCharCode(wire[index]);
|
|
218
|
+
if (rawValue !== ENABLED_FUSE_VALUE && rawValue !== DISABLED_FUSE_VALUE) {
|
|
219
|
+
throw new Error(`Unsupported fuse value "${rawValue}" for ${fuse.key}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
values[fuse.key] = rawValue === ENABLED_FUSE_VALUE;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return values;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function encodeWire(currentFuses, config) {
|
|
229
|
+
if (config.version !== FuseVersion.V1) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`Unsupported Lynxtron fuse version ${config.version}; expected ${FuseVersion.V1}`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const nextFuses = { ...currentFuses };
|
|
236
|
+
|
|
237
|
+
for (const [key, value] of Object.entries(config)) {
|
|
238
|
+
if (key === 'version') {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!Object.values(FuseV1Options).includes(key)) {
|
|
243
|
+
throw new Error(`Unknown Lynxtron fuse "${key}"`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (typeof value !== 'boolean') {
|
|
247
|
+
throw new TypeError(`Fuse "${key}" must be a boolean`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
nextFuses[key] = value;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const bytes = FUSE_SCHEMA.map(({ key }) =>
|
|
254
|
+
nextFuses[key] ? ENABLED_FUSE_VALUE : DISABLED_FUSE_VALUE
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return { nextFuses, wire: Buffer.from(bytes.join(''), 'ascii') };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function getCurrentFuses(target) {
|
|
261
|
+
const binaryPath = await resolveBinaryPath(resolveTarget(target));
|
|
262
|
+
const buffer = await fs.readFile(binaryPath);
|
|
263
|
+
const { wire } = readWire(buffer);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
binaryPath,
|
|
267
|
+
...decodeWire(wire),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function flipFuses(target, config) {
|
|
272
|
+
if (config == null || typeof config !== 'object') {
|
|
273
|
+
throw new TypeError('config must be an object');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const binaryPath = await resolveBinaryPath(resolveTarget(target));
|
|
277
|
+
const buffer = await fs.readFile(binaryPath);
|
|
278
|
+
const { records, wire: currentWire } = readWire(buffer);
|
|
279
|
+
const currentFuses = decodeWire(currentWire);
|
|
280
|
+
const { nextFuses, wire } = encodeWire(currentFuses, config);
|
|
281
|
+
|
|
282
|
+
for (const record of records) {
|
|
283
|
+
wire.copy(buffer, record.wireOffset);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
await fs.writeFile(binaryPath, buffer);
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
binaryPath,
|
|
290
|
+
...nextFuses,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function getFuseDefaults() {
|
|
295
|
+
return FUSE_SCHEMA.reduce(
|
|
296
|
+
(result, fuse) => ({ ...result, [fuse.key]: fuse.defaultValue }),
|
|
297
|
+
{ version: FuseVersion.V1 }
|
|
298
|
+
);
|
|
299
|
+
}
|
package/install.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
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, PLATFROM_EXE_PATH } from './utils/env-config.js';
|
|
6
|
+
|
|
7
|
+
import extractZip from 'extract-zip';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const LYNXTRON_PATH = path.join(__dirname, "dist", PLATFROM_EXE_PATH);
|
|
14
|
+
|
|
15
|
+
const hasDownloadLynxtron = () => {
|
|
16
|
+
return fs.existsSync(LYNXTRON_PATH);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// if lynxtron is already installed, exit.
|
|
20
|
+
if (hasDownloadLynxtron() && !process.env.npm_config_force_download) {
|
|
21
|
+
console.log("lynxtron is already installed");
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const base_url = BASE_URL;
|
|
26
|
+
|
|
27
|
+
let downloadUrl = ''
|
|
28
|
+
if (process.env.npm_config_custom_lynxtron_binary_url) {
|
|
29
|
+
console.log(`using custom lynxtron url: ${process.env.npm_config_custom_lynxtron_binary_url}`);
|
|
30
|
+
downloadUrl = process.env.npm_config_custom_lynxtron_binary_url;
|
|
31
|
+
} else {
|
|
32
|
+
if (!base_url) {
|
|
33
|
+
console.log("lynxtron base url is empty");
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
downloadUrl = `${base_url}/v${VERSION}/lynxtron-v${VERSION}-${PLATFORM}-${ARCH}.zip`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
console.log(`downloading lynxtron from ${downloadUrl}`);
|
|
41
|
+
|
|
42
|
+
const PACKAGE_DIR_PATH = path.join(__dirname, "dist");
|
|
43
|
+
const PACKAGE_PATH = path.join(PACKAGE_DIR_PATH, `${VERSION}.zip`);
|
|
44
|
+
if (fs.existsSync(PACKAGE_PATH)) {
|
|
45
|
+
fs.rmSync(path.join(__dirname, "dist"), { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
fs.mkdirSync(PACKAGE_DIR_PATH, { recursive: true });
|
|
48
|
+
|
|
49
|
+
await downloadBinary(downloadUrl, PACKAGE_PATH, { timeoutMs: 120000 });
|
|
50
|
+
|
|
51
|
+
if (!fs.existsSync(PACKAGE_PATH)) {
|
|
52
|
+
throw new Error("lynxtron download failed");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Begin extract zip file
|
|
56
|
+
console.log(`Begin extract zip file: ${PACKAGE_PATH}`);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Unzip file by extract-zip module
|
|
60
|
+
await extractZip(PACKAGE_PATH, { dir: PACKAGE_DIR_PATH });
|
|
61
|
+
console.log('Unzip completed');
|
|
62
|
+
|
|
63
|
+
// Restore macOS framework symlinks (extract-zip expands symlinks into copies)
|
|
64
|
+
if (PLATFORM === 'darwin' || PLATFORM === 'mas') {
|
|
65
|
+
try {
|
|
66
|
+
const fwBase = path.join(PACKAGE_DIR_PATH, 'lynxtron.app', 'Contents', 'Frameworks', 'Lynxtron Framework.framework');
|
|
67
|
+
const versionsDir = path.join(fwBase, 'Versions');
|
|
68
|
+
// Find the actual version directory (e.g., "1.0")
|
|
69
|
+
const versions = fs.readdirSync(versionsDir).filter(v => v !== 'Current');
|
|
70
|
+
if (versions.length === 1) {
|
|
71
|
+
const ver = versions[0];
|
|
72
|
+
const verDir = path.join(versionsDir, ver);
|
|
73
|
+
const currentLink = path.join(versionsDir, 'Current');
|
|
74
|
+
const topBinary = path.join(fwBase, 'Lynxtron Framework');
|
|
75
|
+
const topResources = path.join(fwBase, 'Resources');
|
|
76
|
+
|
|
77
|
+
// Remove duplicates and create symlinks
|
|
78
|
+
if (fs.existsSync(currentLink) && !fs.lstatSync(currentLink).isSymbolicLink()) {
|
|
79
|
+
fs.rmSync(currentLink, { recursive: true });
|
|
80
|
+
fs.symlinkSync(ver, currentLink);
|
|
81
|
+
console.log(`Restored symlink: Versions/Current → ${ver}`);
|
|
82
|
+
}
|
|
83
|
+
if (fs.existsSync(topBinary) && !fs.lstatSync(topBinary).isSymbolicLink()) {
|
|
84
|
+
fs.unlinkSync(topBinary);
|
|
85
|
+
fs.symlinkSync(path.join('Versions', 'Current', 'Lynxtron Framework'), topBinary);
|
|
86
|
+
console.log('Restored symlink: Lynxtron Framework → Versions/Current/Lynxtron Framework');
|
|
87
|
+
}
|
|
88
|
+
if (fs.existsSync(topResources) && !fs.lstatSync(topResources).isSymbolicLink()) {
|
|
89
|
+
fs.rmSync(topResources, { recursive: true });
|
|
90
|
+
fs.symlinkSync(path.join('Versions', 'Current', 'Resources'), topResources);
|
|
91
|
+
console.log('Restored symlink: Resources → Versions/Current/Resources');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (symlinkError) {
|
|
95
|
+
console.warn('Warning: Could not restore framework symlinks:', symlinkError.message);
|
|
96
|
+
// Non-fatal — app still works with copies, just wastes disk space
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Delete original zip file after unzip to release space
|
|
101
|
+
try {
|
|
102
|
+
fs.unlinkSync(PACKAGE_PATH);
|
|
103
|
+
console.log(`Deleted temporary zip file: ${PACKAGE_PATH}`);
|
|
104
|
+
} catch (deleteError) {
|
|
105
|
+
console.warn('Error deleting zip file:', deleteError);
|
|
106
|
+
// Here we don't throw an error because unzip is already successful, and delete failure does not affect the main function
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Verify unzip success
|
|
110
|
+
if (fs.existsSync(LYNXTRON_PATH)) {
|
|
111
|
+
console.log('Install success: lynxtron has been successfully downloaded and unzipped');
|
|
112
|
+
// For macOS app, may need to set executable permission
|
|
113
|
+
if (PLATFORM === 'darwin' || PLATFORM === 'mas') {
|
|
114
|
+
try {
|
|
115
|
+
fs.chmodSync(LYNXTRON_PATH, 0o755);
|
|
116
|
+
console.log('Set executable permission successfully');
|
|
117
|
+
} catch (chmodError) {
|
|
118
|
+
console.warn('Error setting executable permission:', chmodError);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
throw new Error(`Unzip failed: Expected executable file ${LYNXTRON_PATH} not found`);
|
|
123
|
+
}
|
|
124
|
+
} catch (extractError) {
|
|
125
|
+
console.error('Error extracting zip file:', extractError);
|
|
126
|
+
throw new Error('Install failed: Error extracting zip file');
|
|
127
|
+
}
|
package/lynx.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lynxtron.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const lynxtron = require('lynxtron');
|
|
6
|
+
|
|
7
|
+
function resolveRegisterGlobalEnvModule() {
|
|
8
|
+
if (typeof lynxtron.registerGlobalEnvModule === 'function') {
|
|
9
|
+
return lynxtron.registerGlobalEnvModule;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
return process._linkedBinding('lynx_extension').registerGlobalEnvModule;
|
|
14
|
+
} catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const app = lynxtron.app;
|
|
20
|
+
export const LynxWindow = lynxtron.LynxWindow;
|
|
21
|
+
export const Menu = lynxtron.Menu;
|
|
22
|
+
export const MenuItem = lynxtron.MenuItem;
|
|
23
|
+
export const clipboard = lynxtron.clipboard;
|
|
24
|
+
export const shell = lynxtron.shell;
|
|
25
|
+
export const dialog = lynxtron.dialog;
|
|
26
|
+
export const devtool = lynxtron.devtool;
|
|
27
|
+
export const screen = lynxtron.screen;
|
|
28
|
+
export const nativeImage = lynxtron.nativeImage;
|
|
29
|
+
export const protocol = lynxtron.protocol;
|
|
30
|
+
export const Dock = lynxtron.Dock;
|
|
31
|
+
export const CommandLine = lynxtron.CommandLine;
|
|
32
|
+
export const splitPath = lynxtron.splitPath;
|
|
33
|
+
export const Archive = lynxtron.Archive;
|
|
34
|
+
export const registerGlobalEnvModule = resolveRegisterGlobalEnvModule();
|
|
35
|
+
export const getVar = lynxtron.getVar;
|
|
36
|
+
export const hasVar = lynxtron.hasVar;
|
|
37
|
+
export const setVar = lynxtron.setVar;
|
|
38
|
+
export const Tray = lynxtron.Tray;
|
|
39
|
+
export const LynxTemplateData = lynxtron.LynxTemplateData;
|
|
40
|
+
export const LynxUpdateMeta = lynxtron.LynxUpdateMeta;
|
|
41
|
+
export const powerMonitor = lynxtron.powerMonitor;
|
|
42
|
+
|
|
43
|
+
export const lynx = Object.freeze({});
|
package/lynxtron_bin.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { PLATFROM_EXE_PATH } from './utils/env-config.js';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
export default path.join(__dirname, "dist", PLATFROM_EXE_PATH);
|
|
9
|
+
|
|
10
|
+
// export default "dist/" + getPlatformPath();
|
package/native-paths.cjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
|
|
3
|
+
const packageRoot = __dirname;
|
|
4
|
+
const distRoot = path.join(packageRoot, 'dist');
|
|
5
|
+
|
|
6
|
+
function platformExecutableName() {
|
|
7
|
+
if (process.platform === 'win32') {
|
|
8
|
+
return 'lynxtron.exe';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (process.platform === 'darwin') {
|
|
12
|
+
return path.join('lynxtron.app', 'Contents', 'MacOS', 'lynxtron');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
throw new Error(
|
|
16
|
+
`lynxtron builds are not available on platform: ${process.platform}`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const executablePath = path.join(distRoot, platformExecutableName());
|
|
21
|
+
|
|
22
|
+
const dllPath =
|
|
23
|
+
process.platform === 'win32'
|
|
24
|
+
? path.join(distRoot, 'lynxtron.dll')
|
|
25
|
+
: undefined;
|
|
26
|
+
|
|
27
|
+
const importLibraryPath =
|
|
28
|
+
process.platform === 'win32'
|
|
29
|
+
? path.join(distRoot, 'lynxtron.dll.lib')
|
|
30
|
+
: undefined;
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
packageRoot,
|
|
34
|
+
distRoot,
|
|
35
|
+
executablePath,
|
|
36
|
+
dllPath,
|
|
37
|
+
importLibraryPath,
|
|
38
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynx-js/lynxtron",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "lynxtron npm package test",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./apis/lynxtron.d.ts",
|
|
7
|
+
"main": "lynxtron.js",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/lynx-family/lynxtron.git"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"lynxtron": "cli.js",
|
|
14
|
+
"lynxtron-fuses": "fuses-cli.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"postinstall": "node install.js",
|
|
18
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"extract-zip": "2.0.1",
|
|
22
|
+
"node-fetch": "3.3.2"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^25.0.3"
|
|
26
|
+
},
|
|
6
27
|
"author": "",
|
|
7
|
-
"license": "
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"keywords": [
|
|
30
|
+
"lynxtron"
|
|
31
|
+
],
|
|
32
|
+
"files": [
|
|
33
|
+
"apis",
|
|
34
|
+
"utils",
|
|
35
|
+
"cli.js",
|
|
36
|
+
"fuses.js",
|
|
37
|
+
"fuses-cli.js",
|
|
38
|
+
"install.js",
|
|
39
|
+
"native-paths.cjs",
|
|
40
|
+
"lynx.js",
|
|
41
|
+
"lynxtron.js",
|
|
42
|
+
"lynxtron_bin.js",
|
|
43
|
+
"web-host",
|
|
44
|
+
"web-worker.js",
|
|
45
|
+
"context-bridge.js",
|
|
46
|
+
"README.md",
|
|
47
|
+
"package.json"
|
|
48
|
+
],
|
|
49
|
+
"exports": {
|
|
50
|
+
".": {
|
|
51
|
+
"types": "./apis/lynxtron.d.ts",
|
|
52
|
+
"default": "./lynxtron.js"
|
|
53
|
+
},
|
|
54
|
+
"./lynx": {
|
|
55
|
+
"types": "./apis/lynx.d.ts",
|
|
56
|
+
"default": "./lynx.js"
|
|
57
|
+
},
|
|
58
|
+
"./fuses": "./fuses.js",
|
|
59
|
+
"./native-paths": "./native-paths.cjs",
|
|
60
|
+
"./package.json": "./package.json",
|
|
61
|
+
"./context-bridge": {
|
|
62
|
+
"types": "./apis/api/context-bridge.d.ts",
|
|
63
|
+
"browser": "./web-worker.js",
|
|
64
|
+
"default": "./context-bridge.js"
|
|
65
|
+
},
|
|
66
|
+
"./web-host": {
|
|
67
|
+
"types": "./apis/web-host.d.ts",
|
|
68
|
+
"default": "./web-host/index.js"
|
|
69
|
+
},
|
|
70
|
+
"./web-worker": "./web-worker.js"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=16"
|
|
74
|
+
}
|
|
8
75
|
}
|
|
@@ -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
|
+
}
|