Jacob Schmidt a8415eb1fd Add phone addon and wire UI events
- Introduce client phone addon, UI, and XEH handlers
- Route actor phone interaction to the new phone UI
- Add initial phone state, event handling, and persistence
2026-04-06 19:07:18 -05:00

214 lines
6.8 KiB
Plaintext

#include "..\script_component.hpp"
/*
* File: fnc_extCall.sqf
* Author: IDSolutions
* Date: 2026-01-03
* Last Update: 2026-04-01
* Public: No
*
* Description:
* Call Forge Server extension.
*
* Parameter(s):
* 0: Function name to call <STRING>
* 1: Arguments to pass to function <ARRAY>
*
* Returns:
* Extension result <ARRAY>
* Success <BOOL>
*
* Example(s):
* ["icom:connect", ["127.0.0.1:9090", "server_1"]] call forge_x_component_fnc_extCall params ["_result", "_isSuccess"];
*/
params [["_function", "", [""]], ["_arguments", [], [[]]]];
["INFO", format ["Calling function: %1", _function], nil, nil] call EFUNC(common,log);
private _functionLower = toLower _function;
private _chunkPrefix = "FORGE_TRANSPORT_CHUNK:";
private _chunkPrefixLength = count toArray _chunkPrefix;
private _unsupportedRoutePrefix = "Error: Unsupported transport route";
private _requestChunkSize = 12000;
// Keep bootstrap create/update calls on the direct extension path by default.
// Actor/bank initialization payloads are small enough for normal callExtension
// usage, and their correctness depends on preserving the native argument shape
// of [uid, json]. Transport remains available automatically for genuinely large
// requests through the chunked-request path below.
private _transportResponseFunctions = [
"actor:get",
"actor:hot:init",
"actor:hot:get",
"actor:hot:keys",
"actor:hot:save",
"bank:get",
"bank:hot:init",
"bank:hot:get",
"bank:hot:save",
"cad:view:hydrate",
"cad:groups:build",
"cad:assignments:list",
"cad:orders:list",
"cad:requests:list",
"cad:activity:recent",
"phone:init",
"phone:contacts:list",
"phone:messages:list",
"phone:messages:thread",
"phone:emails:list",
"org:members:get",
"org:assets:get",
"org:fleet:get"
];
private _requiresRedis = !(_functionLower in ["status", "version"])
&& (_functionLower find "icom:" == 0)
&& (_functionLower find "terrain:" == 0);
private _callExtensionCommand = {
params [["_command", "", [""]], ["_commandArguments", [], [[]]]];
("forge_server" callExtension [_command, _commandArguments]) params [
"_response",
"_responseExtCode",
"_responseArmaCode"
];
private _responseSuccess = true;
if (_responseArmaCode != 0 && _responseArmaCode != 301) then {
_responseSuccess = false;
private _armaCodeMessage = createHashMapFromArray [
[101, "SYNTAX_ERROR_WRONG_PARAMS_SIZE"],
[102, "SYNTAX_ERROR_WRONG_PARAMS_TYPE"],
[201, "PARAMS_ERROR_TOO_MANY_ARGS"],
[400, "EXTENSION_LOAD_FAILED"],
[403, "EXTENSION_BLOCKED_BY_BATTLEYE"],
[404, "EXTENSION_NOT_FOUND"]
] getOrDefault [_responseArmaCode, format ["UNKNOWN_%1", _responseArmaCode]];
["WARNING", format ["Arma error: %1", _armaCodeMessage], nil, nil] call EFUNC(common,log);
};
if (_responseExtCode != 0) then {
_responseSuccess = false;
if (_responseExtCode == -1) exitWith {
["WARNING", "Extension not available", nil, nil] call EFUNC(common,log);
[_response, false]
};
if (_responseExtCode == 9) exitWith {
["WARNING", format ["Extension error: %1", _response], nil, nil] call EFUNC(common,log);
[_response, false]
};
["WARNING", format ["Extension error: %1", _responseExtCode], nil, nil] call EFUNC(common,log);
};
[_response, _responseSuccess]
};
private _checkRedisAvailability = {
("forge_server" callExtension ["status", []]) params [
"_redisStatus",
"_statusExtCode",
"_statusArmaCode"
];
private _statusSuccess = (_statusExtCode == 0) && (_statusArmaCode == 0 || _statusArmaCode == 301);
if (!_statusSuccess) exitWith {
["WARNING", "Unable to determine Redis status before extension call", nil, nil] call EFUNC(common,log);
["Error: Redis status check failed", false]
};
if (_redisStatus != "connected") exitWith {
["WARNING", format ["Blocked extension call '%1' because Redis status is '%2'", _function, _redisStatus], nil, nil] call EFUNC(common,log);
[format ["Error: Redis is %1", _redisStatus], false]
};
["", true]
};
private _buildTransportArgumentsJson = {
private _rawArguments = _this;
if !(_rawArguments isEqualType []) then {
_rawArguments = [_rawArguments];
};
private _stringArguments = _rawArguments apply {
if (_x isEqualType "") exitWith { _x };
if (_x isEqualType true) exitWith { ["false", "true"] select _x };
str _x
};
if !(_stringArguments isEqualType []) then {
_stringArguments = [_stringArguments];
};
private _encodedArguments = [];
{
_encodedArguments pushBack (toJSON _x);
} forEach _stringArguments;
format ["[%1]", _encodedArguments joinString ","]
};
if (_requiresRedis) exitWith {
[_function, _arguments] call _checkRedisAvailability params ["_redisResult", "_redisSuccess"];
if (!_redisSuccess) exitWith { [_redisResult, false] };
if (_functionLower in ["status", "version"]) exitWith {
[_function, _arguments] call _callExtensionCommand
};
[_function, _arguments] call _callExtensionCommand
};
if (_functionLower in ["status", "version"]) exitWith {
[_function, _arguments] call _callExtensionCommand
};
private _argumentsJson = _arguments call _buildTransportArgumentsJson;
private _usesTransportResponse = _functionLower in _transportResponseFunctions;
private _usesChunkedRequest = (count toArray _argumentsJson) > _requestChunkSize;
// Most calls should stay direct unless they either need chunked response
// assembly or the request body is large enough to require staging.
if !(_usesTransportResponse || { _usesChunkedRequest }) exitWith {
[_function, _arguments] call _callExtensionCommand
};
private _transportCommand = "transport:invoke";
private _transportArguments = [_function, _argumentsJson];
if (_usesChunkedRequest) then {
["stage", _function, _argumentsJson, _requestChunkSize, _callExtensionCommand] call FUNC(transport) params [
"_stagedTransportCommand",
"_stagedTransportArguments",
"_stageSuccess"
];
if (!_stageSuccess) exitWith {
["Error: Failed to stage chunked extension request", false]
};
_transportCommand = _stagedTransportCommand;
_transportArguments = _stagedTransportArguments;
};
[_transportCommand, _transportArguments] call _callExtensionCommand params ["_result", "_success"];
if (
_success
&& { _result isEqualType "" }
&& { (_result find _unsupportedRoutePrefix) == 0 }
&& { !_usesChunkedRequest }
) exitWith {
[_function, _arguments] call _callExtensionCommand
};
["assemble", _result, _success, _chunkPrefix, _chunkPrefixLength, _callExtensionCommand] call FUNC(transport)