@tsukilabs/nil-ffi 0.7.1 → 0.7.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/dist/def.d.ts +16 -0
- package/dist/error.d.ts +9 -4
- package/dist/index.d.ts +53 -6
- package/dist/index.js +363 -151
- package/dist/queue.d.ts +17 -8
- package/dist/request.d.ts +2 -1
- package/package.json +2 -2
package/dist/def.d.ts
CHANGED
|
@@ -16,6 +16,22 @@ export declare const definitions: {
|
|
|
16
16
|
readonly arguments: readonly [ffi.types.UINT_32];
|
|
17
17
|
readonly return: ffi.types.VOID;
|
|
18
18
|
};
|
|
19
|
+
readonly nil_is_host: {
|
|
20
|
+
readonly arguments: readonly [ffi.types.UINT_32];
|
|
21
|
+
readonly return: ffi.types.VOID;
|
|
22
|
+
};
|
|
23
|
+
readonly nil_start_server: {
|
|
24
|
+
readonly arguments: readonly [ffi.types.UINT_32, ffi.types.POINTER];
|
|
25
|
+
readonly return: ffi.types.VOID;
|
|
26
|
+
};
|
|
27
|
+
readonly nil_start_server_with_savedata: {
|
|
28
|
+
readonly arguments: readonly [ffi.types.UINT_32, ffi.types.POINTER];
|
|
29
|
+
readonly return: ffi.types.VOID;
|
|
30
|
+
};
|
|
31
|
+
readonly nil_stop_server: {
|
|
32
|
+
readonly arguments: readonly [ffi.types.UINT_32];
|
|
33
|
+
readonly return: ffi.types.VOID;
|
|
34
|
+
};
|
|
19
35
|
readonly nil_client_version: {
|
|
20
36
|
readonly arguments: readonly [ffi.types.UINT_32];
|
|
21
37
|
readonly return: ffi.types.VOID;
|
package/dist/error.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { Option } from '@tb-dev/utils';
|
|
2
|
-
import { ffi_Status } from '@tsukilabs/nil-bindings';
|
|
2
|
+
import { ffi_Response, ffi_Status } from '@tsukilabs/nil-bindings';
|
|
3
3
|
export interface FfiErrorOptions extends ErrorOptions {
|
|
4
4
|
readonly status?: Option<ffi_Status>;
|
|
5
5
|
}
|
|
6
|
-
export declare class
|
|
7
|
-
readonly name = "
|
|
6
|
+
export declare class NilError extends Error {
|
|
7
|
+
readonly name = "NilError";
|
|
8
8
|
readonly status: ffi_Status;
|
|
9
9
|
constructor(message: Option<string>, options?: FfiErrorOptions);
|
|
10
|
-
static fromStatus(status: ffi_Status):
|
|
10
|
+
static fromStatus(status: ffi_Status): NilError;
|
|
11
11
|
}
|
|
12
12
|
export declare class HandleClosedError extends Error {
|
|
13
13
|
readonly name = "HandleClosedError";
|
|
14
14
|
constructor(options?: ErrorOptions);
|
|
15
15
|
}
|
|
16
|
+
export declare class UnknownResponseError extends Error {
|
|
17
|
+
readonly name = "UnknownResponseError";
|
|
18
|
+
readonly response: ffi_Response;
|
|
19
|
+
constructor(response: ffi_Response, options?: ErrorOptions);
|
|
20
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,72 @@
|
|
|
1
1
|
import { definitions } from './def';
|
|
2
2
|
import { Option } from '@tb-dev/utils';
|
|
3
|
+
import { QueueOptions } from './queue';
|
|
4
|
+
import { AuthorizeRequest, GetPlayerIdsRequest, GetPlayerIdsResponse, GetPlayerRequest, GetPlayerStatusRequest, GetRemoteWorldsResponse, Password, PlayerExistsRequest, PlayerId, SetPlayerStatusRequest, SpawnPlayerRequest, Token, ValidateTokenRequest, ValidateTokenResponse, WorldId, WorldOptions } from '@tsukilabs/nil-bindings';
|
|
3
5
|
import * as ffi from "node:ffi";
|
|
4
6
|
export * from './def';
|
|
5
7
|
export * from './error';
|
|
6
8
|
export type Handle = ffi.DynamicLibraryResult<typeof definitions>;
|
|
9
|
+
type LocalServer = {
|
|
10
|
+
world: WorldId;
|
|
11
|
+
addr: string;
|
|
12
|
+
};
|
|
13
|
+
type ServerAddr = {
|
|
14
|
+
kind: "remote";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "local";
|
|
17
|
+
addr: string;
|
|
18
|
+
};
|
|
19
|
+
type ffi_UpdateClient = {
|
|
20
|
+
server: ServerAddr;
|
|
21
|
+
world_id?: WorldId | null;
|
|
22
|
+
world_password?: Password | null;
|
|
23
|
+
player_id?: PlayerId | null;
|
|
24
|
+
player_password?: Password | null;
|
|
25
|
+
authorization_token?: Token | null;
|
|
26
|
+
};
|
|
7
27
|
export declare const VERSION: string;
|
|
28
|
+
export declare const USER_AGENT: string;
|
|
8
29
|
export declare class Nil implements Disposable {
|
|
30
|
+
readonly path: string;
|
|
9
31
|
private readonly handle;
|
|
10
32
|
readonly functions: Handle["functions"];
|
|
11
33
|
private readonly queue;
|
|
12
34
|
private disposed;
|
|
13
|
-
constructor(
|
|
35
|
+
private constructor();
|
|
36
|
+
static init(dll: string, options?: NilOptions): Promise<Nil>;
|
|
37
|
+
static initLatest(outDir?: Option<string>, options?: NilOptions): Promise<Nil>;
|
|
38
|
+
static downloadLatest(outDir?: Option<string>): Promise<string>;
|
|
14
39
|
[Symbol.dispose](): void;
|
|
15
40
|
close(): void;
|
|
41
|
+
isClosed(): boolean;
|
|
42
|
+
authorize(req: AuthorizeRequest): Promise<string>;
|
|
16
43
|
getClientVersion(): Promise<string>;
|
|
17
44
|
getFfiVersion(): Promise<string>;
|
|
18
|
-
|
|
19
|
-
|
|
45
|
+
getPlayer(req: GetPlayerRequest): Promise<import('@tsukilabs/nil-bindings').Player>;
|
|
46
|
+
getPlayerIds(req: GetPlayerIdsRequest): Promise<GetPlayerIdsResponse>;
|
|
47
|
+
getPlayerStatus(req: GetPlayerStatusRequest): Promise<import('@tsukilabs/nil-bindings').PlayerStatus>;
|
|
48
|
+
getRemoteWorldLimit(): Promise<number>;
|
|
49
|
+
getRemoteWorldLimitPerUser(): Promise<number>;
|
|
20
50
|
getRemoteWorlds(): Promise<GetRemoteWorldsResponse>;
|
|
51
|
+
getServerAddr(): Promise<ServerAddr>;
|
|
52
|
+
getServerKind(): Promise<import('@tsukilabs/nil-bindings').ServerKind>;
|
|
21
53
|
getUserAgent(): Promise<string>;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
54
|
+
getWorldId(): Promise<Option<string>>;
|
|
55
|
+
isHost(): Promise<boolean>;
|
|
56
|
+
isLocal(): Promise<boolean>;
|
|
57
|
+
isReady(): Promise<boolean>;
|
|
58
|
+
isRemote(): Promise<boolean>;
|
|
59
|
+
playerExists(req: PlayerExistsRequest): Promise<boolean>;
|
|
60
|
+
setPlayerStatus(req: SetPlayerStatusRequest): Promise<import('@tb-dev/utils').nil>;
|
|
61
|
+
setUserAgent(userAgent: string): Promise<import('@tb-dev/utils').nil>;
|
|
62
|
+
spawnPlayer(req: SpawnPlayerRequest): Promise<import('@tb-dev/utils').nil>;
|
|
63
|
+
startServer(options: WorldOptions): Promise<LocalServer>;
|
|
64
|
+
startServerWithSavedata(path: string): Promise<LocalServer>;
|
|
65
|
+
stopClient(): Promise<import('@tb-dev/utils').nil>;
|
|
66
|
+
stopServer(): Promise<import('@tb-dev/utils').nil>;
|
|
67
|
+
updateClient(options: ffi_UpdateClient): Promise<import('@tb-dev/utils').nil>;
|
|
68
|
+
validateToken(req: ValidateTokenRequest): Promise<ValidateTokenResponse>;
|
|
69
|
+
}
|
|
70
|
+
export interface NilOptions {
|
|
71
|
+
onError?: QueueOptions["onError"];
|
|
25
72
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,138 +1,14 @@
|
|
|
1
1
|
import * as ffi from "node:ffi";
|
|
2
|
-
import { isNil } from "es-toolkit/predicate";
|
|
3
2
|
import * as process from "node:process";
|
|
3
|
+
import { cwd } from "node:process";
|
|
4
4
|
import { ffi_Status } from "@tsukilabs/nil-bindings";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (this.#current >= 4294967295) this.#current = 0;
|
|
10
|
-
return ++this.#current;
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region src/ptr.ts
|
|
15
|
-
function allocBuffer(size) {
|
|
16
|
-
return Buffer.alloc(size ?? pointerSize());
|
|
17
|
-
}
|
|
18
|
-
function readBufferPtr(buffer) {
|
|
19
|
-
if (pointerSize() === 8) return buffer.readBigUInt64LE();
|
|
20
|
-
else return BigInt(buffer.readUInt32LE());
|
|
21
|
-
}
|
|
22
|
-
function pointerSize() {
|
|
23
|
-
if (process.arch === "x64" || process.arch === "arm64") return 8;
|
|
24
|
-
else return 4;
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/error.ts
|
|
28
|
-
var FfiError = class FfiError extends Error {
|
|
29
|
-
constructor(message, options) {
|
|
30
|
-
const status = options?.status ?? ffi_Status.ERR_UNKNOWN;
|
|
31
|
-
if (!message && status !== ffi_Status.OK) {
|
|
32
|
-
for (const [key, value] of Object.entries(ffi_Status)) if (value === status) {
|
|
33
|
-
message = key;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
message ??= "ERR_UNKNOWN";
|
|
38
|
-
super(message, options);
|
|
39
|
-
this.name = "FfiError";
|
|
40
|
-
this.status = status;
|
|
41
|
-
}
|
|
42
|
-
static fromStatus(status) {
|
|
43
|
-
return new FfiError(null, { status });
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
var HandleClosedError = class extends Error {
|
|
47
|
-
constructor(options) {
|
|
48
|
-
super("handle is closed", options);
|
|
49
|
-
this.name = "HandleClosedError";
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region src/queue.ts
|
|
54
|
-
var Queue = class Queue {
|
|
55
|
-
static {
|
|
56
|
-
this.POLL_INTERVAL = 16;
|
|
57
|
-
}
|
|
58
|
-
static {
|
|
59
|
-
this.LIMIT_PER_TICK = 200;
|
|
60
|
-
}
|
|
61
|
-
constructor(handle) {
|
|
62
|
-
this.pending = /* @__PURE__ */ new Map();
|
|
63
|
-
this.disposed = false;
|
|
64
|
-
this.requestId = new RequestId();
|
|
65
|
-
this.functions = handle.functions;
|
|
66
|
-
}
|
|
67
|
-
[Symbol.dispose]() {
|
|
68
|
-
if (this.disposed) return;
|
|
69
|
-
this.disposed = true;
|
|
70
|
-
this.rejectAll(new HandleClosedError());
|
|
71
|
-
}
|
|
72
|
-
request(start) {
|
|
73
|
-
if (this.disposed) return Promise.reject(new HandleClosedError());
|
|
74
|
-
const requestId = this.requestId.next();
|
|
75
|
-
return new Promise((resolve, reject) => {
|
|
76
|
-
this.pending.set(requestId, {
|
|
77
|
-
resolve,
|
|
78
|
-
reject
|
|
79
|
-
});
|
|
80
|
-
try {
|
|
81
|
-
start(requestId);
|
|
82
|
-
} catch (err) {
|
|
83
|
-
this.pending.delete(requestId);
|
|
84
|
-
reject(err);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
this.startPolling();
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
startPolling() {
|
|
91
|
-
if (isNil(this.timer) && !this.disposed) this.timer = setTimeout(() => this.poll(), 0);
|
|
92
|
-
}
|
|
93
|
-
poll() {
|
|
94
|
-
this.timer = null;
|
|
95
|
-
if (!this.disposed && this.pending.size > 0) try {
|
|
96
|
-
for (let i = 0; i < Queue.LIMIT_PER_TICK; i++) if (!this.pollOnce()) break;
|
|
97
|
-
} catch (err) {
|
|
98
|
-
this.rejectAll(err);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (!this.disposed && this.pending.size > 0) this.timer = setTimeout(() => this.poll(), Queue.POLL_INTERVAL);
|
|
102
|
-
}
|
|
103
|
-
pollOnce() {
|
|
104
|
-
const out = allocBuffer();
|
|
105
|
-
const status = this.functions.nil_ffi_poll(out);
|
|
106
|
-
if (status === ffi_Status.ERR_NOTHING_TO_POLL) return false;
|
|
107
|
-
if (status !== ffi_Status.OK) throw FfiError.fromStatus(status);
|
|
108
|
-
const ptr = readBufferPtr(out);
|
|
109
|
-
if (ptr === 0n) throw new Error("unexpected null pointer");
|
|
110
|
-
let json;
|
|
111
|
-
try {
|
|
112
|
-
json = ffi.toString(ptr);
|
|
113
|
-
} finally {
|
|
114
|
-
this.functions.nil_ffi_free_str(ptr);
|
|
115
|
-
}
|
|
116
|
-
const entry = JSON.parse(json);
|
|
117
|
-
const response = JSON.parse(entry.json_str);
|
|
118
|
-
const pending = this.pending.get(response.id);
|
|
119
|
-
if (!pending) return true;
|
|
120
|
-
this.pending.delete(response.id);
|
|
121
|
-
if (response.kind === "ok") pending.resolve(response.data);
|
|
122
|
-
else pending.reject(new FfiError(response.error, { status: response.status }));
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
rejectAll(error) {
|
|
126
|
-
if (!isNil(this.timer)) {
|
|
127
|
-
clearTimeout(this.timer);
|
|
128
|
-
this.timer = null;
|
|
129
|
-
}
|
|
130
|
-
for (const { reject } of this.pending.values()) reject(error);
|
|
131
|
-
this.pending.clear();
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
//#endregion
|
|
5
|
+
import { writeFile } from "node:fs/promises";
|
|
6
|
+
import { EventEmitter } from "node:events";
|
|
7
|
+
import { isNil } from "es-toolkit/predicate";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
135
9
|
//#region src/def.ts
|
|
10
|
+
//! Copyright (C) Call of Nil contributors
|
|
11
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
136
12
|
var definitions = {
|
|
137
13
|
nil_ffi_free_str: {
|
|
138
14
|
arguments: [ffi.types.POINTER],
|
|
@@ -150,6 +26,22 @@ var definitions = {
|
|
|
150
26
|
arguments: [ffi.types.UINT_32],
|
|
151
27
|
return: ffi.types.VOID
|
|
152
28
|
},
|
|
29
|
+
nil_is_host: {
|
|
30
|
+
arguments: [ffi.types.UINT_32],
|
|
31
|
+
return: ffi.types.VOID
|
|
32
|
+
},
|
|
33
|
+
nil_start_server: {
|
|
34
|
+
arguments: [ffi.types.UINT_32, ffi.types.POINTER],
|
|
35
|
+
return: ffi.types.VOID
|
|
36
|
+
},
|
|
37
|
+
nil_start_server_with_savedata: {
|
|
38
|
+
arguments: [ffi.types.UINT_32, ffi.types.POINTER],
|
|
39
|
+
return: ffi.types.VOID
|
|
40
|
+
},
|
|
41
|
+
nil_stop_server: {
|
|
42
|
+
arguments: [ffi.types.UINT_32],
|
|
43
|
+
return: ffi.types.VOID
|
|
44
|
+
},
|
|
153
45
|
nil_client_version: {
|
|
154
46
|
arguments: [ffi.types.UINT_32],
|
|
155
47
|
return: ffi.types.VOID
|
|
@@ -680,19 +572,219 @@ var definitions = {
|
|
|
680
572
|
}
|
|
681
573
|
};
|
|
682
574
|
//#endregion
|
|
575
|
+
//#region src/error.ts
|
|
576
|
+
//! Copyright (C) Call of Nil contributors
|
|
577
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
578
|
+
var NilError = class NilError extends Error {
|
|
579
|
+
constructor(message, options) {
|
|
580
|
+
const status = options?.status ?? ffi_Status.ERR_UNKNOWN;
|
|
581
|
+
if (!message && status !== ffi_Status.OK) {
|
|
582
|
+
for (const [key, value] of Object.entries(ffi_Status)) if (value === status) {
|
|
583
|
+
message = key;
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
message ??= "ERR_UNKNOWN";
|
|
588
|
+
super(message, options);
|
|
589
|
+
this.name = "NilError";
|
|
590
|
+
this.status = status;
|
|
591
|
+
}
|
|
592
|
+
static fromStatus(status) {
|
|
593
|
+
return new NilError(null, { status });
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
var HandleClosedError = class extends Error {
|
|
597
|
+
constructor(options) {
|
|
598
|
+
super("handle is closed", options);
|
|
599
|
+
this.name = "HandleClosedError";
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
var UnknownResponseError = class extends Error {
|
|
603
|
+
constructor(response, options) {
|
|
604
|
+
super(`got unknown response with id ${response.id}`, options);
|
|
605
|
+
this.name = "UnknownResponseError";
|
|
606
|
+
this.response = response;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
//#endregion
|
|
610
|
+
//#region src/request.ts
|
|
611
|
+
//! Copyright (C) Call of Nil contributors
|
|
612
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
613
|
+
var RequestId = class {
|
|
614
|
+
#current = 0;
|
|
615
|
+
next() {
|
|
616
|
+
if (this.#current >= 4294967295) this.#current = 0;
|
|
617
|
+
return ++this.#current;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
//#endregion
|
|
621
|
+
//#region src/ptr.ts
|
|
622
|
+
//! Copyright (C) Call of Nil contributors
|
|
623
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
624
|
+
function allocBuffer(size) {
|
|
625
|
+
return Buffer.alloc(size ?? pointerSize());
|
|
626
|
+
}
|
|
627
|
+
function readBufferPtr(buffer) {
|
|
628
|
+
if (pointerSize() === 8) return buffer.readBigUInt64LE();
|
|
629
|
+
else return BigInt(buffer.readUInt32LE());
|
|
630
|
+
}
|
|
631
|
+
function pointerSize() {
|
|
632
|
+
if (process.arch === "x64" || process.arch === "arm64") return 8;
|
|
633
|
+
else return 4;
|
|
634
|
+
}
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/queue.ts
|
|
637
|
+
//! Copyright (C) Call of Nil contributors
|
|
638
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
639
|
+
var QueueEmitter = class extends EventEmitter {
|
|
640
|
+
emit(event, ...args) {
|
|
641
|
+
return super.emit(event, ...args);
|
|
642
|
+
}
|
|
643
|
+
on(event, listener) {
|
|
644
|
+
return super.on(event, listener);
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
var Queue = class Queue {
|
|
648
|
+
static {
|
|
649
|
+
this.POLL_INTERVAL = 16;
|
|
650
|
+
}
|
|
651
|
+
static {
|
|
652
|
+
this.LIMIT_PER_TICK = 200;
|
|
653
|
+
}
|
|
654
|
+
constructor(handle, options) {
|
|
655
|
+
this.disposed = false;
|
|
656
|
+
this.emitter = new QueueEmitter();
|
|
657
|
+
this.requestId = new RequestId();
|
|
658
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
659
|
+
this.functions = handle.functions;
|
|
660
|
+
this.errorHandler = options?.onError ?? console.error;
|
|
661
|
+
this.emitter.on("error", this.errorHandler);
|
|
662
|
+
this.emitter.on("response", this.onResponse.bind(this));
|
|
663
|
+
this.timer = setTimeout(() => this.drain(), 0);
|
|
664
|
+
}
|
|
665
|
+
[Symbol.dispose]() {
|
|
666
|
+
if (this.disposed) return;
|
|
667
|
+
this.disposed = true;
|
|
668
|
+
this.rejectAll(new HandleClosedError());
|
|
669
|
+
this.emitter.removeAllListeners();
|
|
670
|
+
}
|
|
671
|
+
request(start) {
|
|
672
|
+
if (this.disposed) return Promise.reject(new HandleClosedError());
|
|
673
|
+
const requestId = this.requestId.next();
|
|
674
|
+
return new Promise((resolve, reject) => {
|
|
675
|
+
this.pendingRequests.set(requestId, {
|
|
676
|
+
resolve,
|
|
677
|
+
reject
|
|
678
|
+
});
|
|
679
|
+
try {
|
|
680
|
+
start(requestId);
|
|
681
|
+
} catch (err) {
|
|
682
|
+
this.pendingRequests.delete(requestId);
|
|
683
|
+
reject(err);
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
drain() {
|
|
688
|
+
if (this.disposed) return;
|
|
689
|
+
this.clearTimeout();
|
|
690
|
+
try {
|
|
691
|
+
for (let i = 0; i < Queue.LIMIT_PER_TICK; i++) if (!this.poll()) break;
|
|
692
|
+
} catch (err) {
|
|
693
|
+
this.emitter.emit("error", err);
|
|
694
|
+
}
|
|
695
|
+
this.timer = setTimeout(() => this.drain(), Queue.POLL_INTERVAL);
|
|
696
|
+
}
|
|
697
|
+
poll() {
|
|
698
|
+
const out = allocBuffer();
|
|
699
|
+
const status = this.functions.nil_ffi_poll(out);
|
|
700
|
+
if (status === ffi_Status.ERR_NOTHING_TO_POLL) return false;
|
|
701
|
+
if (status !== ffi_Status.OK) throw NilError.fromStatus(status);
|
|
702
|
+
const ptr = readBufferPtr(out);
|
|
703
|
+
if (ptr === 0n) throw new Error("failed to poll: unexpected null pointer");
|
|
704
|
+
try {
|
|
705
|
+
const json = ffi.toString(ptr) ?? "";
|
|
706
|
+
const entry = JSON.parse(json);
|
|
707
|
+
this.dispatch(entry);
|
|
708
|
+
} finally {
|
|
709
|
+
this.functions.nil_ffi_free_str(ptr);
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
dispatch(entry) {
|
|
714
|
+
switch (entry.kind) {
|
|
715
|
+
case "response": {
|
|
716
|
+
const response = JSON.parse(entry.json_str);
|
|
717
|
+
this.emitter.emit("response", response);
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
rejectAll(error) {
|
|
723
|
+
this.clearTimeout();
|
|
724
|
+
this.pendingRequests.values().forEach(({ reject }) => reject(error));
|
|
725
|
+
this.pendingRequests.clear();
|
|
726
|
+
}
|
|
727
|
+
clearTimeout() {
|
|
728
|
+
if (!isNil(this.timer)) {
|
|
729
|
+
clearTimeout(this.timer);
|
|
730
|
+
this.timer = null;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
onResponse(response) {
|
|
734
|
+
const pending = this.pendingRequests.get(response.id);
|
|
735
|
+
if (pending) {
|
|
736
|
+
this.pendingRequests.delete(response.id);
|
|
737
|
+
if (response.kind === "ok") pending.resolve(response.data);
|
|
738
|
+
else {
|
|
739
|
+
const { status, error } = response;
|
|
740
|
+
pending.reject(new NilError(error, { status }));
|
|
741
|
+
}
|
|
742
|
+
} else this.emitter.emit("error", new UnknownResponseError(response));
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
//#endregion
|
|
683
746
|
//#region package.json
|
|
684
|
-
var version = "0.7.
|
|
747
|
+
var version = "0.7.3";
|
|
748
|
+
//#endregion
|
|
749
|
+
//#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
|
|
750
|
+
function __decorate(decorators, target, key, desc) {
|
|
751
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
752
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
753
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
754
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
755
|
+
}
|
|
685
756
|
//#endregion
|
|
686
757
|
//#region src/index.ts
|
|
758
|
+
//! Copyright (C) Call of Nil contributors
|
|
759
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
687
760
|
var VERSION = version;
|
|
688
|
-
var
|
|
689
|
-
|
|
761
|
+
var USER_AGENT = `nil-ffi-node/${VERSION}`;
|
|
762
|
+
var Nil = class Nil {
|
|
763
|
+
constructor(dll, options) {
|
|
690
764
|
this.disposed = false;
|
|
691
765
|
if (!dll.endsWith(ffi.suffix)) dll = `${dll}.${ffi.suffix}`;
|
|
766
|
+
this.path = resolve(dll);
|
|
692
767
|
this.handle = ffi.dlopen(dll, definitions);
|
|
693
768
|
this.functions = this.handle.functions;
|
|
694
|
-
this.queue = new Queue(this.handle);
|
|
695
|
-
|
|
769
|
+
this.queue = new Queue(this.handle, options);
|
|
770
|
+
}
|
|
771
|
+
static async init(dll, options) {
|
|
772
|
+
const nil = new Nil(dll, options);
|
|
773
|
+
await nil.setUserAgent(USER_AGENT);
|
|
774
|
+
return nil;
|
|
775
|
+
}
|
|
776
|
+
static async initLatest(outDir, options) {
|
|
777
|
+
const dll = await this.downloadLatest(outDir);
|
|
778
|
+
return this.init(dll, options);
|
|
779
|
+
}
|
|
780
|
+
static async downloadLatest(outDir) {
|
|
781
|
+
const file = `libnil.${ffi.suffix}`;
|
|
782
|
+
const response = await fetch(`https://tsukilabs.dev.br/${file}`);
|
|
783
|
+
if (!response.ok) throw new Error(`failed to download ${file}: ${response.status} ${response.statusText}`);
|
|
784
|
+
const bytes = await response.bytes();
|
|
785
|
+
const path = join(outDir ?? cwd(), file);
|
|
786
|
+
await writeFile(path, bytes);
|
|
787
|
+
return resolve(path);
|
|
696
788
|
}
|
|
697
789
|
[Symbol.dispose]() {
|
|
698
790
|
this.close();
|
|
@@ -704,58 +796,178 @@ var Nil = class {
|
|
|
704
796
|
this.functions.nil_ffi_shutdown();
|
|
705
797
|
this.handle[Symbol.dispose]();
|
|
706
798
|
}
|
|
799
|
+
isClosed() {
|
|
800
|
+
return this.disposed;
|
|
801
|
+
}
|
|
802
|
+
async authorize(req) {
|
|
803
|
+
return this.queue.request((requestId) => {
|
|
804
|
+
this.functions.nil_authorize(requestId, JSON.stringify(req));
|
|
805
|
+
});
|
|
806
|
+
}
|
|
707
807
|
async getClientVersion() {
|
|
708
|
-
this.throwIfClosed();
|
|
709
808
|
return this.queue.request((requestId) => {
|
|
710
809
|
this.functions.nil_client_version(requestId);
|
|
711
810
|
});
|
|
712
811
|
}
|
|
713
812
|
async getFfiVersion() {
|
|
714
|
-
this.throwIfClosed();
|
|
715
813
|
return this.queue.request((requestId) => {
|
|
716
814
|
this.functions.nil_ffi_version(requestId);
|
|
717
815
|
});
|
|
718
816
|
}
|
|
817
|
+
async getPlayer(req) {
|
|
818
|
+
return this.queue.request((requestId) => {
|
|
819
|
+
this.functions.nil_get_player(requestId, JSON.stringify(req));
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
async getPlayerIds(req) {
|
|
823
|
+
return this.queue.request((requestId) => {
|
|
824
|
+
this.functions.nil_get_player_ids(requestId, JSON.stringify(req));
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
async getPlayerStatus(req) {
|
|
828
|
+
return this.queue.request((requestId) => {
|
|
829
|
+
this.functions.nil_get_player_status(requestId, JSON.stringify(req));
|
|
830
|
+
});
|
|
831
|
+
}
|
|
719
832
|
async getRemoteWorldLimit() {
|
|
720
|
-
this.throwIfClosed();
|
|
721
833
|
return this.queue.request((requestId) => {
|
|
722
834
|
this.functions.nil_get_remote_world_limit(requestId);
|
|
723
835
|
});
|
|
724
836
|
}
|
|
725
837
|
async getRemoteWorldLimitPerUser() {
|
|
726
|
-
this.throwIfClosed();
|
|
727
838
|
return this.queue.request((requestId) => {
|
|
728
839
|
this.functions.nil_get_remote_world_limit_per_user(requestId);
|
|
729
840
|
});
|
|
730
841
|
}
|
|
731
842
|
async getRemoteWorlds() {
|
|
732
|
-
this.throwIfClosed();
|
|
733
843
|
return this.queue.request((requestId) => {
|
|
734
844
|
this.functions.nil_get_remote_worlds(requestId);
|
|
735
845
|
});
|
|
736
846
|
}
|
|
847
|
+
async getServerAddr() {
|
|
848
|
+
return this.queue.request((requestId) => {
|
|
849
|
+
this.functions.nil_server_addr(requestId);
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
async getServerKind() {
|
|
853
|
+
return this.queue.request((requestId) => {
|
|
854
|
+
this.functions.nil_get_server_kind(requestId);
|
|
855
|
+
});
|
|
856
|
+
}
|
|
737
857
|
async getUserAgent() {
|
|
738
|
-
this.throwIfClosed();
|
|
739
858
|
return this.queue.request((requestId) => {
|
|
740
859
|
this.functions.nil_user_agent(requestId);
|
|
741
860
|
});
|
|
742
861
|
}
|
|
743
|
-
async
|
|
744
|
-
this.throwIfClosed();
|
|
862
|
+
async getWorldId() {
|
|
745
863
|
return this.queue.request((requestId) => {
|
|
746
864
|
this.functions.nil_world(requestId);
|
|
747
865
|
});
|
|
748
866
|
}
|
|
867
|
+
async isHost() {
|
|
868
|
+
return this.queue.request((requestId) => {
|
|
869
|
+
this.functions.nil_is_host(requestId);
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
async isLocal() {
|
|
873
|
+
return this.queue.request((requestId) => {
|
|
874
|
+
this.functions.nil_is_local(requestId);
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
async isReady() {
|
|
878
|
+
return this.queue.request((requestId) => {
|
|
879
|
+
this.functions.nil_is_ready(requestId);
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
async isRemote() {
|
|
883
|
+
return this.queue.request((requestId) => {
|
|
884
|
+
this.functions.nil_is_remote(requestId);
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
async playerExists(req) {
|
|
888
|
+
return this.queue.request((requestId) => {
|
|
889
|
+
this.functions.nil_player_exists(requestId, JSON.stringify(req));
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
async setPlayerStatus(req) {
|
|
893
|
+
return this.queue.request((requestId) => {
|
|
894
|
+
this.functions.nil_set_player_status(requestId, JSON.stringify(req));
|
|
895
|
+
});
|
|
896
|
+
}
|
|
749
897
|
async setUserAgent(userAgent) {
|
|
750
|
-
this.
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
898
|
+
return this.queue.request((requestId) => {
|
|
899
|
+
this.functions.nil_set_user_agent(requestId, JSON.stringify(userAgent));
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
async spawnPlayer(req) {
|
|
903
|
+
return this.queue.request((requestId) => {
|
|
904
|
+
this.functions.nil_spawn_player(requestId, JSON.stringify(req));
|
|
754
905
|
});
|
|
755
906
|
}
|
|
756
|
-
|
|
757
|
-
|
|
907
|
+
async startServer(options) {
|
|
908
|
+
return this.queue.request((requestId) => {
|
|
909
|
+
this.functions.nil_start_server(requestId, JSON.stringify(options));
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
async startServerWithSavedata(path) {
|
|
913
|
+
return this.queue.request((requestId) => {
|
|
914
|
+
this.functions.nil_start_server_with_savedata(requestId, JSON.stringify(path));
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
async stopClient() {
|
|
918
|
+
return this.queue.request((requestId) => {
|
|
919
|
+
this.functions.nil_stop_client(requestId);
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
async stopServer() {
|
|
923
|
+
return this.queue.request((requestId) => {
|
|
924
|
+
this.functions.nil_stop_server(requestId);
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
async updateClient(options) {
|
|
928
|
+
return this.queue.request((requestId) => {
|
|
929
|
+
this.functions.nil_update_client(requestId, JSON.stringify(options));
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
async validateToken(req) {
|
|
933
|
+
return this.queue.request((requestId) => {
|
|
934
|
+
this.functions.nil_validate_token(requestId, JSON.stringify(req));
|
|
935
|
+
});
|
|
758
936
|
}
|
|
759
937
|
};
|
|
938
|
+
__decorate([ThrowIfClosed], Nil.prototype, "authorize", null);
|
|
939
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getClientVersion", null);
|
|
940
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getFfiVersion", null);
|
|
941
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getPlayer", null);
|
|
942
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getPlayerIds", null);
|
|
943
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getPlayerStatus", null);
|
|
944
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getRemoteWorldLimit", null);
|
|
945
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getRemoteWorldLimitPerUser", null);
|
|
946
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getRemoteWorlds", null);
|
|
947
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getServerAddr", null);
|
|
948
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getServerKind", null);
|
|
949
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getUserAgent", null);
|
|
950
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getWorldId", null);
|
|
951
|
+
__decorate([ThrowIfClosed], Nil.prototype, "isHost", null);
|
|
952
|
+
__decorate([ThrowIfClosed], Nil.prototype, "isLocal", null);
|
|
953
|
+
__decorate([ThrowIfClosed], Nil.prototype, "isReady", null);
|
|
954
|
+
__decorate([ThrowIfClosed], Nil.prototype, "isRemote", null);
|
|
955
|
+
__decorate([ThrowIfClosed], Nil.prototype, "playerExists", null);
|
|
956
|
+
__decorate([ThrowIfClosed], Nil.prototype, "setPlayerStatus", null);
|
|
957
|
+
__decorate([ThrowIfClosed], Nil.prototype, "setUserAgent", null);
|
|
958
|
+
__decorate([ThrowIfClosed], Nil.prototype, "spawnPlayer", null);
|
|
959
|
+
__decorate([ThrowIfClosed], Nil.prototype, "startServer", null);
|
|
960
|
+
__decorate([ThrowIfClosed], Nil.prototype, "startServerWithSavedata", null);
|
|
961
|
+
__decorate([ThrowIfClosed], Nil.prototype, "stopClient", null);
|
|
962
|
+
__decorate([ThrowIfClosed], Nil.prototype, "stopServer", null);
|
|
963
|
+
__decorate([ThrowIfClosed], Nil.prototype, "updateClient", null);
|
|
964
|
+
__decorate([ThrowIfClosed], Nil.prototype, "validateToken", null);
|
|
965
|
+
function ThrowIfClosed(_target, _key, descriptor) {
|
|
966
|
+
const method = descriptor.value;
|
|
967
|
+
descriptor.value = function(...args) {
|
|
968
|
+
if (this.isClosed()) throw new HandleClosedError();
|
|
969
|
+
return Reflect.apply(method, this, args);
|
|
970
|
+
};
|
|
971
|
+
}
|
|
760
972
|
//#endregion
|
|
761
|
-
export {
|
|
973
|
+
export { HandleClosedError, Nil, NilError, USER_AGENT, UnknownResponseError, VERSION, definitions };
|
package/dist/queue.d.ts
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { Handle } from '.';
|
|
2
|
+
import { nil, Option } from '@tb-dev/utils';
|
|
2
3
|
import { ffi_RequestId } from '@tsukilabs/nil-bindings';
|
|
3
|
-
type RequestFn = (requestId: ffi_RequestId) => void;
|
|
4
4
|
export declare class Queue implements Disposable {
|
|
5
5
|
private static readonly POLL_INTERVAL;
|
|
6
6
|
private static readonly LIMIT_PER_TICK;
|
|
7
|
-
readonly functions: Handle["functions"];
|
|
8
|
-
private timer;
|
|
9
|
-
private readonly pending;
|
|
10
7
|
private disposed;
|
|
8
|
+
private readonly functions;
|
|
9
|
+
private readonly emitter;
|
|
11
10
|
private readonly requestId;
|
|
12
|
-
|
|
11
|
+
private readonly pendingRequests;
|
|
12
|
+
private timer;
|
|
13
|
+
private readonly errorHandler;
|
|
14
|
+
constructor(handle: Handle, options?: QueueOptions);
|
|
13
15
|
[Symbol.dispose](): void;
|
|
14
|
-
request<T =
|
|
15
|
-
private
|
|
16
|
+
request<T = nil>(start: RequestFn): Promise<T>;
|
|
17
|
+
private drain;
|
|
16
18
|
private poll;
|
|
17
|
-
private
|
|
19
|
+
private dispatch;
|
|
18
20
|
private rejectAll;
|
|
21
|
+
private clearTimeout;
|
|
22
|
+
private onResponse;
|
|
23
|
+
}
|
|
24
|
+
type RequestFn = (requestId: ffi_RequestId) => void;
|
|
25
|
+
type ErrorHandler = (error: unknown) => void;
|
|
26
|
+
export interface QueueOptions {
|
|
27
|
+
onError?: Option<ErrorHandler>;
|
|
19
28
|
}
|
|
20
29
|
export {};
|
package/dist/request.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsukilabs/nil-ffi",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "pnpm@11.17.0",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@tb-dev/utils": "^8.1.1",
|
|
32
|
-
"@tsukilabs/nil-bindings": "
|
|
32
|
+
"@tsukilabs/nil-bindings": "^0.7.2",
|
|
33
33
|
"es-toolkit": "^1.50.0"
|
|
34
34
|
},
|
|
35
35
|
"files": ["dist"],
|