Compare commits

...

67 Commits

Author SHA1 Message Date
495217ecd5 upd cli
- handled process exit
- updated docs + readme
- typos fix
2024-10-30 18:31:53 +00:00
7e43e0d106 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	cli/package.json
#	rpc/package.json
2024-10-30 17:01:42 +00:00
2f1d1d9f0d hotfix
- licenses
- package.json consistency
- links bump
2024-10-30 17:00:36 +00:00
cc40d90070 hotfix
- licenses
- package.json consistency
- links bump
2024-10-30 16:59:33 +00:00
469d970654 Merge branch 'dev' 2024-10-30 16:29:29 +00:00
217e0ddf01 Merge pull request 'Server/Client middlewares + JSDoc' (#6) from unstable into dev
Reviewed-on: #6
2024-10-30 16:21:29 +00:00
ea572070ef versions rollback 2024-10-30 16:21:03 +00:00
7308fc26e0 v0.2.1-0.2.0-alpha.2.0 2024-10-30 16:18:08 +00:00
ffd542c1de v0.2.1-0.2.0-alpha.1.0 2024-10-30 16:17:28 +00:00
914300924a v0.2.0 2024-10-30 16:16:45 +00:00
bc295a777b jsdoc 2024-10-30 15:38:06 +00:00
cd27764a30 jsdoc 2024-10-30 15:27:22 +00:00
30d125ce8e version bump 2024-10-29 17:06:14 +00:00
d56475d08a Merge pull request 'Consistency updates + cleanup' (#5) from unstable into dev
Reviewed-on: #5
2024-10-29 17:03:31 +00:00
622676d189 feat core
- server/client middlewares
2024-10-29 17:03:07 +00:00
d8df9fa7ef fix core
- renaming done
2024-10-28 17:40:25 +00:00
d9b917c024 core version bump 2024-10-28 17:37:17 +00:00
cbbf4f38e6 upd core
- typed server inner rpc
- client minor fixes
- sharing browser with rpc on client
2024-10-28 17:30:28 +00:00
3bbf1e2209 upd core
- typed client inner rpc
- better type organization
2024-10-28 16:53:42 +00:00
9fe9bad840 upd core
- renamed cef -> browser
- added browser logger + custom logger option
2024-10-28 16:35:16 +00:00
588bb42d04 upd core
- types now use namespace to avoid bloating
- moved items for further usage into data folder
2024-10-28 15:20:43 +00:00
3a4fe19df0 upd readme bump 2024-10-28 15:14:41 +00:00
c8e5bdf375 Merge branch 'dev' into unstable 2024-10-28 15:10:25 +00:00
a675114b41 upd core
- changed local shared types package name
2024-10-28 15:10:00 +00:00
54781d50b2 upd core
- changed local rpc to remote dependency
2024-10-28 15:04:39 +00:00
de33fe4763 versions bump 2024-10-28 13:20:51 +00:00
d3d75b338d v0.2.5 2024-10-28 13:02:24 +00:00
04eb724073 v0.2.4 2024-10-28 12:59:44 +00:00
e4950b6c99 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	lerna.json
#	rpc/package.json
2024-10-28 12:57:58 +00:00
Oleksandr Honcharov
8f856d929f v0.2.3 2024-10-28 14:54:48 +02:00
Oleksandr Honcharov
ee8210f140 v0.2.2 2024-10-28 14:54:19 +02:00
Oleksandr Honcharov
74c0215366 v0.2.2 2024-10-28 14:53:05 +02:00
19b1b770f6 v0.2.1 2024-10-28 12:51:53 +00:00
Oleksandr Honcharov
96c92f9d19 v0.2.1 2024-10-28 14:51:45 +02:00
2b287f06a2 v0.2.0 2024-10-28 12:46:24 +00:00
2f2b1da99b upd
changed packages to organization
2024-10-28 12:43:15 +00:00
c2684a48fc Merge pull request 'Rpc integration + type fixes' (#3) from dev into master
Reviewed-on: #3
2024-10-28 12:13:19 +00:00
59910303f7 upd core
- moved to rage-fw-rpc
2024-10-28 12:11:24 +00:00
828a3deb33 upd cli
- added rpc tests
- removed git clone
2024-10-28 12:10:58 +00:00
39e6455271 upd rpc
- jsdoc typo fixes
- readme
2024-10-27 16:36:50 +00:00
44d985f498 license 2024-10-27 15:51:22 +00:00
2bcced5a56 upd rpc
- added more comments
2024-10-27 15:39:13 +00:00
830a69e91c upd rpc
- added jsdoc on public methods
2024-10-27 15:28:53 +00:00
e2e58e100a feat rpc
- architecture tweaks for scalability
- added option for global debug logs
- added option for cef debug mode to run in browser only
2024-10-27 14:53:55 +00:00
640adae8a5 feat
- rpc
2024-10-27 11:47:55 +00:00
Oleksandr Honcharov
7369ff8868 lol 2024-10-02 21:44:51 +03:00
cd142e4414 feat
- cef debug mode
2024-10-02 00:48:38 +01:00
ee4baca74a upd
- rpc d.ts configured correctly
- package.json credentials updated
2024-10-01 18:19:46 +01:00
fafcdb50a8 upd
- moved browser to player instance
- logger accepts multiple arguments
- changed error messages
2024-09-30 20:39:20 +01:00
cf9c4ce691 Merge remote-tracking branch 'origin/dev' into dev 2024-09-15 21:55:22 +01:00
de6dbae3a8 upd | new rpc tests 2024-09-15 21:55:08 +01:00
Oleksandr Honcharov
5a92272eaf lol 2024-09-15 21:32:59 +03:00
Oleksandr Honcharov
676ebbe827 lol 2024-09-15 21:32:39 +03:00
Oleksandr Honcharov
b1adb809f8 Merge remote-tracking branch 'origin/dev' into dev 2024-09-15 21:32:32 +03:00
Oleksandr Honcharov
5e8761dd05 lol 2024-09-15 21:32:27 +03:00
bfc380227f Merge remote-tracking branch 'origin/dev' into dev 2024-09-11 23:53:46 +01:00
70df94afa2 fix: microchanges 2024-09-11 23:53:23 +01:00
SashaGoncharov19
987bba717e Merge remote-tracking branch 'origin/dev' into dev 2024-08-15 22:47:43 +03:00
SashaGoncharov19
f285f9c103 framework rpc init 2024-08-15 22:47:22 +03:00
1467bb65b0 fix: missing types 2024-07-21 16:27:37 +01:00
Oleksandr Honcharov
f08291eace deleted types 2024-07-03 14:43:24 +03:00
75b528ca45 renamed types to follow similar naming pattern 2024-06-18 21:03:33 +01:00
22ed5c4588 refactored server file structure + init middlewares 2024-06-18 18:42:31 +01:00
0059a41d0d Update readme.md 2024-06-17 03:47:57 +00:00
cc1ebbd66b quickfix issue template had extra optional label 2024-06-15 14:54:07 +01:00
301a65e77f v0.1.0
Reviewed-on: #2
2024-06-15 13:50:13 +00:00
57d80d41d6 Merge pull request 'v0.1.0' (#1) from dev into unstable
Reviewed-on: #1
Reviewed-by: Oleksandr Honcharov <s0976053529@gmail.com>
2024-06-15 13:48:40 +00:00
90 changed files with 3049 additions and 1726 deletions

View File

@ -28,7 +28,7 @@ body:
- type: textarea - type: textarea
id: reproduction id: reproduction
attributes: attributes:
label: Reproduction (Optional) label: Reproduction
description: Steps to reproduce the bug, if applicable description: Steps to reproduce the bug, if applicable
validations: validations:
required: true required: true
@ -57,7 +57,7 @@ body:
- type: textarea - type: textarea
id: additional_information id: additional_information
attributes: attributes:
label: Additional Information label: Additional Information (Optional)
description: Add any other information that might be useful in diagnosing the issue description: Add any other information that might be useful in diagnosing the issue
validations: validations:
required: false required: false

View File

15
browser/LICENSE Normal file
View File

@ -0,0 +1,15 @@
Custom Attribution-NoDerivs Software License
Copyright (c) 2024 Entity Seven Group
This license allows you to use, copy, and distribute the RageFW (the "Software"), including for commercial purposes, provided that the following conditions are met:
1. **Attribution:** You must give appropriate credit to the original author of the Software, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
2. **No Derivative Works:** You may not modify, transform, or build upon the Software.
3. **Usage and Commercial Use:** You are allowed to use, sell, and gain income from projects that utilize the Software, as long as you comply with the terms of this license.
4. **No Additional Restrictions:** You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

45
browser/package.json Normal file
View File

@ -0,0 +1,45 @@
{
"name": "@entityseven/rage-fw-browser",
"version": "0.2.0",
"main": "dist/index.js",
"types": "dist/src/index.d.ts",
"files": [
"dist/**/*",
"readme.md",
"LICENSE"
],
"scripts": {
"build": "tsup"
},
"dependencies": {
"@entityseven/rage-fw-rpc": "0.2.5"
},
"peerDependencies": {
"@entityseven/rage-fw-shared-types": "0.2.0",
"@ragempcommunity/types-cef": "^2.1.8"
},
"description": "Package used on a browser-side of your Rage:MP Server",
"keywords": [
"ragefw",
"rage-fw",
"ragemp",
"rage:mp",
"rage",
"gta5"
],
"author": "Entity Seven Group",
"contributors": [
{
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
},
{
"name": "Oleksandr Honcharov",
"email": "0976053529@ukr.net",
"url": "https://github.com/SashaGoncharov19/"
}
],
"license": "Custom-Attribution-NoDerivs",
"gitHead": "ffd542c1deddb3033e16e0dae7557313ae09b05f"
}

View File

@ -1,2 +1,2 @@
# RageFW CEF # RageFW Browser
[Read docs for details](https://git.entityseven.com/entityseven/rage-framework/wiki/Docs) [Read docs for details](https://git.entityseven.com/entityseven/rage-framework/wiki)

187
browser/src/core/browser.ts Normal file
View File

@ -0,0 +1,187 @@
import { Helper } from './helper'
import { rpc } from './rpc'
import type * as T from '../types'
import {
RageFW_BrowserEvent,
RageFW_ClientEvent,
RageFW_ServerEvent,
} from '../types'
/** Browser-side interactions */
export class Browser extends Helper {
constructor() {
super()
}
/**
* Setter. Enables console debug logs for events
*/
set debugLogs(debug: boolean) {
this.debugLogs_ = debug
}
/**
* Setter. Provides an ability to specify custom logger function to get special formatting. Using this enables ``debugLogs``
*/
set customLogger(
fn: (method: string, eventName: string, ...args: unknown[]) => unknown,
) {
this.customLogger_ = fn
}
/**
* Registers a browser event with an associated callback
*
* @param eventName - The name of the event to register
* @param callback - The callback function to be executed when the event is triggered
* @returns {Browser} The current browser instance, enabling method chaining
*
* @example
* // Registering an event
* fw.event.register("showNotification", (message, color) => {
* // do something
* })
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public register<EventName extends T.RageFW_BrowserEvent>(
eventName: EventName,
callback: T.RageFW_BrowserCallback<EventName>,
): Browser {
this.log_('register', eventName, callback)
rpc.register<
Parameters<typeof callback>,
ReturnType<typeof callback>,
EventName
>(eventName, async (...data) => await callback(...data))
return this
}
/**
* Unregisters a browser event, removing the associated callback
*
* @param eventName - The name of the event to unregister
* @returns {Browser} The current browser instance, enabling method chaining
*
* @example
* // Unregistering an event
* fw.event.unregister("showNotification")
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public unregister<EventName extends T.RageFW_BrowserEvent>(
eventName: EventName,
): Browser {
rpc.unregister<EventName>(eventName)
return this
}
/**
* Triggers a browser event from the browser with arguments from shared types
*
* Formerly known as ``call`` or ``emit``
*
* @param eventName - The name of the browser event to trigger
* @param [args] - Arguments for the browser event, if present
* @returns {Promise} resolving to the browser's response for the event
*
* @example
* // Triggering a browser event without arguments
* fw.event.trigger("browserEventName")
*
* @example
* // Triggering a browser event with arguments
* fw.event.trigger("browserEventName", ["message to me"])
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public async trigger<EventName extends T.RageFW_BrowserEvent>(
eventName: EventName,
...args: T._BrowserEventHasArgs<EventName> extends true
? [T.RageFW_BrowserArgs<EventName>]
: []
): Promise<T.RageFW_BrowserReturn<EventName>> {
this.log_('[RPC](trigger):', eventName, ...args)
return await rpc.call<
typeof args,
EventName,
T.RageFW_BrowserReturn<EventName>
>(eventName, args)
}
/**
* Triggers a server event from the browser with arguments from shared types
*
* Formerly known as ``callServer`` or ``emitServer``
*
* @param eventName - The name of the server event to trigger
* @param [args] - Arguments for the server event, if present
* @returns {Promise} resolving to the server's response for the event
*
* @example
* // Triggering a server event without arguments
* fw.event.triggerServer("serverEventName")
*
* @example
* // Triggering a server event with arguments
* fw.event.triggerServer("serverEventName", ["message to server"])
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public async triggerServer<EventName extends T.RageFW_ServerEvent>(
eventName: EventName,
...args: T._ServerEventHasArgs<EventName> extends true
? [T.RageFW_ServerArgs<EventName>]
: []
): Promise<T.RageFW_ServerReturn<EventName>> {
this.log_('[RPC](triggerServer):', eventName, ...args)
return await rpc.callServer<
typeof args,
EventName,
T.RageFW_ServerReturn<EventName>
>(eventName, args)
}
/**
* Triggers a client event from the browser with arguments from shared types
*
* Formerly known as ``callClient`` or ``emitClient``
*
* @param eventName - The name of the client event to trigger
* @param [args] - Arguments for the client event, if present
* @returns {Promise} resolving to the client's response for the event
*
* @example
* // Triggering a client event without arguments
* fw.event.triggerClient("clientEventName")
*
* @example
* // Triggering a client event with arguments
* fw.event.triggerClient("clientEventName", ["message to client"])
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public async triggerClient<EventName extends T.RageFW_ClientEvent>(
eventName: EventName,
...args: T._ClientEventHasArgs<EventName> extends true
? [T.RageFW_ClientArgs<EventName>]
: []
): Promise<T.RageFW_ClientReturn<EventName>> {
this.log_('[RPC](triggerClient):', eventName, ...args)
return await rpc.callClient<
typeof args,
EventName,
T.RageFW_ClientReturn<EventName>
>(eventName, args)
}
}
// new Browser()
// .register('customCefEvent', async (a, b) => true)
// .triggerServer('customServerEvent', ['', 1])

View File

@ -0,0 +1,21 @@
export class Helper {
protected debugLogs_: boolean = false
protected customLogger_:
| undefined
| ((method: string, eventName: string, ...args: unknown[]) => unknown) =
undefined
constructor() {}
protected log_(
method: string,
eventName: string,
...args: unknown[]
): void {
if (this.customLogger_) {
this.customLogger_(method, eventName, ...args)
} else if (this.debugLogs_) {
console.log('[RPC](' + method + ')', eventName + ':', ...args)
}
}
}

View File

@ -0,0 +1,3 @@
export * from './browser'
export * from './helper'
export * from './rpc'

6
browser/src/core/rpc.ts Normal file
View File

@ -0,0 +1,6 @@
import { Rpc } from '@entityseven/rage-fw-rpc'
export const rpc = new Rpc({
forceBrowserDevMode: process.env.RageFW_forceBrowserDevMode === 'true',
debugLogs: false,
})

16
browser/src/index.ts Normal file
View File

@ -0,0 +1,16 @@
import { Browser, rpc } from './core'
/**
* Package used on a browser-side of your Rage:MP Server
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
export const fw = {
/** Browser-side interactions */
event: new Browser(),
/** ``rage-fw-rpc`` instance used under the hood. It is highly recommended to use this one if you need it instead of creating a new instance */
rpc,
}
;(async () => {
await fw.event.triggerClient('cefReady')
})()

View File

@ -0,0 +1,40 @@
import type { RageFW_ICustomBrowserEvent } from '@entityseven/rage-fw-shared-types'
export type { RageFW_ICustomBrowserEvent } from '@entityseven/rage-fw-shared-types'
/**
* Union of all available browser event names
* These only include custom events
*/
export type RageFW_BrowserEvent = keyof RageFW_ICustomBrowserEvent
/**
* Array of arguments of an event you pass as a generic
* These only include custom browser events
*/
export type RageFW_BrowserArgs<K extends RageFW_BrowserEvent> = Parameters<
RageFW_ICustomBrowserEvent[K]
>
/**
* Return type of event you pass as a generic
* These only include custom browser events
*/
export type RageFW_BrowserReturn<K extends RageFW_BrowserEvent> = ReturnType<
RageFW_ICustomBrowserEvent[K]
>
/**
* Callback (function) of event you pass as a generic
* These only include custom browser events
*/
export type RageFW_BrowserCallback<K extends keyof RageFW_ICustomBrowserEvent> =
(...args: RageFW_BrowserArgs<K>) => Promise<RageFW_BrowserReturn<K>>
export type _BrowserEventHasArgs<
EventName extends keyof RageFW_ICustomBrowserEvent,
> = keyof RageFW_ICustomBrowserEvent extends never
? false
: Parameters<RageFW_ICustomBrowserEvent[EventName]>[0] extends undefined
? false
: true

View File

@ -1,9 +1,10 @@
import type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types' import type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
export type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types'
export type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
/** /**
* Union of all available client event names * Union of all available client event names
* These only include custom events * These only include custom events and some extras from RageFW
*/ */
export type RageFW_ClientEvent = keyof RageFW_ICustomClientEvent export type RageFW_ClientEvent = keyof RageFW_ICustomClientEvent
@ -11,7 +12,7 @@ export type RageFW_ClientEvent = keyof RageFW_ICustomClientEvent
* Array of arguments of event you pass as a generic * Array of arguments of event you pass as a generic
* These only include custom client events * These only include custom client events
*/ */
export type RageFW_ClientArguments<K extends RageFW_ClientEvent> = Parameters< export type RageFW_ClientArgs<K extends RageFW_ClientEvent> = Parameters<
RageFW_ICustomClientEvent[K] RageFW_ICustomClientEvent[K]
> >

View File

@ -1,3 +1,3 @@
export * from './cef' export * from './browser'
export * from './client' export * from './client'
export * from './server' export * from './server'

View File

@ -1,5 +1,6 @@
import type { RageFW_ICustomServerEvent } from 'rage-fw-shared-types' import type { RageFW_ICustomServerEvent } from '@entityseven/rage-fw-shared-types'
export type { RageFW_ICustomServerEvent } from 'rage-fw-shared-types'
export type { RageFW_ICustomServerEvent } from '@entityseven/rage-fw-shared-types'
/** /**
* Union of all available server event names * Union of all available server event names
@ -11,7 +12,7 @@ export type RageFW_ServerEvent = keyof RageFW_ICustomServerEvent
* Array of arguments of event you pass as a generic * Array of arguments of event you pass as a generic
* These only include custom server events * These only include custom server events
*/ */
export type RageFW_ServerArguments<K extends RageFW_ServerEvent> = Parameters< export type RageFW_ServerArgs<K extends RageFW_ServerEvent> = Parameters<
RageFW_ICustomServerEvent[K] RageFW_ICustomServerEvent[K]
> >

View File

@ -1,24 +0,0 @@
{
"name": "rage-fw-cef",
"version": "0.1.0",
"main": "dist/index.js",
"types": "dist/src/index.d.ts",
"files": [
"dist/**/*",
"readme.md"
],
"scripts": {
"build": "tsup"
},
"dependencies": {
"rage-rpc": "^0.4.0"
},
"peerDependencies": {
"@ragempcommunity/types-cef": "^2.1.8",
"rage-fw-shared-types": "workspace:^"
},
"keywords": [],
"author": "SashaGoncharov19",
"license": "MIT",
"description": "CEF side for rage-fw"
}

View File

@ -1,77 +0,0 @@
import rpc from 'rage-rpc'
import {
_CefEventHasArgs,
_ClientEventHasArgs,
_ServerEventHasArgs,
RageFW_CefArguments,
RageFW_CefCallback,
RageFW_CefReturn,
RageFW_ClientArguments,
RageFW_ClientReturn,
RageFW_ICustomCefEvent,
RageFW_ICustomClientEvent,
RageFW_ICustomServerEvent,
RageFW_ServerArguments,
RageFW_ServerReturn,
} from './types'
class Cef {
public register<EventName extends keyof RageFW_ICustomCefEvent>(
eventName: EventName,
callback: RageFW_CefCallback<EventName>,
): void {
if ('mp' in window) {
rpc.register(eventName, callback)
}
}
public trigger<EventName extends keyof RageFW_ICustomCefEvent>(
eventName: EventName,
...args: _CefEventHasArgs<EventName> extends true
? [RageFW_CefArguments<EventName>]
: []
): Promise<RageFW_CefReturn<EventName>> {
if ('mp' in window) {
return rpc.call(eventName, args)
}
return Promise.reject(
'RageFW was started in window which not contain global variable MP!',
)
}
public triggerServer<EventName extends keyof RageFW_ICustomServerEvent>(
eventName: EventName,
...args: _ServerEventHasArgs<EventName> extends true
? [RageFW_ServerArguments<EventName>]
: []
): Promise<RageFW_ServerReturn<EventName>> {
if ('mp' in window) {
return rpc.callServer(eventName, args)
}
return Promise.reject(
'RageFW was started in window which not contain global variable MP!',
)
}
public triggerClient<EventName extends keyof RageFW_ICustomClientEvent>(
eventName: EventName,
...args: _ClientEventHasArgs<EventName> extends true
? [RageFW_ClientArguments<EventName>]
: []
): Promise<RageFW_ClientReturn<EventName>> {
if ('mp' in window) {
return rpc.callClient(eventName, args)
}
return Promise.reject(
'RageFW was started in window which not contain global variable MP!',
)
}
}
export const fw = {
event: new Cef(),
}

View File

@ -1,39 +0,0 @@
import { RageFW_ICustomCefEvent } from 'rage-fw-shared-types'
export { RageFW_ICustomCefEvent } from 'rage-fw-shared-types'
/**
* Union of all available cef event names
* These only include custom events
*/
export type RageFW_CefEvent = keyof RageFW_ICustomCefEvent
/**
* Array of arguments of an event you pass as a generic
* These only include custom cef events
*/
export type RageFW_CefArguments<K extends RageFW_CefEvent> = Parameters<
RageFW_ICustomCefEvent[K]
>
/**
* Return type of event you pass as a generic
* These only include custom cef events
*/
export type RageFW_CefReturn<K extends RageFW_CefEvent> = ReturnType<
RageFW_ICustomCefEvent[K]
>
/**
* Callback (function) of event you pass as a generic
* These only include custom cef events
*/
export type RageFW_CefCallback<K extends keyof RageFW_ICustomCefEvent> = (
args: RageFW_CefArguments<K>,
) => RageFW_CefReturn<K>
export type _CefEventHasArgs<EventName extends keyof RageFW_ICustomCefEvent> =
keyof RageFW_ICustomCefEvent extends never
? false
: Parameters<RageFW_ICustomCefEvent[EventName]>[0] extends undefined
? false
: true

21
cli/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Entity Seven Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,35 +1,41 @@
{ {
"name": "create-rage-fw", "name": "@entityseven/create-rage-fw",
"version": "0.1.0", "version": "0.1.2",
"bin": { "bin": {
"rage-fw": "dist/index.js" "rage-fw": "dist/index.js"
}, },
"main": "dist/index.js", "main": "dist/index.js",
"files": [
"dist/**/*",
"readme.md",
"LICENSE"
],
"scripts": { "scripts": {
"watch": "tsc -w", "watch": "tsc -w",
"build": "tsup", "build": "tsup",
"start": "npx ./dist create" "start": "npx ./dist create"
}, },
"files": [
"dist/**/*",
"readme.md"
],
"description": "CLI to scaffold a template project for RageFW",
"keywords": [],
"author": "rilaxik",
"license": "ISC",
"dependencies": { "dependencies": {
"@inquirer/prompts": "^5.0.5", "@inquirer/prompts": "^5.0.5",
"axios": "^1.7.2", "ky": "^1.7.2",
"chalk": "4.1.2", "chalk": "4.1.2",
"git-clone": "^0.2.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"devDependencies": { "devDependencies": {
"@types/git-clone": "^0.2.4",
"@types/node": "^20.14.2", "@types/node": "^20.14.2",
"@types/yargs": "^17.0.32", "@types/yargs": "^17.0.32",
"prettier": "^3.3.2", "prettier": "^3.3.2",
"typescript": "^5.4.5" "typescript": "^5.4.5"
},
"description": "CLI to scaffold a preview for Rage-FW",
"keywords": ["create-rage-fw","ragefw-cli", "ragefw", "rage-fw", "ragemp", "rage:mp", "rage", "gta5"],
"author": "Entity Seven Group",
"contributors": [
{
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
} }
],
"license": "MIT"
} }

View File

@ -1,29 +1,41 @@
# RageFW CLI To make you life easier while using RageFW we created a basic CLI. At the moment automation we have only works via [pnpm](https://pnpm.io/) and [bun](https://bun.sh/)
To make you life easier while using RageFW we created a basic CLI. At the moment automation we have only works via [pnpm](https://pnpm.io/) ``pnpm create @entityseven/rage-fw@latest``
``pnpm create rage-fw@latest`` ``bun create @entityseven/rage-fw@latest``
## TL;DR # TL;DR
- ``Initialize new project`` - create new template project - ``Initialize new project`` - create new template project
- ``Test our RPC`` - scaffold an example for ``@entityseven/rage-fw-rpc``
- ``Install RAGE:MP updater`` - download and update RAGE:MP server files - ``Install RAGE:MP updater`` - download and update RAGE:MP server files
## Options # Options
For now, you will see a few available options. They are described in detail below For now, you will see a few available options. They are described in detail below
- ``Initialize new project`` - ``Initialize new project``
- ``Test our RPC``
- ``Install RAGE:MP updater`` - ``Install RAGE:MP updater``
### Initialize new project ## Initialize new project
Using this options will forward you to common project-creation menu Using this option will forward you to common project creation menu
- ``Enter project name`` - ``Enter project name``
This option will specify a name for your project which is used as a folder name too. Defaults to **rage-fw-example**
This option will specify a name for your project which is used as a folder name too. Defaults to **rage-fw** - ``Select front-end``
Use selector menu to choose which front-end framework you want to use. We will do our best to expand this menu with various solutions
- ``Select frontend`` ## Test our RPC
Using this option will forward you to common project creation menu
Use this selector menu to choose which frontend framework you want to use. We will do our best to expand this menu after some time. - ``Enter project name``
Defaults to **React + TypeScript (Vite)** This option will specify a name for your project which is used as a folder name too. Defaults to **rage-fw-rpc-example**
### Install Rage:MP updater - ``Select front-end``
This option will simplify installation process of Rage:MP server files required to start your server Use selector menu to choose which front-end framework you want to use. We will do our best to expand this menu with various solutions
## Install Rage:MP updater
This option will simplify installation and update process of Rage:MP server files required to start your server
# Contribution
If you wish to help us in expanding examples selection with different framework you are very welcome to open PRs and Issues

View File

@ -1,7 +1,7 @@
import c from 'chalk' import c from 'chalk'
import { input, select } from '@inquirer/prompts' import { input, select } from '@inquirer/prompts'
import clone from 'git-clone'
import path from 'node:path' import path from 'node:path'
import { cloneBranch } from '../utils/cloner'
export async function initProject() { export async function initProject() {
let folder let folder
@ -10,7 +10,7 @@ export async function initProject() {
if (!folder) { if (!folder) {
folder = await input({ folder = await input({
message: c.gray('Enter project name:'), message: c.gray('Enter project name:'),
default: 'rage-fw', default: 'rage-fw-example',
}) })
} else { } else {
console.log(c.gray('Project name:'), folder) console.log(c.gray('Project name:'), folder)
@ -18,24 +18,19 @@ export async function initProject() {
if (!framework) { if (!framework) {
framework = await select({ framework = await select({
message: c.gray('Select frontend:'), message: c.gray('Select front-end:'),
default: 'react', default: 'react-18',
loop: true, loop: true,
choices: [ choices: [
{ {
name: 'React + TypeScript (Vite)', name: 'React 18',
value: 'react', value: 'react-18',
description: 'React + TypeScript (Vite) as a frontend', description: 'React 18 + TypeScript (Vite) as a front-end',
}, },
// {
// name: 'vue',
// value: 'vue',
// description: 'npm is the most popular package manager',
// },
], ],
}) })
} else { } else {
console.log(c.gray('Frontend:'), framework) console.log(c.gray('Front-end:'), framework)
} }
console.log( console.log(
@ -43,24 +38,24 @@ export async function initProject() {
folder, folder,
c.gray('with'), c.gray('with'),
framework, framework,
c.gray('as a frontend..'), c.gray('as a front-end..'),
) )
clone( cloneBranch(
'https://git.entityseven.com/entityseven/rage-framework-example', 'https://git.entityseven.com/entityseven/rage-framework-example',
path.join(process.cwd(), folder), path.join(process.cwd(), folder),
{}, framework,
err => { )
if (err) { .then(() => {
console.log(c.red('Error occured: \n', err))
return
}
console.log(c.gray('Scaffolded project into'), folder) console.log(c.gray('Scaffolded project into'), folder)
console.log( console.log(
c.gray( c.gray(
`Project was created ar dir: ${path.join(process.cwd(), folder)}`, `Project was created ar dir: ${path.join(process.cwd(), folder)}`,
), ),
) )
}, })
) .catch(e => {
console.log(c.red('Error occured: \n', e))
console.log(c.red('Please open an issue if you see this'))
})
} }

View File

@ -1,4 +1,3 @@
import axios from 'axios'
import * as fs from 'node:fs' import * as fs from 'node:fs'
const latestReleases = const latestReleases =
@ -13,17 +12,21 @@ type Asset = {
} }
export async function downloadUpdater(): Promise<void> { export async function downloadUpdater(): Promise<void> {
const ky = await import('ky').then(ky => ky.default)
const id = await getLatestReleaseID() const id = await getLatestReleaseID()
const latestAssets = `https://git.entityseven.com/api/v1/repos/entityseven/rage-server-downloader/releases/${id}/assets?page=1&limit=1` const latestAssets = `https://git.entityseven.com/api/v1/repos/entityseven/rage-server-downloader/releases/${id}/assets?page=1&limit=1`
axios.get<Asset[]>(latestAssets).then(async ({ data }) => { ky.get<Asset[]>(latestAssets)
.then(response => response.json())
.then(async data => {
const downloadURL = data[0].browser_download_url const downloadURL = data[0].browser_download_url
const file = await axios.get(data[0].browser_download_url, { const file = await ky.get(data[0].browser_download_url)
responseType: 'arraybuffer', const fileData = Buffer.from(
}) file as unknown as WithImplicitCoercion<string>,
const fileData = Buffer.from(file.data, 'binary') 'binary',
)
const fileSplit = downloadURL.split('/') const fileSplit = downloadURL.split('/')
const fileName = fileSplit[fileSplit.length - 1] const fileName = fileSplit[fileSplit.length - 1]
@ -33,5 +36,9 @@ export async function downloadUpdater(): Promise<void> {
} }
async function getLatestReleaseID() { async function getLatestReleaseID() {
return axios.get<Release[]>(latestReleases).then(({ data }) => data[0].id) const ky = await import('ky').then(ky => ky.default)
return ky
.get<Release[]>(latestReleases)
.then(response => response.json())
.then(data => data[0].id)
} }

View File

@ -0,0 +1,68 @@
import c from 'chalk'
import { input, select } from '@inquirer/prompts'
import path from 'node:path'
import { cloneBranch } from '../utils/cloner'
const choices = {
'rpc-react-18': {
name: 'React 18',
value: 'rpc-react-18',
description: 'Vite + React 18 + TypeScript as a front-end',
},
'rpc-svelte-5': {
name: 'Svelte 5',
value: 'rpc-svelte-5',
description: 'Vite + Svelte 5 + TypeScript as a front-end',
},
} as const
export async function testRpc() {
let folder
let framework
if (!folder) {
folder = await input({
message: c.gray('Enter project name:'),
default: 'rage-fw-rpc-example',
})
} else {
console.log(c.gray('Project name:'), folder)
}
if (!framework) {
framework = await select({
message: c.gray('Select front-end:'),
default: 'rpc-react-18',
loop: true,
choices: Object.values(choices),
})
} else {
console.log(c.gray('Front-end:'), framework)
}
console.log(
c.gray('\nScaffolding template project into'),
folder,
c.gray('with'),
choices[framework].name,
c.gray('as a frontend..'),
)
cloneBranch(
'https://git.entityseven.com/entityseven/rage-framework-example',
path.join(process.cwd(), folder),
framework,
)
.then(() => {
console.log(c.gray('Scaffolded project into'), folder)
console.log(
c.gray(
`Project was created at dir: ${path.join(process.cwd(), folder)}`,
),
)
})
.catch(e => {
console.log(c.red('Error occured: \n', e))
console.log(c.red('Please open an issue if you see this'))
})
}

View File

@ -1,40 +1,67 @@
import c from 'chalk' import c from 'chalk'
import { select } from '@inquirer/prompts' import { select } from '@inquirer/prompts'
import { checkForUpdate } from './utils/update' import { checkForUpdates } from './utils/update'
import { initProject } from './commands/create' import { initProject } from './commands/create'
import { downloadUpdater } from './commands/download-updater' import { downloadUpdater } from './commands/download-updater'
import { testRpc } from './commands/test-rpc'
enum Actions { enum Actions {
INIT_PROJECT = 'INIT_PROJECT', INIT_PROJECT = 'INIT_PROJECT',
TEST_RPC = 'TEST_RPC',
UPDATER = 'UPDATER', UPDATER = 'UPDATER',
} }
;(async () => { process.on('exit', () => {
await checkForUpdate() console.log(c.blueBright('\n\nRage FW CLI | Exiting..'))
process.exit(0)
})
console.log( process.on('SIGINT', () => {
c.blueBright('Rage FW CLI | Powered by Entity Seven Group <3'), console.log(c.blueBright('\n\nRage FW CLI | Exiting..'))
) process.exit(0)
})
;(async () => {
await checkForUpdates()
console.log(c.blueBright('Rage FW CLI | Powered by Entity Seven Group <3'))
const action = await select({ const action = await select({
message: c.gray('Select action:'), message: c.gray('Select action:'),
choices: [ choices: [
{ {
name: 'Initialize new project', name: 'Initialize a new project',
value: Actions.INIT_PROJECT, value: Actions.INIT_PROJECT,
description: 'Initialize new project and start develop', description: 'Initialize a new project and start developing',
},
{
name: 'Test our Rpc',
value: Actions.TEST_RPC,
description:
'Initialize a new skeleton project with our Rpc all set',
}, },
{ {
name: 'Install RAGE:MP updater', name: 'Install RAGE:MP updater',
value: Actions.UPDATER, value: Actions.UPDATER,
description: description:
'Use our custom updater to download and update RAGE:MP server files.', 'Use our tool to download or update RAGE:MP server files in just two clicks',
}, },
], ],
loop: true, loop: true,
}) })
if (action === Actions.INIT_PROJECT) await initProject() switch (action) {
if (action === Actions.UPDATER) await downloadUpdater() case Actions.INIT_PROJECT:
await initProject()
break
case Actions.TEST_RPC:
await testRpc()
break
case Actions.UPDATER:
await downloadUpdater()
break
default:
console.log(c.red('Something went wrong..'))
console.log(c.red('Please open an issue if you see this'))
}
})() })()

24
cli/src/utils/cloner.ts Normal file
View File

@ -0,0 +1,24 @@
import { exec } from 'child_process'
export async function cloneBranch(link: string, path: string, branch: string) {
return new Promise((resolve, reject) => {
const args = ['--single-branch', '-b', branch, '--', link, path]
const proc = exec('git clone ' + args.join(' '))
proc.on('close', (status: number) => {
if (status == 0) {
resolve(true)
} else if (status == 128) {
reject(
`Folder already exists. 'git clone' from branch ${branch} failed with status ` +
status,
)
} else {
reject(
`'git clone' from branch ${branch} failed with status ` +
status,
)
}
})
})
}

View File

@ -1,4 +1,3 @@
import axios from 'axios'
import c from 'chalk' import c from 'chalk'
import yargs from 'yargs' import yargs from 'yargs'
@ -10,12 +9,15 @@ type Version = {
message: string message: string
} }
export async function checkForUpdate(): Promise<void> { export async function checkForUpdates(): Promise<void> {
const ky = await import('ky').then(ky => ky.default)
return new Promise(res => { return new Promise(res => {
yargs.showVersion(version => yargs.showVersion(version =>
axios ky
.get<Version[]>(latestVersionURL) .get<Version[]>(latestVersionURL)
.then(({ data }) => { .then(response => response.json<Version[]>())
.then(data => {
const latestVersion = data[0].name const latestVersion = data[0].name
if (latestVersion !== `v${version}`) if (latestVersion !== `v${version}`)

15
client/LICENSE Normal file
View File

@ -0,0 +1,15 @@
Custom Attribution-NoDerivs Software License
Copyright (c) 2024 Entity Seven Group
This license allows you to use, copy, and distribute the RageFW (the "Software"), including for commercial purposes, provided that the following conditions are met:
1. **Attribution:** You must give appropriate credit to the original author of the Software, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
2. **No Derivative Works:** You may not modify, transform, or build upon the Software.
3. **Usage and Commercial Use:** You are allowed to use, sell, and gain income from projects that utilize the Software, as long as you comply with the terms of this license.
4. **No Additional Restrictions:** You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,25 +1,38 @@
{ {
"name": "rage-fw-client", "name": "@entityseven/rage-fw-client",
"version": "0.1.0", "version": "0.2.0",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
"files": [ "files": [
"dist/**/*", "dist/**/*",
"readme.md" "readme.md",
"LICENSE"
], ],
"scripts": { "scripts": {
"build": "tsup" "build": "tsup"
}, },
"dependencies": { "dependencies": {
"rage-rpc": "^0.4.0" "@entityseven/rage-fw-rpc": "0.2.5"
}, },
"peerDependencies": { "peerDependencies": {
"@ragempcommunity/types-client": "^2.1.8", "@entityseven/rage-fw-shared-types": "0.2.0",
"rage-fw-shared-types": "workspace:^" "@ragempcommunity/types-client": "^2.1.8"
}, },
"keywords": [], "description": "Package used on a client-side of your Rage:MP Server",
"author": "SashaGoncharov19", "keywords": ["rage-fw-client", "ragefw", "rage-fw", "ragemp", "rage:mp", "rage", "gta5"],
"license": "MIT", "author": "Entity Seven Group",
"description": "Client side of rage-fw", "contributors": [
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0" {
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
},
{
"name": "Oleksandr Honcharov",
"email": "0976053529@ukr.net",
"url": "https://github.com/SashaGoncharov19/"
}
],
"license": "Custom-Attribution-NoDerivs",
"gitHead": "ffd542c1deddb3033e16e0dae7557313ae09b05f"
} }

View File

@ -1,2 +1,2 @@
# RageFW Client # RageFW Client
[Read docs for details](https://git.entityseven.com/entityseven/rage-framework/wiki/Docs) [Read docs for details](https://git.entityseven.com/entityseven/rage-framework/wiki)

86
client/src/core/client.ts Normal file
View File

@ -0,0 +1,86 @@
import { rpc } from './rpc'
import { Middleware } from './middleware'
import type * as T from '../types'
/** Client-side interactions */
export class Client {
/**
* Registers a client event with an associated callback
*
* @param eventName - The name of the event to register
* @param callback - The callback function to be executed when the event is triggered
* @param [options] - Optional settings for callback execution
* @param [options.middlewares] - Middleware functions to be checked before the callback executes
* @returns {Client} The current client instance, enabling method chaining
*
* @example
* // Registering an event
* fw.event.register("playerDeath", (player, reason, killer) => {
* fw.system.log.info(player, reason, killer)
* })
*
* @example
* // Registering an event with middlewares
* fw.event.register("playerDeath", (player, reason, killer) => {
* fw.system.log.info(player, reason, killer)
* }, {
* middlewares: [ignoreSuicide] // <- your middlewares here
* })
*
* // or
*
* fw.event.register("playerDeath", (player, reason, killer) => {
* fw.system.log.info(player, reason, killer)
* }, {
* middlewares: {
* executables: [ignoreSuicide], // <- your middlewares here
* onError: (msg) => fw.system.log.info(`${player.socialClub} has commited suicide`)
* }
* })
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public register<EventName extends T.RageFW_ClientEvent>(
eventName: EventName,
callback: T.RageFW_ClientCallback<EventName>,
options?: {
middlewares?: T.RageFW_MiddlewareOptions<EventName>
},
): Client {
rpc.register<
Parameters<typeof callback>,
ReturnType<typeof callback> | Promise<unknown>,
EventName
>(eventName, async (...data) => {
if (!options?.middlewares) return await callback(...data)
await Middleware.process(options.middlewares, callback, data)
})
return this
}
/**
* Unregisters a client event, removing the associated callback
*
* @param eventName - The name of the event to unregister
* @returns {Client} The current client instance, enabling method chaining
*
* @example
* // Unregistering an event
* fw.event.unregister("playerDeath")
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public unregister<EventName extends T.RageFW_ClientEvent>(
eventName: EventName,
): Client {
rpc.unregister<EventName>(eventName)
return this
}
}
// new Client()
// .register('customClientEvent', async (a, b) => true)
// .unregister('customClientEvent')

5
client/src/core/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from './client'
export * from './logger'
export * from './middleware'
export * from './player'
export * from './rpc'

40
client/src/core/logger.ts Normal file
View File

@ -0,0 +1,40 @@
/**
* Used to log to a client in-game console
*/
export class Logger {
/**
* Informational logs. Colored in white
*
* @example
* fw.system.log.info('some information to be logged')
*/
public info(...message: unknown[]) {
mp.console.logInfo(
`[${new Date().toLocaleTimeString()}] [INFO] ${message.join(' ')}`,
)
}
/**
* Warning logs. Colored in yellow
*
* @example
* fw.system.log.warn('warning message')
*/
public warn(...message: unknown[]) {
mp.console.logWarning(
`[${new Date().toLocaleTimeString()}] [WARN] ${message.join(' ')}`,
)
}
/**
* Error logs. Colored in red
*
* @example
* fw.system.log.info('some error information')
*/
public error(...message: unknown[]) {
mp.console.logError(
`[${new Date().toLocaleTimeString()}] [ERROR] ${message.join(' ')}`,
)
}
}

View File

@ -0,0 +1,54 @@
import type * as T from '../types'
export class Middleware {
constructor() {}
private static async execute<EventName extends T.RageFW_ClientEvent>(
middlewares: T.RageFW_MiddlewareFunction<EventName>[],
args: T.RageFW_ClientArgs<EventName>,
): Promise<T.RageFW_MiddlewareResponseInternal> {
for (let i = 0; i < middlewares.length; i++) {
const result = await middlewares[i](...args)
if (typeof result === 'boolean' && !result)
return { success: result, id: i }
if (typeof result !== 'boolean' && !result.success)
return { ...result, id: i }
}
return {
success: true,
}
}
public static async process<EventName extends T.RageFW_ClientEvent>(
middlewareOptions: T.RageFW_MiddlewareOptions<EventName>,
callback: T.RageFW_ClientCallback<EventName>,
args: T.RageFW_ClientArgs<EventName>,
) {
if (Array.isArray(middlewareOptions)) {
const middlewaresResponse = await Middleware.execute(
middlewareOptions,
args,
)
if (middlewaresResponse.success) return await callback(...args)
} else {
const middlewaresResponse = await Middleware.execute(
middlewareOptions.executables,
args,
)
if (middlewaresResponse.success) {
return await callback(...args)
} else {
middlewareOptions.onError(
middlewaresResponse.message ??
'Middleware with id ' +
middlewaresResponse.id +
' failed',
)
}
}
}
}

118
client/src/core/player.ts Normal file
View File

@ -0,0 +1,118 @@
import { rpc } from './rpc'
import type * as T from '../types'
/** Handles event manipulations that require player to be present in context */
export class Player {
private _browser: BrowserMp | undefined = undefined
/**
* Setter. Also shares browser with ``rage-fw-rpc``
*/
set browser(browser: BrowserMp) {
this._browser = browser
rpc.browser = browser
}
/**
* Triggers a client event from the client with arguments from shared types
*
* Formerly known as ``call`` or ``emit``
*
* @param eventName - The name of the client event to trigger
* @param [args] - Arguments for the client event, if present
* @returns {Promise} resolving to the client's response for the event
*
* @example
* // Triggering a client event without arguments
* fw.player.trigger("clientEventName")
*
* @example
* // Triggering a client event with arguments
* fw.player.trigger("clientEventName", ["message to me"])
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public async trigger<EventName extends keyof T.RageFW_ICustomClientEvent>(
eventName: EventName,
...args: T._ClientEventHasArgs<EventName> extends true
? [T.RageFW_ClientArgs<EventName>]
: []
): Promise<T.RageFW_ClientReturn<EventName>> {
return await rpc.call<
typeof args,
EventName,
T.RageFW_ClientReturn<EventName>
>(eventName, args)
}
/**
* Triggers a server event from the client with arguments from shared types
*
* Formerly known as ``callServer`` or ``emitServer``
*
* @param eventName - The name of the server event to trigger
* @param [args] - Arguments for the server event, if present
* @returns {Promise} resolving to the server's response for the event
*
* @example
* // Triggering a server event without arguments
* fw.player.triggerServer("serverEventName")
*
* @example
* // Triggering a server event with arguments
* fw.player.triggerServer("serverEventName", ["message to server"])
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public async triggerServer<EventName extends T.RageFW_ServerEvent>(
eventName: EventName,
...args: T._ServerEventHasArgs<EventName> extends true
? [T.RageFW_ServerArgs<EventName>]
: []
): Promise<T.RageFW_ClientServerReturn<EventName>> {
return await rpc.callServer<
typeof args,
EventName,
T.RageFW_ClientServerReturn<EventName>
>(eventName, args)
}
/**
* Triggers a browser event from the client with arguments from shared types
*
* Formerly known as ``callBrowser`` or ``emitBrowser``
*
* @param eventName - The name of the browser event to trigger
* @param [args] - Arguments for the browser event, if present
* @returns {Promise} resolving to the browser's response for the event
*
* @example
* // Triggering a browser event without arguments
* fw.player.triggerBrowser("browserEventName")
*
* @example
* // Triggering a browser event with arguments
* fw.player.triggerBrowser("browserEventName", ["message to browser"])
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public async triggerBrowser<EventName extends T.RageFW_BrowserEvent>(
eventName: EventName,
...args: T._BrowserEventHasArgs<EventName> extends true
? [T.RageFW_BrowserArgs<EventName>]
: []
): Promise<T.RageFW_BrowserReturn<EventName>> {
if (!this._browser)
throw new Error('You need to initialize browser first')
return await rpc.callBrowser<
typeof args,
EventName,
T.RageFW_BrowserReturn<EventName>
>(eventName, args)
}
}
// new Player().trigger('customClientEvent', ['', 1])
// new Player().triggerServer('customServerEvent', ['', 1])
// new Player().triggerBrowser('customCefEvent', ['', 1])

5
client/src/core/rpc.ts Normal file
View File

@ -0,0 +1,5 @@
import { Rpc } from '@entityseven/rage-fw-rpc'
export const rpc = new Rpc({
debugLogs: false,
})

99
client/src/data/keys.ts Normal file
View File

@ -0,0 +1,99 @@
export const KEY_NONE = 0
export const KEY_ENTER = 0x0d
export const KEY_TAB = 0x09
export const KEY_BACKSPACE = 0x08
export const KEY_SHIFT = 0x10
export const KEY_CTRL = 0x11
export const KEY_ALT = 0x12
export const KEY_PAUSE = 0x13
export const KEY_CAPS_LOCK = 0x14
export const KEY_ESCAPE = 0x1b
export const KEY_SPACE = 0x20
export const KEY_PAGE_UP = 0x21
export const KEY_PAGE_DOWN = 0x22
export const KEY_END = 0x23
export const KEY_HOME = 0x24
export const KEY_LEFT = 0x25
export const KEY_UP = 0x26
export const KEY_RIGHT = 0x27
export const KEY_DOWN = 0x28
export const KEY_INSERT = 0x2d
export const KEY_DELETE = 0x2e
export const KEY_0 = 0x30
export const KEY_1 = 0x31
export const KEY_2 = 0x32
export const KEY_3 = 0x33
export const KEY_4 = 0x34
export const KEY_5 = 0x35
export const KEY_6 = 0x36
export const KEY_7 = 0x37
export const KEY_8 = 0x38
export const KEY_9 = 0x39
export const KEY_A = 0x41
export const KEY_B = 0x42
export const KEY_C = 0x43
export const KEY_D = 0x44
export const KEY_E = 0x45
export const KEY_F = 0x46
export const KEY_G = 0x47
export const KEY_H = 0x48
export const KEY_I = 0x49
export const KEY_J = 0x4a
export const KEY_K = 0x4b
export const KEY_L = 0x4c
export const KEY_M = 0x4d
export const KEY_N = 0x4e
export const KEY_O = 0x4f
export const KEY_P = 0x50
export const KEY_Q = 0x51
export const KEY_R = 0x52
export const KEY_S = 0x53
export const KEY_T = 0x54
export const KEY_U = 0x55
export const KEY_V = 0x56
export const KEY_W = 0x57
export const KEY_X = 0x58
export const KEY_Y = 0x59
export const KEY_Z = 0x5a
export const KEY_LEFT_WINDOWS = 0x5b
export const KEY_RIGHT_WINDOWS = 0x5c
export const KEY_CONTEXT_MENU = 0x5d
export const KEY_NUMPAD_0 = 0x60
export const KEY_NUMPAD_1 = 0x61
export const KEY_NUMPAD_2 = 0x62
export const KEY_NUMPAD_3 = 0x63
export const KEY_NUMPAD_4 = 0x64
export const KEY_NUMPAD_5 = 0x65
export const KEY_NUMPAD_6 = 0x66
export const KEY_NUMPAD_7 = 0x67
export const KEY_NUMPAD_8 = 0x68
export const KEY_NUMPAD_9 = 0x69
export const KEY_MULTIPLY = 0x6a
export const KEY_ADD = 0x6b
export const KEY_SEPARATOR = 0x6c
export const KEY_SUBTRACT = 0x6d
export const KEY_DECIMAL = 0x6e
export const KEY_DIVIDE = 0x6f
export const KEY_F1 = 0x70
export const KEY_F2 = 0x71
export const KEY_F3 = 0x72
export const KEY_F4 = 0x73
export const KEY_F5 = 0x74
export const KEY_F6 = 0x75
export const KEY_F7 = 0x76
export const KEY_F8 = 0x77
export const KEY_F9 = 0x78
export const KEY_F10 = 0x79
export const KEY_F11 = 0x7a
export const KEY_F12 = 0x7b
export const KEY_NUM_LOCK = 0x90
export const KEY_SCROLL_LOCK = 0x91
export const KEY_LEFT_SHIFT = 0xa0
export const KEY_RIGHT_SHIFT = 0xa1
export const KEY_LEFT_CTRL = 0xa2
export const KEY_RIGHT_CTRL = 0xa3
export const KEY_LEFT_ALT = 0xa4
export const KEY_RIGHT_ALT = 0xa5
export const VK_TILDE = 0xc0
export const VK_LBUTTON = 0x01
export const VK_RBUTTON = 0x02

View File

@ -1,87 +1,22 @@
import rpc from 'rage-rpc' import { Client, Logger, Player, rpc } from './core'
import Logger from './logger' export type { RageFW_MiddlewareFunction } from './types'
import {
_CefEventHasArgs,
_ClientEventHasArgs,
_ServerEventHasArgs,
RageFW_CefArgs,
RageFW_CefEvent,
RageFW_CefReturn,
RageFW_ClientEvent,
RageFW_ClientEventArguments,
RageFW_ClientEventCallback,
RageFW_ClientEventReturn,
RageFW_ClientServerEvent,
RageFW_ClientServerEventArguments,
RageFW_ClientServerEventReturn,
} from './types'
import type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types'
class Client {
public register<EventName extends RageFW_ClientEvent>(
eventName: EventName,
callback: RageFW_ClientEventCallback<EventName>,
): void {
rpc.register(eventName, data => {
return callback(data)
})
}
public unregister<EventName extends RageFW_ClientEvent>(
eventName: EventName,
): void {
rpc.unregister(eventName)
}
}
class Player {
public browser: BrowserMp | undefined
public trigger<EventName extends keyof RageFW_ICustomClientEvent>(
eventName: EventName,
...args: _ClientEventHasArgs<EventName> extends true
? [RageFW_ClientEventArguments<EventName>]
: []
): Promise<RageFW_ClientEventReturn<EventName>> {
return rpc.call<RageFW_ClientEventReturn<EventName>>(eventName, args)
}
public triggerServer<EventName extends RageFW_ClientServerEvent>(
eventName: EventName,
...args: _ServerEventHasArgs<EventName> extends true
? [RageFW_ClientServerEventArguments<EventName>]
: []
): Promise<RageFW_ClientServerEventReturn<EventName>> {
return rpc.callServer(eventName, args)
}
public triggerBrowser<EventName extends RageFW_CefEvent>(
eventName: EventName,
...args: _CefEventHasArgs<EventName> extends true
? [RageFW_CefArgs<EventName>]
: []
): Promise<RageFW_CefReturn<EventName>> {
if (!this.browser)
throw new Error('You need to initialize browser first!')
return rpc.callBrowser(this.browser, eventName, args)
}
}
class Browser extends Player {
public registerBrowser(browser: BrowserMp) {
this.browser = browser
return browser
}
}
/**
* Package used on a client-side of your Rage:MP Server
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
export const fw = { export const fw = {
/** Client-side interactions */
event: new Client(), event: new Client(),
/** Handles event manipulations that require player to be present in context */
player: new Player(), player: new Player(),
browser: new Browser(), /** Handles functions used to interact with the client environment */
system: { system: {
/** Used to log in a client in-game console */
log: new Logger(), log: new Logger(),
}, },
/** ``rage-fw-rpc`` instance used under the hood. It is highly recommended to use this one if you need it instead of creating a new instance */
rpc,
} }

View File

@ -1,19 +0,0 @@
export default class Logger {
public error(message: unknown) {
mp.console.logError(
`[${new Date().toLocaleTimeString()}] [ERROR] ${message}`,
)
}
public warn(message: unknown) {
mp.console.logWarning(
`[${new Date().toLocaleTimeString()}] [WARN] ${message}`,
)
}
public info(message: unknown) {
mp.console.logInfo(
`[${new Date().toLocaleTimeString()}] [INFO] ${message}`,
)
}
}

View File

@ -1,20 +1,29 @@
/// <reference types="@ragempcommunity/types-client" /> /// <reference types="@ragempcommunity/types-client" />
import type { RageFW_ICustomCefEvent } from 'rage-fw-shared-types' import type { RageFW_ICustomBrowserEvent } from '@entityseven/rage-fw-shared-types'
export type RageFW_CefEvent = keyof RageFW_ICustomCefEvent /**
* Union of all available browser event names callable from client
* These only include custom events
*/
export type RageFW_BrowserEvent = keyof RageFW_ICustomBrowserEvent
export type RageFW_CefArgs<K extends RageFW_CefEvent> = Parameters< /**
RageFW_ICustomCefEvent[K] * Array of arguments for an event, name of which you pass as a generic
* These only include custom events
*/
export type RageFW_BrowserArgs<K extends RageFW_BrowserEvent> = Parameters<
RageFW_ICustomBrowserEvent[K]
> >
export type RageFW_CefReturn<K extends RageFW_CefEvent> = ReturnType< export type RageFW_BrowserReturn<K extends RageFW_BrowserEvent> = ReturnType<
RageFW_ICustomCefEvent[K] RageFW_ICustomBrowserEvent[K]
> >
export type _CefEventHasArgs<EventName extends keyof RageFW_ICustomCefEvent> = export type _BrowserEventHasArgs<
keyof RageFW_ICustomCefEvent extends never EventName extends keyof RageFW_ICustomBrowserEvent,
> = keyof RageFW_ICustomBrowserEvent extends never
? false ? false
: Parameters<RageFW_ICustomCefEvent[EventName]>[0] extends undefined : Parameters<RageFW_ICustomBrowserEvent[EventName]>[0] extends undefined
? false ? false
: true : true

View File

@ -1,6 +1,8 @@
/// <reference types="@ragempcommunity/types-client" /> /// <reference types="@ragempcommunity/types-client" />
import type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types' import type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
export type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
/** /**
* Union of all available client event names * Union of all available client event names
@ -14,30 +16,35 @@ export type RageFW_ClientEvent =
* Array of arguments for an event, name of which you pass as a generic * Array of arguments for an event, name of which you pass as a generic
* These include custom and system events * These include custom and system events
*/ */
export type RageFW_ClientEventArguments<K extends RageFW_ClientEvent> = export type RageFW_ClientArgs<K extends RageFW_ClientEvent> =
K extends keyof RageFW_ICustomClientEvent K extends keyof RageFW_ICustomClientEvent
? Parameters<RageFW_ICustomClientEvent[K]> ? Parameters<RageFW_ICustomClientEvent[K]>
: K extends keyof IClientEvents : K extends keyof IClientEvents
? Parameters<IClientEvents[K]> ? Parameters<IClientEvents[K]>
: never : never
/**
* Callback (function) for an event, name of which you pass as a generic
* These only include custom events
*/
export type RageFW_ClientEventCallback<K extends RageFW_ClientEvent> = (
args: RageFW_ClientEventArguments<K>,
) => RageFW_ClientEventReturn<K>
/** /**
* Return type for an event, name of which you pass as a generic * Return type for an event, name of which you pass as a generic
* These only include custom events * These include custom and system events
*/ */
export type RageFW_ClientEventReturn<K extends RageFW_ClientEvent> = export type RageFW_ClientReturn<K extends RageFW_ClientEvent> =
K extends keyof RageFW_ICustomClientEvent K extends keyof RageFW_ICustomClientEvent
? ReturnType<RageFW_ICustomClientEvent[K]> ? ReturnType<RageFW_ICustomClientEvent[K]>
: never : K extends keyof IClientEvents
? ReturnType<IClientEvents[K]>
: void
/**
* Callback (function) for an event, name of which you pass as a generic
* These include custom and system events
*/
export type RageFW_ClientCallback<K extends RageFW_ClientEvent> = (
...args: RageFW_ClientArgs<K>
) => Promise<RageFW_ClientReturn<K>>
/**
*
*/
export type _ClientEventHasArgs< export type _ClientEventHasArgs<
EventName extends keyof RageFW_ICustomClientEvent, EventName extends keyof RageFW_ICustomClientEvent,
> = keyof RageFW_ICustomClientEvent extends never > = keyof RageFW_ICustomClientEvent extends never

View File

@ -1,3 +1,4 @@
export * from './client'
export * from './server'
export * from './browser' export * from './browser'
export * from './client'
export * from './middleware'
export * from './server'

View File

@ -0,0 +1,26 @@
import type * as T from './client'
export type RageFW_MiddlewareResponse =
| {
success: boolean
message?: string
}
| boolean
export type RageFW_MiddlewareResponseInternal = {
success: boolean
message?: string
id?: number
}
export type RageFW_MiddlewareFunction<EventName extends T.RageFW_ClientEvent> =
(
...args: T.RageFW_ClientArgs<EventName>
) => Promise<RageFW_MiddlewareResponse>
export type RageFW_MiddlewareOptions<EventName extends T.RageFW_ClientEvent> =
| RageFW_MiddlewareFunction<EventName>[]
| {
executables: RageFW_MiddlewareFunction<EventName>[]
onError: (error: string) => unknown
}

View File

@ -3,21 +3,20 @@
import type { import type {
RageFW_ICustomClientEvent, RageFW_ICustomClientEvent,
RageFW_ICustomServerEvent, RageFW_ICustomServerEvent,
} from 'rage-fw-shared-types' } from '@entityseven/rage-fw-shared-types'
/** /**
* Union of all available server event names callable from client * Union of all available server event names callable from client
* These only include custom events * These only include custom events
*/ */
export type RageFW_ClientServerEvent = keyof RageFW_ICustomServerEvent export type RageFW_ServerEvent = keyof RageFW_ICustomServerEvent
/** /**
* Array of arguments for an event, name of which you pass as a generic * Array of arguments for an event, name of which you pass as a generic
* These only include custom events * These only include custom events
*/ */
export type RageFW_ClientServerEventArguments< export type RageFW_ServerArgs<K extends RageFW_ServerEvent> =
K extends RageFW_ClientServerEvent, K extends keyof RageFW_ICustomServerEvent
> = K extends keyof RageFW_ICustomServerEvent
? Parameters<RageFW_ICustomServerEvent[K]> ? Parameters<RageFW_ICustomServerEvent[K]>
: never : never
@ -25,7 +24,7 @@ export type RageFW_ClientServerEventArguments<
* Return type for an event, name of which you pass as a generic * Return type for an event, name of which you pass as a generic
* These only include custom events * These only include custom events
*/ */
export type RageFW_ClientServerEventReturn<K extends RageFW_ClientServerEvent> = export type RageFW_ClientServerReturn<K extends RageFW_ServerEvent> =
K extends keyof RageFW_ICustomServerEvent K extends keyof RageFW_ICustomServerEvent
? ReturnType<RageFW_ICustomServerEvent[K]> ? ReturnType<RageFW_ICustomServerEvent[K]>
: never : never

View File

@ -1,5 +1,5 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "0.1.0", "version": "0.2.1-0.2.0-alpha.2.0",
"npmClient": "pnpm" "npmClient": "pnpm"
} }

View File

@ -1,9 +1,13 @@
{ {
"private": true,
"scripts": { "scripts": {
"publish": "pnpm build && lerna publish --force-publish", "publish": "lerna publish --force-publish",
"build": "lerna run build", "build": "lerna run build",
"lint": "eslint --c .eslintrc.yaml --ext .ts client/ server/ shared-types/" "lint": "eslint --c .eslintrc.yaml --ext .ts client/ server/ shared-types/",
"rebuild:browser": "cd browser && pnpm build",
"rebuild:client": "cd client && pnpm build",
"rebuild:server": "cd server && pnpm build",
"rebuild": "pnpm rebuild:browser && pnpm rebuild:client && pnpm rebuild:server"
}, },
"dependencies": { "dependencies": {
"@microsoft/api-extractor": "^7.47.0", "@microsoft/api-extractor": "^7.47.0",
@ -12,6 +16,7 @@
"@ragempcommunity/types-server": "^2.1.8", "@ragempcommunity/types-server": "^2.1.8",
"@typescript-eslint/eslint-plugin": "^7.13.0", "@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0", "@typescript-eslint/parser": "^7.13.0",
"@types/node": "^22.8.1",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"lerna": "^8.1.3", "lerna": "^8.1.3",
"prettier": "^3.3.1", "prettier": "^3.3.1",
@ -20,5 +25,20 @@
"typescript": "^5.4.5", "typescript": "^5.4.5",
"winston": "^3.13.0" "winston": "^3.13.0"
}, },
"type": "module" "private": true,
"type": "module",
"license": "SEE INDIVIDUALLY FOR EACH PACKAGE",
"author": "Entity Seven Group",
"contributors": [
{
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
},
{
"name": "Oleksandr Honcharov",
"email": "0976053529@ukr.net",
"url": "https://github.com/SashaGoncharov19/"
}
]
} }

366
pnpm-lock.yaml generated
View File

@ -9,7 +9,7 @@ importers:
dependencies: dependencies:
'@microsoft/api-extractor': '@microsoft/api-extractor':
specifier: ^7.47.0 specifier: ^7.47.0
version: 7.47.0(@types/node@20.14.2) version: 7.47.0(@types/node@22.8.1)
'@ragempcommunity/types-cef': '@ragempcommunity/types-cef':
specifier: ^2.1.8 specifier: ^2.1.8
version: 2.1.8 version: 2.1.8
@ -19,6 +19,9 @@ importers:
'@ragempcommunity/types-server': '@ragempcommunity/types-server':
specifier: ^2.1.8 specifier: ^2.1.8
version: 2.1.8 version: 2.1.8
'@types/node':
specifier: ^22.8.1
version: 22.8.1
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^7.13.0 specifier: ^7.13.0
version: 7.13.0(@typescript-eslint/parser@7.13.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) version: 7.13.0(@typescript-eslint/parser@7.13.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
@ -39,7 +42,7 @@ importers:
version: 0.4.0 version: 0.4.0
tsup: tsup:
specifier: ^8.1.0 specifier: ^8.1.0
version: 8.1.0(@microsoft/api-extractor@7.47.0(@types/node@20.14.2))(typescript@5.4.5) version: 8.1.0(@microsoft/api-extractor@7.47.0(@types/node@22.8.1))(typescript@5.4.5)
typescript: typescript:
specifier: ^5.4.5 specifier: ^5.4.5
version: 5.4.5 version: 5.4.5
@ -47,75 +50,41 @@ importers:
specifier: ^3.13.0 specifier: ^3.13.0
version: 3.13.0 version: 3.13.0
cef: browser:
dependencies: dependencies:
'@entityseven/rage-fw-rpc':
specifier: latest
version: 0.2.5(typescript@5.4.5)
'@entityseven/rage-fw-shared-types':
specifier: workspace:^
version: link:../shared-types
'@ragempcommunity/types-cef': '@ragempcommunity/types-cef':
specifier: ^2.1.8 specifier: ^2.1.8
version: 2.1.8 version: 2.1.8
rage-fw-shared-types:
specifier: workspace:^
version: link:../shared-types
rage-rpc:
specifier: ^0.4.0
version: 0.4.0
cli:
dependencies:
'@inquirer/prompts':
specifier: ^5.0.5
version: 5.0.5
axios:
specifier: ^1.7.2
version: 1.7.2
chalk:
specifier: 4.1.2
version: 4.1.2
git-clone:
specifier: ^0.2.0
version: 0.2.0
yargs:
specifier: ^17.7.2
version: 17.7.2
devDependencies:
'@types/git-clone':
specifier: ^0.2.4
version: 0.2.4
'@types/node':
specifier: ^20.14.2
version: 20.14.2
'@types/yargs':
specifier: ^17.0.32
version: 17.0.32
prettier:
specifier: ^3.3.2
version: 3.3.2
typescript:
specifier: ^5.4.5
version: 5.4.5
client: client:
dependencies: dependencies:
'@entityseven/rage-fw-rpc':
specifier: latest
version: 0.2.5(typescript@5.4.5)
'@entityseven/rage-fw-shared-types':
specifier: workspace:^
version: link:../shared-types
'@ragempcommunity/types-client': '@ragempcommunity/types-client':
specifier: ^2.1.8 specifier: ^2.1.8
version: 2.1.8 version: 2.1.8
rage-fw-shared-types:
specifier: workspace:^
version: link:../shared-types
rage-rpc:
specifier: ^0.4.0
version: 0.4.0
server: server:
dependencies: dependencies:
'@entityseven/rage-fw-rpc':
specifier: latest
version: 0.2.5(typescript@5.4.5)
'@entityseven/rage-fw-shared-types':
specifier: workspace:^
version: link:../shared-types
'@ragempcommunity/types-server': '@ragempcommunity/types-server':
specifier: ^2.1.8 specifier: ^2.1.8
version: 2.1.8 version: 2.1.8
rage-fw-shared-types:
specifier: workspace:^
version: link:../shared-types
rage-rpc:
specifier: ^0.4.0
version: 0.4.0
shared-types: {} shared-types: {}
@ -154,6 +123,14 @@ packages:
integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==, integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==,
} }
'@entityseven/rage-fw-rpc@0.2.5':
resolution:
{
integrity: sha512-U9V+eZTQbkL+JZEU3TS+fCroPyvSvBXICLikcC/h9gCNHhFiS6vm+W+92LqGzBi3K+aZEnsx57hAhhObB03g0w==,
}
peerDependencies:
typescript: ^5
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.21.5':
resolution: resolution:
{ {
@ -420,90 +397,6 @@ packages:
} }
engines: { node: '>=6.9.0' } engines: { node: '>=6.9.0' }
'@inquirer/checkbox@2.3.5':
resolution:
{
integrity: sha512-3V0OSykTkE/38GG1DhxRGLBmqefgzRg2EK5A375zz+XEvIWfAHcac31e+zlBDPypRHxhmXc/Oh6v9eOPbH3nAg==,
}
engines: { node: '>=18' }
'@inquirer/confirm@3.1.9':
resolution:
{
integrity: sha512-UF09aejxCi4Xqm6N/jJAiFXArXfi9al52AFaSD+2uIHnhZGtd1d6lIGTRMPouVSJxbGEi+HkOWSYaiEY/+szUw==,
}
engines: { node: '>=18' }
'@inquirer/core@8.2.2':
resolution:
{
integrity: sha512-K8SuNX45jEFlX3EBJpu9B+S2TISzMPGXZIuJ9ME924SqbdW6Pt6fIkKvXg7mOEOKJ4WxpQsxj0UTfcL/A434Ww==,
}
engines: { node: '>=18' }
'@inquirer/editor@2.1.9':
resolution:
{
integrity: sha512-5xCD7CoCh993YqXcsZPt45qkE3gl+03Yfv9vmAkptRi4nrzaUDmyhgBzndKdRG8SrKbQLBmOtztnRLGxvG/ahg==,
}
engines: { node: '>=18' }
'@inquirer/expand@2.1.9':
resolution:
{
integrity: sha512-ymnR8qu2ie/3JpOeyZ3QSGJ+ai8qqtjBwopxLjzIZm7mZVKT6SV1sURzijkOLRgGUHwPemOfYX5biqXuqhpoBg==,
}
engines: { node: '>=18' }
'@inquirer/figures@1.0.3':
resolution:
{
integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==,
}
engines: { node: '>=18' }
'@inquirer/input@2.1.9':
resolution:
{
integrity: sha512-1xTCHmIe48x9CG1+8glAHrVVdH+QfYhzgBUbgyoVpp5NovnXgRcjSn/SNulepxf9Ol8HDq3gzw3ZCAUr+h1Eyg==,
}
engines: { node: '>=18' }
'@inquirer/password@2.1.9':
resolution:
{
integrity: sha512-QPtVcT12Fkn0TyuZJelR7QOtc5l1d/6pB5EfkHOivTzC6QTFxRCHl+Gx7Q3E2U/kgJeCCmDov6itDFggk9nkgA==,
}
engines: { node: '>=18' }
'@inquirer/prompts@5.0.5':
resolution:
{
integrity: sha512-LV2XZzc8ls4zhUzYNSpsXcnA8djOptY4G01lFzp3Bey6E1oiZMzIU25N9cb5AOwNz6pqDXpjLwRFQmLQ8h6PaQ==,
}
engines: { node: '>=18' }
'@inquirer/rawlist@2.1.9':
resolution:
{
integrity: sha512-GuMmfa/v1ZJqEWSkUx1hMxzs5/0DCUP0S8IicV/wu8QrbjfBOh+7mIQgtsvh8IJ3sRkRcQ+9wh9CE9jiYqyMgw==,
}
engines: { node: '>=18' }
'@inquirer/select@2.3.5':
resolution:
{
integrity: sha512-IyBj8oEtmdF2Gx4FJTPtEya37MD6s0KATKsHqgmls0lK7EQbhYSq9GQlcFq6cBsYe/cgQ0Fg2cCqYYPi/d/fxQ==,
}
engines: { node: '>=18' }
'@inquirer/type@1.3.3':
resolution:
{
integrity: sha512-xTUt0NulylX27/zMx04ZYar/kr1raaiFTVvQ5feljQsiAgdm0WPj4S73/ye0fbslh+15QrIuDvfCXTek7pMY5A==,
}
engines: { node: '>=18' }
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
resolution: resolution:
{ {
@ -1187,12 +1080,6 @@ packages:
integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==, integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==,
} }
'@types/git-clone@0.2.4':
resolution:
{
integrity: sha512-1ybApDpKU12dychtOp2zBe93ZwAsxVSjOqKUqH7NCDm4GXuPnjmcz2P9K2S1z+BCX2AnLmFFuB6pI6CMZ3j9sQ==,
}
'@types/minimatch@3.0.5': '@types/minimatch@3.0.5':
resolution: resolution:
{ {
@ -1205,16 +1092,10 @@ packages:
integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==, integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==,
} }
'@types/mute-stream@0.0.4': '@types/node@22.8.1':
resolution: resolution:
{ {
integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==, integrity: sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==,
}
'@types/node@20.14.2':
resolution:
{
integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==,
} }
'@types/normalize-package-data@2.4.4': '@types/normalize-package-data@2.4.4':
@ -1229,24 +1110,6 @@ packages:
integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==, integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==,
} }
'@types/wrap-ansi@3.0.0':
resolution:
{
integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==,
}
'@types/yargs-parser@21.0.3':
resolution:
{
integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==,
}
'@types/yargs@17.0.32':
resolution:
{
integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==,
}
'@typescript-eslint/eslint-plugin@7.13.0': '@typescript-eslint/eslint-plugin@7.13.0':
resolution: resolution:
{ {
@ -1821,13 +1684,6 @@ packages:
} }
engines: { node: '>= 10' } engines: { node: '>= 10' }
cli-width@4.1.0:
resolution:
{
integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==,
}
engines: { node: '>= 12' }
cliui@7.0.4: cliui@7.0.4:
resolution: resolution:
{ {
@ -2300,6 +2156,7 @@ packages:
integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==, integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==,
} }
engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 }
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true hasBin: true
espree@9.6.1: espree@9.6.1:
@ -2619,12 +2476,6 @@ packages:
} }
engines: { node: '>=10' } engines: { node: '>=10' }
git-clone@0.2.0:
resolution:
{
integrity: sha512-1UAkEPIFbyjHaddljUKvPhhLRnrKaImT71T7rdvSvWLXw95nLdhdi6Qmlx0KOWoV1qqvHGLq5lMLJEZM0JXk8A==,
}
git-raw-commits@3.0.0: git-raw-commits@3.0.0:
resolution: resolution:
{ {
@ -5253,10 +5104,10 @@ packages:
engines: { node: '>=0.8.0' } engines: { node: '>=0.8.0' }
hasBin: true hasBin: true
undici-types@5.26.5: undici-types@6.19.8:
resolution: resolution:
{ {
integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==, integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==,
} }
unique-filename@3.0.0: unique-filename@3.0.0:
@ -5557,6 +5408,10 @@ snapshots:
enabled: 2.0.0 enabled: 2.0.0
kuler: 2.0.0 kuler: 2.0.0
'@entityseven/rage-fw-rpc@0.2.5(typescript@5.4.5)':
dependencies:
typescript: 5.4.5
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.21.5':
optional: true optional: true
@ -5663,87 +5518,6 @@ snapshots:
'@hutson/parse-repository-url@3.0.2': {} '@hutson/parse-repository-url@3.0.2': {}
'@inquirer/checkbox@2.3.5':
dependencies:
'@inquirer/core': 8.2.2
'@inquirer/figures': 1.0.3
'@inquirer/type': 1.3.3
ansi-escapes: 4.3.2
chalk: 4.1.2
'@inquirer/confirm@3.1.9':
dependencies:
'@inquirer/core': 8.2.2
'@inquirer/type': 1.3.3
'@inquirer/core@8.2.2':
dependencies:
'@inquirer/figures': 1.0.3
'@inquirer/type': 1.3.3
'@types/mute-stream': 0.0.4
'@types/node': 20.14.2
'@types/wrap-ansi': 3.0.0
ansi-escapes: 4.3.2
chalk: 4.1.2
cli-spinners: 2.9.2
cli-width: 4.1.0
mute-stream: 1.0.0
signal-exit: 4.1.0
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
'@inquirer/editor@2.1.9':
dependencies:
'@inquirer/core': 8.2.2
'@inquirer/type': 1.3.3
external-editor: 3.1.0
'@inquirer/expand@2.1.9':
dependencies:
'@inquirer/core': 8.2.2
'@inquirer/type': 1.3.3
chalk: 4.1.2
'@inquirer/figures@1.0.3': {}
'@inquirer/input@2.1.9':
dependencies:
'@inquirer/core': 8.2.2
'@inquirer/type': 1.3.3
'@inquirer/password@2.1.9':
dependencies:
'@inquirer/core': 8.2.2
'@inquirer/type': 1.3.3
ansi-escapes: 4.3.2
'@inquirer/prompts@5.0.5':
dependencies:
'@inquirer/checkbox': 2.3.5
'@inquirer/confirm': 3.1.9
'@inquirer/editor': 2.1.9
'@inquirer/expand': 2.1.9
'@inquirer/input': 2.1.9
'@inquirer/password': 2.1.9
'@inquirer/rawlist': 2.1.9
'@inquirer/select': 2.3.5
'@inquirer/rawlist@2.1.9':
dependencies:
'@inquirer/core': 8.2.2
'@inquirer/type': 1.3.3
chalk: 4.1.2
'@inquirer/select@2.3.5':
dependencies:
'@inquirer/core': 8.2.2
'@inquirer/figures': 1.0.3
'@inquirer/type': 1.3.3
ansi-escapes: 4.3.2
chalk: 4.1.2
'@inquirer/type@1.3.3': {}
'@isaacs/cliui@8.0.2': '@isaacs/cliui@8.0.2':
dependencies: dependencies:
string-width: 5.1.2 string-width: 5.1.2
@ -5849,23 +5623,23 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@microsoft/api-extractor-model@7.29.2(@types/node@20.14.2)': '@microsoft/api-extractor-model@7.29.2(@types/node@22.8.1)':
dependencies: dependencies:
'@microsoft/tsdoc': 0.15.0 '@microsoft/tsdoc': 0.15.0
'@microsoft/tsdoc-config': 0.17.0 '@microsoft/tsdoc-config': 0.17.0
'@rushstack/node-core-library': 5.4.1(@types/node@20.14.2) '@rushstack/node-core-library': 5.4.1(@types/node@22.8.1)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
'@microsoft/api-extractor@7.47.0(@types/node@20.14.2)': '@microsoft/api-extractor@7.47.0(@types/node@22.8.1)':
dependencies: dependencies:
'@microsoft/api-extractor-model': 7.29.2(@types/node@20.14.2) '@microsoft/api-extractor-model': 7.29.2(@types/node@22.8.1)
'@microsoft/tsdoc': 0.15.0 '@microsoft/tsdoc': 0.15.0
'@microsoft/tsdoc-config': 0.17.0 '@microsoft/tsdoc-config': 0.17.0
'@rushstack/node-core-library': 5.4.1(@types/node@20.14.2) '@rushstack/node-core-library': 5.4.1(@types/node@22.8.1)
'@rushstack/rig-package': 0.5.2 '@rushstack/rig-package': 0.5.2
'@rushstack/terminal': 0.13.0(@types/node@20.14.2) '@rushstack/terminal': 0.13.0(@types/node@22.8.1)
'@rushstack/ts-command-line': 4.22.0(@types/node@20.14.2) '@rushstack/ts-command-line': 4.22.0(@types/node@22.8.1)
lodash: 4.17.21 lodash: 4.17.21
minimatch: 3.0.8 minimatch: 3.0.8
resolve: 1.22.8 resolve: 1.22.8
@ -6144,7 +5918,7 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.18.0': '@rollup/rollup-win32-x64-msvc@4.18.0':
optional: true optional: true
'@rushstack/node-core-library@5.4.1(@types/node@20.14.2)': '@rushstack/node-core-library@5.4.1(@types/node@22.8.1)':
dependencies: dependencies:
ajv: 8.13.0 ajv: 8.13.0
ajv-draft-04: 1.0.0(ajv@8.13.0) ajv-draft-04: 1.0.0(ajv@8.13.0)
@ -6155,23 +5929,23 @@ snapshots:
resolve: 1.22.8 resolve: 1.22.8
semver: 7.5.4 semver: 7.5.4
optionalDependencies: optionalDependencies:
'@types/node': 20.14.2 '@types/node': 22.8.1
'@rushstack/rig-package@0.5.2': '@rushstack/rig-package@0.5.2':
dependencies: dependencies:
resolve: 1.22.8 resolve: 1.22.8
strip-json-comments: 3.1.1 strip-json-comments: 3.1.1
'@rushstack/terminal@0.13.0(@types/node@20.14.2)': '@rushstack/terminal@0.13.0(@types/node@22.8.1)':
dependencies: dependencies:
'@rushstack/node-core-library': 5.4.1(@types/node@20.14.2) '@rushstack/node-core-library': 5.4.1(@types/node@22.8.1)
supports-color: 8.1.1 supports-color: 8.1.1
optionalDependencies: optionalDependencies:
'@types/node': 20.14.2 '@types/node': 22.8.1
'@rushstack/ts-command-line@4.22.0(@types/node@20.14.2)': '@rushstack/ts-command-line@4.22.0(@types/node@22.8.1)':
dependencies: dependencies:
'@rushstack/terminal': 0.13.0(@types/node@20.14.2) '@rushstack/terminal': 0.13.0(@types/node@22.8.1)
'@types/argparse': 1.0.38 '@types/argparse': 1.0.38
argparse: 1.0.10 argparse: 1.0.10
string-argv: 0.3.2 string-argv: 0.3.2
@ -6253,32 +6027,18 @@ snapshots:
'@types/estree@1.0.5': {} '@types/estree@1.0.5': {}
'@types/git-clone@0.2.4': {}
'@types/minimatch@3.0.5': {} '@types/minimatch@3.0.5': {}
'@types/minimist@1.2.5': {} '@types/minimist@1.2.5': {}
'@types/mute-stream@0.0.4': '@types/node@22.8.1':
dependencies: dependencies:
'@types/node': 20.14.2 undici-types: 6.19.8
'@types/node@20.14.2':
dependencies:
undici-types: 5.26.5
'@types/normalize-package-data@2.4.4': {} '@types/normalize-package-data@2.4.4': {}
'@types/triple-beam@1.3.5': {} '@types/triple-beam@1.3.5': {}
'@types/wrap-ansi@3.0.0': {}
'@types/yargs-parser@21.0.3': {}
'@types/yargs@17.0.32':
dependencies:
'@types/yargs-parser': 21.0.3
'@typescript-eslint/eslint-plugin@7.13.0(@typescript-eslint/parser@7.13.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': '@typescript-eslint/eslint-plugin@7.13.0(@typescript-eslint/parser@7.13.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)':
dependencies: dependencies:
'@eslint-community/regexpp': 4.10.1 '@eslint-community/regexpp': 4.10.1
@ -6637,8 +6397,6 @@ snapshots:
cli-width@3.0.0: {} cli-width@3.0.0: {}
cli-width@4.1.0: {}
cliui@7.0.4: cliui@7.0.4:
dependencies: dependencies:
string-width: 4.2.3 string-width: 4.2.3
@ -7144,8 +6902,6 @@ snapshots:
get-stream@6.0.1: {} get-stream@6.0.1: {}
git-clone@0.2.0: {}
git-raw-commits@3.0.0: git-raw-commits@3.0.0:
dependencies: dependencies:
dargs: 7.0.0 dargs: 7.0.0
@ -7891,7 +7647,7 @@ snapshots:
array-differ: 3.0.0 array-differ: 3.0.0
array-union: 2.1.0 array-union: 2.1.0
arrify: 2.0.1 arrify: 2.0.1
minimatch: 3.0.5 minimatch: 3.1.2
mute-stream@0.0.8: {} mute-stream@0.0.8: {}
@ -8767,7 +8523,7 @@ snapshots:
tslib@2.6.3: {} tslib@2.6.3: {}
tsup@8.1.0(@microsoft/api-extractor@7.47.0(@types/node@20.14.2))(typescript@5.4.5): tsup@8.1.0(@microsoft/api-extractor@7.47.0(@types/node@22.8.1))(typescript@5.4.5):
dependencies: dependencies:
bundle-require: 4.2.1(esbuild@0.21.5) bundle-require: 4.2.1(esbuild@0.21.5)
cac: 6.7.14 cac: 6.7.14
@ -8784,7 +8540,7 @@ snapshots:
sucrase: 3.35.0 sucrase: 3.35.0
tree-kill: 1.2.2 tree-kill: 1.2.2
optionalDependencies: optionalDependencies:
'@microsoft/api-extractor': 7.47.0(@types/node@20.14.2) '@microsoft/api-extractor': 7.47.0(@types/node@22.8.1)
typescript: 5.4.5 typescript: 5.4.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -8831,7 +8587,7 @@ snapshots:
uglify-js@3.18.0: uglify-js@3.18.0:
optional: true optional: true
undici-types@5.26.5: {} undici-types@6.19.8: {}
unique-filename@3.0.0: unique-filename@3.0.0:
dependencies: dependencies:

View File

@ -1,6 +1,5 @@
packages: packages:
- "server" - "server"
- "client" - "client"
- "cef" - "browser"
- "cli"
- "shared-types" - "shared-types"

View File

@ -1 +0,0 @@
Currently not maintained.

View File

@ -1,24 +0,0 @@
{
"name": "rage-fw-rpc",
"version": "0.0.23-alpha.0",
"main": "dist/index.js",
"types": "dist/src/index.d.ts",
"files": [
"dist/**/*"
],
"scripts": {
"build": "tsup"
},
"dependencies": {
"rage-rpc": "^0.4.0"
},
"peerDependencies": {
"@ragempcommunity/types-client": "^2.1.8",
"rage-fw-shared-types": "workspace:^"
},
"keywords": [],
"author": "SashaGoncharov19",
"license": "MIT",
"description": "Client side of rage-fw",
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0"
}

View File

@ -1,42 +0,0 @@
declare var mp: any;
declare var global: any;
declare var window: any;
declare type ProcedureListener = (args: any, info: ProcedureListenerInfo) => any;
declare interface Player {
call: (eventName: string, args?: any[]) => void;
[property: string]: any;
}
declare interface Browser {
url: string;
execute: (code: string) => void;
[property: string]: any;
}
declare interface ProcedureListenerInfo {
environment: string;
id?: string;
player?: Player;
browser?: Browser;
}
declare interface CallOptions {
timeout?: number;
noRet?: boolean;
}
declare interface Event {
req?: number;
ret?: number;
b?: string;
id: string;
name?: string;
args?: any;
env: string;
fenv?: string;
res?: any;
err?: any;
noRet?: number;
}

View File

@ -1,568 +0,0 @@
import * as util from './util';
const environment = util.getEnvironment();
if(!environment) throw 'Unknown RAGE environment';
const ERR_NOT_FOUND = 'PROCEDURE_NOT_FOUND';
const IDENTIFIER = '__rpc:id';
const PROCESS_EVENT = '__rpc:process';
const BROWSER_REGISTER = '__rpc:browserRegister';
const BROWSER_UNREGISTER = '__rpc:browserUnregister';
const TRIGGER_EVENT = '__rpc:triggerEvent';
const TRIGGER_EVENT_BROWSERS = '__rpc:triggerEventBrowsers';
const glob = environment === 'cef' ? window : global;
if(!glob[PROCESS_EVENT]){
glob.__rpcListeners = {};
glob.__rpcPending = {};
glob.__rpcEvListeners = {};
glob[PROCESS_EVENT] = (player: Player | string, rawData?: string) => {
if(environment !== "server") rawData = player as string;
const data: Event = util.parseData(rawData);
if(data.req){ // someone is trying to remotely call a procedure
const info: ProcedureListenerInfo = {
id: data.id,
environment: data.fenv || data.env
};
if(environment === "server") info.player = player as Player;
const part = {
ret: 1,
id: data.id,
env: environment
};
let ret: (ev: Event) => void;
switch(environment){
case "server":
ret = ev => info.player.call(PROCESS_EVENT, [util.stringifyData(ev)]);
break;
case "client": {
if(data.env === "server"){
ret = ev => mp.events.callRemote(PROCESS_EVENT, util.stringifyData(ev));
}else if(data.env === "cef"){
const browser = data.b && glob.__rpcBrowsers[data.b];
info.browser = browser;
ret = ev => browser && util.isBrowserValid(browser) && passEventToBrowser(browser, ev, true);
}
break;
}
case "cef": {
ret = ev => mp.trigger(PROCESS_EVENT, util.stringifyData(ev));
}
}
if(ret){
const promise = callProcedure(data.name, data.args, info);
if(!data.noRet) promise.then(res => ret({ ...part, res })).catch(err => ret({ ...part, err: err ? err : null }));
}
}else if(data.ret){ // a previously called remote procedure has returned
const info = glob.__rpcPending[data.id];
if(environment === "server" && info.player !== player) return;
if(info){
info.resolve(data.hasOwnProperty('err') ? util.promiseReject(data.err) : util.promiseResolve(data.res));
delete glob.__rpcPending[data.id];
}
}
};
if(environment !== "cef"){
mp.events.add(PROCESS_EVENT, glob[PROCESS_EVENT]);
if(environment === "client"){
// set up internal pass-through events
register('__rpc:callServer', ([name, args, noRet], info) => _callServer(name, args, { fenv: info.environment, noRet }));
register('__rpc:callBrowsers', ([name, args, noRet], info) => _callBrowsers(null, name, args, { fenv: info.environment, noRet }));
// set up browser identifiers
glob.__rpcBrowsers = {};
const initBrowser = (browser: Browser): void => {
const id = util.uid();
Object.keys(glob.__rpcBrowsers).forEach(key => {
const b = glob.__rpcBrowsers[key];
if(!b || !util.isBrowserValid(b) || b === browser) delete glob.__rpcBrowsers[key];
});
glob.__rpcBrowsers[id] = browser;
browser.execute(`
window.name = '${id}';
if(typeof window['${IDENTIFIER}'] === 'undefined'){
window['${IDENTIFIER}'] = Promise.resolve(window.name);
}else{
window['${IDENTIFIER}:resolve'](window.name);
}
`);
};
mp.browsers.forEach(initBrowser);
mp.events.add('browserCreated', initBrowser);
// set up browser registration map
glob.__rpcBrowserProcedures = {};
mp.events.add(BROWSER_REGISTER, (data: string) => {
const [browserId, name] = JSON.parse(data);
glob.__rpcBrowserProcedures[name] = browserId;
});
mp.events.add(BROWSER_UNREGISTER, (data: string) => {
const [browserId, name] = JSON.parse(data);
if(glob.__rpcBrowserProcedures[name] === browserId) delete glob.__rpcBrowserProcedures[name];
});
register(TRIGGER_EVENT_BROWSERS, ([name, args], info) => {
Object.values(glob.__rpcBrowsers).forEach(browser => {
_callBrowser(browser, TRIGGER_EVENT, [name, args], { fenv: info.environment, noRet: 1 });
});
});
}
}else{
if(typeof glob[IDENTIFIER] === 'undefined'){
glob[IDENTIFIER] = new Promise(resolve => {
if (window.name) {
resolve(window.name);
}else{
glob[IDENTIFIER+':resolve'] = resolve;
}
});
}
}
register(TRIGGER_EVENT, ([name, args], info) => callEvent(name, args, info));
}
function passEventToBrowser(browser: Browser, data: Event, ignoreNotFound: boolean): void {
const raw = util.stringifyData(data);
browser.execute(`var process = window["${PROCESS_EVENT}"]; if(process){ process(${JSON.stringify(raw)}); }else{ ${ignoreNotFound ? '' : `mp.trigger("${PROCESS_EVENT}", '{"ret":1,"id":"${data.id}","err":"${ERR_NOT_FOUND}","env":"cef"}');`} }`);
}
function callProcedure(name: string, args: any, info: ProcedureListenerInfo): Promise<any> {
const listener = glob.__rpcListeners[name];
if(!listener) return util.promiseReject(ERR_NOT_FOUND);
return util.promiseResolve(listener(args, info));
}
/**
* Register a procedure.
* @param {string} name - The name of the procedure.
* @param {function} cb - The procedure's callback. The return value will be sent back to the caller.
* @returns {Function} The function, which unregister the event.
*/
export function register(name: string, cb: ProcedureListener): Function {
if(arguments.length !== 2) throw 'register expects 2 arguments: "name" and "cb"';
if(environment === "cef") glob[IDENTIFIER].then((id: string) => mp.trigger(BROWSER_REGISTER, JSON.stringify([id, name])));
glob.__rpcListeners[name] = cb;
return () => unregister(name);
}
/**
* Unregister a procedure.
* @param {string} name - The name of the procedure.
*/
export function unregister(name: string): void {
if(arguments.length !== 1) throw 'unregister expects 1 argument: "name"';
if(environment === "cef") glob[IDENTIFIER].then((id: string) => mp.trigger(BROWSER_UNREGISTER, JSON.stringify([id, name])));
glob.__rpcListeners[name] = undefined;
}
/**
* Calls a local procedure. Only procedures registered in the same context will be resolved.
*
* Can be called from any environment.
*
* @param name - The name of the locally registered procedure.
* @param args - Any parameters for the procedure.
* @param options - Any options.
* @returns The result from the procedure.
*/
export function call(name: string, args?: any, options: CallOptions = {}): Promise<any> {
if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('call expects 1 to 3 arguments: "name", optional "args", and optional "options"');
return util.promiseTimeout(callProcedure(name, args, { environment }), options.timeout);
}
function _callServer(name: string, args?: any, extraData: any = {}): Promise<any> {
switch(environment){
case "server": {
return call(name, args);
}
case "client": {
const id = util.uid();
return new Promise(resolve => {
if(!extraData.noRet){
glob.__rpcPending[id] = {
resolve
};
}
const event: Event = {
req: 1,
id,
name,
env: environment,
args,
...extraData
};
mp.events.callRemote(PROCESS_EVENT, util.stringifyData(event));
});
}
case "cef": {
return callClient('__rpc:callServer', [name, args, +extraData.noRet]);
}
}
}
/**
* Calls a remote procedure registered on the server.
*
* Can be called from any environment.
*
* @param name - The name of the registered procedure.
* @param args - Any parameters for the procedure.
* @param options - Any options.
* @returns The result from the procedure.
*/
export function callServer(name: string, args?: any, options: CallOptions = {}): Promise<any> {
if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('callServer expects 1 to 3 arguments: "name", optional "args", and optional "options"');
let extraData: any = {};
if(options.noRet) extraData.noRet = 1;
return util.promiseTimeout(_callServer(name, args, extraData), options.timeout);
}
function _callClient(player: Player, name: string, args?: any, extraData: any = {}): Promise<any> {
switch(environment){
case 'client': {
return call(name, args);
}
case 'server': {
const id = util.uid();
return new Promise(resolve => {
if(!extraData.noRet){
glob.__rpcPending[id] = {
resolve,
player
};
}
const event: Event = {
req: 1,
id,
name,
env: environment,
args,
...extraData
};
player.call(PROCESS_EVENT, [util.stringifyData(event)]);
});
}
case 'cef': {
const id = util.uid();
return glob[IDENTIFIER].then((browserId: string) => {
return new Promise(resolve => {
if(!extraData.noRet){
glob.__rpcPending[id] = {
resolve
};
}
const event: Event = {
b: browserId,
req: 1,
id,
name,
env: environment,
args,
...extraData
};
mp.trigger(PROCESS_EVENT, util.stringifyData(event));
});
});
}
}
}
/**
* Calls a remote procedure registered on the client.
*
* Can be called from any environment.
*
* @param player - The player to call the procedure on.
* @param name - The name of the registered procedure.
* @param args - Any parameters for the procedure.
* @param options - Any options.
* @returns The result from the procedure.
*/
export function callClient(player: Player | string, name?: string | any, args?: any, options: CallOptions = {}): Promise<any> {
switch(environment){
case 'client': {
options = args || {};
args = name;
name = player;
player = null;
if((arguments.length < 1 || arguments.length > 3) || typeof name !== 'string') return util.promiseReject('callClient from the client expects 1 to 3 arguments: "name", optional "args", and optional "options"');
break;
}
case 'server': {
if((arguments.length < 2 || arguments.length > 4) || typeof player !== 'object') return util.promiseReject('callClient from the server expects 2 to 4 arguments: "player", "name", optional "args", and optional "options"');
break;
}
case 'cef': {
options = args || {};
args = name;
name = player;
player = null;
if((arguments.length < 1 || arguments.length > 3) || typeof name !== 'string') return util.promiseReject('callClient from the browser expects 1 to 3 arguments: "name", optional "args", and optional "options"');
break;
}
}
let extraData: any = {};
if(options.noRet) extraData.noRet = 1;
return util.promiseTimeout(_callClient(player as Player, name, args, extraData), options.timeout);
}
function _callBrowser(browser: Browser, name: string, args?: any, extraData: any = {}): Promise<any> {
return new Promise(resolve => {
const id = util.uid();
if(!extraData.noRet){
glob.__rpcPending[id] = {
resolve
};
}
passEventToBrowser(browser, {
req: 1,
id,
name,
env: environment,
args,
...extraData
}, false);
});
}
function _callBrowsers(player: Player, name: string, args?: any, extraData: any = {}): Promise<any> {
switch(environment){
case 'client':
const browserId = glob.__rpcBrowserProcedures[name];
if(!browserId) return util.promiseReject(ERR_NOT_FOUND);
const browser = glob.__rpcBrowsers[browserId];
if(!browser || !util.isBrowserValid(browser)) return util.promiseReject(ERR_NOT_FOUND);
return _callBrowser(browser, name, args, extraData);
case 'server':
return _callClient(player, '__rpc:callBrowsers', [name, args, +extraData.noRet], extraData);
case 'cef':
return _callClient(null, '__rpc:callBrowsers', [name, args, +extraData.noRet], extraData);
}
}
/**
* Calls a remote procedure registered in any browser context.
*
* Can be called from any environment.
*
* @param player - The player to call the procedure on.
* @param name - The name of the registered procedure.
* @param args - Any parameters for the procedure.
* @param options - Any options.
* @returns The result from the procedure.
*/
export function callBrowsers(player: Player | string, name?: string | any, args?: any, options: CallOptions = {}): Promise<any> {
let promise;
let extraData: any = {};
switch(environment){
case 'client':
case 'cef':
options = args || {};
args = name;
name = player;
if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('callBrowsers from the client or browser expects 1 to 3 arguments: "name", optional "args", and optional "options"');
if(options.noRet) extraData.noRet = 1;
promise = _callBrowsers(null, name, args, extraData);
break;
case 'server':
if(arguments.length < 2 || arguments.length > 4) return util.promiseReject('callBrowsers from the server expects 2 to 4 arguments: "player", "name", optional "args", and optional "options"');
if(options.noRet) extraData.noRet = 1;
promise = _callBrowsers(player as Player, name, args, extraData);
break;
}
if(promise){
return util.promiseTimeout(promise, options.timeout);
}
}
/**
* Calls a remote procedure registered in a specific browser instance.
*
* Client-side environment only.
*
* @param browser - The browser instance.
* @param name - The name of the registered procedure.
* @param args - Any parameters for the procedure.
* @param options - Any options.
* @returns The result from the procedure.
*/
export function callBrowser(browser: Browser, name: string, args?: any, options: CallOptions = {}): Promise<any> {
if(environment !== 'client') return util.promiseReject('callBrowser can only be used in the client environment');
if(arguments.length < 2 || arguments.length > 4) return util.promiseReject('callBrowser expects 2 to 4 arguments: "browser", "name", optional "args", and optional "options"');
let extraData: any = {};
if(options.noRet) extraData.noRet = 1;
return util.promiseTimeout(_callBrowser(browser, name, args, extraData), options.timeout);
}
function callEvent(name: string, args: any, info: ProcedureListenerInfo){
const listeners = glob.__rpcEvListeners[name];
if(listeners){
listeners.forEach(listener => listener(args, info));
}
}
/**
* Register an event handler.
* @param {string} name - The name of the event.
* @param cb - The callback for the event.
* @returns {Function} The function, which off the event.
*/
export function on(name: string, cb: ProcedureListener): Function {
if(arguments.length !== 2) throw 'on expects 2 arguments: "name" and "cb"';
const listeners = glob.__rpcEvListeners[name] || new Set();
listeners.add(cb);
glob.__rpcEvListeners[name] = listeners;
return () => off(name, cb);
}
/**
* Unregister an event handler.
* @param {string} name - The name of the event.
* @param cb - The callback for the event.
*/
export function off(name: string, cb: ProcedureListener){
if(arguments.length !== 2) throw 'off expects 2 arguments: "name" and "cb"';
const listeners = glob.__rpcEvListeners[name];
if(listeners){
listeners.delete(cb);
}
}
/**
* Triggers a local event. Only events registered in the same context will be triggered.
*
* Can be called from any environment.
*
* @param name - The name of the locally registered event.
* @param args - Any parameters for the event.
*/
export function trigger(name: string, args?: any){
if(arguments.length < 1 || arguments.length > 2) throw 'trigger expects 1 or 2 arguments: "name", and optional "args"';
callEvent(name, args, { environment });
}
/**
* Triggers an event registered on the client.
*
* Can be called from any environment.
*
* @param player - The player to call the procedure on.
* @param name - The name of the event.
* @param args - Any parameters for the event.
*/
export function triggerClient(player: Player | string, name?: string | any, args?: any){
switch(environment){
case 'client': {
args = name;
name = player;
player = null;
if((arguments.length < 1 || arguments.length > 2) || typeof name !== 'string') throw 'triggerClient from the client expects 1 or 2 arguments: "name", and optional "args"';
break;
}
case 'server': {
if((arguments.length < 2 || arguments.length > 3) || typeof player !== 'object') throw 'triggerClient from the server expects 2 or 3 arguments: "player", "name", and optional "args"';
break;
}
case 'cef': {
args = name;
name = player;
player = null;
if((arguments.length < 1 || arguments.length > 2) || typeof name !== 'string') throw 'triggerClient from the browser expects 1 or 2 arguments: "name", and optional "args"';
break;
}
}
_callClient(player as Player, TRIGGER_EVENT, [name, args], { noRet: 1 });
}
/**
* Triggers an event registered on the server.
*
* Can be called from any environment.
*
* @param name - The name of the event.
* @param args - Any parameters for the event.
*/
export function triggerServer(name: string, args?: any){
if(arguments.length < 1 || arguments.length > 2) throw 'triggerServer expects 1 or 2 arguments: "name", and optional "args"';
_callServer(TRIGGER_EVENT, [name, args], { noRet: 1 });
}
/**
* Triggers an event registered in any browser context.
*
* Can be called from any environment.
*
* @param player - The player to call the procedure on.
* @param name - The name of the event.
* @param args - Any parameters for the event.
*/
export function triggerBrowsers(player: Player | string, name?: string | any, args?: any){
switch(environment){
case 'client':
case 'cef':
args = name;
name = player;
player = null;
if(arguments.length < 1 || arguments.length > 2) throw 'triggerBrowsers from the client or browser expects 1 or 2 arguments: "name", and optional "args"';
break;
case 'server':
if(arguments.length < 2 || arguments.length > 3) throw 'triggerBrowsers from the server expects 2 or 3 arguments: "player", "name", and optional "args"';
break;
}
_callClient(player as Player, TRIGGER_EVENT_BROWSERS, [name, args], { noRet: 1 });
}
/**
* Triggers an event registered in a specific browser instance.
*
* Client-side environment only.
*
* @param browser - The browser instance.
* @param name - The name of the event.
* @param args - Any parameters for the event.
*/
export function triggerBrowser(browser: Browser, name: string, args?: any){
if(environment !== 'client') throw 'callBrowser can only be used in the client environment';
if(arguments.length < 2 || arguments.length > 4) throw 'callBrowser expects 2 or 3 arguments: "browser", "name", and optional "args"';
_callBrowser(browser, TRIGGER_EVENT, [name, args], { noRet: 1});
}
export default {
register,
unregister,
call,
callServer,
callClient,
callBrowsers,
callBrowser,
on,
off,
trigger,
triggerServer,
triggerClient,
triggerBrowsers,
triggerBrowser
};

View File

@ -1,122 +0,0 @@
enum MpTypes {
Blip = 'b',
Checkpoint = 'cp',
Colshape = 'c',
Label = 'l',
Marker = 'm',
Object = 'o',
Pickup = 'p',
Player = 'pl',
Vehicle = 'v'
}
function isObjectMpType(obj: any, type: MpTypes){
const client = getEnvironment() === 'client';
if(obj && typeof obj === 'object' && typeof obj.id !== 'undefined'){
const test = (type, collection, mpType) => client ? obj.type === type && collection.at(obj.id) === obj : obj instanceof mpType;
switch(type){
case MpTypes.Blip: return test('blip', mp.blips, mp.Blip);
case MpTypes.Checkpoint: return test('checkpoint', mp.checkpoints, mp.Checkpoint);
case MpTypes.Colshape: return test('colshape', mp.colshapes, mp.Colshape);
case MpTypes.Label: return test('textlabel', mp.labels, mp.TextLabel);
case MpTypes.Marker: return test('marker', mp.markers, mp.Marker);
case MpTypes.Object: return test('object', mp.objects, mp.Object);
case MpTypes.Pickup: return test('pickup', mp.pickups, mp.Pickup);
case MpTypes.Player: return test('player', mp.players, mp.Player);
case MpTypes.Vehicle: return test('vehicle', mp.vehicles, mp.Vehicle);
}
}
return false;
}
export function uid(): string {
const first = (Math.random() * 46656) | 0;
const second = (Math.random() * 46656) | 0;
const firstPart = ('000' + first.toString(36)).slice(-3);
const secondPart = ('000' + second.toString(36)).slice(-3);
return firstPart + secondPart;
}
export function getEnvironment(): string {
if ('mp' in window) return 'cef';
if (mp.joaat) return 'server';
else if (mp.game && mp.game.joaat) return 'client';
}
export function stringifyData(data: any): string {
const env = getEnvironment();
return JSON.stringify(data, (_, value) => {
if(env === 'client' || env === 'server' && value && typeof value === 'object'){
let type;
if(isObjectMpType(value, MpTypes.Blip)) type = MpTypes.Blip;
else if(isObjectMpType(value, MpTypes.Checkpoint)) type = MpTypes.Checkpoint;
else if(isObjectMpType(value, MpTypes.Colshape)) type = MpTypes.Colshape;
else if(isObjectMpType(value, MpTypes.Marker)) type = MpTypes.Marker;
else if(isObjectMpType(value, MpTypes.Object)) type = MpTypes.Object;
else if(isObjectMpType(value, MpTypes.Pickup)) type = MpTypes.Pickup;
else if(isObjectMpType(value, MpTypes.Player)) type = MpTypes.Player;
else if(isObjectMpType(value, MpTypes.Vehicle)) type = MpTypes.Vehicle;
if(type) return {
__t: type,
i: typeof value.remoteId === 'number' ? value.remoteId : value.id
};
}
return value;
});
}
export function parseData(data: string): any {
const env = getEnvironment();
return JSON.parse(data, (_, value) => {
if((env === 'client' || env === 'server') && value && typeof value === 'object' && typeof value['__t'] === 'string' && typeof value.i === 'number' && Object.keys(value).length === 2){
const id = value.i;
const type = value['__t'];
let collection;
switch(type){
case MpTypes.Blip: collection = mp.blips; break;
case MpTypes.Checkpoint: collection = mp.checkpoints; break;
case MpTypes.Colshape: collection = mp.colshapes; break;
case MpTypes.Label: collection = mp.labels; break;
case MpTypes.Marker: collection = mp.markers; break;
case MpTypes.Object: collection = mp.objects; break;
case MpTypes.Pickup: collection = mp.pickups; break;
case MpTypes.Player: collection = mp.players; break;
case MpTypes.Vehicle: collection = mp.vehicles; break;
}
if(collection) return collection[env === 'client' ? 'atRemoteId' : 'at'](id);
}
return value;
});
}
export function promiseResolve(result: any): Promise<any> {
return new Promise(resolve => setTimeout(() => resolve(result), 0));
}
export function promiseReject(error: any): Promise<any> {
return new Promise((_, reject) => setTimeout(() => reject(error), 0));
}
export function promiseTimeout(promise: Promise<any>, timeout?: number){
if(typeof timeout === 'number'){
return Promise.race([
new Promise((_, reject) => {
setTimeout(() => reject('TIMEOUT'), timeout);
}),
promise
]);
}else return promise;
}
export function isBrowserValid(browser: Browser): boolean {
try {
browser.url;
}catch(e){ return false; }
return true;
}

View File

@ -1,25 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Base",
"exclude": [
"node_modules"
],
"compilerOptions": {
"incremental": false,
"composite": false,
"target": "ES2022",
"experimentalDecorators": true,
"moduleDetection": "auto",
"module": "CommonJS",
"resolveJsonModule": true,
"declaration": false,
"declarationMap": false,
"sourceMap": false,
"downlevelIteration": false,
"inlineSourceMap": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}

View File

@ -13,7 +13,7 @@ You can find out more about our CLI [here](https://git.entityseven.com/entitysev
At the moment automation we have only works via [pnpm](https://pnpm.io/). To scaffold a basic project with minor settings you can use our CLI: At the moment automation we have only works via [pnpm](https://pnpm.io/). To scaffold a basic project with minor settings you can use our CLI:
``pnpm create rage-fw`` ``pnpm create @entityseven/rage-fw``
This will give you a few options, among them, you can find ``Initialize new project``. Use that option to scaffold a new project for yourself using the preferred frontend framework This will give you a few options, among them, you can find ``Initialize new project``. Use that option to scaffold a new project for yourself using the preferred frontend framework

6
rpc/.prettierrc.yaml Normal file
View File

@ -0,0 +1,6 @@
tabWidth: 4
printWidth: 80
singleQuote: true
semi: false
arrowParens: avoid
endOfLine: auto

21
rpc/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Entity Seven Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

26
rpc/index.d.ts vendored Normal file
View File

@ -0,0 +1,26 @@
export {}
interface Mp {
trigger(event: string, data?: any): void
events: {
add(event: string, data: any): void
call(event: string, data: any): void
callRemote(event: string, data: any): void
remove(event: string): void
}
joaat?: unknown
game: {
joaat?: unknown
}
console: {
logInfo: (...args: unknown[]) => void
}
}
declare global {
const mp: Mp
const global: Record<string, (...args: any[]) => unknown>
interface Window {
[p: string]: any
}
}

44
rpc/package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "@entityseven/rage-fw-rpc",
"version": "0.2.5",
"main": "dist/index.js",
"types": "dist/src/index.d.ts",
"scripts": {
"build": "tsup",
"start": "npx ./dist create"
},
"files": [
"dist/**/*",
"readme.md",
"LICENSE"
],
"devDependencies": {
"@microsoft/api-extractor": "^7.47.9",
"prettier": "^3.3.2",
"tsup": "^8.3.0"
},
"peerDependencies": {
"typescript": "^5"
},
"description": "Rage-FW RPC (Remote procedure caller) for Rage:MP",
"keywords": [
"ragefw",
"rage-fw",
"ragemp",
"rage",
"rpc",
"rage-rpc",
"ragerpc",
"gta5"
],
"author": "Entity Seven Group",
"contributors": [
{
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
}
],
"license": "MIT",
"gitHead": "04eb7240735c4a0e4855ebabbe8d5b326819fa76"
}

98
rpc/readme.md Normal file
View File

@ -0,0 +1,98 @@
# Rage-FW-RPC
is an all-in package with asynchronous RPC implementation for RageMP servers in JS/TS
# Installation
``` shell
npm i @entityseven/rage-fw-rpc
```
```shell
pnpm i @entityseven/rage-fw-rpc
```
```shell
yarn add @entityseven/rage-fw-rpc
```
Import installed package and initialize rpc:
```ts
// lib/rpc.js
import { Rpc } from 'rage-fw-rpc'
export const rpc = new Rpc(/* options */)
```
# Motivation
The idea was to create an extensible package, with various features to simplify the development process and provide as much comfort as possible. It should also be using similar architecture as the framework it was specially built for
Inspired by usage of [rage-rpc](https://github.com/micaww/rage-rpc)
# Features
- Type-safe events via [TS generics](https://www.typescriptlang.org/docs/handbook/2/generics.html), avoiding type wrappers
- Built-in logging options for each environment
- Error-safe developer mode for browser
- Calls can receive response from called environments via Promises (browser -> server -> browser, etc.)
- Actual human-readable errors
# Docs
## [Extended version available here](https://git.entityseven.com/entityseven/rage-framework/wiki/RPC%400.2.5)
## register
Registers a callback function for a specified event
```ts
rpc.register('playerJoin', (player) => {
console.log(`Connected: ${player.socialClub}`)
})
```
## unregister
Unregisters callback function for a specified event
```ts
rpc.unregister('playerDamage')
```
## callClient
Calls a client-side event from server or browser
From browser:
```ts
rpc.callClient('updatePlayerData', ['argument']).then(response => {
console.log(`Received: ${response}`)
})
```
From server (requires player):
```ts
rpc.callClient(player, 'updatePlayerData', ['argument']).then(response => {
console.log(`Received: ${response}`)
})
```
## callServer
Calls a server-side event from browser or client
```ts
rpc.callServer('updatePlayerData', ['argument']).then(response => {
console.log(`Received: ${response}`)
})
```
## callBrowser
Calls a server-side event from browser or client
From client:
```ts
rpc.callBrowser('updatePlayerData', ['argument']).then(response => {
console.log(`Received: ${response}`)
})
```
From client (requires player):
```ts
rpc.callBrowser(player, 'updatePlayerData', ['argument']).then(response => {
console.log(`Received: ${response}`)
})
```
## call
Calls an event in current environment
```ts
rpc.call('triggerSomething').then(response => {
console.log(`Received: ${response}`)
})
```

88
rpc/src/browser.ts Normal file
View File

@ -0,0 +1,88 @@
import { Wrapper } from './wrapper'
import {
Environment,
Events,
RPCEventType,
RPCState,
RpcWrapperConfig,
Utils,
} from './utils'
/**
* NOT INTENDED FOR OUT-OF-CONTEXT USE
*/
export class Browser extends Wrapper {
constructor(
options: RpcWrapperConfig = {
forceBrowserDevMode: false,
},
) {
super(options)
}
/**
* NOT INTENDED FOR OUT-OF-CONTEXT USE
*/
public _resolveEmitDestination(dataRaw: string) {
let state = Utils.prepareExecution(dataRaw)
switch (state.calledTo) {
case Environment.BROWSER:
this.emit(dataRaw)
break
default:
this.emitClient(dataRaw)
break
}
}
private emitClient(dataRaw: string) {
mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw)
}
// called to browser
private async emit(dataRaw: string) {
let state = Utils.prepareExecution(dataRaw)
const responseEventName = Utils.generateResponseEventName(state.uuid)
// check availability
state = this.verifyEvent_(state)
if (state.knownError) {
this.triggerError_(state, state.knownError)
}
// execute + generate response
const response = await this.state_[state.eventName](
...(Array.isArray(state.data) ? state.data : []),
)
const responseState: RPCState = {
uuid: Utils.generateUUID(),
eventName: state.eventName,
calledFrom: Environment.SERVER,
calledTo: state.calledFrom,
knownError: undefined,
data: response,
type: RPCEventType.RESPONSE,
}
const responseDataRaw = Utils.prepareTransfer(responseState)
// send response
switch (state.calledFrom) {
case Environment.BROWSER:
try {
mp.events.call(responseEventName, responseDataRaw)
} catch (e) {
void e
}
break
default:
try {
mp.trigger(responseEventName, responseDataRaw)
} catch (e) {
void e
}
break
}
}
}

175
rpc/src/client.ts Normal file
View File

@ -0,0 +1,175 @@
import { Wrapper } from './wrapper'
import {
Environment,
Errors,
Events,
RPCEventType,
RPCState,
RpcWrapperConfig,
Utils,
} from './utils'
/**
* NOT INTENDED FOR OUT-OF-CONTEXT USE
*/
export class Client extends Wrapper {
private _browser: any = null
constructor(
options: RpcWrapperConfig = {
forceBrowserDevMode: false,
},
) {
super(options)
}
set browser(browser: any) {
this._browser = browser
}
/**
* NOT INTENDED FOR OUT-OF-CONTEXT USE
*/
public _resolveEmitDestination(dataRaw: string) {
const state = Utils.prepareExecution(dataRaw)
switch (state.calledTo) {
case Environment.SERVER:
this.emitServer(dataRaw)
break
case Environment.BROWSER:
this.emitBrowser(dataRaw)
break
case Environment.CLIENT:
this.emit(state)
break
default:
this.triggerError_(state, Errors.UNKNOWN_ENVIRONMENT)
break
}
}
// called to client
private async emit(state: RPCState) {
this.errorNoBrowser()
// check availability
state = this.verifyEvent_(state)
if (state.knownError) {
this.triggerError_(state, state.knownError)
}
// execute + generate response
const responseEventName = Utils.generateResponseEventName(state.uuid)
const response = await this.state_[state.eventName](
...(Array.isArray(state.data) ? state.data : []),
)
const responseState: RPCState = {
uuid: Utils.generateUUID(),
eventName: state.eventName,
calledFrom: state.calledTo,
calledTo: state.calledFrom,
knownError: undefined,
data: response,
type: RPCEventType.RESPONSE,
}
// send response
switch (state.calledFrom) {
case Environment.CLIENT:
try {
mp.events.call(
responseEventName,
Utils.prepareTransfer(responseState),
)
} catch (e) {
void e
}
break
case Environment.SERVER:
try {
mp.events.callRemote(
responseEventName,
Utils.prepareTransfer(responseState),
)
} catch (e) {
void e
}
break
case Environment.BROWSER:
try {
this._browser.call(
responseEventName,
Utils.prepareTransfer(responseState),
)
} catch (e) {
void e
}
break
}
}
// called to server
private emitServer(dataRaw: string) {
this.errorNoBrowser()
const state = Utils.prepareExecution(dataRaw)
// if event is called from browser we will forward response through client via this
if (state.calledFrom === Environment.BROWSER) {
const responseEventName = Utils.generateResponseEventName(
state.uuid,
)
const timeout = setTimeout(() => {
clearTimeout(timeout)
mp.events.remove(responseEventName)
}, 10000)
mp.events.add(responseEventName, (responseDataRaw: string) => {
this._browser.call(responseEventName, responseDataRaw)
clearTimeout(timeout)
mp.events.remove(responseEventName)
})
}
mp.events.callRemote(Events.SERVER_EVENT_LISTENER, dataRaw)
}
// called to browser
private emitBrowser(dataRaw: string) {
this.errorNoBrowser()
const state = Utils.prepareExecution(dataRaw)
// if event is called from server we will forward response through client via this
if (state.calledFrom === Environment.SERVER) {
const responseEventName = Utils.generateResponseEventName(
state.uuid,
)
const timeout = setTimeout(() => {
clearTimeout(timeout)
mp.events.remove(responseEventName)
}, 10000)
mp.events.add(responseEventName, (responseDataRaw: string) => {
mp.events.callRemote(responseEventName, responseDataRaw)
clearTimeout(timeout)
mp.events.remove(responseEventName)
})
}
this._browser.call(Events.LOCAL_EVENT_LISTENER, dataRaw)
}
private errorNoBrowser() {
if (!this._browser) throw new Error(Errors.NO_BROWSER)
}
}

536
rpc/src/index.ts Normal file
View File

@ -0,0 +1,536 @@
import { Wrapper } from './wrapper'
import {
Environment,
Errors,
Events,
nativeClientEvents,
nativeServerEvents,
type PlayerMp,
RpcConfig,
RPCEventType,
RPCState,
Utils,
} from './utils'
import { Server } from './server'
import { Client } from './client'
import { Browser } from './browser'
class Rpc extends Wrapper {
private _server: Server
private _client: Client
private _browser: Browser
constructor(
options: RpcConfig = {
forceBrowserDevMode: false,
debugLogs: false,
},
) {
super(options)
this._server = new Server(options)
this._client = new Client(options)
this._browser = new Browser(options)
this.debug_ = !!options.debugLogs
if (options.forceBrowserDevMode) return
if (this.environment_ === Environment.UNKNOWN)
throw new Error(Errors.UNKNOWN_ENVIRONMENT)
mp.events.add(
Events.LOCAL_EVENT_LISTENER,
async (player: PlayerMp | string, dataRaw: string) => {
switch (this.environment_) {
case Environment.SERVER:
this._server._resolveEmitDestination(
player as PlayerMp,
dataRaw,
)
break
case Environment.CLIENT:
dataRaw = player as string
this._client._resolveEmitDestination(dataRaw)
break
case Environment.BROWSER:
dataRaw = player as string
this._browser._resolveEmitDestination(dataRaw)
break
default:
void { player, dataRaw }
break
}
},
)
}
set browser(browser: any) {
this._client.browser = browser
}
/**
* Registers a callback function for a specified event
*
* @template CallbackArguments - An array of argument types that the callback function accepts
* @template CallbackReturn - The type of the value returned by the callback function
* @template EventName - A string representing the event name or union of names
*
* @param {EventName} eventName - The name of the event to register the callback for
* @param {(...args: CallbackArguments) => CallbackReturn} cb - The callback function that is called when the event is triggered
*
* @returns {void}
*
* @example
* register<[PlayerMp]>('playerJoin', (player) => {
* console.log(`Connected: ${player.socialClub}`)
* })
*/
public register<
CallbackArguments extends unknown[] = unknown[],
CallbackReturn extends unknown = unknown,
EventName extends string = string,
>(
eventName: EventName,
cb: (...args: CallbackArguments) => CallbackReturn,
): void {
this.log('register', eventName, cb)
if (this.forceBrowserDevMode_) return
Utils.errorUnknownEnvironment(this.environment_)
if (
(this.environment_ === Environment.CLIENT &&
nativeClientEvents.has(eventName)) ||
(this.environment_ === Environment.SERVER &&
nativeServerEvents.has(eventName))
) {
mp.events.add(eventName, cb)
} else {
this.state_[eventName] = cb
}
}
/**
* Unregisters callback function for a specified event
*
* @template EventName - A string representing the event name or union of names
*
* @param {EventName} eventName - The name of the event to register the callback for
*
* @returns {void}
*
* @example
* unregister('playerJoin')
*/
public unregister<EventName extends string = string>(
eventName: EventName,
): void {
this.log('unregister', eventName)
if (this.forceBrowserDevMode_) return
Utils.errorUnknownEnvironment(this.environment_)
delete this.state_[eventName]
}
/**
* Calls a client-side event from server or browser
*
* @template Arguments - An array of argument types to be passed to the client event
* @template EventName - A string representing the client event name or union of names
* @template Return - The type of the value returned by the client event
*
* @param {EventName} eventName - The name of the client event to be called
* @param {Arguments} [args] - Optional arguments to pass to the client event
* @returns {Promise<Return>} A promise resolving to the return value of the client event
*
* @example
* // Calls an event on client without specifying a player
* callClient<[], string, object>('onDataRequest').then(response => {
* console.log(`Received: ${response}`) // ^ object
* })
*/
public async callClient<
Arguments extends unknown[] = unknown[],
EventName extends string = string,
Return extends unknown = unknown,
>(eventName: EventName, args?: Arguments): Promise<Return>
/**
* Calls a client-side event from server or browser
*
* @template Arguments - An array of argument types to be passed to the client event
* @template EventName - A string representing the client event name or union of names
* @template Return - The type of the value returned by the client event
*
* @param {PlayerMp} player - The player for whom the client event is called
* @param {EventName} eventName - The name of the client event to be called
* @param {Arguments} [args] - Optional arguments to pass to the client event
* @returns {Promise<Return>} A promise resolving to the return value of the client event
*
* @example
* // Calls an event on client for a specific player
* callClient<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]).then(result => {
* console.log(`Action success: ${result}`) // ^ boolean
* })
*/
public async callClient<
Arguments extends unknown[] = unknown[],
EventName extends string = string,
Return extends unknown = unknown,
>(player: PlayerMp, eventName: EventName, args?: Arguments): Promise<Return>
public async callClient(
playerOrEventName: PlayerMp | string,
eventNameOrArgs?: string | unknown[],
args?: unknown[],
) {
_is1StParamPlayer(playerOrEventName)
? this.log(
'callClient',
eventNameOrArgs as string,
playerOrEventName,
eventNameOrArgs,
args,
)
: this.log(
'callClient',
playerOrEventName as string,
eventNameOrArgs,
)
if (this.forceBrowserDevMode_) return
Utils.errorUnknownEnvironment(this.environment_)
function _is1StParamPlayer(x: unknown): x is PlayerMp {
return typeof x === 'object'
}
function _is2NdParamEventName(x: unknown): x is string {
return typeof x === 'string'
}
if (this.environment_ === Environment.CLIENT) {
// client
return await this.call(
playerOrEventName as string,
args as unknown[],
)
}
// server
if (
this.environment_ === Environment.SERVER &&
_is1StParamPlayer(playerOrEventName) &&
_is2NdParamEventName(eventNameOrArgs)
) {
const state: RPCState = {
uuid: Utils.generateUUID(),
eventName: eventNameOrArgs,
calledTo: Environment.CLIENT,
calledFrom: this.environment_,
knownError: undefined,
data: args as unknown[],
type: RPCEventType.EVENT,
}
const dataRaw = Utils.prepareTransfer(state)
playerOrEventName.call(Events.LOCAL_EVENT_LISTENER, [dataRaw])
return (await this.responseHandler(state.uuid)).data
}
// browser
if (
this.environment_ === Environment.BROWSER &&
!_is1StParamPlayer(playerOrEventName) &&
!_is2NdParamEventName(eventNameOrArgs)
) {
const state: RPCState = {
uuid: Utils.generateUUID(),
eventName: playerOrEventName,
calledTo: Environment.CLIENT,
calledFrom: this.environment_,
knownError: undefined,
data: eventNameOrArgs,
type: RPCEventType.EVENT,
}
const dataRaw = Utils.prepareTransfer(state)
mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw)
return (await this.responseHandler(state.uuid)).data
}
}
/**
* Calls a server-side event from browser or client
*
* @template Arguments - An array of argument types to be passed to the server event
* @template EventName - A string representing the server event name or union of names
* @template Return - The type of the value returned by the server event
*
* @param {EventName} eventName - The name of the server event to be called
* @param {Arguments} [args] - Optional arguments to pass to the server event
* @returns {Promise<Return>} A promise resolving to the return value of the server event
*
* @example
* // Calls an event on server
* callServer<[], string, object>('onDataRequest').then(response => {
* console.log(`Received: ${response}`) // ^ object
* })
*/
public async callServer<
Arguments extends unknown[] = unknown[],
EventName extends string = string,
Return extends unknown = unknown,
>(eventName: EventName, args?: Arguments): Promise<Return> {
this.log('callServer', eventName, args)
if (this.forceBrowserDevMode_)
return undefined as unknown as Promise<Return>
Utils.errorUnknownEnvironment(this.environment_)
const state: RPCState = {
uuid: Utils.generateUUID(),
eventName,
calledTo: Environment.SERVER,
calledFrom: this.environment_,
knownError: undefined,
data: args,
type: RPCEventType.EVENT,
}
const dataRaw = Utils.prepareTransfer(state)
switch (this.environment_) {
case Environment.SERVER:
return this.callSelf(state)
case Environment.CLIENT:
mp.events.callRemote(Events.LOCAL_EVENT_LISTENER, dataRaw)
break
case Environment.BROWSER:
mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw)
break
}
return (await this.responseHandler(state.uuid)).data
}
/**
* Calls a browser-side event from server or client
*
* @template Arguments - An array of argument types to be passed to the browser event
* @template EventName - A string representing the browser event name or union of names
* @template Return - The type of the value returned by the browser event
*
* @param {EventName} eventName - The name of the browser event to be called
* @param {Arguments} [args] - Optional arguments to pass to the browser event
* @returns {Promise<Return>} A promise resolving to the return value of the browser event
*
* @example
* // Calls an event on browser without specifying a player
* callBrowser<[], string, object>('onDataRequest').then(response => {
* console.log(`Received: ${response}`) // ^ object
* })
*/
public async callBrowser<
Arguments extends unknown[] = unknown[],
EventName extends string = string,
Return extends unknown = unknown,
>(eventName: EventName, args?: Arguments): Promise<Return>
/**
* Calls a browser-side event from server or client
*
* @template Arguments - An array of argument types to be passed to the browser event
* @template EventName - A string representing the browser event name or union of names
* @template Return - The type of the value returned by the browser event
*
* @param {PlayerMp} player - The player for whom the browser event is called
* @param {EventName} eventName - The name of the browser event to be called
* @param {Arguments} [args] - Optional arguments to pass to the browser event
* @returns {Promise<Return>} A promise resolving to the return value of the browser event
*
* @example
* // Calls an event on a browser for a specific player
* callBrowser<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]).then(result => {
* console.log(`Action success: ${result}`) // ^ boolean
* })
*/
public async callBrowser<
Arguments extends unknown[] = unknown[],
EventName extends string = string,
Return extends unknown = unknown,
>(player: PlayerMp, eventName: EventName, args?: Arguments): Promise<Return>
public async callBrowser(
playerOrEventName: PlayerMp | string,
eventNameOrArgs?: string | unknown[],
args?: unknown[],
) {
_is1StParamPlayer(playerOrEventName)
? this.log(
'DEV callClient',
eventNameOrArgs as string,
playerOrEventName,
eventNameOrArgs,
args,
)
: this.log(
'DEV callClient',
playerOrEventName as string,
eventNameOrArgs,
)
if (this.forceBrowserDevMode_) return
Utils.errorUnknownEnvironment(this.environment_)
function _is1StParamPlayer(x: unknown): x is PlayerMp {
return typeof x === 'object'
}
function _is2NdParamEventName(x: unknown): x is string {
return typeof x === 'string'
}
const state: RPCState = {
uuid: Utils.generateUUID(),
eventName: !_is1StParamPlayer(playerOrEventName)
? playerOrEventName
: _is2NdParamEventName(eventNameOrArgs)
? eventNameOrArgs
: '',
calledTo: Environment.BROWSER,
calledFrom: this.environment_,
knownError: undefined,
data: _is1StParamPlayer(playerOrEventName) ? args : eventNameOrArgs,
type: RPCEventType.EVENT,
}
const dataRaw = Utils.prepareTransfer(state)
switch (this.environment_) {
case Environment.BROWSER:
return this.callSelf(state)
case Environment.CLIENT:
mp.events.callRemote(Events.LOCAL_EVENT_LISTENER, dataRaw)
break
case Environment.SERVER:
;(playerOrEventName as PlayerMp).call(
Events.LOCAL_EVENT_LISTENER,
[dataRaw],
)
break
}
return (await this.responseHandler(state.uuid)).data
}
/**
* Calls an event in current environment
*
* @template Arguments - An array of argument types to be passed to the event
* @template EventName - A string representing the event name or union of names
* @template Return - The type of the value returned by the event
*
* @param {EventName} eventName - The name of the event to be called
* @param {Arguments} [args] - Optional arguments to pass to the event
* @returns {Promise<Return>} A promise resolving to the return value of the event
*
* @example
* // Calls an event in current environment
* call<[], string, number>('getSomething').then(response => {
* console.log(`Received: ${response}`) // ^ number
* })
*/
public async call<
Arguments extends unknown[] = unknown[],
EventName extends string = string,
Return extends unknown = unknown,
>(eventName: EventName, args?: Arguments): Promise<Return> {
this.log('call', eventName, args)
if (this.forceBrowserDevMode_)
return undefined as unknown as Promise<Return>
Utils.errorUnknownEnvironment(this.environment_)
let state: RPCState = {
uuid: Utils.generateUUID(),
eventName,
calledTo: this.environment_,
calledFrom: this.environment_,
knownError: undefined,
data: args,
type: RPCEventType.EVENT,
}
return await this.callSelf<Return>(state)
}
/**
* redirects an event in cases of it calling its own environment
*/
private async callSelf<Return extends unknown = unknown>(
state: RPCState,
): Promise<Return> {
state = this.verifyEvent_(state)
if (state.knownError) {
this.triggerError_(state, state.knownError)
}
return await this.state_[state.eventName](...state.data)
}
/**
* returns cross-environment response
*/
private async responseHandler(uuid: string): Promise<RPCState> {
const responseEventName = Utils.generateResponseEventName(uuid)
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
clearTimeout(timeout)
mp.events.remove(responseEventName)
reject(Errors.EVENT_RESPONSE_TIMEOUT)
}, 10000)
mp.events.add(
responseEventName,
(player: PlayerMp | string, dataRaw: string) => {
switch (this.environment_) {
case Environment.SERVER:
resolve(Utils.prepareExecution(dataRaw))
clearTimeout(timeout)
mp.events.remove(responseEventName)
break
case Environment.CLIENT:
dataRaw = player as string
resolve(Utils.prepareExecution(dataRaw))
clearTimeout(timeout)
mp.events.remove(responseEventName)
break
case Environment.BROWSER:
dataRaw = player as string
resolve(Utils.prepareExecution(dataRaw))
clearTimeout(timeout)
mp.events.remove(responseEventName)
break
default:
void { player, dataRaw }
break
}
},
)
})
}
}
export { Rpc }

104
rpc/src/server.ts Normal file
View File

@ -0,0 +1,104 @@
import { Wrapper } from './wrapper'
import {
Environment,
Events,
type PlayerMp,
RPCEventType,
RPCState,
RpcWrapperConfig,
Utils,
} from './utils'
/**
* NOT INTENDED FOR OUT-OF-CONTEXT USE
*/
export class Server extends Wrapper {
constructor(
options: RpcWrapperConfig = {
forceBrowserDevMode: false,
},
) {
super(options)
if (!!options.forceBrowserDevMode) return
// specific event to save player in context as it is not available on server -> server calls
mp.events.add(
Events.SERVER_EVENT_LISTENER,
async (player: PlayerMp, dataRaw: string) => {
this.emit(player, dataRaw)
},
)
}
/**
* NOT INTENDED FOR OUT-OF-CONTEXT USE
*/
public _resolveEmitDestination(player: PlayerMp, dataRaw: string) {
let state = Utils.prepareExecution(dataRaw)
switch (state.calledTo) {
case Environment.SERVER:
this.emit(player, dataRaw)
break
default:
this.emitClient(player as PlayerMp, dataRaw)
break
}
}
private emitClient(player: PlayerMp, dataRaw: string) {
player.call(Events.LOCAL_EVENT_LISTENER, [dataRaw])
}
// called to server
private async emit(player: PlayerMp, dataRaw: string) {
let state = Utils.prepareExecution(dataRaw)
const responseEventName = Utils.generateResponseEventName(state.uuid)
// check availability
state = this.verifyEvent_(state)
if (state.knownError) {
this.triggerError_(state, state.knownError)
}
// execute + generate response
const response = await this.state_[state.eventName](
player,
...(Array.isArray(state.data) ? state.data : []),
)
const responseState: RPCState = {
uuid: Utils.generateUUID(),
eventName: state.eventName,
calledFrom: Environment.SERVER,
calledTo: state.calledFrom,
knownError: undefined,
data: response,
type: RPCEventType.RESPONSE,
}
// send response
switch (state.calledFrom) {
case Environment.SERVER:
try {
mp.events.call(
responseEventName,
Utils.prepareTransfer(responseState),
)
} catch (e) {
void e
}
break
default:
try {
player.call(responseEventName, [
Utils.prepareTransfer(responseState),
])
} catch (e) {
void e
}
break
}
}
}

171
rpc/src/utils.ts Normal file
View File

@ -0,0 +1,171 @@
export enum Environment {
BROWSER = 'BROWSER',
CLIENT = 'CLIENT',
SERVER = 'SERVER',
UNKNOWN = 'UNKNOWN',
}
export enum Events {
LOCAL_EVENT_LISTENER = '__rpc:listener',
SERVER_EVENT_LISTENER = '__rpc:serverListener',
EVENT_RESPONSE = '__rpc:response',
}
export enum Errors {
EVENT_NOT_REGISTERED = 'Event not registered',
UNKNOWN_ENVIRONMENT = 'Unknown environment',
NO_BROWSER = 'You need to initialize browser first',
EVENT_RESPONSE_TIMEOUT = 'Response was timed out after 10s of inactivity',
}
export type RPCState = {
eventName: string
uuid: string
calledFrom: Environment
calledTo: Environment
knownError?: string
data?: any
type: RPCEventType
}
export type PlayerMp = {
call: (event: string, args?: unknown[]) => void
}
export interface RpcWrapperConfig {
forceBrowserDevMode?: boolean
}
export interface RpcConfig extends RpcWrapperConfig {
debugLogs?: boolean
}
export class Utils {
// todo type for dev browser
public static getEnvironment(): Environment {
if ('joaat' in mp) return Environment.SERVER
if (
'game' in mp &&
'joaat' in (mp as { game: { joaat?: unknown } }).game
)
return Environment.CLIENT
if (window && 'mp' in window) return Environment.BROWSER
return Environment.UNKNOWN
}
public static prepareExecution(data: string): RPCState {
return JSON.parse(data)
}
public static prepareTransfer(data: RPCState): string {
return JSON.stringify(data)
}
public static generateUUID(): string {
let uuid = '',
random
for (let i = 0; i < 32; i++) {
random = (Math.random() * 16) | 0
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-'
}
uuid += (
i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random
).toString(16)
}
return uuid
}
public static generateResponseEventName(uuid: string): string {
return `${Events.EVENT_RESPONSE}_${uuid}`
}
public static errorUnknownEnvironment(environment: Environment) {
if (environment === Environment.UNKNOWN)
throw new Error(Errors.UNKNOWN_ENVIRONMENT)
}
}
export enum RPCEventType {
EVENT = 'event',
RESPONSE = 'response',
}
export const nativeClientEvents = new Set([
'browserCreated',
'browserDomReady',
'browserLoadingFailed',
'playerEnterCheckpoint',
'playerExitCheckpoint',
'consoleCommand',
'click',
'playerChat',
'playerCommand',
'playerDeath',
'playerJoin',
'playerQuit',
'playerReady',
'playerResurrect',
'playerRuleTriggered',
'playerSpawn',
'playerWeaponShot',
'dummyEntityCreated',
'dummyEntityDestroyed',
'entityControllerChange',
'incomingDamage',
'outgoingDamage',
'meleeActionDamage',
'playerEnterVehicle',
'playerLeaveVehicle',
'playerStartTalking',
'playerStopTalking',
'entityStreamIn',
'entityStreamOut',
'render',
'playerCreateWaypoint',
'playerReachWaypoint',
'playerEnterColshape',
'playerExitColshape',
'explosion',
'projectile',
'uncaughtException',
'unhandledRejection',
])
export const nativeServerEvents = new Set([
'entityCreated',
// 'entityDestroyed',
'entityModelChange',
'incomingConnection',
'packagesLoaded',
'playerChat',
'playerCommand',
'playerDamage',
'playerDeath',
'playerEnterCheckpoint',
'playerEnterColshape',
'playerEnterVehicle',
'playerExitCheckpoint',
'playerExitColshape',
'playerExitVehicle',
'playerJoin',
'playerQuit',
'playerReachWaypoint',
'playerReady',
'playerSpawn',
'playerStartEnterVehicle',
'playerStartExitVehicle',
'playerStreamIn',
'playerStreamOut',
'playerWeaponChange',
'serverShutdown',
'trailerAttached',
'vehicleDamage',
'vehicleDeath',
'vehicleHornToggle',
'vehicleSirenToggle',
])

61
rpc/src/wrapper.ts Normal file
View File

@ -0,0 +1,61 @@
import { Environment, Errors, RPCState, RpcWrapperConfig, Utils } from './utils'
export class Wrapper {
protected environment_ = Environment.UNKNOWN
protected state_: any
protected console_ =
this.environment_ === Environment.CLIENT
? mp.console.logInfo
: console.log
protected debug_ = false
protected forceBrowserDevMode_ = false
constructor(
options: RpcWrapperConfig = {
forceBrowserDevMode: false,
},
) {
if (options.forceBrowserDevMode) {
this.environment_ = Environment.UNKNOWN
this.state_ = window
} else {
this.environment_ = Utils.getEnvironment()
this.state_ =
this.environment_ === Environment.BROWSER ? window : global
}
this.forceBrowserDevMode_ = !!options.forceBrowserDevMode
}
// checks if event is available (registered) in current environment
protected verifyEvent_(data: string | RPCState): RPCState {
let rpcData =
typeof data === 'string' ? Utils.prepareExecution(data) : data
if (!this.state_[rpcData.eventName]) {
rpcData.knownError = Errors.EVENT_NOT_REGISTERED
}
return rpcData
}
protected triggerError_(rpcData: RPCState, error?: any) {
const errorMessage = [
`${rpcData.knownError}`,
`Caller: ${rpcData.calledFrom}`,
`Receiver: ${this.environment_}`,
`Event: ${rpcData.eventName}`,
]
if (error) {
errorMessage.push(`Additional Info: ${error}`)
}
throw new Error(errorMessage.join('\n | '))
}
protected log(method: string, eventName: string, ...args: unknown[]): void {
if (this.debug_)
this.console_('RPC | [' + method + '] ' + eventName + ':', ...args)
}
}

29
rpc/tsconfig.json Normal file
View File

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"lib": [
"ES6",
"dom"
],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "bin",
"esModuleInterop": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
},
"include": [
"src/**/*",
"./index.d.ts"
],
"exclude": [
"node_modules",
"dist"
]
}

View File

@ -4,7 +4,6 @@ export default defineConfig({
entry: ['src/index.ts'], entry: ['src/index.ts'],
outDir: './dist', outDir: './dist',
format: ['cjs'], format: ['cjs'],
noExternal: ['rage-rpc'],
experimentalDts: true, experimentalDts: true,
splitting: false, splitting: false,
sourcemap: false, sourcemap: false,

15
server/LICENSE Normal file
View File

@ -0,0 +1,15 @@
Custom Attribution-NoDerivs Software License
Copyright (c) 2024 Entity Seven Group
This license allows you to use, copy, and distribute the RageFW (the "Software"), including for commercial purposes, provided that the following conditions are met:
1. **Attribution:** You must give appropriate credit to the original author of the Software, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
2. **No Derivative Works:** You may not modify, transform, or build upon the Software.
3. **Usage and Commercial Use:** You are allowed to use, sell, and gain income from projects that utilize the Software, as long as you comply with the terms of this license.
4. **No Additional Restrictions:** You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,25 +1,38 @@
{ {
"name": "rage-fw-server", "name": "@entityseven/rage-fw-server",
"version": "0.1.0", "version": "0.2.0",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
"files": [ "files": [
"dist/**/*", "dist/**/*",
"readme.md" "readme.md",
"LICENSE"
], ],
"scripts": { "scripts": {
"build": "tsup" "build": "tsup"
}, },
"dependencies": { "dependencies": {
"rage-rpc": "^0.4.0" "@entityseven/rage-fw-rpc": "0.2.5"
}, },
"peerDependencies": { "peerDependencies": {
"@ragempcommunity/types-server": "^2.1.8", "@entityseven/rage-fw-shared-types": "0.2.0",
"rage-fw-shared-types": "workspace:^" "@ragempcommunity/types-server": "^2.1.8"
}, },
"keywords": [], "description": "Package used on a server-side of your Rage:MP Server",
"author": "SashaGoncharov19", "keywords": ["rage-fw-server", "ragefw", "rage-fw", "ragemp", "rage:mp", "rage", "gta5"],
"license": "MIT", "author": "Entity Seven Group",
"description": "Server side for rage-fw", "contributors": [
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0" {
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
},
{
"name": "Oleksandr Honcharov",
"email": "0976053529@ukr.net",
"url": "https://github.com/SashaGoncharov19/"
}
],
"license": "Custom-Attribution-NoDerivs",
"gitHead": "ffd542c1deddb3033e16e0dae7557313ae09b05f"
} }

View File

@ -1,2 +1,2 @@
# RageFW Server # RageFW Server
[Read docs for details](https://git.entityseven.com/entityseven/rage-framework/wiki/Docs) [Read docs for details](https://git.entityseven.com/entityseven/rage-framework/wiki)

5
server/src/core/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from './logger'
export * from './middleware'
export * from './player'
export * from './rpc'
export * from './server'

56
server/src/core/logger.ts Normal file
View File

@ -0,0 +1,56 @@
import winston, { format } from 'winston'
const { timestamp, printf, colorize } = format
/** Used to log in a server console */
export class Logger {
private format = printf(({ message, level, timestamp }) => {
return `[${new Date(timestamp).toLocaleTimeString()}] [${level}]: ${message}`
})
private systemLogger = winston.createLogger({
transports: [new winston.transports.Console()],
format: format.combine(
colorize({
level: true,
colors: {
error: 'red',
warn: 'yellow',
info: 'white',
},
}),
timestamp(),
this.format,
),
})
/**
* Informational logs. Colored in white
*
* @example
* fw.system.log.info('some information to be logged')
*/
public info(...message: unknown[]) {
this.systemLogger.info(message.join(' '))
}
/**
* Warning logs. Colored in yellow
*
* @example
* fw.system.log.warn('warning message')
*/
public warn(...message: unknown[]) {
this.systemLogger.warn(message.join(' '))
}
/**
* Error logs. Colored in red
*
* @example
* fw.system.log.info('some error information')
*/
public error(...message: unknown[]) {
this.systemLogger.error(message.join(' '))
}
}

View File

@ -0,0 +1,54 @@
import type * as T from '../types'
export class Middleware {
constructor() {}
private static async execute<EventName extends T.RageFW_ServerEvent>(
middlewares: T.RageFW_MiddlewareFunction<EventName>[],
args: T.RageFW_ServerArgs<EventName>,
): Promise<T.RageFW_MiddlewareResponseInternal> {
for (let i = 0; i < middlewares.length; i++) {
const result = await middlewares[i](...args)
if (typeof result === 'boolean' && !result)
return { success: result, id: i }
if (typeof result !== 'boolean' && !result.success)
return { ...result, id: i }
}
return {
success: true,
}
}
public static async process<EventName extends T.RageFW_ServerEvent>(
middlewareOptions: T.RageFW_MiddlewareOptions<EventName>,
callback: T.RageFW_ServerCallback<EventName>,
args: T.RageFW_ServerArgs<EventName>,
) {
if (Array.isArray(middlewareOptions)) {
const middlewaresResponse = await Middleware.execute(
middlewareOptions,
args,
)
if (middlewaresResponse.success) return await callback(...args)
} else {
const middlewaresResponse = await Middleware.execute(
middlewareOptions.executables,
args,
)
if (middlewaresResponse.success) {
return await callback(...args)
} else {
middlewareOptions.onError(
middlewaresResponse.message ??
'Middleware with id ' +
middlewaresResponse.id +
' failed',
)
}
}
}
}

68
server/src/core/player.ts Normal file
View File

@ -0,0 +1,68 @@
import { rpc } from './rpc'
import type * as T from '../types'
/** Handles event manipulations that require player to be present in context */
export class Player {
/**
* Triggers a client event from the server with arguments from shared types
*
* Formerly known as ``callClient`` or ``emitClient``
*
* @param {PlayerMp} player - Player object as an event target
* @param eventName - The name of the client event to trigger
* @param [args] - Arguments for the client event, if present
* @returns {Promise} resolving to the client's response for the event
*
* @example
* // Triggering a client event without arguments
* fw.player.triggerClient("clientEventName")
*
* @example
* // Triggering a client event with arguments
* fw.player.triggerClient("clientEventName", ["message to client"])
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public async triggerClient<EventName extends T.RageFW_ClientEvent>(
player: PlayerMp,
eventName: EventName,
...args: T._ClientEventHasArgs<EventName> extends true
? [T.RageFW_ClientArgs<EventName>]
: []
): Promise<T.RageFW_ClientReturn<EventName>> {
return await rpc.callClient(player, eventName, args)
}
/**
* Triggers a browser event from the server with arguments from shared types
*
* Formerly known as ``callBrowser`` or ``emitBrowser``
*
* @param {PlayerMp} player - Player object as an event target
* @param eventName - The name of the browser event to trigger
* @param [args] - Arguments for the browser event, if present
* @returns {Promise} resolving to the browser's response for the event
*
* @example
* // Triggering a browser event without arguments
* fw.player.triggerBrowser("browserEventName")
*
* @example
* // Triggering a browser event with arguments
* fw.player.triggerBrowser("browserEventName", ["message to browser"])
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public async triggerBrowser<EventName extends T.RageFW_BrowserEvent>(
player: PlayerMp,
eventName: EventName,
...args: T._BrowserEventHasArgs<EventName> extends true
? [T.RageFW_BrowserArgs<EventName>]
: []
): Promise<T.RageFW_BrowserReturn<EventName>> {
return await rpc.callBrowser(player, eventName, args)
}
}
// new Player().triggerBrowser({} as PlayerMp, 'customCefEvent', ['', 1])
// new Player().triggerClient({} as PlayerMp, 'customClientEvent', ['', 1])

5
server/src/core/rpc.ts Normal file
View File

@ -0,0 +1,5 @@
import { Rpc } from '@entityseven/rage-fw-rpc'
export const rpc = new Rpc({
debugLogs: false,
})

96
server/src/core/server.ts Normal file
View File

@ -0,0 +1,96 @@
import { rpc } from './rpc'
import { Middleware } from './middleware'
import type * as T from '../types'
/** Server-side interactions */
export class Server {
/**
* Registers a server event with an associated callback
*
* @param eventName - The name of the event to register
* @param callback - The callback function to be executed when the event is triggered
* @param [options] - Optional settings for callback execution
* @param [options.middlewares] - Middleware functions to be checked before the callback executes
* @returns {Server} The current server instance, enabling method chaining
*
* @example
* // Registering an event
* fw.event.register("playerJoin", (player) => {
* fw.system.log.info(`${player.socialClub} has joined the game`)
* })
*
* @example
* // Registering an event with middlewares
* fw.event.register("playerJoin", (player) => {
* fw.system.log.info(`${player.name} has joined the game`)
* }, {
* middlewares: [ignoreBots] // <- your middlewares here
* })
*
* // or
*
* fw.event.register("playerJoin", (player) => {
* fw.system.log.info(`${player.socialClub} has joined the game`)
* }, {
* middlewares: {
* executables: [ignoreBots], // <- your middlewares here
* onError: (msg) => fw.system.log.info(`[BOT] ${player.socialClub} has joined the game`)
* }
* })
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public register<EventName extends T.RageFW_ServerEvent>(
eventName: EventName,
callback: T.RageFW_ServerCallback<EventName>,
options?: {
middlewares?: T.RageFW_MiddlewareOptions<EventName>
},
): Server {
rpc.register<
Parameters<typeof callback>,
ReturnType<typeof callback> | Promise<unknown>,
EventName
>(eventName, async (...data) => {
if (!options?.middlewares) return await callback(...data)
await Middleware.process(options.middlewares, callback, data)
})
return this
}
/**
* Unregisters a server event, removing the associated callback
*
* @param eventName - The name of the event to unregister
* @returns {Server} The current server instance, enabling method chaining
*
* @example
* // Unregistering an event
* fw.event.unregister("playerJoin")
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
public unregister<EventName extends T.RageFW_ServerEvent>(
eventName: EventName,
): Server {
rpc.unregister<EventName>(eventName)
return this
}
// fixme
// public trigger<EventName extends keyof T.RageFW_ICustomServerEvent>(
// eventName: EventName,
// ...args: T._ServerEventHasArgs<EventName> extends true
// ? [T.RageFW_ServerArgs<EventName>]
// : []
// ): Promise<T.RageFW_ServerReturn<EventName>> {
// return rpc.call(eventName, args)
// }
}
// new Server()
// .register('customServerEvent', async (a, b, c) => true)
// .unregister('customServerEvent')

View File

@ -1,152 +1,24 @@
import rpc from 'rage-rpc' import { Logger, Player, Server, rpc } from './core'
import Logger from './logger' export type { RageFW_MiddlewareFunction } from './types'
import {
_CefEventHasArgs,
_ClientEventHasArgs,
_ServerEventHasArgs,
RageFW_CefArgs,
RageFW_CefEvent,
RageFW_CefReturn,
RageFW_ClientEvent,
RageFW_ICustomServerEvent,
RageFW_ServerClientEventArguments,
RageFW_ServerClientEventReturn,
RageFW_ServerEvent,
RageFW_ServerEventArguments,
RageFW_ServerEventCallback,
RageFW_ServerEventCallbackCustom,
RageFW_ServerEventCallbackNative,
RageFW_ServerEventReturn,
} from './types'
import { nativeEvents } from './native.events'
class Server {
private isNativeEvent(eventName: string): eventName is keyof IServerEvents {
return nativeEvents.includes(eventName)
}
private registerCustom<EventName extends keyof RageFW_ICustomServerEvent>(
eventName: EventName,
callback: RageFW_ServerEventCallbackCustom<EventName>,
): void {
rpc.register(
eventName,
async (args: RageFW_ServerEventArguments<EventName>, info) => {
callback([info.player as PlayerMp, ...args])
},
)
}
private registerNative<EventName extends keyof IServerEvents>(
eventName: EventName,
callback: RageFW_ServerEventCallbackNative<EventName>,
): void {
mp.events.add(
eventName,
(...args: Parameters<IServerEvents[EventName]>) =>
callback([...args]),
)
}
public register<EventName extends RageFW_ServerEvent>(
eventName: EventName,
callback: RageFW_ServerEventCallback<EventName>,
): void {
if (this.isNativeEvent(eventName)) {
this.registerNative(
eventName,
callback as RageFW_ServerEventCallbackNative,
)
} else {
this.registerCustom(
eventName,
callback as unknown as RageFW_ServerEventCallbackCustom,
)
}
}
public registerMany<EventName extends RageFW_ServerEvent>(events: {
[event in EventName]: RageFW_ServerEventCallback<event>
}): void {
Object.entries<RageFW_ServerEventCallback<EventName>>(events).map(
([eventName, callback]) => {
if (this.isNativeEvent(eventName)) {
this.registerNative(
eventName,
callback as RageFW_ServerEventCallbackNative,
)
} else {
this.registerCustom(
eventName as keyof RageFW_ICustomServerEvent,
callback as unknown as RageFW_ServerEventCallbackCustom,
)
}
},
)
}
private unregisterCustom<EventName extends keyof RageFW_ICustomServerEvent>(
eventName: EventName,
): void {
rpc.unregister(eventName)
}
private unregisterNative<EventName extends keyof IServerEvents>(
eventName: EventName,
): void {
mp.events.remove(eventName)
}
public unregister<EventName extends RageFW_ServerEvent>(
eventName: EventName,
): void {
if (this.isNativeEvent(eventName)) {
this.unregisterNative(eventName)
} else {
this.unregisterCustom(eventName)
}
}
public trigger<EventName extends keyof RageFW_ICustomServerEvent>(
eventName: EventName,
...args: _ServerEventHasArgs<EventName> extends true
? [RageFW_ServerEventArguments<EventName>]
: []
): Promise<RageFW_ServerEventReturn<EventName>> {
return rpc.call<RageFW_ServerEventReturn<EventName>>(eventName, args)
}
}
class Player {
public triggerClient<EventName extends RageFW_ClientEvent>(
player: PlayerMp,
eventName: EventName,
...args: _ClientEventHasArgs<EventName> extends true
? [RageFW_ServerClientEventArguments<EventName>]
: []
): Promise<RageFW_ServerClientEventReturn<EventName>> {
return rpc.callClient(player, eventName, args)
}
public triggerBrowser<EventName extends RageFW_CefEvent>(
player: PlayerMp,
eventName: EventName,
...args: _CefEventHasArgs<EventName> extends true
? [RageFW_CefArgs<EventName>]
: []
): Promise<RageFW_CefReturn<EventName>> {
return rpc.callBrowsers(player, eventName, args)
}
}
/**
* Package used on a server-side of your Rage:MP Server
*
* @see {@link https://git.entityseven.com/entityseven/rage-framework/wiki Wiki}
*/
export const fw = { export const fw = {
/** Server-side interactions */
event: new Server(), event: new Server(),
/** Handles event manipulations that require player to be present in context */
player: new Player(), player: new Player(),
/** Handles functions used to interact with the client environment */
system: { system: {
/** Used to log in a server console */
log: new Logger(), log: new Logger(),
}, },
/** ``rage-fw-rpc`` instance used under the hood. It is highly recommended to use this one if you need it instead of creating a new instance */
rpc,
} }
fw.system.log.info( fw.system.log.info(

View File

@ -1,36 +0,0 @@
import winston, { format } from 'winston'
const { timestamp, printf, colorize } = format
export default class Logger {
private format = printf(({ message, level, timestamp }) => {
return `[${new Date(timestamp).toLocaleTimeString()}] [${level}]: ${message}`
})
private systemLogger = winston.createLogger({
transports: [new winston.transports.Console()],
format: format.combine(
colorize({
level: true,
colors: {
error: 'red',
warn: 'yellow',
info: 'white',
},
}),
timestamp(),
this.format,
),
})
public info(message: unknown) {
this.systemLogger.info(message)
}
public warn(message: unknown) {
this.systemLogger.warn(message)
}
public error(message: unknown) {
this.systemLogger.error(message)
}
}

View File

@ -1,18 +1,34 @@
import type { RageFW_ICustomCefEvent } from 'rage-fw-shared-types' import type { RageFW_ICustomBrowserEvent } from '@entityseven/rage-fw-shared-types'
export type RageFW_CefEvent = keyof RageFW_ICustomCefEvent /**
* Union of all available browser event names
* These only include custom events
*/
export type RageFW_BrowserEvent = keyof RageFW_ICustomBrowserEvent
export type RageFW_CefArgs<K extends RageFW_CefEvent> = Parameters< /**
RageFW_ICustomCefEvent[K] * Array of arguments of an event you pass as a generic
* These only include custom events
*/
export type RageFW_BrowserArgs<K extends RageFW_BrowserEvent> = Parameters<
RageFW_ICustomBrowserEvent[K]
> >
export type RageFW_CefReturn<K extends RageFW_CefEvent> = ReturnType< /**
RageFW_ICustomCefEvent[K] * Return type of event you pass as a generic
* These only include custom events
*/
export type RageFW_BrowserReturn<K extends RageFW_BrowserEvent> = ReturnType<
RageFW_ICustomBrowserEvent[K]
> >
export type _CefEventHasArgs<EventName extends keyof RageFW_ICustomCefEvent> = /**
keyof RageFW_ICustomCefEvent extends never *
*/
export type _BrowserEventHasArgs<
EventName extends keyof RageFW_ICustomBrowserEvent,
> = keyof RageFW_ICustomBrowserEvent extends never
? false ? false
: Parameters<RageFW_ICustomCefEvent[EventName]>[0] extends undefined : Parameters<RageFW_ICustomBrowserEvent[EventName]>[0] extends undefined
? false ? false
: true : true

View File

@ -1,6 +1,6 @@
/// <reference types="@ragempcommunity/types-server" /> /// <reference types="@ragempcommunity/types-server" />
import type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types' import type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
/** /**
* Union of all available client event names * Union of all available client event names
@ -12,7 +12,7 @@ export type RageFW_ClientEvent = keyof RageFW_ICustomClientEvent
* Array of arguments of an event you pass as a generic * Array of arguments of an event you pass as a generic
* These only include custom events * These only include custom events
*/ */
export type RageFW_ServerClientEventArguments<K extends RageFW_ClientEvent> = export type RageFW_ClientArgs<K extends RageFW_ClientEvent> =
K extends RageFW_ClientEvent K extends RageFW_ClientEvent
? Parameters<RageFW_ICustomClientEvent[K]> ? Parameters<RageFW_ICustomClientEvent[K]>
: never : never
@ -21,11 +21,14 @@ export type RageFW_ServerClientEventArguments<K extends RageFW_ClientEvent> =
* Return type of event you pass as a generic * Return type of event you pass as a generic
* These only include custom events * These only include custom events
*/ */
export type RageFW_ServerClientEventReturn<K extends RageFW_ClientEvent> = export type RageFW_ClientReturn<K extends RageFW_ClientEvent> =
K extends RageFW_ClientEvent K extends RageFW_ClientEvent
? ReturnType<RageFW_ICustomClientEvent[K]> ? ReturnType<RageFW_ICustomClientEvent[K]>
: never : never
/**
*
*/
export type _ClientEventHasArgs< export type _ClientEventHasArgs<
EventName extends keyof RageFW_ICustomClientEvent, EventName extends keyof RageFW_ICustomClientEvent,
> = keyof RageFW_ICustomClientEvent extends never > = keyof RageFW_ICustomClientEvent extends never

View File

@ -1,3 +1,4 @@
export * from './client'
export * from './server'
export * from './browser' export * from './browser'
export * from './client'
export * from './middleware'
export * from './server'

View File

@ -0,0 +1,26 @@
import type * as T from './server'
export type RageFW_MiddlewareResponse =
| {
success: boolean
message?: string
}
| boolean
export type RageFW_MiddlewareResponseInternal = {
success: boolean
message?: string
id?: number
}
export type RageFW_MiddlewareFunction<EventName extends T.RageFW_ServerEvent> =
(
...args: T.RageFW_ServerArgs<EventName>
) => Promise<RageFW_MiddlewareResponse>
export type RageFW_MiddlewareOptions<EventName extends T.RageFW_ServerEvent> =
| RageFW_MiddlewareFunction<EventName>[]
| {
executables: RageFW_MiddlewareFunction<EventName>[]
onError: (error: string) => unknown
}

View File

@ -3,8 +3,9 @@
import type { import type {
RageFW_ICustomClientEvent, RageFW_ICustomClientEvent,
RageFW_ICustomServerEvent, RageFW_ICustomServerEvent,
} from 'rage-fw-shared-types' } from '@entityseven/rage-fw-shared-types'
export type { RageFW_ICustomServerEvent } from 'rage-fw-shared-types'
export type { RageFW_ICustomServerEvent } from '@entityseven/rage-fw-shared-types'
/** /**
* Union of all available server event names * Union of all available server event names
@ -18,57 +19,48 @@ export type RageFW_ServerEvent =
* Array of arguments for an event, name of which you pass as a generic * Array of arguments for an event, name of which you pass as a generic
* These also include system events * These also include system events
*/ */
export type RageFW_ServerEventArguments<K extends RageFW_ServerEvent> = export type RageFW_ServerArgs<K extends RageFW_ServerEvent> =
K extends keyof RageFW_ICustomServerEvent K extends keyof RageFW_ICustomServerEvent
? Parameters<RageFW_ICustomServerEvent[K]> ? [PlayerMp, ...Parameters<RageFW_ICustomServerEvent[K]>]
: K extends keyof IServerEvents : K extends keyof IServerEvents
? Parameters<IServerEvents[K]> ? [PlayerMp, Parameters<IServerEvents[K]>]
: never
/**
* Callback (function) for an event, name of which you pass as a generic
* These include system and custom events
*/
export type RageFW_ServerEventCallback<K extends RageFW_ServerEvent> =
K extends keyof RageFW_ICustomServerEvent
? RageFW_ServerEventCallbackCustom<K>
: K extends keyof IServerEvents
? RageFW_ServerEventCallbackNative<K>
: never : never
/** /**
* Return type for an event, name of which you pass as a generic * Return type for an event, name of which you pass as a generic
* These include system and custom events * These include system and custom events
*/ */
export type RageFW_ServerEventReturn<K extends RageFW_ServerEvent> = export type RageFW_ServerReturn<K extends RageFW_ServerEvent> =
K extends keyof RageFW_ICustomServerEvent K extends keyof RageFW_ICustomServerEvent
? ReturnType<RageFW_ICustomServerEvent[K]> ? ReturnType<RageFW_ICustomServerEvent[K]>
: K extends keyof IServerEvents : K extends keyof IServerEvents
? ReturnType<IServerEvents[K]> ? ReturnType<IServerEvents[K]>
: never : void
/** /**
* Array of arguments for an event, name of which you pass as a generic * Callback (function) for an event, name of which you pass as a generic
* These only include custom events * These include system and custom events
*/ */
export type RageFW_ServerEventCallbackCustom< export type RageFW_ServerCallback<K extends RageFW_ServerEvent> = (
K extends keyof RageFW_ICustomServerEvent = keyof RageFW_ICustomServerEvent, ...args: RageFW_ServerArgs<K>
> = ( ) => Promise<RageFW_ServerReturn<K>>
payload: [player: PlayerMp, ...args: RageFW_ServerEventArguments<K>],
) => RageFW_ServerEventReturn<K>
/** /**
* Array of arguments for an event, name of which you pass as a generic *
* These only include system events
*/ */
export type RageFW_ServerEventCallbackNative< export type _ServerEventHasArgs<EventName extends RageFW_ServerEvent> =
K extends keyof IServerEvents = keyof IServerEvents, EventName extends keyof RageFW_ICustomServerEvent
> = (payload: Parameters<IServerEvents[K]>) => ReturnType<IServerEvents[K]> ? keyof RageFW_ICustomClientEvent extends never
export type _ServerEventHasArgs<
EventName extends keyof RageFW_ICustomServerEvent,
> = keyof RageFW_ICustomClientEvent extends never
? false ? false
: Parameters<RageFW_ICustomServerEvent[EventName]>[0] extends undefined : Parameters<
RageFW_ICustomServerEvent[EventName]
>[0] extends undefined
? false ? false
: true : true
: EventName extends keyof IServerEvents
? keyof IServerEvents extends never
? false
: Parameters<IServerEvents[EventName]>[0] extends undefined
? false
: true
: false

13
shared-types/LICENSE Normal file
View File

@ -0,0 +1,13 @@
# Custom Attribution-NoDerivs Software License
This license allows you to use, copy, and distribute the RageFW (the "Software"), including for commercial purposes, provided that the following conditions are met:
1. **Attribution:** You must give appropriate credit to the original author of the Software, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
2. **No Derivative Works:** You may not modify, transform, or build upon the Software.
3. **Usage and Commercial Use:** You are allowed to use, sell, and gain income from projects that utilize the Software, as long as you comply with the terms of this license.
4. **No Additional Restrictions:** You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,12 +1,28 @@
{ {
"name": "rage-fw-shared-types", "name": "@entityseven/rage-fw-shared-types",
"version": "0.1.0", "version": "0.2.0",
"types": "types/types/index.d.ts", "types": "types/types/index.d.ts",
"files": [ "files": [
"types/**/*" "types/**/*",
"readme.md",
"LICENSE"
], ],
"author": "SashaGoncharov19", "description": "Package used among all environments of your Rage-FW based server",
"license": "MIT", "keywords": ["rage-fw-shared-types", "rage-fw-shared","ragefw", "rage-fw", "ragemp", "rage:mp", "rage", "gta5"],
"type": "module", "type": "module",
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0" "author": "Entity Seven Group",
"contributors": [
{
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
},
{
"name": "Oleksandr Honcharov",
"email": "0976053529@ukr.net",
"url": "https://github.com/SashaGoncharov19/"
}
],
"license": "Custom-Attribution-NoDerivs",
"gitHead": "ffd542c1deddb3033e16e0dae7557313ae09b05f"
} }

View File

@ -1,2 +1,2 @@
# RageFW Shared types # RageFW Shared types
[Read docs for details](https://git.entityseven.com/entityseven/rage-framework/wiki/Docs) [Read docs for details](https://git.entityseven.com/entityseven/rage-framework/wiki)

View File

@ -1,7 +1,14 @@
declare module 'rage-fw-shared-types' { declare module '@entityseven/rage-fw-shared-types' {
export interface RageFW_ICustomServerEvent {} export interface RageFW_ICustomServerEvent {
customServerEvent(arg1: string, arg2: number): boolean
export interface RageFW_ICustomClientEvent {} }
export interface RageFW_ICustomCefEvent {} export interface RageFW_ICustomClientEvent {
cefReady(): void
customClientEvent(arg1: string, arg2: number): boolean
}
export interface RageFW_ICustomBrowserEvent {
customCefEvent(arg1: string, arg2: number): boolean
}
} }