@open-core/framework 0.3.2 → 0.3.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 +3 -20
- package/dist/adapters/contracts/IResourceInfo.d.ts +1 -0
- package/dist/adapters/fivem/fivem-resourceinfo.d.ts +1 -0
- package/dist/adapters/fivem/fivem-resourceinfo.js +12 -0
- package/dist/adapters/node/node-resourceinfo.d.ts +1 -0
- package/dist/adapters/node/node-resourceinfo.js +3 -0
- package/dist/kernel/di/container.d.ts +1 -0
- package/dist/kernel/di/container.js +1 -0
- package/dist/kernel/index.d.ts +1 -1
- package/dist/kernel/index.js +1 -3
- package/dist/runtime/server/bootstrap.js +19 -0
- package/dist/runtime/server/controllers/command-export.controller.js +2 -0
- package/dist/runtime/server/decorators/binary-call.d.ts +15 -0
- package/dist/runtime/server/decorators/binary-call.js +27 -0
- package/dist/runtime/server/decorators/binary-service.d.ts +27 -0
- package/dist/runtime/server/decorators/binary-service.js +76 -0
- package/dist/runtime/server/decorators/index.d.ts +2 -0
- package/dist/runtime/server/decorators/index.js +2 -0
- package/dist/runtime/server/services/binary/binary-process.manager.d.ts +17 -0
- package/dist/runtime/server/services/binary/binary-process.manager.js +273 -0
- package/dist/runtime/server/services/binary/binary.types.d.ts +6 -0
- package/dist/runtime/server/services/binary/binary.types.js +2 -0
- package/dist/runtime/server/services/services.register.js +4 -0
- package/dist/runtime/server/system/metadata-server.keys.d.ts +3 -0
- package/dist/runtime/server/system/metadata-server.keys.js +3 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -19,17 +19,7 @@ It is not a gamemode or RP framework. It provides:
|
|
|
19
19
|
|
|
20
20
|
License: MPL-2.0
|
|
21
21
|
|
|
22
|
-
[Discord Community](https://discord.gg/99g3FgvkPs) | [
|
|
23
|
-
|
|
24
|
-
## Scope
|
|
25
|
-
|
|
26
|
-
This package (`@open-core/framework`) contains transversal infrastructure only.
|
|
27
|
-
|
|
28
|
-
- Controllers, services, decorators, and processors
|
|
29
|
-
- Session/lifecycle primitives and contracts
|
|
30
|
-
- Adapters and capability registration
|
|
31
|
-
|
|
32
|
-
Gameplay logic must live in separate resources/modules.
|
|
22
|
+
[Discord Community](https://discord.gg/99g3FgvkPs) | [Docs](https://opencorejs.dev) | [OpenCore CLI](https://github.com/newcore-network/opencore-cli)
|
|
33
23
|
|
|
34
24
|
## Installation
|
|
35
25
|
|
|
@@ -46,13 +36,11 @@ The package exposes subpath entry points:
|
|
|
46
36
|
- `@open-core/framework` (root)
|
|
47
37
|
- `@open-core/framework/server`
|
|
48
38
|
- `@open-core/framework/client`
|
|
49
|
-
- `@open-core/framework/shared`
|
|
50
|
-
- `@open-core/framework/utils`
|
|
51
39
|
|
|
52
40
|
Most projects will import the `Server`/`Client` namespaces:
|
|
53
41
|
|
|
54
42
|
```ts
|
|
55
|
-
import { Server } from '@open-core/framework'
|
|
43
|
+
import { Server } from '@open-core/framework/server'
|
|
56
44
|
```
|
|
57
45
|
|
|
58
46
|
## Architecture
|
|
@@ -81,11 +69,7 @@ Initialize the server runtime:
|
|
|
81
69
|
import { Server } from '@open-core/framework/server'
|
|
82
70
|
|
|
83
71
|
await Server.init({
|
|
84
|
-
mode: '
|
|
85
|
-
features: {
|
|
86
|
-
commands: { enabled: true },
|
|
87
|
-
netEvents: { enabled: true },
|
|
88
|
-
},
|
|
72
|
+
mode: 'CORE'
|
|
89
73
|
})
|
|
90
74
|
```
|
|
91
75
|
|
|
@@ -95,7 +79,6 @@ Some features require providers (depending on your mode and configuration). Conf
|
|
|
95
79
|
import { Server } from '@open-core/framework/server'
|
|
96
80
|
|
|
97
81
|
Server.setPrincipalProvider(MyPrincipalProvider)
|
|
98
|
-
Server.setAuthProvider(MyAuthProvider)
|
|
99
82
|
Server.setSecurityHandler(MySecurityHandler)
|
|
100
83
|
Server.setPersistenceProvider(MyPlayerPersistence)
|
|
101
84
|
Server.setNetEventSecurityObserver(MyNetEventSecurityObserver)
|
|
@@ -12,5 +12,17 @@ class FiveMResourceInfo extends IResourceInfo_1.IResourceInfo {
|
|
|
12
12
|
}
|
|
13
13
|
return 'default';
|
|
14
14
|
}
|
|
15
|
+
getCurrentResourcePath() {
|
|
16
|
+
const fn = globalThis.GetResourcePath;
|
|
17
|
+
if (typeof fn === 'function') {
|
|
18
|
+
const name = this.getCurrentResourceName();
|
|
19
|
+
if (name) {
|
|
20
|
+
const path = fn(name);
|
|
21
|
+
if (typeof path === 'string' && path.trim())
|
|
22
|
+
return path;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return process.cwd();
|
|
26
|
+
}
|
|
15
27
|
}
|
|
16
28
|
exports.FiveMResourceInfo = FiveMResourceInfo;
|
|
@@ -20,6 +20,9 @@ let NodeResourceInfo = class NodeResourceInfo {
|
|
|
20
20
|
getCurrentResourceName() {
|
|
21
21
|
return process.env.RESOURCE_NAME || 'default';
|
|
22
22
|
}
|
|
23
|
+
getCurrentResourcePath() {
|
|
24
|
+
return process.env.RESOURCE_PATH || process.cwd();
|
|
25
|
+
}
|
|
23
26
|
};
|
|
24
27
|
exports.NodeResourceInfo = NodeResourceInfo;
|
|
25
28
|
exports.NodeResourceInfo = NodeResourceInfo = __decorate([
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* This is the global container for tsyringe, it contains all the dependencies to resolve, etc. Please use with caution.
|
|
3
|
+
* When you use in Server is the server-side container ONLY, if you use in Client is ONLY the container of client-side
|
|
3
4
|
*/
|
|
4
5
|
export declare const GLOBAL_CONTAINER: import("tsyringe").DependencyContainer;
|
|
@@ -4,5 +4,6 @@ exports.GLOBAL_CONTAINER = void 0;
|
|
|
4
4
|
const tsyringe_1 = require("tsyringe");
|
|
5
5
|
/**
|
|
6
6
|
* This is the global container for tsyringe, it contains all the dependencies to resolve, etc. Please use with caution.
|
|
7
|
+
* When you use in Server is the server-side container ONLY, if you use in Client is ONLY the container of client-side
|
|
7
8
|
*/
|
|
8
9
|
exports.GLOBAL_CONTAINER = tsyringe_1.container;
|
package/dist/kernel/index.d.ts
CHANGED
package/dist/kernel/index.js
CHANGED
|
@@ -14,10 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.GLOBAL_CONTAINER = void 0;
|
|
18
17
|
// External to export
|
|
19
|
-
|
|
20
|
-
Object.defineProperty(exports, "GLOBAL_CONTAINER", { enumerable: true, get: function () { return container_1.GLOBAL_CONTAINER; } });
|
|
18
|
+
__exportStar(require("./di/container"), exports);
|
|
21
19
|
__exportStar(require("./error"), exports);
|
|
22
20
|
__exportStar(require("./logger"), exports);
|
|
23
21
|
__exportStar(require("./schema"), exports);
|
|
@@ -39,10 +39,13 @@ const register_capabilities_1 = require("../../adapters/register-capabilities");
|
|
|
39
39
|
const index_1 = require("../../kernel/di/index");
|
|
40
40
|
const logger_1 = require("../../kernel/logger");
|
|
41
41
|
const index_2 = require("./contracts/index");
|
|
42
|
+
const binary_service_1 = require("./decorators/binary-service");
|
|
42
43
|
const controller_1 = require("./decorators/controller");
|
|
43
44
|
const runtime_1 = require("./runtime");
|
|
45
|
+
const binary_process_manager_1 = require("./services/binary/binary-process.manager");
|
|
44
46
|
const session_recovery_service_1 = require("./services/core/session-recovery.service");
|
|
45
47
|
const services_register_1 = require("./services/services.register");
|
|
48
|
+
const metadata_server_keys_1 = require("./system/metadata-server.keys");
|
|
46
49
|
const processors_register_1 = require("./system/processors.register");
|
|
47
50
|
const CORE_WAIT_TIMEOUT = 10000;
|
|
48
51
|
function checkProviders(ctx) {
|
|
@@ -150,6 +153,22 @@ async function initServer(options) {
|
|
|
150
153
|
checkProviders(ctx);
|
|
151
154
|
const scanner = index_1.GLOBAL_CONTAINER.resolve(index_1.MetadataScanner);
|
|
152
155
|
scanner.scan((0, controller_1.getServerControllerRegistry)());
|
|
156
|
+
const binaryServices = (0, binary_service_1.getServerBinaryServiceRegistry)();
|
|
157
|
+
for (const serviceClass of binaryServices) {
|
|
158
|
+
const metadata = Reflect.getMetadata(metadata_server_keys_1.METADATA_KEYS.BINARY_SERVICE, serviceClass);
|
|
159
|
+
if (!metadata)
|
|
160
|
+
continue;
|
|
161
|
+
const manager = index_1.GLOBAL_CONTAINER.resolve(binary_process_manager_1.BinaryProcessManager);
|
|
162
|
+
try {
|
|
163
|
+
manager.register(metadata, metadata.serviceClass);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
167
|
+
logger_1.loggers.bootstrap.error(`[BinaryService] Failed to register ${metadata.name}`, {
|
|
168
|
+
error: message,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
153
172
|
// Initialize DevMode if enabled
|
|
154
173
|
if ((_d = ctx.devMode) === null || _d === void 0 ? void 0 : _d.enabled) {
|
|
155
174
|
await initDevMode(ctx.devMode);
|
|
@@ -79,6 +79,8 @@ let CommandExportController = class CommandExportController {
|
|
|
79
79
|
logger_1.loggers.command.warn(`Rejected suspicious command: ${command}`, {
|
|
80
80
|
playerId: player.clientID,
|
|
81
81
|
playerName: player.name,
|
|
82
|
+
accountID: player.accountID,
|
|
83
|
+
args: args,
|
|
82
84
|
});
|
|
83
85
|
return;
|
|
84
86
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface BinaryCallOptions {
|
|
2
|
+
action?: string;
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
service?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* marks a method as a remote binary action executed by a BinaryService. When a method decorated with @BinaryCall is invoked, its implementation is never executed locally. Instead, OpenCore replaces the method at runtime with an asynchronous proxy that:
|
|
8
|
+
* - Serializes the method arguments
|
|
9
|
+
* - Sends a request to the associated binary process
|
|
10
|
+
* - Waits for a response
|
|
11
|
+
* - Resolves or rejects the returned Promise
|
|
12
|
+
*
|
|
13
|
+
* From the developer’s perspective, the method behaves like a normal async function, while the actual logic is executed externally.
|
|
14
|
+
*/
|
|
15
|
+
export declare function BinaryCall(options?: BinaryCallOptions): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BinaryCall = BinaryCall;
|
|
4
|
+
const metadata_server_keys_1 = require("../system/metadata-server.keys");
|
|
5
|
+
/**
|
|
6
|
+
* marks a method as a remote binary action executed by a BinaryService. When a method decorated with @BinaryCall is invoked, its implementation is never executed locally. Instead, OpenCore replaces the method at runtime with an asynchronous proxy that:
|
|
7
|
+
* - Serializes the method arguments
|
|
8
|
+
* - Sends a request to the associated binary process
|
|
9
|
+
* - Waits for a response
|
|
10
|
+
* - Resolves or rejects the returned Promise
|
|
11
|
+
*
|
|
12
|
+
* From the developer’s perspective, the method behaves like a normal async function, while the actual logic is executed externally.
|
|
13
|
+
*/
|
|
14
|
+
function BinaryCall(options = {}) {
|
|
15
|
+
return (target, propertyKey, descriptor) => {
|
|
16
|
+
var _a;
|
|
17
|
+
if (!descriptor.value) {
|
|
18
|
+
throw new Error(`@BinaryCall(): descriptor.value is undefined for method '${propertyKey}'`);
|
|
19
|
+
}
|
|
20
|
+
Reflect.defineMetadata(metadata_server_keys_1.METADATA_KEYS.BINARY_CALL, {
|
|
21
|
+
methodName: propertyKey,
|
|
22
|
+
action: (_a = options.action) !== null && _a !== void 0 ? _a : propertyKey,
|
|
23
|
+
timeoutMs: options.timeoutMs,
|
|
24
|
+
service: options.service,
|
|
25
|
+
}, target, propertyKey);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ClassConstructor } from '../../../kernel/di/class-constructor';
|
|
2
|
+
export interface BinaryServiceOptions {
|
|
3
|
+
/** Logical identifier of the service inside OpenCore. This name is used to route binary calls and must be unique per resource. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Logical binary filename (without extension). The framework resolves the correct executable automatically depending on the platform */
|
|
6
|
+
binary: string;
|
|
7
|
+
/**
|
|
8
|
+
* Default timeout for binary calls in milliseconds. If the binary does not respond within this time, the call is rejected automatically.
|
|
9
|
+
*/
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface BinaryServiceMetadata extends BinaryServiceOptions {
|
|
13
|
+
serviceClass: ClassConstructor;
|
|
14
|
+
}
|
|
15
|
+
export declare const _serverBinaryServiceRegistryByResource: Map<string, Set<ClassConstructor>>;
|
|
16
|
+
export declare function getServerBinaryServiceRegistry(resourceName?: string): ClassConstructor[];
|
|
17
|
+
/**
|
|
18
|
+
* Declares a native external binary service managed by OpenCore.
|
|
19
|
+
* A BinaryService represents a persistent operating system process executed outside the FiveM runtime, allowing heavy or sensitive logic to run in complete isolation while being consumed from TypeScript as normal asynchronous methods.
|
|
20
|
+
* Binary services communicate with OpenCore using a simple JSON-based RPC protocol over stdin / stdout.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* - The binary name is logical (no extensions). The framework resolves platform-specific files.
|
|
24
|
+
* - A single process is spawned and kept alive for the resource lifecycle.
|
|
25
|
+
* - This system is language-agnostic and can be implemented in any language capable of reading from standard input and writing to standard output.
|
|
26
|
+
*/
|
|
27
|
+
export declare function BinaryService(options: BinaryServiceOptions): (target: ClassConstructor) => void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports._serverBinaryServiceRegistryByResource = void 0;
|
|
7
|
+
exports.getServerBinaryServiceRegistry = getServerBinaryServiceRegistry;
|
|
8
|
+
exports.BinaryService = BinaryService;
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const metadata_server_keys_1 = require("../system/metadata-server.keys");
|
|
11
|
+
const bind_1 = require("./bind");
|
|
12
|
+
exports._serverBinaryServiceRegistryByResource = new Map();
|
|
13
|
+
function getCurrentResourceNameSafe() {
|
|
14
|
+
const fn = globalThis.GetCurrentResourceName;
|
|
15
|
+
if (typeof fn === 'function') {
|
|
16
|
+
const name = fn();
|
|
17
|
+
if (typeof name === 'string' && name.trim())
|
|
18
|
+
return name;
|
|
19
|
+
}
|
|
20
|
+
return 'default';
|
|
21
|
+
}
|
|
22
|
+
function getServerBinaryServiceRegistry(resourceName) {
|
|
23
|
+
const key = resourceName !== null && resourceName !== void 0 ? resourceName : getCurrentResourceNameSafe();
|
|
24
|
+
let registry = exports._serverBinaryServiceRegistryByResource.get(key);
|
|
25
|
+
if (!registry) {
|
|
26
|
+
registry = new Set();
|
|
27
|
+
exports._serverBinaryServiceRegistryByResource.set(key, registry);
|
|
28
|
+
}
|
|
29
|
+
return Array.from(registry);
|
|
30
|
+
}
|
|
31
|
+
function assertValidBinaryName(binary) {
|
|
32
|
+
if (!binary || !binary.trim()) {
|
|
33
|
+
throw new Error('[OpenCore] BinaryService requires a non-empty binary name');
|
|
34
|
+
}
|
|
35
|
+
if (binary.includes('.') || node_path_1.default.extname(binary)) {
|
|
36
|
+
throw new Error(`[OpenCore] BinaryService binary name must not include extensions: ${binary}`);
|
|
37
|
+
}
|
|
38
|
+
if (/[/\\]/.test(binary)) {
|
|
39
|
+
throw new Error(`[OpenCore] BinaryService binary name must not include path separators: ${binary}`);
|
|
40
|
+
}
|
|
41
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(binary)) {
|
|
42
|
+
throw new Error(`[OpenCore] BinaryService binary name has invalid characters: ${binary}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function assertValidServiceName(name) {
|
|
46
|
+
if (!name || !name.trim()) {
|
|
47
|
+
throw new Error('[OpenCore] BinaryService requires a non-empty service name');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Declares a native external binary service managed by OpenCore.
|
|
52
|
+
* A BinaryService represents a persistent operating system process executed outside the FiveM runtime, allowing heavy or sensitive logic to run in complete isolation while being consumed from TypeScript as normal asynchronous methods.
|
|
53
|
+
* Binary services communicate with OpenCore using a simple JSON-based RPC protocol over stdin / stdout.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* - The binary name is logical (no extensions). The framework resolves platform-specific files.
|
|
57
|
+
* - A single process is spawned and kept alive for the resource lifecycle.
|
|
58
|
+
* - This system is language-agnostic and can be implemented in any language capable of reading from standard input and writing to standard output.
|
|
59
|
+
*/
|
|
60
|
+
function BinaryService(options) {
|
|
61
|
+
return (target) => {
|
|
62
|
+
assertValidServiceName(options.name);
|
|
63
|
+
assertValidBinaryName(options.binary);
|
|
64
|
+
(0, bind_1.Bind)('singleton')(target);
|
|
65
|
+
const metadata = Object.assign(Object.assign({}, options), { serviceClass: target });
|
|
66
|
+
Reflect.defineMetadata(metadata_server_keys_1.METADATA_KEYS.BINARY_SERVICE, metadata, target);
|
|
67
|
+
Reflect.defineMetadata(metadata_server_keys_1.METADATA_KEYS.BINARY_SERVICE_NAME, metadata.name, target);
|
|
68
|
+
const key = getCurrentResourceNameSafe();
|
|
69
|
+
let registry = exports._serverBinaryServiceRegistryByResource.get(key);
|
|
70
|
+
if (!registry) {
|
|
71
|
+
registry = new Set();
|
|
72
|
+
exports._serverBinaryServiceRegistryByResource.set(key, registry);
|
|
73
|
+
}
|
|
74
|
+
registry.add(target);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -15,6 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.Controller = exports.Command = void 0;
|
|
18
|
+
__exportStar(require("./binary-call"), exports);
|
|
19
|
+
__exportStar(require("./binary-service"), exports);
|
|
18
20
|
__exportStar(require("./bind"), exports);
|
|
19
21
|
var command_1 = require("./command");
|
|
20
22
|
Object.defineProperty(exports, "Command", { enumerable: true, get: function () { return command_1.Command; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IResourceInfo } from '../../../../adapters/contracts/IResourceInfo';
|
|
2
|
+
import { BinaryServiceOptions } from '../../decorators/binary-service';
|
|
3
|
+
export declare class BinaryProcessManager {
|
|
4
|
+
private resourceInfo;
|
|
5
|
+
private services;
|
|
6
|
+
constructor(resourceInfo: IResourceInfo);
|
|
7
|
+
register(options: BinaryServiceOptions, serviceClass: new (...args: any[]) => any): void;
|
|
8
|
+
call(serviceName: string, action: string, params: unknown[], timeoutMs?: number): Promise<unknown>;
|
|
9
|
+
private resolveServiceInstance;
|
|
10
|
+
private applyBinaryProxies;
|
|
11
|
+
private resolveBinaryPath;
|
|
12
|
+
private spawnProcess;
|
|
13
|
+
private handleStdout;
|
|
14
|
+
private handleResponse;
|
|
15
|
+
private failPending;
|
|
16
|
+
private writeLine;
|
|
17
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.BinaryProcessManager = void 0;
|
|
19
|
+
const node_child_process_1 = require("node:child_process");
|
|
20
|
+
const node_events_1 = require("node:events");
|
|
21
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
22
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
23
|
+
const tsyringe_1 = require("tsyringe");
|
|
24
|
+
const uuid_1 = require("uuid");
|
|
25
|
+
const IResourceInfo_1 = require("../../../../adapters/contracts/IResourceInfo");
|
|
26
|
+
const container_1 = require("../../../../kernel/di/container");
|
|
27
|
+
const app_error_1 = require("../../../../kernel/error/app.error");
|
|
28
|
+
const logger_1 = require("../../../../kernel/logger");
|
|
29
|
+
const metadata_server_keys_1 = require("../../system/metadata-server.keys");
|
|
30
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
31
|
+
let BinaryProcessManager = class BinaryProcessManager {
|
|
32
|
+
constructor(resourceInfo) {
|
|
33
|
+
this.resourceInfo = resourceInfo;
|
|
34
|
+
this.services = new Map();
|
|
35
|
+
}
|
|
36
|
+
register(options, serviceClass) {
|
|
37
|
+
var _a;
|
|
38
|
+
if (this.services.has(options.name)) {
|
|
39
|
+
logger_1.loggers.bootstrap.warn(`[BinaryService] Duplicate service name skipped`, {
|
|
40
|
+
name: options.name,
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const entry = {
|
|
45
|
+
name: options.name,
|
|
46
|
+
binary: options.binary,
|
|
47
|
+
timeoutMs: (_a = options.timeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_TIMEOUT_MS,
|
|
48
|
+
status: 'offline',
|
|
49
|
+
pending: new Map(),
|
|
50
|
+
buffer: '',
|
|
51
|
+
serviceClass,
|
|
52
|
+
};
|
|
53
|
+
this.services.set(options.name, entry);
|
|
54
|
+
logger_1.loggers.bootstrap.debug(`[BinaryService] Registered service`, {
|
|
55
|
+
name: options.name,
|
|
56
|
+
binary: options.binary,
|
|
57
|
+
});
|
|
58
|
+
const instance = this.resolveServiceInstance(serviceClass);
|
|
59
|
+
this.applyBinaryProxies(instance, options);
|
|
60
|
+
const binaryPath = this.resolveBinaryPath(options.binary);
|
|
61
|
+
if (!binaryPath) {
|
|
62
|
+
entry.status = 'missing';
|
|
63
|
+
logger_1.loggers.bootstrap.error(`[BinaryService] Binary not found`, {
|
|
64
|
+
name: options.name,
|
|
65
|
+
binary: options.binary,
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
entry.binaryPath = binaryPath;
|
|
70
|
+
logger_1.loggers.bootstrap.debug(`[BinaryService] Resolved binary path`, {
|
|
71
|
+
name: options.name,
|
|
72
|
+
path: binaryPath,
|
|
73
|
+
});
|
|
74
|
+
this.spawnProcess(entry);
|
|
75
|
+
}
|
|
76
|
+
async call(serviceName, action, params, timeoutMs) {
|
|
77
|
+
const entry = this.services.get(serviceName);
|
|
78
|
+
if (!entry || entry.status !== 'online' || !entry.process) {
|
|
79
|
+
throw new app_error_1.AppError('COMMON:UNKNOWN', `[OpenCore] BinaryService '${serviceName}' is not available`, 'server');
|
|
80
|
+
}
|
|
81
|
+
const id = (0, uuid_1.v4)();
|
|
82
|
+
const payload = JSON.stringify({ id, action, params });
|
|
83
|
+
logger_1.loggers.bootstrap.debug(`[BinaryService] Calling action`, { service: serviceName, action, id });
|
|
84
|
+
await this.writeLine(entry, payload);
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const timeout = setTimeout(() => {
|
|
87
|
+
entry.pending.delete(id);
|
|
88
|
+
logger_1.loggers.bootstrap.error(`[BinaryService] Call timeout`, {
|
|
89
|
+
service: serviceName,
|
|
90
|
+
action,
|
|
91
|
+
id,
|
|
92
|
+
timeoutMs: timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : entry.timeoutMs,
|
|
93
|
+
});
|
|
94
|
+
reject(new app_error_1.AppError('COMMON:UNKNOWN', `[OpenCore] BinaryService '${serviceName}' call timeout`, 'server'));
|
|
95
|
+
}, timeoutMs !== null && timeoutMs !== void 0 ? timeoutMs : entry.timeoutMs);
|
|
96
|
+
entry.pending.set(id, {
|
|
97
|
+
resolve,
|
|
98
|
+
reject,
|
|
99
|
+
timeout,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
resolveServiceInstance(serviceClass) {
|
|
104
|
+
return container_1.GLOBAL_CONTAINER.resolve(serviceClass);
|
|
105
|
+
}
|
|
106
|
+
applyBinaryProxies(instance, options) {
|
|
107
|
+
var _a;
|
|
108
|
+
const proto = Object.getPrototypeOf(instance);
|
|
109
|
+
const methodNames = Object.getOwnPropertyNames(proto).filter((name) => name !== 'constructor' && typeof instance[name] === 'function');
|
|
110
|
+
for (const methodName of methodNames) {
|
|
111
|
+
const metadata = Reflect.getMetadata(metadata_server_keys_1.METADATA_KEYS.BINARY_CALL, proto, methodName);
|
|
112
|
+
if (!metadata)
|
|
113
|
+
continue;
|
|
114
|
+
const serviceName = (_a = metadata.service) !== null && _a !== void 0 ? _a : options.name;
|
|
115
|
+
instance[methodName] = async (...args) => {
|
|
116
|
+
var _a;
|
|
117
|
+
return this.call(serviceName, metadata.action, args, (_a = metadata.timeoutMs) !== null && _a !== void 0 ? _a : options.timeoutMs);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
resolveBinaryPath(binary) {
|
|
122
|
+
const resourceRoot = this.resourceInfo.getCurrentResourcePath();
|
|
123
|
+
const platform = process.platform;
|
|
124
|
+
const filename = platform === 'win32' ? `${binary}.exe` : binary;
|
|
125
|
+
const paths = [
|
|
126
|
+
node_path_1.default.join(resourceRoot, 'bin', platform, filename),
|
|
127
|
+
node_path_1.default.join(resourceRoot, 'bin', filename),
|
|
128
|
+
node_path_1.default.join(resourceRoot, filename),
|
|
129
|
+
];
|
|
130
|
+
for (const p of paths) {
|
|
131
|
+
if (node_fs_1.default.existsSync(p))
|
|
132
|
+
return p;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
spawnProcess(entry) {
|
|
137
|
+
if (!entry.binaryPath) {
|
|
138
|
+
entry.status = 'missing';
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const child = (0, node_child_process_1.spawn)(entry.binaryPath, [], {
|
|
142
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
143
|
+
windowsHide: true,
|
|
144
|
+
});
|
|
145
|
+
entry.process = child;
|
|
146
|
+
entry.status = 'online';
|
|
147
|
+
logger_1.loggers.bootstrap.debug(`[BinaryService] Process spawned`, { name: entry.name, pid: child.pid });
|
|
148
|
+
child.stdout.on('data', (chunk) => this.handleStdout(entry, chunk));
|
|
149
|
+
child.stderr.on('data', (chunk) => {
|
|
150
|
+
logger_1.loggers.bootstrap.warn(`[BinaryService] ${entry.name} stderr`, {
|
|
151
|
+
message: chunk.toString(),
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
child.on('exit', (code, signal) => {
|
|
155
|
+
entry.status = 'offline';
|
|
156
|
+
this.failPending(entry, `Process exited (code=${code}, signal=${signal})`);
|
|
157
|
+
logger_1.loggers.bootstrap.error(`[BinaryService] ${entry.name} exited`, {
|
|
158
|
+
code,
|
|
159
|
+
signal,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
child.on('error', (error) => {
|
|
163
|
+
entry.status = 'offline';
|
|
164
|
+
this.failPending(entry, error.message);
|
|
165
|
+
logger_1.loggers.bootstrap.error(`[BinaryService] ${entry.name} process error`, {
|
|
166
|
+
error: error.message,
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
logger_1.loggers.bootstrap.info(`[BinaryService] ${entry.name} started`, {
|
|
170
|
+
binary: entry.binaryPath,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
handleStdout(entry, chunk) {
|
|
174
|
+
entry.buffer += chunk.toString();
|
|
175
|
+
let newlineIndex = entry.buffer.indexOf('\n');
|
|
176
|
+
while (newlineIndex !== -1) {
|
|
177
|
+
const line = entry.buffer.slice(0, newlineIndex);
|
|
178
|
+
entry.buffer = entry.buffer.slice(newlineIndex + 1);
|
|
179
|
+
const trimmed = line.trim();
|
|
180
|
+
if (trimmed) {
|
|
181
|
+
this.handleResponse(entry, trimmed);
|
|
182
|
+
}
|
|
183
|
+
newlineIndex = entry.buffer.indexOf('\n');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
handleResponse(entry, rawLine) {
|
|
187
|
+
let response = null;
|
|
188
|
+
try {
|
|
189
|
+
response = JSON.parse(rawLine);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (error instanceof Error) {
|
|
193
|
+
logger_1.loggers.bootstrap.warn(`[BinaryService] ${entry.name} invalid JSON response`, {
|
|
194
|
+
line: rawLine,
|
|
195
|
+
error: error.message,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
logger_1.loggers.bootstrap.warn(`[BinaryService] ${entry.name} invalid JSON response`, {
|
|
200
|
+
line: rawLine,
|
|
201
|
+
error: `unknown error: ${error}`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (!(response === null || response === void 0 ? void 0 : response.id)) {
|
|
207
|
+
logger_1.loggers.bootstrap.warn(`[BinaryService] ${entry.name} response missing id`, {
|
|
208
|
+
response,
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const pending = entry.pending.get(response.id);
|
|
213
|
+
if (!pending) {
|
|
214
|
+
logger_1.loggers.bootstrap.warn(`[BinaryService] ${entry.name} response without pending request`, {
|
|
215
|
+
id: response.id,
|
|
216
|
+
status: response.status,
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
logger_1.loggers.bootstrap.debug(`[BinaryService] Received response`, {
|
|
221
|
+
service: entry.name,
|
|
222
|
+
id: response.id,
|
|
223
|
+
status: response.status,
|
|
224
|
+
});
|
|
225
|
+
entry.pending.delete(response.id);
|
|
226
|
+
if (pending.timeout)
|
|
227
|
+
clearTimeout(pending.timeout);
|
|
228
|
+
if (response.status === 'ok') {
|
|
229
|
+
logger_1.loggers.bootstrap.debug(`[BinaryService] Call success`, {
|
|
230
|
+
service: entry.name,
|
|
231
|
+
id: response.id,
|
|
232
|
+
});
|
|
233
|
+
pending.resolve(response.result);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const message = response.error instanceof Error
|
|
237
|
+
? response.error.message
|
|
238
|
+
: typeof response.error === 'string'
|
|
239
|
+
? response.error
|
|
240
|
+
: 'Binary call failed';
|
|
241
|
+
logger_1.loggers.bootstrap.error(`[BinaryService] Call error response`, {
|
|
242
|
+
service: entry.name,
|
|
243
|
+
id: response.id,
|
|
244
|
+
error: response.error,
|
|
245
|
+
message,
|
|
246
|
+
});
|
|
247
|
+
pending.reject(new app_error_1.AppError('COMMON:UNKNOWN', message, 'external', response.error));
|
|
248
|
+
}
|
|
249
|
+
failPending(entry, reason) {
|
|
250
|
+
for (const [id, pending] of entry.pending) {
|
|
251
|
+
if (pending.timeout)
|
|
252
|
+
clearTimeout(pending.timeout);
|
|
253
|
+
pending.reject(new app_error_1.AppError('COMMON:UNKNOWN', reason, 'external'));
|
|
254
|
+
entry.pending.delete(id);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async writeLine(entry, payload) {
|
|
258
|
+
var _a;
|
|
259
|
+
const stdin = (_a = entry.process) === null || _a === void 0 ? void 0 : _a.stdin;
|
|
260
|
+
if (!stdin)
|
|
261
|
+
return;
|
|
262
|
+
const line = `${payload}\n`;
|
|
263
|
+
if (!stdin.write(line)) {
|
|
264
|
+
await (0, node_events_1.once)(stdin, 'drain');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
exports.BinaryProcessManager = BinaryProcessManager;
|
|
269
|
+
exports.BinaryProcessManager = BinaryProcessManager = __decorate([
|
|
270
|
+
(0, tsyringe_1.injectable)(),
|
|
271
|
+
__param(0, (0, tsyringe_1.inject)(IResourceInfo_1.IResourceInfo)),
|
|
272
|
+
__metadata("design:paramtypes", [IResourceInfo_1.IResourceInfo])
|
|
273
|
+
], BinaryProcessManager);
|
|
@@ -4,6 +4,7 @@ exports.registerServicesServer = registerServicesServer;
|
|
|
4
4
|
const index_1 = require("../../../kernel/di/index");
|
|
5
5
|
const world_1 = require("../../core/world");
|
|
6
6
|
const principal_provider_contract_1 = require("../contracts/security/principal-provider.contract");
|
|
7
|
+
const binary_process_manager_1 = require("./binary/binary-process.manager");
|
|
7
8
|
const chat_service_1 = require("./chat.service");
|
|
8
9
|
const command_service_1 = require("./core/command.service");
|
|
9
10
|
const player_service_1 = require("./core/player.service");
|
|
@@ -85,4 +86,7 @@ function registerServicesServer(ctx) {
|
|
|
85
86
|
if (features.chat.enabled) {
|
|
86
87
|
index_1.GLOBAL_CONTAINER.registerSingleton(chat_service_1.ChatService);
|
|
87
88
|
}
|
|
89
|
+
if (!index_1.GLOBAL_CONTAINER.isRegistered(binary_process_manager_1.BinaryProcessManager)) {
|
|
90
|
+
index_1.GLOBAL_CONTAINER.registerSingleton(binary_process_manager_1.BinaryProcessManager);
|
|
91
|
+
}
|
|
88
92
|
}
|
|
@@ -12,4 +12,7 @@ exports.METADATA_KEYS = {
|
|
|
12
12
|
PUBLIC: 'decorator:meta:public',
|
|
13
13
|
THROTTLE: 'decorator:throttle',
|
|
14
14
|
REQUIRES_STATE: 'decorator:requires_state',
|
|
15
|
+
BINARY_SERVICE: 'decorator:meta:binary_service',
|
|
16
|
+
BINARY_SERVICE_NAME: 'decorator:meta:binary_service_name',
|
|
17
|
+
BINARY_CALL: 'decorator:meta:binary_call',
|
|
15
18
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-core/framework",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Secure, Event-Driven, OOP Engine for FiveM. Stop scripting, start engineering.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -68,12 +68,14 @@
|
|
|
68
68
|
"author": "OpenCore Team",
|
|
69
69
|
"license": "MPL-2.0",
|
|
70
70
|
"packageManager": "pnpm@10.13.1",
|
|
71
|
-
"
|
|
71
|
+
"peerDependencies": {
|
|
72
72
|
"reflect-metadata": "^0.2.2",
|
|
73
73
|
"tsyringe": "^4.10.0",
|
|
74
|
-
"uuid": "^13.0.0",
|
|
75
74
|
"zod": "^4.3.5"
|
|
76
75
|
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"uuid": "^13.0.0"
|
|
78
|
+
},
|
|
77
79
|
"devDependencies": {
|
|
78
80
|
"@biomejs/biome": "^2.3.11",
|
|
79
81
|
"@citizenfx/client": "2.0.22443-1",
|