diff --git a/client/src/CJavaScriptResource.cpp b/client/src/CJavaScriptResource.cpp index a5ceeec56..bf000ed09 100644 --- a/client/src/CJavaScriptResource.cpp +++ b/client/src/CJavaScriptResource.cpp @@ -16,7 +16,7 @@ v8::Local CJavaScriptResource::CompileAndRun(const std::string& path if(maybeMod.IsEmpty()) { - js::Logger::Error("[JS] Failed to compile file", path); + js::Logger::Error("Failed to compile file", path); tryCatch.Check(true, true); return v8::Local(); } @@ -26,11 +26,11 @@ v8::Local CJavaScriptResource::CompileAndRun(const std::string& path if(!InstantiateModule(GetContext(), mod) || tryCatch.HasCaught()) { - js::Logger::Error("[JS] Failed to instantiate file", path); + js::Logger::Error("Failed to instantiate file", path); if(mod->GetStatus() == v8::Module::kErrored) { js::Object exceptionObj = mod->GetException().As(); - js::Logger::Error("[JS]", exceptionObj.Get("message")); + js::Logger::Error(exceptionObj.Get("message")); std::string stack = exceptionObj.Get("stack"); if(!stack.empty()) js::Logger::Error(stack); } @@ -40,11 +40,11 @@ v8::Local CJavaScriptResource::CompileAndRun(const std::string& path v8::MaybeLocal maybeResult = EvaluateModule(GetContext(), mod); if(maybeResult.IsEmpty() || maybeResult.ToLocalChecked().As()->State() == v8::Promise::PromiseState::kRejected) { - js::Logger::Error("[JS] Failed to start file", path); + js::Logger::Error("Failed to start file", path); if(mod->GetStatus() == v8::Module::kErrored) { js::Object exceptionObj = mod->GetException().As(); - js::Logger::Error("[JS]", exceptionObj.Get("message")); + js::Logger::Error(exceptionObj.Get("message")); std::string stack = exceptionObj.Get("stack"); if(!stack.empty()) js::Logger::Error(stack); } @@ -106,7 +106,7 @@ bool CJavaScriptResource::Start() if (IsCompatibilityModeEnabled()) { auto resourceName = resource->GetName(); - js::Logger::Colored << "~y~[JS] Compatibility mode is enabled for resource " << resourceName << js::Logger::Endl; + js::Logger::Colored << "~y~Compatibility mode is enabled for resource " << resourceName << js::Logger::Endl; } return true; diff --git a/client/src/CJavaScriptRuntime.cpp b/client/src/CJavaScriptRuntime.cpp index d000c67cc..39a019e50 100644 --- a/client/src/CJavaScriptRuntime.cpp +++ b/client/src/CJavaScriptRuntime.cpp @@ -4,7 +4,7 @@ void CJavaScriptRuntime::OnFatalError(const char* location, const char* message) { - js::Logger::Error("[JS] V8 fatal error!", location, message); + js::Logger::Error("V8 fatal error!", location, message); } void CJavaScriptRuntime::OnHeapOOM(const char* location, const v8::OOMDetails& details) @@ -15,7 +15,7 @@ void CJavaScriptRuntime::OnHeapOOM(const char* location, const v8::OOMDetails& d size_t CJavaScriptRuntime::OnNearHeapLimit(void*, size_t current, size_t initial) { - js::Logger::Warn("[JS] The remaining V8 heap space is approaching critical levels. Increasing heap limit..."); + js::Logger::Warn("The remaining V8 heap space is approaching critical levels. Increasing heap limit..."); // Increase the heap limit by 100MB if the heap limit has not exceeded 4GB uint64_t currentLimitMb = (current / 1024) / 1024; @@ -103,7 +103,7 @@ void CJavaScriptRuntime::InitializeImportMetaObject(v8::Local conte void CJavaScriptRuntime::MessageListener(v8::Local message, v8::Local error) { - js::Logger::Warn("[JS] V8 message received!", js::CppValue(message->Get())); + js::Logger::Warn("V8 message received!", js::CppValue(message->Get())); } void CJavaScriptRuntime::SetupIsolateHandlers() diff --git a/client/src/Main.cpp b/client/src/Main.cpp index e7ed17093..79355b68b 100644 --- a/client/src/Main.cpp +++ b/client/src/Main.cpp @@ -13,6 +13,23 @@ #define JSV2_ENTRY_POINT CreateScriptRuntimeJSv2 #endif +static void ModuleCommand(const std::vector& args) +{ + if (args.empty()) + { + js::Logger::Colored("~y~Usage: ~w~js-module-v2 [options]"); + js::Logger::Colored("~y~Options:"); + js::Logger::Colored(" ~ly~--version ~w~- Version info"); + } + else if (args[0] == "--version") + { + js::Logger::Colored("~g~JS Module v2:"); + js::Logger::Colored("~ly~module:", MODULE_VERSION); + js::Logger::Colored("~ly~cpp-sdk:", ALT_SDK_VERSION); + js::Logger::Colored("~ly~v8:", std::to_string(V8_MAJOR_VERSION) + "." + std::to_string(V8_MINOR_VERSION) + "." + std::to_string(V8_BUILD_NUMBER)); + } +} + ALTV_JSV2_EXPORT alt::IScriptRuntime* JSV2_ENTRY_POINT(alt::ICore* core) { alt::ICore::SetInstance(core); @@ -20,6 +37,7 @@ ALTV_JSV2_EXPORT alt::IScriptRuntime* JSV2_ENTRY_POINT(alt::ICore* core) CJavaScriptRuntime& runtime = CJavaScriptRuntime::Instance(); if(!runtime.Initialize()) return nullptr; + core->SubscribeCommand("js-module-v2", ModuleCommand); core->SubscribeCommand("debughandles", js::DebugHandlesCommand); core->SubscribeCommand("dumpbinding", js::DumpBindingCommand); core->SubscribeCommand("dumpallbindings", js::DumpAllBindingsCommand); diff --git a/client/src/classes/TextDecoder.cpp b/client/src/classes/TextDecoder.cpp index b941f8907..f7879e163 100644 --- a/client/src/classes/TextDecoder.cpp +++ b/client/src/classes/TextDecoder.cpp @@ -1,6 +1,6 @@ #include "Class.h" -static void Constructor(js::FunctionContext& ctx) +static void TextDecoderConstructor(js::FunctionContext& ctx) { if (!ctx.CheckCtor()) return; if (!ctx.CheckArgCount(0, 2)) return; @@ -35,7 +35,7 @@ static void EncodingGetter(js::PropertyContext& ctx) ctx.Return(ctx.GetThis()->Get(ctx.GetContext(), js::JSValue("__encoding")).ToLocalChecked()); } -extern js::Class textDecoderClass("TextDecoder", Constructor, [](js::ClassTemplate& tpl) +extern js::Class textDecoderClass("TextDecoder", TextDecoderConstructor, [](js::ClassTemplate& tpl) { tpl.Property("encoding", EncodingGetter); }); diff --git a/client/src/classes/TextEncoder.cpp b/client/src/classes/TextEncoder.cpp index 9179ed8a5..1768b11a8 100644 --- a/client/src/classes/TextEncoder.cpp +++ b/client/src/classes/TextEncoder.cpp @@ -1,11 +1,12 @@ #include "Class.h" +static void TextEncoderConstructor(js::FunctionContext&) { } + static void EncodingGetter(js::LazyPropertyContext& ctx) { ctx.Return(std::string("utf-8")); } - static void Encode(js::FunctionContext& ctx) { if (!ctx.CheckArgCount(1)) return; @@ -48,7 +49,7 @@ static void EncodeInto(js::FunctionContext& ctx) ctx.Return(result); } -extern js::Class textEncoderClass("TextEncoder", [](js::ClassTemplate& tpl) +extern js::Class textEncoderClass("TextEncoder", TextEncoderConstructor, [](js::ClassTemplate& tpl) { tpl.LazyProperty("encoding", EncodingGetter); diff --git a/client/src/helpers/IExceptionHandler.cpp b/client/src/helpers/IExceptionHandler.cpp index 8a6b683d2..1abd5fe51 100644 --- a/client/src/helpers/IExceptionHandler.cpp +++ b/client/src/helpers/IExceptionHandler.cpp @@ -27,8 +27,8 @@ void IExceptionHandler::OnPromiseRejectAfterResolve(v8::PromiseRejectMessage& me v8::Isolate* isolate = resource->GetIsolate(); std::string rejectionMsg = *v8::String::Utf8Value(isolate, message.GetValue()->ToString(resource->GetContext()).ToLocalChecked()); - js::Logger::Error("[JS] Promise rejected after already being resolved in resource '" + resourceName + "'"); - if(!rejectionMsg.empty()) js::Logger::Error("[JS]", rejectionMsg); + js::Logger::Error("Promise rejected after already being resolved in resource '" + resourceName + "'"); + if(!rejectionMsg.empty()) js::Logger::Error(rejectionMsg); } void IExceptionHandler::OnPromiseResolveAfterResolve(v8::PromiseRejectMessage& message) @@ -38,8 +38,8 @@ void IExceptionHandler::OnPromiseResolveAfterResolve(v8::PromiseRejectMessage& m v8::Isolate* isolate = resource->GetIsolate(); std::string rejectionMsg = *v8::String::Utf8Value(isolate, message.GetValue()->ToString(resource->GetContext()).ToLocalChecked()); - js::Logger::Error("[JS] Promise resolved after already being resolved in resource '" + resourceName + "'"); - if(!rejectionMsg.empty()) js::Logger::Error("[JS]", rejectionMsg); + js::Logger::Error("Promise resolved after already being resolved in resource '" + resourceName + "'"); + if(!rejectionMsg.empty()) js::Logger::Error(rejectionMsg); } void IExceptionHandler::ProcessExceptions() @@ -51,9 +51,9 @@ void IExceptionHandler::ProcessExceptions() for(PromiseRejection& rejection : promiseRejections) { std::string rejectionMsg = *v8::String::Utf8Value(isolate, rejection.value.Get(isolate)->ToString(resource->GetContext()).ToLocalChecked()); - js::Logger::Error("[JS] Unhandled promise rejection in resource '" + resourceName + "' in file '" + rejection.location.file + "' at line " + std::to_string(rejection.location.line)); - if(!rejectionMsg.empty()) js::Logger::Error("[JS]", rejectionMsg); - if(!rejection.stackTrace.IsEmpty()) js::Logger::Error("[JS]", rejection.stackTrace.ToString()); + js::Logger::Error("Unhandled promise rejection in resource '" + resourceName + "' in file '" + rejection.location.file + "' at line " + std::to_string(rejection.location.line)); + if(!rejectionMsg.empty()) js::Logger::Error(rejectionMsg); + if(!rejection.stackTrace.IsEmpty()) js::Logger::Error(rejection.stackTrace.ToString()); } promiseRejections.clear(); } diff --git a/client/src/helpers/IModuleHandler.cpp b/client/src/helpers/IModuleHandler.cpp index d94d64396..4c173b47f 100644 --- a/client/src/helpers/IModuleHandler.cpp +++ b/client/src/helpers/IModuleHandler.cpp @@ -238,7 +238,7 @@ v8::MaybeLocal IModuleHandler::CompileBytecode(const std::string& na v8::MaybeLocal module = v8::ScriptCompiler::CompileModule(isolate, &source, v8::ScriptCompiler::kConsumeCodeCache); if(cachedData->rejected) { - js::Logger::Error("[JS] Trying to load invalid bytecode"); + js::Logger::Error("Trying to load invalid bytecode"); return v8::MaybeLocal(); } return module; diff --git a/client/src/helpers/NativeInvoker.cpp b/client/src/helpers/NativeInvoker.cpp index 23d67fba7..0f4b3b962 100644 --- a/client/src/helpers/NativeInvoker.cpp +++ b/client/src/helpers/NativeInvoker.cpp @@ -89,7 +89,7 @@ bool js::NativeInvoker::PushArgs(js::FunctionContext& ctx, alt::INative* native) } default: { - Logger::Warn("[JS] Unknown native argument type", magic_enum::enum_name(nativeArgs[i]), "for native", native->GetName(), "at index", i); + Logger::Warn("Unknown native argument type", magic_enum::enum_name(nativeArgs[i]), "for native", native->GetName(), "at index", i); break; } } @@ -115,7 +115,7 @@ v8::Local js::NativeInvoker::GetPointerReturnValue(alt::INative::Type return resource->CreateVector3({ vector->x, vector->y, vector->z }); } } - // js::Logger::Warn("[JS] Unknown native pointer return type:", magic_enum::enum_name(type), (int)type); + // js::Logger::Warn("Unknown native pointer return type:", magic_enum::enum_name(type), (int)type); return v8::Undefined(resource->GetIsolate()); } @@ -137,7 +137,7 @@ v8::Local js::NativeInvoker::GetReturnValue() case Type::ARG_STRING: return js::JSValue(nativeContext->ResultString()); case Type::ARG_VOID: return v8::Undefined(resource->GetIsolate()); } - js::Logger::Warn("[JS] Unknown native return type:", magic_enum::enum_name(native->GetRetnType()), (int)native->GetRetnType()); + js::Logger::Warn("Unknown native return type:", magic_enum::enum_name(native->GetRetnType()), (int)native->GetRetnType()); return v8::Undefined(resource->GetIsolate()); } diff --git a/server/js/compatibility/classes/player.js b/server/js/compatibility/classes/player.js index 6c30528b6..2bc6c89aa 100644 --- a/server/js/compatibility/classes/player.js +++ b/server/js/compatibility/classes/player.js @@ -7,9 +7,8 @@ const { extendClassWithProperties, overrideLazyProperty } = requireBinding("shar const { SharedPlayer } = requireBinding("shared/compatibility/classes/sharedPlayer.js"); -// NOTE(xLuxy): Store the original spawn method to call it later since we can't call it directly using super -// and we need to override it const originalSpawnMethod = alt.Player.prototype.spawn; +const originalSetDlcClothes = alt.Player.prototype.setDlcClothes; class Player { onCreate() { @@ -65,6 +64,10 @@ class Player { originalSpawnMethod.call(this, pos, delay); } + setDlcClothes(dlc, component, drawable, texture, palette = 2) { + return originalSetDlcClothes.call(this, component, drawable, texture, palette, dlc); + } + isEntityInStreamRange(entity) { return this.isEntityInStreamingRange(entity); } @@ -128,7 +131,7 @@ extendClassWithProperties( alt.Player, { whitelist: { - nonStatic: ["spawn"] + nonStatic: ["spawn", "setDlcClothes"] } }, Player, diff --git a/server/src/CNodeResource.cpp b/server/src/CNodeResource.cpp index 6d0466524..af7c08edc 100644 --- a/server/src/CNodeResource.cpp +++ b/server/src/CNodeResource.cpp @@ -84,7 +84,7 @@ bool CNodeResource::Start() if (IsCompatibilityModeEnabled()) { auto resourceName = resource->GetName(); - js::Logger::Colored << "~y~[JS] Compatibility mode is enabled for resource " << resourceName << js::Logger::Endl; + js::Logger::Colored << "~y~Compatibility mode is enabled for resource " << resourceName << js::Logger::Endl; } return true; diff --git a/server/src/Main.cpp b/server/src/Main.cpp index 35f2de04e..ef8d6d559 100644 --- a/server/src/Main.cpp +++ b/server/src/Main.cpp @@ -10,18 +10,19 @@ static void ModuleCommand(const std::vector& args) { - if(args.size() == 0) + if (args.empty()) { js::Logger::Colored("~y~Usage: ~w~js-module-v2 [options]"); js::Logger::Colored("~y~Options:"); js::Logger::Colored(" ~ly~--version ~w~- Version info"); } - else if(args[0] == "--version") + else if (args[0] == "--version") { js::Logger::Colored("~g~JS Module v2:"); js::Logger::Colored("~ly~module:", MODULE_VERSION); js::Logger::Colored("~ly~cpp-sdk:", ALT_SDK_VERSION); js::Logger::Colored("~ly~nodejs:", std::to_string(NODE_MAJOR_VERSION) + "." + std::to_string(NODE_MINOR_VERSION) + "." + std::to_string(NODE_PATCH_VERSION)); + js::Logger::Colored("~ly~v8:", std::to_string(V8_MAJOR_VERSION) + "." + std::to_string(V8_MINOR_VERSION) + "." + std::to_string(V8_BUILD_NUMBER)); } } diff --git a/server/src/classes/Vehicle.cpp b/server/src/classes/Vehicle.cpp index 9b1cc9ce2..a85819f29 100644 --- a/server/src/classes/Vehicle.cpp +++ b/server/src/classes/Vehicle.cpp @@ -1,25 +1,22 @@ #include "Class.h" -static void NeonSetter(js::DynamicPropertySetterContext& ctx) +static void NeonSetter(js::PropertyContext& ctx) { - if(!ctx.CheckParent()) return; - alt::IVehicle* vehicle = ctx.GetParent(); - - bool val; - if(!ctx.GetValue(val)) return; + if (!ctx.CheckThis()) return; + alt::IVehicle* vehicle = ctx.GetThisObject(); bool left, right, front, back; vehicle->GetNeonActive(&left, &right, &front, &back); - std::string prop = ctx.GetProperty(); - if(prop == "left") left = val; - else if(prop == "right") - right = val; - else if(prop == "front") - front = val; - else if(prop == "back") - back = val; - vehicle->SetNeonActive(left, right, front, back); + js::Object prop; + if (!ctx.GetValue(prop, js::Type::OBJECT)) return; + + vehicle->SetNeonActive( + prop.Get("left", left), + prop.Get("right", right), + prop.Get("front", front), + prop.Get("back", back) + ); } static void SetNeonActive(js::FunctionContext& ctx) @@ -116,7 +113,8 @@ extern js::Class vehicleClass("Vehicle", &sharedVehicleClass, nullptr, [](js::Cl { tpl.BindToType(alt::IBaseObject::Type::VEHICLE); - tpl.DynamicProperty("neon", nullptr, &NeonSetter, nullptr, nullptr); + tpl.Property("neon", nullptr, &NeonSetter); + tpl.Method("setNeonActive", &SetNeonActive); tpl.Property("modKit", &ModKitGetter, &ModKitSetter); diff --git a/shared/js/compatibility/enums.js b/shared/js/compatibility/enums.js new file mode 100644 index 000000000..ccba12749 --- /dev/null +++ b/shared/js/compatibility/enums.js @@ -0,0 +1,8 @@ +/// +/// +/// +// import * as alt from "@altv/shared"; + +for (const enumName in alt.Enums) { + cppBindings.registerCompatibilityExport(enumName, alt.Enums[enumName]); +} diff --git a/shared/js/compatibility/utils/classes.js b/shared/js/compatibility/utils/classes.js index f2042c117..97aded690 100644 --- a/shared/js/compatibility/utils/classes.js +++ b/shared/js/compatibility/utils/classes.js @@ -37,7 +37,7 @@ function applyNonStaticProperties(baseClass, cls, options) { ["get", "set", "value"].forEach((key) => { if (key in newDescriptor && (!mergedDescriptor[key] || isWhitelisted)) { if (options.verbose) { - alt.log(`~ly~[JS] ~lr~Merged ${key} for ${prop} from ${prot.constructor.name} to ${baseClass.name}`); + alt.log(`~lr~Merged ${key} for ${prop} from ${prot.constructor.name} to ${baseClass.name}`); } mergedDescriptor[key] = newDescriptor[key]; } @@ -50,7 +50,7 @@ function applyNonStaticProperties(baseClass, cls, options) { if (options.verbose) { const action = baseDescriptor ? "Merged" : "Applied"; - alt.log(`~ly~[JS] ~lb~${action} non-static property ${prop} from ${prot.constructor.name} to ${baseClass.name}`); + alt.log(`~lb~${action} non-static property ${prop} from ${prot.constructor.name} to ${baseClass.name}`); } } } @@ -67,7 +67,7 @@ function applyStaticProperties(baseClass, cls, options) { if (doesPropertyExist && !canBeOverriden) { if (options.verbose) { const reason = !canBeOverriden ? "blacklisted" : "already exists"; - alt.log(`~ly~[JS] ~lb~Skipping static property ${propKey} in ${cls.name}: ${reason}`); + alt.log(`~lb~Skipping static property ${propKey} in ${cls.name}: ${reason}`); } continue; @@ -82,7 +82,7 @@ function applyStaticProperties(baseClass, cls, options) { Object.defineProperty(baseClass, propKey, descriptor); if (options.verbose) { - alt.log(`~ly~[JS] ~lb~Applied static property ${propKey} in ${cls.name} to ${baseClass.name}`); + alt.log(`~lb~Applied static property ${propKey} in ${cls.name} to ${baseClass.name}`); } } } @@ -132,7 +132,7 @@ export function overrideLazyProperty(instance, propertyName, value) { const descriptor = Object.getOwnPropertyDescriptor(instance, propertyName); if (!descriptor) { - alt.log(`~ly~[JS] ~lr~Lazy Property ${propertyName} does not exist in ${instance.constructor.name} to override property`); + alt.log(`~lr~Lazy Property ${propertyName} does not exist in ${instance.constructor.name} to override property`); return; } diff --git a/shared/js/entity.js b/shared/js/entity.js index f2a353773..e8895deda 100644 --- a/shared/js/entity.js +++ b/shared/js/entity.js @@ -55,10 +55,14 @@ alt.Events.onBaseObjectRemove(({ object }) => { }); export function addEntityToAll(entity) { + if (entity == null || !(entity instanceof alt.BaseObject)) return; + addEntityToAllWithType(entity, entity.type); } export function removeEntityFromAll(entity) { + if (entity == null || !(entity instanceof alt.BaseObject)) return; + entityAllSetDirty = true; entityAllSet.delete(entity); const all = entityAllMap.get(entity.type); diff --git a/shared/js/events.js b/shared/js/events.js index 7c3adfdbd..4a6538721 100644 --- a/shared/js/events.js +++ b/shared/js/events.js @@ -1,5 +1,5 @@ /** @type {typeof import("./utils.js")} */ -const { assert, assertIsType } = requireBinding("shared/utils.js"); +const { assert, assertIsType, isAsyncFunction } = requireBinding("shared/utils.js"); /** @type {typeof import("../../shared/js/helpers/events.js")} */ const { emitRaw } = requireBinding("shared/helpers/events.js"); @@ -88,29 +88,43 @@ export class Event { static #handleScriptEvent(ctx, local) { const name = ctx.eventName; const handlers = local ? Event.#localScriptEventHandlers.get(name) : Event.#remoteScriptEventHandlers.get(name); - if (!handlers) return; + if (!handlers) { + return []; + } + const isPlayerScriptEvent = alt.isServer && !local; + const promises = []; for (let eventHandler of handlers) { const { handler, location, onlyOnce, eventName } = eventHandler; try { - const startTime = Date.now(); - if (isPlayerScriptEvent) handler(ctx.player, ...ctx.args); - else handler(...ctx.args); - const duration = Date.now() - startTime; + const startTime = alt.getNetTime(); + + const isAsync = isAsyncFunction(handler); + if (isPlayerScriptEvent) { + if (isAsync) promises.push(handler(ctx.player, ...ctx.args)); + else handler(ctx.player, ...ctx.args); + } else { + if (isAsync) promises.push(handler(...ctx.args)); + else handler(...ctx.args); + } + + const duration = alt.getNetTime() - startTime; if (duration > Event.#warningThreshold) { - alt.logWarning(`[JS] Event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for script event '${name}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); + alt.logWarning(`Event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for script event '${name}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); } if (onlyOnce) eventHandler.destroy(); } catch (e) { - alt.logError(`[JS] Exception caught while invoking script event '${name}' handler`); + alt.logError(`Exception caught while invoking script event '${name}' handler`); alt.logError(e); Event.invoke(alt.Enums.CustomEventType.ERROR, { error: e, location, stack: e.stack }, true); } } + + return promises; } static getEventHandlers() { @@ -186,7 +200,7 @@ export class Event { */ static #invokeGeneric(eventType, ctx, custom) { const handlers = Event.#genericHandlers; - if (!handlers.size) return; + if (!handlers.size) return []; const genericCtx = Object.freeze({ ...ctx, @@ -194,21 +208,27 @@ export class Event { customEvent: custom }); + const promises = []; for (let { handler, location } of handlers) { try { - const startTime = Date.now(); - handler(genericCtx); - const duration = Date.now() - startTime; + const startTime = alt.getNetTime(); + + if (isAsyncFunction(handler)) promises.push(handler(genericCtx)); + else handler(genericCtx); + + const duration = alt.getNetTime() - startTime; if (duration > Event.#warningThreshold) { - alt.logWarning(`[JS] Generic event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for event '${Event.getEventName(eventType, custom)}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); + alt.logWarning(`Generic event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for event '${Event.getEventName(eventType, custom)}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); } } catch (e) { - alt.logError(`[JS] Exception caught while invoking generic event handler`); + alt.logError(`Exception caught while invoking generic event handler`); alt.logError(e); Event.invoke(alt.Enums.CustomEventType.ERROR, { error: e, location, stack: e.stack }, true); } } + + return promises; } /** @@ -250,30 +270,43 @@ export class Event { * @param {boolean} custom */ static invoke(eventType, ctx, custom) { - Event.#invokeGeneric(eventType, ctx, custom); - if (eventType === alt.Enums.EventType.CLIENT_SCRIPT_EVENT) Event.#handleScriptEvent(ctx, alt.isClient); - else if (eventType === alt.Enums.EventType.SERVER_SCRIPT_EVENT) Event.#handleScriptEvent(ctx, alt.isServer); + let promises = Event.#invokeGeneric(eventType, ctx, custom); + + if (eventType === alt.Enums.EventType.CLIENT_SCRIPT_EVENT) promises = [...Event.#handleScriptEvent(ctx, alt.isClient), ...promises]; + else if (eventType === alt.Enums.EventType.SERVER_SCRIPT_EVENT) promises = [...Event.#handleScriptEvent(ctx, alt.isServer), ...promises]; const map = custom ? Event.#customHandlers : Event.#handlers; const handlers = map.get(eventType); - if (!handlers) return; + + if (!handlers) { + return promises; + } + for (const eventHandler of handlers) { const { handler, location, onlyOnce } = eventHandler; try { - const startTime = Date.now(); - handler(ctx); - const duration = Date.now() - startTime; - if (duration > Event.#warningThreshold) - alt.logWarning(`[JS] Event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for event '${Event.getEventName(eventType, custom)}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); - if (onlyOnce) eventHandler.destroy(); + const startTime = alt.getNetTime(); + if (isAsyncFunction(handler)) promises.push(handler(ctx)); + else handler(ctx); + + const duration = alt.getNetTime() - startTime; + if (duration > Event.#warningThreshold) { + alt.logWarning(`Event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for event '${Event.getEventName(eventType, custom)}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); + } + + if (onlyOnce) { + eventHandler.destroy(); + } } catch (e) { - alt.logError(`[JS] Exception caught while invoking event handler`); + alt.logError(`Exception caught while invoking event handler`); alt.logError(e); Event.invoke(alt.Enums.CustomEventType.ERROR, { error: e, location, stack: e.stack }, true); } } + + return promises; } } @@ -404,7 +437,12 @@ Object.defineProperties(alt.Events, { alt.Events.setWarningThreshold = Event.setWarningThreshold; alt.Events.setSourceLocationFrameSkipCount = Event.setSourceLocationFrameSkipCount; -function onEvent(custom, eventType, eventData) { - return Event.invoke(eventType, eventData, custom); +async function onEvent(custom, eventType, eventData) { + const promises = Event.invoke(eventType, eventData, custom); + + if (promises.length >= 1) { + return Promise.all(promises); + } } + cppBindings.registerExport(cppBindings.BindingExport.ON_EVENT, onEvent); diff --git a/shared/js/events/script.js b/shared/js/events/script.js index 841118dd2..57817e586 100644 --- a/shared/js/events/script.js +++ b/shared/js/events/script.js @@ -37,7 +37,11 @@ alt.Events.onAnyResourceStart(({ resource }) => { Event.invoke(alt.Enums.CustomEventType.RESOURCE_START, {}, true); }); -alt.Events.onAnyResourceStop(({ resource }) => { +alt.Events.onAnyResourceStop(async ({ resource }) => { if (resource.name !== alt.Resource.current.name) return; - Event.invoke(alt.Enums.CustomEventType.RESOURCE_STOP, {}, true); + + const promises = Event.invoke(alt.Enums.CustomEventType.RESOURCE_STOP, {}, true); + if (promises.length >= 1) { + await Promise.all(promises); + } }); diff --git a/shared/js/logging.js b/shared/js/logging.js index ef52dda1b..c4961f232 100644 --- a/shared/js/logging.js +++ b/shared/js/logging.js @@ -2814,32 +2814,45 @@ cppBindings.registerExport(cppBindings.BindingExport.LOG_INSPECT, inspectMultipl /** @type {Map} */ const timeLabelMap = new Map(); function time(label) { - if (timeLabelMap.has(label ?? "Timer")) throw new Error(`Label '${label ?? "Timer"}' already running`); - timeLabelMap.set(label ?? "Timer", Date.now()); + if (timeLabelMap.has(label ?? "Timer")) { + throw new Error(`Label '${label ?? "Timer"}' already running`); + } + + timeLabelMap.set(label ?? "Timer", alt.getNetTime()); } function timeLog(label) { - const start = timeLabelMap.get(label ?? "Timer"); - if (start === undefined) throw new Error(`No such label '${label ?? "Timer"}' running`); - const duration = Date.now() - start; - alt.log(`[JS] ${label ?? "Timer"}: ${duration}ms`); + const startTime = timeLabelMap.get(label ?? "Timer"); + if (startTime === undefined) { + throw new Error(`No such label '${label ?? "Timer"}' running`); + } + + const duration = alt.getNetTime() - startTime; + alt.log(`${label ?? "Timer"}: ${duration}ms`); } function timeEnd(label) { - const start = timeLabelMap.get(label ?? "Timer"); - if (start === undefined) throw new Error(`No such label '${label ?? "Timer"}' running`); - const duration = Date.now() - start; - alt.log(`[JS] ${label ?? "Timer"}: ${duration}ms`); + const startTime = timeLabelMap.get(label ?? "Timer"); + if (startTime === undefined) { + throw new Error(`No such label '${label ?? "Timer"}' running`); + } + + const duration = alt.getNetTime() - startTime; + alt.log(`${label ?? "Timer"}: ${duration}ms`); timeLabelMap.delete(label ?? "Timer"); } function logDebug(...args) { if (!alt.isDebug) return; + alt.log(...args); } alt.logDebug = logDebug; if (alt.isClient) { - if (!globalThis.console) globalThis.console = {}; + if (!globalThis.console) { + globalThis.console = {}; + } + globalThis.console.log = alt.log; globalThis.console.debug = alt.logDebug; globalThis.console.warn = alt.logWarning; diff --git a/shared/js/timers.js b/shared/js/timers.js index 71036aad7..bd9a8a0aa 100644 --- a/shared/js/timers.js +++ b/shared/js/timers.js @@ -38,6 +38,7 @@ class Timer { lastTick; /** @type {boolean} */ once; + /** @type {{ fileName: string, lineNumber: number }} */ location; @@ -66,14 +67,26 @@ class Timer { return timers.get(id) || null; } + /** + * + * @param {number | Timer} idOrHandle + */ + static isValid(idOrHandle) { + if (!idOrHandle) return false; + + const id = idOrHandle instanceof Timer ? idOrHandle.id : idOrHandle; + return timers.has(id); + } + constructor(type, callback, interval, once, args) { assertIsType(type, "number", "Expected a number as first argument"); assertIsType(callback, "function", "Expected a function as second argument"); assertIsType(interval, "number", "Expected a number as third argument"); + assertIsType(once, "boolean", "Expected a boolean as fourth argument"); this.interval = interval; this.callback = callback.bind(this, ...(Array.isArray(args) ? args : [])); - this.lastTick = Date.now(); + this.lastTick = alt.getNetTime(); this.once = once; this.#_type = type; this.#_id = Timer.#timerIncrementer++; @@ -86,21 +99,21 @@ class Timer { } tick() { - const now = Date.now(); + const now = alt.getNetTime(); if (this.interval === 0 || now - this.lastTick >= this.interval) { try { this.callback(); } catch (e) { - alt.logError(`[JS] Exception caught while invoking timer callback`); + alt.logError(`Exception caught while invoking timer callback`); alt.logError(e); Event.invoke(alt.Enums.CustomEventType.ERROR, { error: e, location: this.location, stack: e.stack }, true); } - this.lastTick = Date.now(); + this.lastTick = alt.getNetTime(); const duration = this.lastTick - now; if (duration > Timer.#_warningThreshold) { - alt.logWarning(`[JS] Timer callback in resource '${cppBindings.resourceName}' (${this.location.fileName}:${this.location.lineNumber}) took ${duration}ms to execute (Threshold: ${Timer.#_warningThreshold}ms)`); + alt.logWarning(`Timer callback in resource '${cppBindings.resourceName}' (${this.location.fileName}:${this.location.lineNumber}) took ${duration}ms to execute (Threshold: ${Timer.#_warningThreshold}ms)`); } if (this.once) this.destroy(); @@ -141,9 +154,11 @@ const timeMap = new Map(); function time(name) { const key = typeof name == "string" ? name : ""; - if (timeMap.has(key)) throw new Error(`Benchmark timer ${key} already exists`); + if (timeMap.has(key)) { + throw new Error(`Benchmark timer ${key} already exists`); + } - timeMap.set(key, Date.now()); + timeMap.set(key, alt.getNetTime()); } /** @@ -153,9 +168,11 @@ function time(name) { function timeEnd(name) { const key = typeof name == "string" ? name : ""; - if (!timeMap.has(key)) throw new Error(`Benchmark timer ${key} not found`); + if (!timeMap.has(key)) { + throw new Error(`Benchmark timer ${key} not found`); + } - const diff = Date.now() - timeMap.get(key); + const diff = alt.getNetTime() - timeMap.get(key); timeMap.delete(key); alt.log(`Timer ${key}: ${diff}ms`); @@ -167,6 +184,7 @@ alt.Timers.EveryTick = EveryTick; alt.Timers.NextTick = NextTick; alt.Timers.getByID = Timer.getByID; +alt.Timers.isValid = Timer.isValid; alt.Timers.setInterval = (callback, interval, ...args) => new Interval(callback, interval, ...args); alt.Timers.setTimeout = (callback, timeout, ...args) => new Timeout(callback, timeout, ...args); @@ -201,6 +219,7 @@ globalThis.clearInterval = (interval) => { interval.destroy(); } }; + globalThis.clearTimeout = (timeout) => { if (timeout instanceof Timeout) { timeout.destroy(); diff --git a/shared/js/utils.js b/shared/js/utils.js index b55e0093f..7d5f5a54d 100644 --- a/shared/js/utils.js +++ b/shared/js/utils.js @@ -14,17 +14,17 @@ export function waitFor(cb, timeout = 2000) { assertIsType(cb, "function", "Expected a function as first argument"); assertIsType(timeout, "number", "Expected a number or undefined as second argument"); - const checkUntil = Date.now() + timeout; + const checkUntil = alt.getNetTime() + timeout; const sourceLocation = cppBindings.getCurrentSourceLocation(); const source = `resource: ${cppBindings.resourceName} source: ${sourceLocation.fileName}:${sourceLocation.lineNumber}`; return new Promise((resolve, reject) => { alt.Timers.everyTick(function () { - if (Date.now() > checkUntil) { + if (alt.getNetTime() > checkUntil) { this.destroy(); return reject(new Error(`waitFor timed out (limit was ${timeout}ms, ${source})`)); } - + let result; try { result = cb(); @@ -95,6 +95,10 @@ export function assertVector3(val, message = "Expected Vector3") { return assert(isVector3(val), message); } +export function isAsyncFunction(val) { + return typeof val == "function" && val.constructor.name === "AsyncFunction"; +} + alt.Utils.AssertionError = AssertionError; alt.Utils.assert = assert; alt.Utils.assertIsObject = assertIsObject; @@ -107,6 +111,8 @@ alt.Utils.assertVector3 = assertVector3; alt.Utils.isVector2 = isVector2; alt.Utils.isVector3 = isVector3; +alt.Utils.isAsyncFunction = isAsyncFunction; + export function hash(str) { assertIsType(str, "string", "Expected a string as first argument"); diff --git a/shared/src/Bindings.cpp b/shared/src/Bindings.cpp index f2ce29641..b414633ab 100644 --- a/shared/src/Bindings.cpp +++ b/shared/src/Bindings.cpp @@ -59,15 +59,15 @@ void js::Binding::CleanupForResource(IResource* resource) void js::Binding::Dump() { - Logger::Warn("[JS] Binding:", GetName()); - Logger::Warn("[JS] Valid:", IsValid()); - Logger::Warn("[JS] Scope:", magic_enum::enum_name(GetScope())); - Logger::Warn("[JS] Source size:", strlen(GetSource())); + Logger::Warn("Binding:", GetName()); + Logger::Warn(" Valid:", IsValid()); + Logger::Warn(" Scope:", magic_enum::enum_name(GetScope())); + Logger::Warn(" Source size:", strlen(GetSource())); Logger::Warn(GetSource()); } void js::Binding::DumpAll() { - Logger::Warn("[JS] Bindings count:", __bindings.size()); + Logger::Warn("Bindings count:", __bindings.size()); for(auto& [name, binding] : __bindings) binding.Dump(); } diff --git a/shared/src/Event.cpp b/shared/src/Event.cpp index a7294c17d..28c63567d 100644 --- a/shared/src/Event.cpp +++ b/shared/src/Event.cpp @@ -1,18 +1,21 @@ #include "Event.h" #include "interfaces/IResource.h" -#include "magic_enum/include/magic_enum.hpp" extern js::Class eventContextClass, cancellableEventContextClass; -js::Promise js::Event::CallEventBinding(bool custom, int type, EventArgs& args, IResource* resource) +std::optional js::Event::CallEventBinding(bool custom, int type, EventArgs& args, IResource* resource) { - v8::Isolate* isolate = resource->GetIsolate(); - v8::Local context = resource->GetContext(); js::Function onEvent = resource->GetBindingExport(BindingExport::ON_EVENT); - if(!onEvent.IsValid()) return js::Promise{ v8::Local() }; + if (!onEvent.IsValid()) + return std::nullopt; std::optional> result = onEvent.Call>(custom, type, args.Get()); - return js::Promise{ result.value_or(v8::Local()).As() }; + auto promise = js::Promise{ result.value_or(v8::Local()).As() }; + + if (!promise.Get().IsEmpty() && !promise.Get()->HasHandler()) + return promise; + + return std::nullopt; } void js::Event::SendEvent(const alt::CEvent* ev, IResource* resource) @@ -21,27 +24,25 @@ void js::Event::SendEvent(const alt::CEvent* ev, IResource* resource) if(!eventHandler) return; EventArgs eventArgs; - if(ev->IsCancellable()) eventArgs = cancellableEventContextClass.Create(resource->GetContext(), (void*)ev); + if(ev->IsCancellable()) + eventArgs = cancellableEventContextClass.Create(resource->GetContext(), (void*)ev); else eventArgs = eventContextClass.Create(resource->GetContext(), (void*)ev); eventHandler->argsCb(ev, eventArgs); - js::Promise promise = CallEventBinding(false, (int)ev->GetType(), eventArgs, resource); - eventArgs.Get()->SetAlignedPointerInInternalField(1, nullptr); - - if (!promise.IsValid()) return; - - if (ev->GetType() == alt::CEvent::Type::RESOURCE_STOP && static_cast(ev)->GetResource() == resource->GetResource()) + auto promise = CallEventBinding(false, (int)ev->GetType(), eventArgs, resource); + if (promise.has_value() && ev->GetType() == alt::CEvent::Type::RESOURCE_STOP) { - promise.Await(); + promise->Await(); } + + eventArgs.Get()->SetAlignedPointerInInternalField(1, nullptr); } void js::Event::SendEvent(EventType type, EventArgs& args, IResource* resource) { - js::Promise promise = CallEventBinding(true, (int)type, args, resource); - if(!promise.IsValid()) return; + CallEventBinding(true, (int)type, args, resource); } // Class diff --git a/shared/src/Event.h b/shared/src/Event.h index a55a50d1d..fc88f65f4 100644 --- a/shared/src/Event.h +++ b/shared/src/Event.h @@ -43,6 +43,7 @@ namespace js static std::unordered_map eventHandlerMap; return eventHandlerMap; } + static Event* GetEventHandler(alt::CEvent::Type type) { auto& eventHandlerMap = GetEventHandlerMap(); @@ -50,7 +51,8 @@ namespace js if(it == eventHandlerMap.end()) return nullptr; return it->second; } - static js::Promise CallEventBinding(bool custom, int type, EventArgs& args, IResource* resource); + + static std::optional CallEventBinding(bool custom, int type, EventArgs& args, IResource* resource); public: Event(alt::CEvent::Type _type, EventArgsCallback _argsCb) : type(_type), argsCb(_argsCb) diff --git a/shared/src/classes/SharedVehicle.cpp b/shared/src/classes/SharedVehicle.cpp index 868a95916..8bee89dd8 100644 --- a/shared/src/classes/SharedVehicle.cpp +++ b/shared/src/classes/SharedVehicle.cpp @@ -1,23 +1,20 @@ #include "Class.h" -static void NeonGetter(js::DynamicPropertyGetterContext& ctx) +static void NeonGetter(js::PropertyContext& ctx) { - if(!ctx.CheckParent()) return; - alt::IVehicle* vehicle = ctx.GetParent(); + if (!ctx.CheckThis()) return; + alt::IVehicle* vehicle = ctx.GetThisObject(); bool left, right, front, back; vehicle->GetNeonActive(&left, &right, &front, &back); - bool val = false; - std::string prop = ctx.GetProperty(); - if(prop == "left") val = left; - else if(prop == "right") - val = right; - else if(prop == "front") - val = front; - else if(prop == "back") - val = back; - ctx.Return(val); + js::Object neonState; + neonState.Set("left", left); + neonState.Set("right", right); + neonState.Set("front", front); + neonState.Set("back", back); + + ctx.Return(neonState); } static void GetNeonActive(js::FunctionContext& ctx) @@ -52,7 +49,7 @@ static void NeonEnumerator(js::DynamicPropertyEnumeratorContext& ctx) extern js::Class entityClass; extern js::Class sharedVehicleClass("SharedVehicle", &entityClass, nullptr, [](js::ClassTemplate& tpl) { - tpl.DynamicProperty("neon", &NeonGetter, nullptr, nullptr, &NeonEnumerator); + tpl.Property("neon", &NeonGetter, nullptr); tpl.Method("getNeonActive", &GetNeonActive); tpl.Property<&alt::IVehicle::GetDriver>("driver"); diff --git a/shared/src/helpers/JS.cpp b/shared/src/helpers/JS.cpp index cedb288a2..446c975aa 100644 --- a/shared/src/helpers/JS.cpp +++ b/shared/src/helpers/JS.cpp @@ -120,13 +120,15 @@ void js::TryCatch::PrintError(bool skipLocation) std::string stack = stackTrace.IsEmpty() ? "" : *v8::String::Utf8Value(isolate, stackTrace.ToLocalChecked()); std::string exceptionStr = *v8::String::Utf8Value(isolate, exception); - if(!skipLocation) Logger::Error("[JS] Exception caught in resource '" + resource->GetName() + "' in file '" + file + "' at line " + lineStr); + if(!skipLocation) Logger::Error("Exception caught in resource '" + resource->GetName() + "' in file '" + file + "' at line " + lineStr); if(!exceptionStr.empty() && stack.empty()) { - Logger::Error("[JS]", exceptionStr); - Logger::Error("[JS] ", sourceLine); + Logger::Error(exceptionStr); + Logger::Error(" ", sourceLine); } - if(!stack.empty()) Logger::Error("[JS]", stack); + + if(!stack.empty()) + Logger::Error(stack); js::Event::EventArgs args; args.Set("error", exception); diff --git a/shared/src/helpers/JS.h b/shared/src/helpers/JS.h index da1679afe..1db725c43 100644 --- a/shared/src/helpers/JS.h +++ b/shared/src/helpers/JS.h @@ -640,15 +640,11 @@ namespace js { v8::Promise::PromiseState state = promise->State(); - switch(state) + switch (state) { case v8::Promise::PromiseState::kPending: internal::RunEventLoop(); break; case v8::Promise::PromiseState::kFulfilled: return true; case v8::Promise::PromiseState::kRejected: return false; - - // NOTE (xLuxy): I have no idea why state can be 3 or what it means - it's undocumented - // state is probably state - 1? - case 3: return false; } std::this_thread::sleep_for(std::chrono::milliseconds(1)); diff --git a/shared/src/interfaces/IAltResource.h b/shared/src/interfaces/IAltResource.h index 8c0885a4a..584dcda9a 100644 --- a/shared/src/interfaces/IAltResource.h +++ b/shared/src/interfaces/IAltResource.h @@ -73,9 +73,10 @@ namespace js if(context.IsEmpty()) return; IResource::Scope scope(this); - if (ev->GetType() == alt::CEvent::Type::RESOURCE_STOP) DestroyResourceObject(static_cast(ev)->GetResource()); - Event::SendEvent(ev, this); + + if (ev->GetType() == alt::CEvent::Type::RESOURCE_STOP) + DestroyResourceObject(static_cast(ev)->GetResource()); } void OnTick() override diff --git a/types/client/index.d.ts b/types/client/index.d.ts index 943739166..6c5d40efa 100644 --- a/types/client/index.d.ts +++ b/types/client/index.d.ts @@ -576,6 +576,9 @@ declare module "@altv/client" { static create(): HttpClient; static getByID(id: number): HttpClient | null; + + static setFactory(factory: typeof HttpClient): void; + static getFactory(): T; } export abstract class Object extends Entity { @@ -1855,7 +1858,9 @@ declare module "@altv/client" { // SHARED resource events export function onResourceStart(callback: GenericEventCallback): altShared.Events.EventHandler; + export function onceResourceStart(callback: GenericEventCallback): altShared.Events.EventHandler; export function onResourceStop(callback: GenericEventCallback): altShared.Events.EventHandler; + export function onceResourceStop(callback: GenericEventCallback): altShared.Events.EventHandler; export function onResourceError(callback: GenericEventCallback): altShared.Events.EventHandler; // Custom events diff --git a/types/client/package.json b/types/client/package.json index dd23873d6..fa8aeac31 100644 --- a/types/client/package.json +++ b/types/client/package.json @@ -1,6 +1,6 @@ { "name": "@altv/client", - "version": "0.0.45", + "version": "0.0.47", "description": "This package contains the type definitions for the alt:V JS module v2 client types", "types": "index.d.ts", "files": [ diff --git a/types/server/index.d.ts b/types/server/index.d.ts index e26b17837..b802dd015 100644 --- a/types/server/index.d.ts +++ b/types/server/index.d.ts @@ -613,7 +613,7 @@ declare module "@altv/server" { } export abstract class Vehicle extends Entity { - readonly neon: altShared.VehicleNeonState; + neon: altShared.VehicleNeonState; readonly driver?: Player; readonly isDestroyed: boolean; readonly modKitsCount: number; @@ -1036,6 +1036,9 @@ declare module "@altv/server" { // static readonly all: ReadonlyArray; static getByID(id: number): ConnectionInfo | undefined; + + static setFactory(factory: typeof ConnectionInfo): void; + static getFactory(): T; } export namespace Events { diff --git a/types/server/package.json b/types/server/package.json index c6a6c7df7..6a858d8d0 100644 --- a/types/server/package.json +++ b/types/server/package.json @@ -1,6 +1,6 @@ { "name": "@altv/server", - "version": "0.0.46", + "version": "0.0.48", "description": "This package contains the type definitions for the alt:V JS module v2 server types", "types": "index.d.ts", "files": [ diff --git a/types/shared/index.d.ts b/types/shared/index.d.ts index 34a90defd..305677c95 100644 --- a/types/shared/index.d.ts +++ b/types/shared/index.d.ts @@ -649,6 +649,7 @@ declare module "@altv/shared" { export function timeEnd(name?: string): void; export function getByID(id: number): Timer | null; + export function isValid(idOrHandle?: number | Timer): boolean; } // DO NOT TOUCH THIS - This is only here so client / server can extend Utils namespace using merging @@ -686,6 +687,8 @@ declare module "@altv/shared" { export function isVector3(val: unknown): boolean; export function assertVector3(val: IVector3, message?: string): void; + export function isAsyncFunction(val: unknown): boolean; + interface ClosestEntityOptions { pos?: IVector3; // default: localPlayer.pos - required for server! range?: number; // default: infinity @@ -3465,7 +3468,7 @@ declare abstract class Timer { public interval: number; public callback: Function; public lastTick: number; - public once?: boolean; + public once: boolean; public location: import("@altv/shared").SourceLocation; public get type(): import("@altv/shared").Enums.TimerType; diff --git a/types/shared/package.json b/types/shared/package.json index 5cbb7ea75..83a6fa752 100644 --- a/types/shared/package.json +++ b/types/shared/package.json @@ -1,6 +1,6 @@ { "name": "@altv/shared", - "version": "0.0.21", + "version": "0.0.24", "description": "This package contains the type definitions for the alt:V JS module v2 shared types", "types": "index.d.ts", "files": [