fix(rpc): rework native events + dedupe

This commit is contained in:
2026-09-28 02:05:40 +01:00
parent c085fd2c8f
commit 3e3548a9cc
5 changed files with 344 additions and 596 deletions
+12 -6
View File
@@ -1,7 +1,7 @@
import type * as s from '@entityseven/fivem-rpc-shared-types'
import { Emitter } from '../utils/emitter'
import { RPCError } from '../utils/errors'
import { RPCError, unknownNativeMessage } from '../utils/errors'
import { stringify, stringifyWeb } from '../utils/funcs'
import {
NATIVE_CLIENT_EVENTS,
@@ -12,7 +12,7 @@ import {
RPCErrors,
RPCEvents,
type RPCNativeClientEvents,
type RPCNativeClientNetworksEvents,
type RPCNativeClientNetworkEvents,
type RPCState,
type RPCStateRaw,
type RPCStateWeb,
@@ -233,7 +233,10 @@ export class RPCInstanceClient extends RPCInstanceBase {
CallbackArguments extends Parameters<RPCNativeClientEvents[EventName]>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
if (!NATIVE_CLIENT_EVENTS.includes(eventName)) {
throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE)
throw new RPCError(
RPCErrors.UNKNOWN_NATIVE,
unknownNativeMessage(eventName, 'NATIVE_CLIENT_EVENTS'),
)
}
this.log(`onNativeEvent ${eventName}`)
@@ -244,13 +247,16 @@ export class RPCInstanceClient extends RPCInstanceBase {
}
public onNativeNetworkEvent<
EventName extends keyof RPCNativeClientNetworksEvents,
EventName extends keyof RPCNativeClientNetworkEvents,
CallbackArguments extends Parameters<
RPCNativeClientNetworksEvents[EventName]
RPCNativeClientNetworkEvents[EventName]
>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
if (!NATIVE_CLIENT_NETWORK_EVENTS.includes(eventName)) {
throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE)
throw new RPCError(
RPCErrors.UNKNOWN_NATIVE,
unknownNativeMessage(eventName, 'NATIVE_CLIENT_NETWORK_EVENTS'),
)
}
this.log(`onNativeNetworkEvent ${eventName}`)
+5 -2
View File
@@ -1,7 +1,7 @@
import type * as s from '@entityseven/fivem-rpc-shared-types'
import { Emitter } from '../utils/emitter'
import { RPCError } from '../utils/errors'
import { RPCError, unknownNativeMessage } from '../utils/errors'
import { stringify } from '../utils/funcs'
import { NATIVE_SERVER_EVENTS } from '../utils/native'
import {
@@ -202,7 +202,10 @@ export class RPCInstanceServer extends RPCInstanceBase {
CallbackArguments extends Parameters<RPCNativeServerEvents[EventName]>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
if (!NATIVE_SERVER_EVENTS.includes(eventName)) {
throw new RPCError(RPCErrors.UNKNOWN_NATIVE, RPCErrors.UNKNOWN_NATIVE)
throw new RPCError(
RPCErrors.UNKNOWN_NATIVE,
unknownNativeMessage(eventName, 'NATIVE_SERVER_EVENTS'),
)
}
this.log(`onNativeEvent ${eventName}`)
+5
View File
@@ -66,6 +66,11 @@ export function notRegisteredMessage(
return `No listener for "${event}" on ${receiver}. Register it with rpc.${listenerName(receiver, sender)}("${event}", ...) in ${receiver} code.`
}
/** **Internal** */
export function unknownNativeMessage(event: string, list: string): string {
return `"${event}" is not in ${list}. Events not listed there can be registered with FiveM's on("${event}", ...) directly.`
}
/** **Internal** */
export function handlerErrorMessage(
event: string,
+49 -48
View File
@@ -1,56 +1,57 @@
import type {
RPCNativeClientEvents,
RPCNativeClientNetworkEventsNames,
RPCNativeServerEvents,
} from './types'
import type { RPCNativeClientEvents, RPCNativeServerEvents } from './types'
/**
* https://docs.fivem.net/docs/scripting-reference/events/server-events/
* @readonly
*/
export const NATIVE_SERVER_EVENTS: readonly (keyof RPCNativeServerEvents)[] = [
'entityCreated',
'entityCreating',
'entityRemoved',
'onResourceListRefresh',
'onResourceStart',
'onResourceStarting',
'onResourceStop',
'onServerResourceStart',
'onServerResourceStop',
'playerConnecting',
'playerEnteredScope',
'playerJoining',
'playerLeftScope',
'ptFxEvent',
'removeAllWeaponsEvent',
'startProjectileEvent',
'weaponDamageEvent',
] as const
// Server and client lists are built from an object that must name exactly the
// keys of the typed event map, so the list and the types cannot drift apart.
/**
* https://docs.fivem.net/docs/scripting-reference/events/client-events/
* @readonly
*/
export const NATIVE_CLIENT_EVENTS: readonly (keyof RPCNativeClientEvents)[] = [
'entityDamaged',
'gameEventTriggered',
'mumbleConnected',
'mumbleDisconnected',
'onClientResourceStart',
'onClientResourceStop',
'onResourceStart',
'onResourceStarting',
'onResourceStop',
'populationPedCreating',
] as const
const serverEvents = {
entityCreated: true,
entityCreating: true,
entityRemoved: true,
onResourceListRefresh: true,
onResourceStart: true,
onResourceStarting: true,
onResourceStop: true,
onServerResourceStart: true,
onServerResourceStop: true,
playerConnecting: true,
playerEnteredScope: true,
playerJoining: true,
playerLeftScope: true,
ptFxEvent: true,
removeAllWeaponsEvent: true,
startProjectileEvent: true,
weaponDamageEvent: true,
} satisfies Record<keyof RPCNativeServerEvents, true>
/** https://docs.fivem.net/docs/scripting-reference/events/server-events/ */
export const NATIVE_SERVER_EVENTS = Object.keys(
serverEvents,
) as readonly (keyof RPCNativeServerEvents)[]
const clientEvents = {
entityDamaged: true,
gameEventTriggered: true,
mumbleConnected: true,
mumbleDisconnected: true,
onClientResourceStart: true,
onClientResourceStop: true,
onResourceStart: true,
onResourceStarting: true,
onResourceStop: true,
populationPedCreating: true,
} satisfies Record<keyof RPCNativeClientEvents, true>
/** https://docs.fivem.net/docs/scripting-reference/events/client-events/ */
export const NATIVE_CLIENT_EVENTS = Object.keys(
clientEvents,
) as readonly (keyof RPCNativeClientEvents)[]
/**
* https://docs.fivem.net/docs/game-references/game-events/
* @readonly
*
* Source of truth for `RPCNativeClientNetworkEventsNames`.
*/
export const NATIVE_CLIENT_NETWORK_EVENTS: readonly RPCNativeClientNetworkEventsNames[] =
[
export const NATIVE_CLIENT_NETWORK_EVENTS = [
'CEventAcquaintancePed',
'CEventAcquaintancePedDead',
'CEventAcquaintancePedDislike',
@@ -318,4 +319,4 @@ export const NATIVE_CLIENT_NETWORK_EVENTS: readonly RPCNativeClientNetworkEvents
'CEventVehicleDamageWeapon',
'CEventVehicleOnFire',
'CEventWrithe',
] as const
] as const
+6 -273
View File
@@ -1,6 +1,7 @@
import type { RPCInstanceClient } from '../core/client'
import type { RPCInstanceServer } from '../core/server'
import type { RPCInstanceWebview } from '../core/webview'
import type { NATIVE_CLIENT_NETWORK_EVENTS } from './native'
/**
* Possible environment states for `RPCConfig`
@@ -101,7 +102,7 @@ export enum RPCEvents {
*/
export enum RPCErrors {
EVENT_NOT_REGISTERED = 'Event not registered',
UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)',
UNKNOWN_NATIVE = 'Unknown native event',
UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")',
TIMEOUT = 'Timed out waiting for response',
HANDLER_ERROR = 'Listener threw an error',
@@ -252,7 +253,7 @@ export type RPCNativeClientEvents = {
baseDamage: number,
): void
gameEventTriggered(
name: RPCNativeClientNetworksEvents | string,
name: RPCNativeClientNetworkEventsNames | (string & {}),
data: number[],
): void
mumbleConnected(address: string, reconnecting: boolean): void
@@ -274,7 +275,7 @@ export type RPCNativeClientEvents = {
): void
}
export type RPCNativeClientNetworksEvents = {
export type RPCNativeClientNetworkEvents = {
[name in RPCNativeClientNetworkEventsNames]: (
entities: number[],
eventEntity: number,
@@ -282,274 +283,6 @@ export type RPCNativeClientNetworksEvents = {
) => void
}
/**
* https://docs.fivem.net/docs/game-references/game-events/
*/
/** https://docs.fivem.net/docs/game-references/game-events/ */
export type RPCNativeClientNetworkEventsNames =
| 'CEventAcquaintancePed'
| 'CEventAcquaintancePedDead'
| 'CEventAcquaintancePedDislike'
| 'CEventAcquaintancePedHate'
| 'CEventAcquaintancePedLike'
| 'CEventAcquaintancePedWanted'
| 'CEventAgitated'
| 'CEventAgitatedAction'
| 'CEventCallForCover'
| 'CEventCarUndriveable'
| 'CEventClimbLadderOnRoute'
| 'CEventClimbNavMeshOnRoute'
| 'CEventCombatTaunt'
| 'CEventCommunicateEvent'
| 'CEventCopCarBeingStolen'
| 'CEventCrimeCryForHelp'
| 'CEventCrimeReported'
| 'CEventDamage'
| 'CEventDataDecisionMaker'
| 'CEventDataFileMounter'
| 'CEventDataResponseAggressiveRubberneck'
| 'CEventDataResponseDeferToScenarioPointFlags'
| 'CEventDataResponseFriendlyAimedAt'
| 'CEventDataResponseFriendlyNearMiss'
| 'CEventDataResponsePlayerDeath'
| 'CEventDataResponsePoliceTaskWanted'
| 'CEventDataResponseSwatTaskWanted'
| 'CEventDataResponseTask'
| 'CEventDataResponseTaskAgitated'
| 'CEventDataResponseTaskCombat'
| 'CEventDataResponseTaskCower'
| 'CEventDataResponseTaskCrouch'
| 'CEventDataResponseTaskDuckAndCover'
| 'CEventDataResponseTaskEscapeBlast'
| 'CEventDataResponseTaskEvasiveStep'
| 'CEventDataResponseTaskExhaustedFlee'
| 'CEventDataResponseTaskExplosion'
| 'CEventDataResponseTaskFlee'
| 'CEventDataResponseTaskFlyAway'
| 'CEventDataResponseTaskGrowlAndFlee'
| 'CEventDataResponseTaskGunAimedAt'
| 'CEventDataResponseTaskHandsUp'
| 'CEventDataResponseTaskHeadTrack'
| 'CEventDataResponseTaskLeaveCarAndFlee'
| 'CEventDataResponseTaskScenarioFlee'
| 'CEventDataResponseTaskSharkAttack'
| 'CEventDataResponseTaskShockingEventBackAway'
| 'CEventDataResponseTaskShockingEventGoto'
| 'CEventDataResponseTaskShockingEventHurryAway'
| 'CEventDataResponseTaskShockingEventReact'
| 'CEventDataResponseTaskShockingEventReactToAircraft'
| 'CEventDataResponseTaskShockingEventStopAndStare'
| 'CEventDataResponseTaskShockingEventThreatResponse'
| 'CEventDataResponseTaskShockingEventWatch'
| 'CEventDataResponseTaskShockingNiceCar'
| 'CEventDataResponseTaskShockingPoliceInvestigate'
| 'CEventDataResponseTaskThreat'
| 'CEventDataResponseTaskTurnToFace'
| 'CEventDataResponseTaskWalkAway'
| 'CEventDataResponseTaskWalkRoundEntity'
| 'CEventDataResponseTaskWalkRoundFire'
| 'CEventDeadPedFound'
| 'CEventDeath'
| 'CEventDecisionMakerResponse'
| 'CEventDisturbance'
| 'CEventDraggedOutCar'
| 'CEventEditableResponse'
| 'CEventEncroachingPed'
| 'CEventEntityDamaged'
| 'CEventEntityDestroyed'
| 'CEventExplosion'
| 'CEventExplosionHeard'
| 'CEventFireNearby'
| 'CEventFootStepHeard'
| 'CEventFriendlyAimedAt'
| 'CEventFriendlyFireNearMiss'
| 'CEventGetOutOfWater'
| 'CEventGivePedTask'
| 'CEventGroupScriptAI'
| 'CEventGroupScriptNetwork'
| 'CEventGunAimedAt'
| 'CEventGunShot'
| 'CEventGunShotBulletImpact'
| 'CEventGunShotWhizzedBy'
| 'CEventHelpAmbientFriend'
| 'CEventHurtTransition'
| 'CEventInAir'
| 'CEventInfo'
| 'CEventInfoBase'
| 'CEventInjuredCryForHelp'
| 'CEventLeaderEnteredCarAsDriver'
| 'CEventLeaderExitedCarAsDriver'
| 'CEventLeaderHolsteredWeapon'
| 'CEventLeaderLeftCover'
| 'CEventLeaderUnholsteredWeapon'
| 'CEventMeleeAction'
| 'CEventMustLeaveBoat'
| 'CEventNetworkAdminInvited'
| 'CEventNetworkAttemptHostMigration'
| 'CEventNetworkBail'
| 'CEventNetworkCashTransactionLog'
| 'CEventNetworkCheatTriggered'
| 'CEventNetworkClanInviteReceived'
| 'CEventNetworkClanJoined'
| 'CEventNetworkClanKicked'
| 'CEventNetworkClanLeft'
| 'CEventNetworkClanRankChanged'
| 'CEventNetworkCloudEvent'
| 'CEventNetworkCloudFileResponse'
| 'CEventNetworkEmailReceivedEvent'
| 'CEventNetworkEndMatch'
| 'CEventNetworkEndSession'
| 'CEventNetworkEntityDamage'
| 'CEventNetworkFindSession'
| 'CEventNetworkFollowInviteReceived'
| 'CEventNetworkHostMigration'
| 'CEventNetworkHostSession'
| 'CEventNetworkIncrementStat'
| 'CEventNetworkInviteAccepted'
| 'CEventNetworkInviteConfirmed'
| 'CEventNetworkInviteRejected'
| 'CEventNetworkJoinSession'
| 'CEventNetworkJoinSessionResponse'
| 'CEventNetworkOnlinePermissionsUpdated'
| 'CEventNetworkPedLeftBehind'
| 'CEventNetworkPickupRespawned'
| 'CEventNetworkPlayerArrest'
| 'CEventNetworkPlayerCollectedAmbientPickup'
| 'CEventNetworkPlayerCollectedPickup'
| 'CEventNetworkPlayerCollectedPortablePickup'
| 'CEventNetworkPlayerDroppedPortablePickup'
| 'CEventNetworkPlayerEnteredVehicle'
| 'CEventNetworkPlayerJoinScript'
| 'CEventNetworkPlayerLeftScript'
| 'CEventNetworkPlayerScript'
| 'CEventNetworkPlayerSession'
| 'CEventNetworkPlayerSpawn'
| 'CEventNetworkPresenceInvite'
| 'CEventNetworkPresenceInviteRemoved'
| 'CEventNetworkPresenceInviteReply'
| 'CEventNetworkPresenceTriggerEvent'
| 'CEventNetworkPresence_StatUpdate'
| 'CEventNetworkPrimaryClanChanged'
| 'CEventNetworkRequestDelay'
| 'CEventNetworkRosChanged'
| 'CEventNetworkScAdminPlayerUpdated'
| 'CEventNetworkScAdminReceivedCash'
| 'CEventNetworkScriptEvent'
| 'CEventNetworkSessionEvent'
| 'CEventNetworkShopTransaction'
| 'CEventNetworkSignInStateChanged'
| 'CEventNetworkSocialClubAccountLinked'
| 'CEventNetworkSpectateLocal'
| 'CEventNetworkStartMatch'
| 'CEventNetworkStartSession'
| 'CEventNetworkStorePlayerLeft'
| 'CEventNetworkSummon'
| 'CEventNetworkSystemServiceEvent'
| 'CEventNetworkTextMessageReceived'
| 'CEventNetworkTimedExplosion'
| 'CEventNetworkTransitionEvent'
| 'CEventNetworkTransitionGamerInstruction'
| 'CEventNetworkTransitionMemberJoined'
| 'CEventNetworkTransitionMemberLeft'
| 'CEventNetworkTransitionParameterChanged'
| 'CEventNetworkTransitionStarted'
| 'CEventNetworkTransitionStringChanged'
| 'CEventNetworkVehicleUndrivable'
| 'CEventNetworkVoiceConnectionRequested'
| 'CEventNetworkVoiceConnectionResponse'
| 'CEventNetworkVoiceConnectionTerminated'
| 'CEventNetworkVoiceSessionEnded'
| 'CEventNetworkVoiceSessionStarted'
| 'CEventNetworkWithData'
| 'CEventNetwork_InboxMsgReceived'
| 'CEventNewTask'
| 'CEventObjectCollision'
| 'CEventOnFire'
| 'CEventOpenDoor'
| 'CEventPedCollisionWithPed'
| 'CEventPedCollisionWithPlayer'
| 'CEventPedEnteredMyVehicle'
| 'CEventPedJackingMyVehicle'
| 'CEventPedOnCarRoof'
| 'CEventPedSeenDeadPed'
| 'CEventPlayerCollisionWithPed'
| 'CEventPlayerDeath'
| 'CEventPlayerUnableToEnterVehicle'
| 'CEventPotentialBeWalkedInto'
| 'CEventPotentialBlast'
| 'CEventPotentialGetRunOver'
| 'CEventPotentialWalkIntoVehicle'
| 'CEventProvidingCover'
| 'CEventRanOverPed'
| 'CEventReactionEnemyPed'
| 'CEventReactionInvestigateDeadPed'
| 'CEventReactionInvestigateThreat'
| 'CEventRequestHelp'
| 'CEventRequestHelpWithConfrontation'
| 'CEventRespondedToThreat'
| 'CEventScanner'
| 'CEventScenarioForceAction'
| 'CEventScriptCommand'
| 'CEventScriptWithData'
| 'CEventShocking'
| 'CEventShockingBicycleCrash'
| 'CEventShockingBicycleOnPavement'
| 'CEventShockingCarAlarm'
| 'CEventShockingCarChase'
| 'CEventShockingCarCrash'
| 'CEventShockingCarOnCar'
| 'CEventShockingCarPileUp'
| 'CEventShockingDangerousAnimal'
| 'CEventShockingDeadBody'
| 'CEventShockingDrivingOnPavement'
| 'CEventShockingEngineRevved'
| 'CEventShockingExplosion'
| 'CEventShockingFire'
| 'CEventShockingGunFight'
| 'CEventShockingGunshotFired'
| 'CEventShockingHelicopterOverhead'
| 'CEventShockingHornSounded'
| 'CEventShockingInDangerousVehicle'
| 'CEventShockingInjuredPed'
| 'CEventShockingMadDriver'
| 'CEventShockingMadDriverBicycle'
| 'CEventShockingMadDriverExtreme'
| 'CEventShockingMugging'
| 'CEventShockingNonViolentWeaponAimedAt'
| 'CEventShockingParachuterOverhead'
| 'CEventShockingPedKnockedIntoByPlayer'
| 'CEventShockingPedRunOver'
| 'CEventShockingPedShot'
| 'CEventShockingPlaneFlyby'
| 'CEventShockingPotentialBlast'
| 'CEventShockingPropertyDamage'
| 'CEventShockingRunningPed'
| 'CEventShockingRunningStampede'
| 'CEventShockingSeenCarStolen'
| 'CEventShockingSeenConfrontation'
| 'CEventShockingSeenGangFight'
| 'CEventShockingSeenInsult'
| 'CEventShockingSeenMeleeAction'
| 'CEventShockingSeenNiceCar'
| 'CEventShockingSeenPedKilled'
| 'CEventShockingSiren'
| 'CEventShockingStudioBomb'
| 'CEventShockingVehicleTowed'
| 'CEventShockingVisibleWeapon'
| 'CEventShockingWeaponThreat'
| 'CEventShockingWeirdPed'
| 'CEventShockingWeirdPedApproaching'
| 'CEventShoutBlockingLos'
| 'CEventShoutTargetPosition'
| 'CEventShovePed'
| 'CEventSoundBase'
| 'CEventStatChangedValue'
| 'CEventStaticCountReachedMax'
| 'CEventStuckInAir'
| 'CEventSuspiciousActivity'
| 'CEventSwitch2NM'
| 'CEventUnidentifiedPed'
| 'CEventVehicleCollision'
| 'CEventVehicleDamage'
| 'CEventVehicleDamageWeapon'
| 'CEventVehicleOnFire'
| 'CEventWrithe'
(typeof NATIVE_CLIENT_NETWORK_EVENTS)[number]