@open-core/framework 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,6 @@
1
+ export interface OnViewOptions {
2
+ viewId?: string;
3
+ }
1
4
  /**
2
5
  * Registers a method as a WebView callback handler.
3
6
  *
@@ -5,7 +8,12 @@
5
8
  * This decorator only stores metadata. During bootstrap, the framework binds the decorated method
6
9
  * to the active WebView runtime callback.
7
10
  *
11
+ * If `options.viewId` is provided, the handler will only receive messages from the
12
+ * specified WebView viewId. Without it, the handler receives events from all views.
13
+ *
8
14
  * @param eventName - Callback name.
15
+ * @param options - Optional configuration. Pass `{ viewId: 'my-view' }` to
16
+ * filter incoming messages by viewId.
9
17
  *
10
18
  * @example
11
19
  * ```ts
@@ -15,7 +23,12 @@
15
23
  * saveSettings(payload: unknown) {
16
24
  * // ...
17
25
  * }
26
+ *
27
+ * @Client.onView('system-ui:ready', { viewId: 'system-ui' })
28
+ * onSystemUiReady() {
29
+ * // Only fires for the 'system-ui' WebView
30
+ * }
18
31
  * }
19
32
  * ```
20
33
  */
21
- export declare function OnView(eventName: string): (target: any, propertyKey: string) => void;
34
+ export declare function OnView(eventName: string, options?: OnViewOptions): (target: any, propertyKey: string) => void;
@@ -6,7 +6,12 @@ import { METADATA_KEYS } from '../system/metadata-client.keys';
6
6
  * This decorator only stores metadata. During bootstrap, the framework binds the decorated method
7
7
  * to the active WebView runtime callback.
8
8
  *
9
+ * If `options.viewId` is provided, the handler will only receive messages from the
10
+ * specified WebView viewId. Without it, the handler receives events from all views.
11
+ *
9
12
  * @param eventName - Callback name.
13
+ * @param options - Optional configuration. Pass `{ viewId: 'my-view' }` to
14
+ * filter incoming messages by viewId.
10
15
  *
11
16
  * @example
12
17
  * ```ts
@@ -16,11 +21,16 @@ import { METADATA_KEYS } from '../system/metadata-client.keys';
16
21
  * saveSettings(payload: unknown) {
17
22
  * // ...
18
23
  * }
24
+ *
25
+ * @Client.onView('system-ui:ready', { viewId: 'system-ui' })
26
+ * onSystemUiReady() {
27
+ * // Only fires for the 'system-ui' WebView
28
+ * }
19
29
  * }
20
30
  * ```
21
31
  */
22
- export function OnView(eventName) {
32
+ export function OnView(eventName, options) {
23
33
  return (target, propertyKey) => {
24
- Reflect.defineMetadata(METADATA_KEYS.VIEW, { eventName }, target, propertyKey);
34
+ Reflect.defineMetadata(METADATA_KEYS.VIEW, { eventName, viewId: options?.viewId }, target, propertyKey);
25
35
  };
26
36
  }
@@ -6,5 +6,6 @@ export declare class ViewProcessor implements DecoratorProcessor {
6
6
  constructor(webviews: WebViewService);
7
7
  process(target: any, methodName: string, metadata: {
8
8
  eventName: string;
9
+ viewId?: string;
9
10
  }): void;
10
11
  }
@@ -26,6 +26,8 @@ let ViewProcessor = class ViewProcessor {
26
26
  this.webviews.onMessage(async (message) => {
27
27
  if (message.event !== metadata.eventName)
28
28
  return;
29
+ if (metadata.viewId && message.viewId !== metadata.viewId)
30
+ return;
29
31
  try {
30
32
  await handler(message.payload);
31
33
  }
@@ -36,7 +38,7 @@ let ViewProcessor = class ViewProcessor {
36
38
  }, error);
37
39
  }
38
40
  });
39
- loggers.webView.debug(`Registered WebView callback: ${metadata.eventName} -> ${handlerName}`);
41
+ loggers.webView.debug(`Registered WebView callback: ${metadata.eventName}${metadata.viewId ? ` [viewId=${metadata.viewId}]` : ''} -> ${handlerName}`);
40
42
  }
41
43
  };
42
44
  ViewProcessor = __decorate([
@@ -32,3 +32,4 @@ export declare class NuiBridge<TSend extends Record<string, any> = Record<string
32
32
  }
33
33
  export declare const WebView: WebViewBridge<Record<string, any>, Record<string, any>>;
34
34
  export declare const NUI: WebViewBridge<Record<string, any>, Record<string, any>>;
35
+ export declare function createWebView<TSend extends Record<string, any> = Record<string, any>, TReceive extends Record<string, any> = Record<string, any>>(viewId: string): WebViewBridge<TSend, TReceive>;
@@ -101,3 +101,6 @@ function resolveWebViewService() {
101
101
  }
102
102
  export const WebView = new WebViewBridge(resolveWebViewService);
103
103
  export const NUI = WebView;
104
+ export function createWebView(viewId) {
105
+ return new WebViewBridge(resolveWebViewService, viewId);
106
+ }
@@ -23,6 +23,8 @@ export declare class PlayerExportController implements InternalPlayerExports {
23
23
  isPlayerOnline(accountId: string): boolean;
24
24
  getPlayerMeta(clientID: number, key: string): Promise<any>;
25
25
  setPlayerMeta(clientID: number, key: string, value: any): void;
26
+ linkPlayerAccount(clientID: number, accountID: string): void;
27
+ unlinkPlayerAccount(clientID: number): void;
26
28
  getPlayerStates(clientID: number): string[];
27
29
  hasPlayerState(clientID: number, state: string): boolean;
28
30
  addPlayerState(clientID: number, state: string): void;
@@ -11,6 +11,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
13
  import { inject } from 'tsyringe';
14
+ import { loggers } from '../../../kernel/logger';
14
15
  import { Controller } from '../decorators/controller';
15
16
  import { Export } from '../decorators/export';
16
17
  import { serializeServerPlayerData } from '../adapter/serialization';
@@ -67,6 +68,27 @@ let PlayerExportController = class PlayerExportController {
67
68
  setPlayerMeta(clientID, key, value) {
68
69
  this.playerService.setMeta(clientID, key, value);
69
70
  }
71
+ linkPlayerAccount(clientID, accountID) {
72
+ const player = this.playerService.getByClient(clientID);
73
+ if (!player)
74
+ return;
75
+ player.linkAccount(accountID);
76
+ loggers.session.debug('Remote player account linked in CORE', {
77
+ clientID,
78
+ accountID,
79
+ });
80
+ }
81
+ unlinkPlayerAccount(clientID) {
82
+ const player = this.playerService.getByClient(clientID);
83
+ if (!player)
84
+ return;
85
+ const previousAccountID = player.accountID;
86
+ player.unlinkAccount();
87
+ loggers.session.debug('Remote player account unlinked in CORE', {
88
+ clientID,
89
+ accountID: previousAccountID,
90
+ });
91
+ }
70
92
  // ═══════════════════════════════════════════════════════════════
71
93
  // State Management
72
94
  // ═══════════════════════════════════════════════════════════════
@@ -141,6 +163,18 @@ __decorate([
141
163
  __metadata("design:paramtypes", [Number, String, Object]),
142
164
  __metadata("design:returntype", void 0)
143
165
  ], PlayerExportController.prototype, "setPlayerMeta", null);
166
+ __decorate([
167
+ Export(),
168
+ __metadata("design:type", Function),
169
+ __metadata("design:paramtypes", [Number, String]),
170
+ __metadata("design:returntype", void 0)
171
+ ], PlayerExportController.prototype, "linkPlayerAccount", null);
172
+ __decorate([
173
+ Export(),
174
+ __metadata("design:type", Function),
175
+ __metadata("design:paramtypes", [Number]),
176
+ __metadata("design:returntype", void 0)
177
+ ], PlayerExportController.prototype, "unlinkPlayerAccount", null);
144
178
  __decorate([
145
179
  Export(),
146
180
  __metadata("design:type", Function),
@@ -35,6 +35,10 @@ export declare class RemotePlayerImplementation extends Players {
35
35
  */
36
36
  private get core();
37
37
  private createPlayerFromData;
38
+ /**
39
+ * Proxies remote session mutations to CORE so security-critical data remains authoritative.
40
+ */
41
+ private attachAuthoritativeMutators;
38
42
  /**
39
43
  * Returns a Player instance with real session data from CORE.
40
44
  */
@@ -77,7 +77,73 @@ let RemotePlayerImplementation = class RemotePlayerImplementation extends Player
77
77
  return coreExports;
78
78
  }
79
79
  createPlayerFromData(data) {
80
- return createRemoteServerPlayer(data, this.playerAdapters);
80
+ const player = createRemoteServerPlayer(data, this.playerAdapters);
81
+ this.attachAuthoritativeMutators(player);
82
+ return player;
83
+ }
84
+ /**
85
+ * Proxies remote session mutations to CORE so security-critical data remains authoritative.
86
+ */
87
+ attachAuthoritativeMutators(player) {
88
+ const core = this.core;
89
+ const originalSetMeta = player.setMeta.bind(player);
90
+ const originalLinkAccount = player.linkAccount.bind(player);
91
+ const originalUnlinkAccount = player.unlinkAccount.bind(player);
92
+ const originalAddState = player.addState.bind(player);
93
+ const originalRemoveState = player.removeState.bind(player);
94
+ const originalToggleState = player.toggleState.bind(player);
95
+ player.setMeta = (key, value) => {
96
+ core.setPlayerMeta(player.clientID, key, value);
97
+ originalSetMeta(key, value);
98
+ loggers.session.debug('Remote player meta delegated to CORE', {
99
+ clientID: player.clientID,
100
+ key,
101
+ });
102
+ };
103
+ player.linkAccount = (accountID) => {
104
+ core.linkPlayerAccount(player.clientID, accountID.toString());
105
+ originalLinkAccount(accountID);
106
+ loggers.session.debug('Remote player linkAccount delegated to CORE', {
107
+ clientID: player.clientID,
108
+ accountID: accountID.toString(),
109
+ });
110
+ };
111
+ player.unlinkAccount = () => {
112
+ const previousAccountID = player.accountID;
113
+ core.unlinkPlayerAccount(player.clientID);
114
+ originalUnlinkAccount();
115
+ loggers.session.debug('Remote player unlinkAccount delegated to CORE', {
116
+ clientID: player.clientID,
117
+ accountID: previousAccountID,
118
+ });
119
+ };
120
+ player.addState = (state) => {
121
+ core.addPlayerState(player.clientID, state);
122
+ originalAddState(state);
123
+ loggers.session.debug('Remote player state added in CORE', {
124
+ clientID: player.clientID,
125
+ state,
126
+ });
127
+ };
128
+ player.removeState = (state) => {
129
+ core.removePlayerState(player.clientID, state);
130
+ originalRemoveState(state);
131
+ loggers.session.debug('Remote player state removed in CORE', {
132
+ clientID: player.clientID,
133
+ state,
134
+ });
135
+ };
136
+ player.toggleState = (state, force) => {
137
+ const next = force ?? !player.hasState(state);
138
+ if (next) {
139
+ player.addState(state);
140
+ }
141
+ else {
142
+ player.removeState(state);
143
+ }
144
+ originalToggleState(state, next);
145
+ return next;
146
+ };
81
147
  }
82
148
  /**
83
149
  * Returns a Player instance with real session data from CORE.
@@ -225,6 +225,19 @@ export interface InternalPlayerExports {
225
225
  * @param value - Value to store
226
226
  */
227
227
  setPlayerMeta(clientID: number, key: string, value: unknown): void;
228
+ /**
229
+ * Links a persistent account to the authoritative CORE player session.
230
+ *
231
+ * @param clientID - FiveM client/server ID
232
+ * @param accountID - Persistent account identifier
233
+ */
234
+ linkPlayerAccount(clientID: number, accountID: string): void;
235
+ /**
236
+ * Removes any linked account from the authoritative CORE player session.
237
+ *
238
+ * @param clientID - FiveM client/server ID
239
+ */
240
+ unlinkPlayerAccount(clientID: number): void;
228
241
  /**
229
242
  * Gets complete serialized player data.
230
243
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-core/framework",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Secure, event-driven TypeScript Framework & Runtime engine for CitizenFX (Cfx).",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",