@player-devtools/plugin 0.0.7--canary.8.483

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.
@@ -0,0 +1,39 @@
1
+ import type {
2
+ DevtoolsDataChangeEvent,
3
+ Transaction,
4
+ } from "@player-devtools/types";
5
+ import type { Flow } from "@player-ui/player";
6
+
7
+ const NOOP_ID = -1;
8
+
9
+ /**
10
+ * Generates a data change transaction for the player devtools plugin.
11
+ *
12
+ * This function creates a transaction object that represents a change in data
13
+ * within a player devtools plugin. The transaction includes details such as the
14
+ * plugin ID, the changed data, and the player ID. It is used to communicate
15
+ * changes between the plugin and devtools.
16
+ */
17
+ export const genDataChangeTransaction = ({
18
+ playerID,
19
+ data,
20
+ pluginID,
21
+ }: {
22
+ playerID: string;
23
+ data: Flow["data"];
24
+ pluginID: string;
25
+ }): Transaction<DevtoolsDataChangeEvent> => {
26
+ return {
27
+ id: NOOP_ID,
28
+ type: "PLAYER_DEVTOOLS_PLUGIN_DATA_CHANGE",
29
+ payload: {
30
+ pluginID,
31
+ data,
32
+ },
33
+ sender: playerID,
34
+ context: "player",
35
+ target: "player",
36
+ timestamp: Date.now(),
37
+ _messenger_: true,
38
+ };
39
+ };
@@ -0,0 +1,2 @@
1
+ export { generateUUID } from "./uuid";
2
+ export { genDataChangeTransaction } from "./genDataChangeTransaction";
@@ -0,0 +1,22 @@
1
+ export function generateUUID(): string {
2
+ // Public Domain/MIT
3
+ let d = new Date().getTime(); //Timestamp
4
+ let d2 =
5
+ (typeof performance !== "undefined" &&
6
+ performance.now &&
7
+ performance.now() * 1000) ||
8
+ 0; //Time in microseconds since page-load or 0 if unsupported
9
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
10
+ let r = Math.random() * 16; //random number between 0 and 16
11
+ if (d > 0) {
12
+ //Use timestamp until depleted
13
+ r = (d + r) % 16 | 0;
14
+ d = Math.floor(d / 16);
15
+ } else {
16
+ //Use microseconds since page-load if supported
17
+ r = (d2 + r) % 16 | 0;
18
+ d2 = Math.floor(d2 / 16);
19
+ }
20
+ return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
21
+ });
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export {
2
+ DevtoolsPlugin,
3
+ type DevtoolsPluginOptions,
4
+ type PluginStore,
5
+ type DevtoolsHandler,
6
+ } from "./plugin";
7
+ export * from "./helpers";
8
+ export { PLUGIN_INACTIVE_WARNING } from "./constants";
package/src/plugin.ts ADDED
@@ -0,0 +1,176 @@
1
+ import type { Messenger } from "@player-devtools/messenger";
2
+ import {
3
+ PluginData,
4
+ DevtoolsPluginsStore,
5
+ PlayerInitEvent,
6
+ ExtensionSupportedEvents,
7
+ Transaction,
8
+ DevtoolsPluginInteractionEvent,
9
+ } from "@player-devtools/types";
10
+ import { dsetAssign } from "@player-devtools/utils";
11
+ import type { DataModel, Player, PlayerPlugin } from "@player-ui/player";
12
+ import { produce } from "immer";
13
+ import { useStateReducer, type Store, type Unsubscribe } from "./state";
14
+ import { reducer } from "./reducer";
15
+ import { PLUGIN_INACTIVE_WARNING, INTERACTIONS } from "./constants";
16
+ import { genDataChangeTransaction } from "./helpers";
17
+ import { dequal } from "dequal";
18
+
19
+ export interface DevtoolsHandler {
20
+ // TODO: Could return bool to signifiy handled to avoid double processing?
21
+ processInteraction(interaction: DevtoolsPluginInteractionEvent): void;
22
+ checkIfDevtoolsIsActive(): boolean;
23
+ log?(message: string): void;
24
+ }
25
+
26
+ export interface DevtoolsPluginOptions {
27
+ playerID: string;
28
+ pluginData: PluginData;
29
+ handler: DevtoolsHandler;
30
+ }
31
+
32
+ const INITIAL_STATE: DevtoolsPluginsStore = {
33
+ messages: [],
34
+ plugins: {},
35
+ interactions: [],
36
+ currentPlayer: "",
37
+ };
38
+
39
+ // TODO: Rename to DevtoolsPluginStore? Need to rename DevtoolsPluginsStore to DevtoolsPluginState
40
+ export type PluginStore = Store<
41
+ DevtoolsPluginsStore,
42
+ Transaction<ExtensionSupportedEvents>
43
+ >;
44
+
45
+ /** Entrypoint for devtools plugins with platform-agnostic components */
46
+ export class DevtoolsPlugin implements PlayerPlugin, DevtoolsHandler {
47
+ name: string = "DevtoolsPlugin";
48
+
49
+ private loggedWarning = false;
50
+
51
+ store: PluginStore = useStateReducer(reducer, INITIAL_STATE);
52
+ protected lastProcessedInteraction = 0;
53
+
54
+ get pluginID(): string {
55
+ return this.options.pluginData.id;
56
+ }
57
+
58
+ get playerID(): string {
59
+ return this.options.playerID;
60
+ }
61
+
62
+ constructor(protected options: DevtoolsPluginOptions) {
63
+ this.store.subscribe(({ interactions }) => {
64
+ if (this.lastProcessedInteraction < (interactions.length ?? 0)) {
65
+ interactions
66
+ .slice(this.lastProcessedInteraction)
67
+ .forEach(this.processInteraction.bind(this));
68
+ }
69
+ });
70
+ }
71
+
72
+ registerMessenger(
73
+ messenger: Messenger<ExtensionSupportedEvents>,
74
+ ): Unsubscribe {
75
+ // Propagate new messages from state to devtools via the messenger
76
+ let lastMessageIndex = -1;
77
+ return this.store.subscribe(({ messages }) => {
78
+ const start = lastMessageIndex + 1;
79
+ if (messages.length > start) {
80
+ const newlyAdded = messages.slice(start);
81
+ lastMessageIndex = messages.length - 1;
82
+ for (const msg of newlyAdded) {
83
+ messenger.sendMessage(msg);
84
+ }
85
+ }
86
+ });
87
+ }
88
+
89
+ protected dispatchPlayerInit(): void {
90
+ // Initial plugin content
91
+ const transaction: Transaction<PlayerInitEvent> = {
92
+ id: -1,
93
+ type: "PLAYER_DEVTOOLS_PLAYER_INIT",
94
+ payload: {
95
+ plugins: {
96
+ [this.pluginID]: this.options.pluginData,
97
+ },
98
+ },
99
+ sender: this.options.playerID,
100
+ context: "player",
101
+ target: "player",
102
+ timestamp: Date.now(),
103
+ _messenger_: true,
104
+ };
105
+
106
+ this.store.dispatch(transaction);
107
+ }
108
+
109
+ // By default, we'll only write to the keys defined in data -- if undefined, data will be cleared
110
+ protected dispatchDataUpdate(data?: DataModel): void {
111
+ const state = this.store.getState();
112
+
113
+ const { plugins } = produce(this.store.getState(), (draft) => {
114
+ if (!data)
115
+ dsetAssign(draft, ["plugins", this.pluginID, "flow", "data"], data);
116
+ else
117
+ Object.entries(data).forEach(([key, value]) => {
118
+ dsetAssign(
119
+ draft,
120
+ ["plugins", this.pluginID, "flow", "data", key],
121
+ value,
122
+ );
123
+ });
124
+ });
125
+
126
+ const newData = plugins[this.pluginID]!.flow.data;
127
+ if (dequal(state.plugins[this.pluginID]?.flow?.data, newData)) return;
128
+
129
+ const transaction = genDataChangeTransaction({
130
+ playerID: this.playerID,
131
+ pluginID: this.pluginID,
132
+ data: newData,
133
+ });
134
+
135
+ this.store.dispatch(transaction);
136
+ }
137
+
138
+ checkIfDevtoolsIsActive(): boolean {
139
+ const isActive = this.options.handler.checkIfDevtoolsIsActive();
140
+ if (!isActive && !this.loggedWarning) {
141
+ this.options.handler.log?.(PLUGIN_INACTIVE_WARNING);
142
+ this.loggedWarning = true;
143
+ }
144
+
145
+ return isActive;
146
+ }
147
+
148
+ processInteraction(interaction: DevtoolsPluginInteractionEvent): void {
149
+ this.options.handler.processInteraction(interaction);
150
+
151
+ const {
152
+ payload: { type, payload },
153
+ } = interaction;
154
+
155
+ if (type === INTERACTIONS.PLAYER_SELECTED && payload) {
156
+ this.store.dispatch({
157
+ id: -1,
158
+ type: "PLAYER_DEVTOOLS_SELECTED_PLAYER_CHANGE",
159
+ payload: { playerID: payload },
160
+ sender: this.playerID,
161
+ context: "player",
162
+ target: this.playerID,
163
+ timestamp: Date.now(),
164
+ _messenger_: true,
165
+ });
166
+ }
167
+
168
+ this.lastProcessedInteraction += 1;
169
+ }
170
+
171
+ apply(player: Player): void {
172
+ if (!this.checkIfDevtoolsIsActive()) return;
173
+
174
+ this.dispatchPlayerInit();
175
+ }
176
+ }
package/src/reducer.ts ADDED
@@ -0,0 +1,80 @@
1
+ import { produce } from "immer";
2
+ import { dequal } from "dequal";
3
+ import type {
4
+ DevtoolsDataChangeEvent,
5
+ DevtoolsPluginsStore,
6
+ ExtensionSupportedEvents,
7
+ PlayerInitEvent,
8
+ Transaction,
9
+ } from "@player-devtools/types";
10
+ import { dsetAssign } from "@player-devtools/utils";
11
+
12
+ const containsInteraction = (
13
+ interactions: DevtoolsPluginsStore["interactions"],
14
+ interaction: DevtoolsPluginsStore["interactions"][number],
15
+ ) => {
16
+ return interactions.filter((i) => dequal(i, interaction)).length > 0;
17
+ };
18
+
19
+ /** devtools plugin state reducer */
20
+ export const reducer = (
21
+ state: DevtoolsPluginsStore,
22
+ transaction: Transaction<ExtensionSupportedEvents>,
23
+ ): DevtoolsPluginsStore => {
24
+ switch (transaction.type) {
25
+ case "PLAYER_DEVTOOLS_PLAYER_INIT":
26
+ return produce(state, (draft) => {
27
+ const { payload } = transaction;
28
+ dsetAssign(draft, ["plugins"], payload.plugins);
29
+
30
+ const message: PlayerInitEvent = {
31
+ type: "PLAYER_DEVTOOLS_PLAYER_INIT",
32
+ payload,
33
+ };
34
+
35
+ draft.messages.push(message);
36
+ });
37
+ case "PLAYER_DEVTOOLS_PLUGIN_DATA_CHANGE":
38
+ return produce(state, (draft) => {
39
+ const { payload } = transaction;
40
+
41
+ if (!payload.data) return state;
42
+
43
+ try {
44
+ dsetAssign(
45
+ draft,
46
+ ["plugins", transaction.payload.pluginID, "flow", "data"],
47
+ transaction.payload.data,
48
+ );
49
+ } catch {
50
+ console.error("error setting data:", transaction.payload.data);
51
+ }
52
+ const message: DevtoolsDataChangeEvent = {
53
+ type: "PLAYER_DEVTOOLS_PLUGIN_DATA_CHANGE",
54
+ payload,
55
+ };
56
+
57
+ draft.messages.push(message);
58
+ });
59
+ case "PLAYER_DEVTOOLS_PLUGIN_INTERACTION":
60
+ return produce(state, (draft) => {
61
+ if (containsInteraction(draft.interactions, transaction)) return state;
62
+
63
+ dsetAssign(
64
+ draft,
65
+ ["interactions"],
66
+ [...draft.interactions, transaction],
67
+ );
68
+ });
69
+ case "PLAYER_DEVTOOLS_SELECTED_PLAYER_CHANGE": {
70
+ const { playerID } = transaction.payload;
71
+
72
+ if (!playerID) return state;
73
+ return produce(state, (draft) => {
74
+ dsetAssign(draft, ["currentPlayer"], playerID);
75
+ });
76
+ }
77
+ default:
78
+ return state;
79
+ }
80
+ };
package/src/state.ts ADDED
@@ -0,0 +1,43 @@
1
+ export type Reducer<T, A> = (state: T, action: A) => T;
2
+ export type Dispatch<A> = (action: A) => void;
3
+ export type Subscriber<T> = (state: T) => void;
4
+ export type Subscribe<T> = (subscriber: Subscriber<T>) => Unsubscribe;
5
+ export type Unsubscribe = () => void;
6
+
7
+ export interface Store<State, Action> {
8
+ getState: () => State;
9
+ subscribe: Subscribe<State>;
10
+ dispatch: Dispatch<Action>;
11
+ }
12
+
13
+ export const useStateReducer = <State, Action>(
14
+ reducer: Reducer<State, Action>,
15
+ initialState: State,
16
+ ): Store<State, Action> => {
17
+ let state = initialState;
18
+ const subscribers = new Set<Subscriber<State>>();
19
+ return {
20
+ getState: () => state,
21
+
22
+ /** Subscribe to state changes; returns an unsubscribe function. */
23
+ subscribe(subscriber: Subscriber<State>): Unsubscribe {
24
+ subscribers.add(subscriber);
25
+ subscriber(state);
26
+ return () => subscribers.delete(subscriber);
27
+ },
28
+
29
+ /** Dispatch an action through the reducer, then run side-effects. */
30
+ dispatch(action: Action): void {
31
+ const prevState = state;
32
+ const nextState = reducer(prevState, action);
33
+
34
+ // Only proceed if state actually changed by reference
35
+ if (nextState !== prevState) {
36
+ state = nextState;
37
+
38
+ // Notify subscribers
39
+ for (const sub of subscribers) sub(state);
40
+ }
41
+ },
42
+ };
43
+ };
@@ -0,0 +1,5 @@
1
+ export declare const INTERACTIONS: {
2
+ PLAYER_SELECTED: string;
3
+ };
4
+ export declare const PLUGIN_INACTIVE_WARNING = "The plugin has been registered, but the Player development tools are not active. If you are working in a production environment, it is recommended to remove the plugin. To activate, enable through the browser extension popup for web or configure the FlipperClient for mobile.";
5
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,16 @@
1
+ import type { DevtoolsDataChangeEvent, Transaction } from "@player-devtools/types";
2
+ import type { Flow } from "@player-ui/player";
3
+ /**
4
+ * Generates a data change transaction for the player devtools plugin.
5
+ *
6
+ * This function creates a transaction object that represents a change in data
7
+ * within a player devtools plugin. The transaction includes details such as the
8
+ * plugin ID, the changed data, and the player ID. It is used to communicate
9
+ * changes between the plugin and devtools.
10
+ */
11
+ export declare const genDataChangeTransaction: ({ playerID, data, pluginID, }: {
12
+ playerID: string;
13
+ data: Flow["data"];
14
+ pluginID: string;
15
+ }) => Transaction<DevtoolsDataChangeEvent>;
16
+ //# sourceMappingURL=genDataChangeTransaction.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { generateUUID } from "./uuid";
2
+ export { genDataChangeTransaction } from "./genDataChangeTransaction";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export declare function generateUUID(): string;
2
+ //# sourceMappingURL=uuid.d.ts.map
@@ -0,0 +1,4 @@
1
+ export { DevtoolsPlugin, type DevtoolsPluginOptions, type PluginStore, type DevtoolsHandler, } from "./plugin";
2
+ export * from "./helpers";
3
+ export { PLUGIN_INACTIVE_WARNING } from "./constants";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,33 @@
1
+ import type { Messenger } from "@player-devtools/messenger";
2
+ import { PluginData, DevtoolsPluginsStore, ExtensionSupportedEvents, Transaction, DevtoolsPluginInteractionEvent } from "@player-devtools/types";
3
+ import type { DataModel, Player, PlayerPlugin } from "@player-ui/player";
4
+ import { type Store, type Unsubscribe } from "./state";
5
+ export interface DevtoolsHandler {
6
+ processInteraction(interaction: DevtoolsPluginInteractionEvent): void;
7
+ checkIfDevtoolsIsActive(): boolean;
8
+ log?(message: string): void;
9
+ }
10
+ export interface DevtoolsPluginOptions {
11
+ playerID: string;
12
+ pluginData: PluginData;
13
+ handler: DevtoolsHandler;
14
+ }
15
+ export type PluginStore = Store<DevtoolsPluginsStore, Transaction<ExtensionSupportedEvents>>;
16
+ /** Entrypoint for devtools plugins with platform-agnostic components */
17
+ export declare class DevtoolsPlugin implements PlayerPlugin, DevtoolsHandler {
18
+ protected options: DevtoolsPluginOptions;
19
+ name: string;
20
+ private loggedWarning;
21
+ store: PluginStore;
22
+ protected lastProcessedInteraction: number;
23
+ get pluginID(): string;
24
+ get playerID(): string;
25
+ constructor(options: DevtoolsPluginOptions);
26
+ registerMessenger(messenger: Messenger<ExtensionSupportedEvents>): Unsubscribe;
27
+ protected dispatchPlayerInit(): void;
28
+ protected dispatchDataUpdate(data?: DataModel): void;
29
+ checkIfDevtoolsIsActive(): boolean;
30
+ processInteraction(interaction: DevtoolsPluginInteractionEvent): void;
31
+ apply(player: Player): void;
32
+ }
33
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1,4 @@
1
+ import type { DevtoolsPluginsStore, ExtensionSupportedEvents, Transaction } from "@player-devtools/types";
2
+ /** devtools plugin state reducer */
3
+ export declare const reducer: (state: DevtoolsPluginsStore, transaction: Transaction<ExtensionSupportedEvents>) => DevtoolsPluginsStore;
4
+ //# sourceMappingURL=reducer.d.ts.map
@@ -0,0 +1,12 @@
1
+ export type Reducer<T, A> = (state: T, action: A) => T;
2
+ export type Dispatch<A> = (action: A) => void;
3
+ export type Subscriber<T> = (state: T) => void;
4
+ export type Subscribe<T> = (subscriber: Subscriber<T>) => Unsubscribe;
5
+ export type Unsubscribe = () => void;
6
+ export interface Store<State, Action> {
7
+ getState: () => State;
8
+ subscribe: Subscribe<State>;
9
+ dispatch: Dispatch<Action>;
10
+ }
11
+ export declare const useStateReducer: <State, Action>(reducer: Reducer<State, Action>, initialState: State) => Store<State, Action>;
12
+ //# sourceMappingURL=state.d.ts.map