@docverse-pdf/server 1.1.0 → 1.1.1
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/dist/unoServer.d.ts +19 -4
- package/dist/unoServer.js +52 -13
- package/package.json +1 -1
package/dist/unoServer.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export interface UnoServerOptions {
|
|
2
|
-
/** TCP port the unoserver daemon listens on (default: 2003). */
|
|
2
|
+
/** TCP port the unoserver daemon listens on for its own RPC (default: 2003). */
|
|
3
3
|
port?: number;
|
|
4
|
+
/**
|
|
5
|
+
* TCP port used by the underlying LibreOffice UNO socket. Must be unique per
|
|
6
|
+
* daemon — otherwise multiple daemons collide on the default 2002.
|
|
7
|
+
* (default: undefined = unoserver picks 2002, which only works for one daemon.)
|
|
8
|
+
*/
|
|
9
|
+
unoPort?: number;
|
|
4
10
|
/** Host/interface the daemon binds to (default: 127.0.0.1). */
|
|
5
11
|
host?: string;
|
|
6
12
|
/** Path to the `unoserver` executable (default: auto-detect). */
|
|
@@ -9,18 +15,27 @@ export interface UnoServerOptions {
|
|
|
9
15
|
unoconvertPath?: string;
|
|
10
16
|
/** Path to the LibreOffice `soffice` binary (passed to unoserver --executable). */
|
|
11
17
|
libreOfficePath?: string;
|
|
12
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Dedicated UNO user-profile dir for this daemon (passed to unoserver
|
|
20
|
+
* --user-installation). **Must be an absolute filesystem path, not a
|
|
21
|
+
* `file://` URI** — unoserver converts it internally via `Path(...).as_uri()`.
|
|
22
|
+
*/
|
|
13
23
|
userInstallation?: string;
|
|
14
24
|
/** Max time (ms) to wait for the daemon to become ready (default: 15000). */
|
|
15
25
|
startTimeout?: number;
|
|
16
26
|
/** Max time (ms) to wait for a single conversion (default: 30000). */
|
|
17
27
|
convertTimeout?: number;
|
|
18
28
|
}
|
|
19
|
-
export interface UnoServerPoolOptions extends Omit<UnoServerOptions, 'port' | 'userInstallation'> {
|
|
29
|
+
export interface UnoServerPoolOptions extends Omit<UnoServerOptions, 'port' | 'unoPort' | 'userInstallation'> {
|
|
20
30
|
/** Number of parallel unoserver daemons (default: 2). */
|
|
21
31
|
workers?: number;
|
|
22
|
-
/** First
|
|
32
|
+
/** First daemon RPC port; each worker gets basePort+i (default: 2003). */
|
|
23
33
|
basePort?: number;
|
|
34
|
+
/**
|
|
35
|
+
* First LibreOffice UNO-socket port; each worker gets unoBasePort+i
|
|
36
|
+
* (default: 2100). Must be in a non-overlapping range with `basePort`.
|
|
37
|
+
*/
|
|
38
|
+
unoBasePort?: number;
|
|
24
39
|
/** Base dir for per-worker user-profile dirs (default: os.tmpdir()/docverse_unoserver). */
|
|
25
40
|
tempDir?: string;
|
|
26
41
|
}
|
package/dist/unoServer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, execSync } from 'child_process';
|
|
2
2
|
import { createConnection } from 'net';
|
|
3
|
+
import { resolve as resolvePath } from 'path';
|
|
3
4
|
// ═══════════════════════════════════════════════
|
|
4
5
|
// HELPERS
|
|
5
6
|
// ═══════════════════════════════════════════════
|
|
@@ -104,11 +105,14 @@ export class UnoServer {
|
|
|
104
105
|
constructor(options = {}) {
|
|
105
106
|
this.opts = {
|
|
106
107
|
port: options.port ?? 2003,
|
|
108
|
+
unoPort: options.unoPort,
|
|
107
109
|
host: options.host ?? '127.0.0.1',
|
|
108
110
|
unoserverPath: options.unoserverPath ?? findUnoserver(),
|
|
109
111
|
unoconvertPath: options.unoconvertPath ?? findUnoconvert(),
|
|
110
112
|
libreOfficePath: options.libreOfficePath ?? findSoffice(),
|
|
111
|
-
userInstallation: options.userInstallation
|
|
113
|
+
userInstallation: options.userInstallation
|
|
114
|
+
? resolvePath(options.userInstallation)
|
|
115
|
+
: undefined,
|
|
112
116
|
startTimeout: options.startTimeout ?? 15000,
|
|
113
117
|
convertTimeout: options.convertTimeout ?? 30000,
|
|
114
118
|
};
|
|
@@ -126,6 +130,9 @@ export class UnoServer {
|
|
|
126
130
|
if (this.ready)
|
|
127
131
|
return;
|
|
128
132
|
const args = ['--interface', this.opts.host, '--port', String(this.opts.port)];
|
|
133
|
+
if (this.opts.unoPort !== undefined) {
|
|
134
|
+
args.push('--uno-port', String(this.opts.unoPort));
|
|
135
|
+
}
|
|
129
136
|
if (this.opts.libreOfficePath) {
|
|
130
137
|
args.push('--executable', this.opts.libreOfficePath);
|
|
131
138
|
}
|
|
@@ -136,24 +143,52 @@ export class UnoServer {
|
|
|
136
143
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
137
144
|
detached: false,
|
|
138
145
|
});
|
|
139
|
-
proc
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
this.proc = proc;
|
|
147
|
+
// Capture stderr so startup failures surface the real reason, not just a
|
|
148
|
+
// generic "did not become ready" timeout.
|
|
149
|
+
const stderrChunks = [];
|
|
150
|
+
proc.stderr?.on('data', (c) => stderrChunks.push(c));
|
|
151
|
+
// Also drain stdout (unoserver is quiet here but we must consume it to
|
|
152
|
+
// avoid filling the pipe buffer on some platforms).
|
|
153
|
+
proc.stdout?.on('data', () => { });
|
|
154
|
+
const collectStderr = () => Buffer.concat(stderrChunks).toString('utf8').trim();
|
|
155
|
+
let earlyExit = null;
|
|
156
|
+
const earlyExitPromise = new Promise((_, reject) => {
|
|
157
|
+
proc.once('exit', (code, signal) => {
|
|
158
|
+
this.ready = false;
|
|
159
|
+
this.proc = null;
|
|
160
|
+
if (earlyExit === null) {
|
|
161
|
+
earlyExit = { code, signal };
|
|
162
|
+
reject(new Error(`unoserver exited during startup (code=${code}, signal=${signal})\n` +
|
|
163
|
+
`stderr:\n${collectStderr() || '(empty)'}`));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
144
166
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
167
|
+
const spawnErrorPromise = new Promise((_, reject) => {
|
|
168
|
+
proc.once('error', (err) => {
|
|
169
|
+
this.ready = false;
|
|
170
|
+
reject(new Error(`unoserver spawn error: ${err.message}`));
|
|
171
|
+
});
|
|
148
172
|
});
|
|
149
|
-
this.proc = proc;
|
|
150
173
|
const deadline = Date.now() + this.opts.startTimeout;
|
|
151
174
|
try {
|
|
152
|
-
await
|
|
175
|
+
await Promise.race([
|
|
176
|
+
waitForPort(this.opts.host, this.opts.port, deadline),
|
|
177
|
+
earlyExitPromise,
|
|
178
|
+
spawnErrorPromise,
|
|
179
|
+
]);
|
|
153
180
|
}
|
|
154
181
|
catch (err) {
|
|
155
182
|
await this.stop();
|
|
156
|
-
|
|
183
|
+
if (earlyExit !== null) {
|
|
184
|
+
// Early-exit error is already descriptive; rethrow as-is.
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
// Timeout path — attach whatever stderr we collected for debugging.
|
|
188
|
+
const stderr = collectStderr();
|
|
189
|
+
const message = `unoserver did not become ready on ${this.opts.host}:${this.opts.port} ` +
|
|
190
|
+
`within ${this.opts.startTimeout}ms\nstderr:\n${stderr || '(empty)'}`;
|
|
191
|
+
throw new Error(message);
|
|
157
192
|
}
|
|
158
193
|
this.ready = true;
|
|
159
194
|
}
|
|
@@ -257,6 +292,7 @@ export class UnoServerPool {
|
|
|
257
292
|
this.opts = {
|
|
258
293
|
workers: options.workers ?? 2,
|
|
259
294
|
basePort: options.basePort ?? 2003,
|
|
295
|
+
unoBasePort: options.unoBasePort ?? 2100,
|
|
260
296
|
tempDir: options.tempDir ?? `${process.env.TMPDIR ?? '/tmp'}/docverse_unoserver`,
|
|
261
297
|
startTimeout: options.startTimeout ?? 15000,
|
|
262
298
|
convertTimeout: options.convertTimeout ?? 30000,
|
|
@@ -275,9 +311,12 @@ export class UnoServerPool {
|
|
|
275
311
|
}
|
|
276
312
|
const servers = [];
|
|
277
313
|
for (let i = 0; i < this.opts.workers; i++) {
|
|
278
|
-
|
|
314
|
+
// unoserver expects a raw absolute path for --user-installation; passing
|
|
315
|
+
// a `file://` URI trips its internal `Path(...).as_uri()` and crashes.
|
|
316
|
+
const userInstallation = resolvePath(this.opts.tempDir, `worker_${i}`);
|
|
279
317
|
servers.push(new UnoServer({
|
|
280
318
|
port: this.opts.basePort + i,
|
|
319
|
+
unoPort: this.opts.unoBasePort + i,
|
|
281
320
|
host: this.opts.host,
|
|
282
321
|
unoserverPath: this.opts.unoserverPath,
|
|
283
322
|
unoconvertPath: this.opts.unoconvertPath,
|
package/package.json
CHANGED