Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ workspace(

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# TODO: Maybe put in rules?
http_archive(
name = "rules_pkg",
sha256 = "8a298e832762eda1830597d64fe7db58178aa84cd5926d76d5b744d6558941c2",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz",
"https://github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz",
],
)

load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")

rules_pkg_dependencies()

http_archive(
name = "rules_player",
strip_prefix = "rules_player-0.10.0",
Expand Down
4 changes: 3 additions & 1 deletion devtools/client/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# devtools-client

Package responsible for providing the common constructs (TODO: maybe even including redux) responsible for managing state and consuming events and RPCs.
Package responsible for providing the common constructs responsible for managing Redux state and consuming events and methods from a devtools client, i.e. flipper plugin or web extension.

TODO: Usage instructions - createDevtoolsStore
2 changes: 0 additions & 2 deletions devtools/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export * from './redux';
export * from './rpc';
export * from '@player-tools/devtools-common';
38 changes: 0 additions & 38 deletions devtools/client/src/redux/actions.ts

This file was deleted.

16 changes: 16 additions & 0 deletions devtools/client/src/redux/actions/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Events } from '@player-tools/devtools-common';
import { ActionCreatorWithPayload, createAction } from '@reduxjs/toolkit';
import { AnyAction } from 'redux';

/** Redux actions associated against all possible event types */
type EventActions = {
[key in Events.EventTypes]: ActionCreatorWithPayload<Events.ByType<key>, key>;
};

/** Redux actions associated against all defined event types */
export const Actions: EventActions = Object.fromEntries(
Events.EventTypes.map((event) => [
event,
createAction<Events.ByType<typeof event>>(event),
])
) as EventActions;
20 changes: 20 additions & 0 deletions devtools/client/src/redux/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createAction } from '@reduxjs/toolkit';
import type { Events } from '@player-tools/devtools-common';

export { Actions as EventActions } from './events';
export * from './methods';

/** Explicit actions that don't correspond to a specific event or method */
export const Actions = {
// Explicit actions TODO: Is this level of redundancy okay?
'selected-player': createAction<string | undefined>('selected-player'),
'player-timeline-event': createAction<Events.TimelineEvents>(
'player-timeline-event'
),

// Reset actions
'clear-selected-data-details': createAction('clear-selected-data-details'),
'clear-console': createAction('clear-console'),
'clear-logs': createAction('clear-logs'),
'clear-store': createAction('clear-store'),
};
43 changes: 43 additions & 0 deletions devtools/client/src/redux/actions/methods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
BACKGROUND_SOURCE,
createLogger,
Methods,
} from '@player-tools/devtools-common';
import { createAsyncThunk, type AsyncThunk } from '@reduxjs/toolkit';

const logger = createLogger(BACKGROUND_SOURCE);

/** Type describing an object containing async thunks for each Method defined */
export type MethodThunks = {
[key in Methods.Method['type']]: AsyncThunk<
Methods.ByType<key>['result'],
Methods.ByType<key>,
any
>;
};

/** Signature for handling method requests */
export type MethodHandler = <T extends Methods.MethodTypes>(
method: Methods.ByType<T>
) => Promise<Methods.ByType<T>['result']>;

/** Utility for building async thunks for all known method types */
export const buildMethodThunks = (
onMethodRequest: MethodHandler
): MethodThunks =>
Object.fromEntries(
Methods.MethodTypes.map((method) => [
method,
createAsyncThunk<
Methods.ByType<typeof method>['result'],
Methods.ByType<typeof method>
>(method, async (method) => {
logger.log(`Requesting ${method.type}`, method.params);
const data = (await onMethodRequest(method)) as Methods.ByType<
typeof method.type
>['result'];
logger.log(`Response from ${method.type}`, data);
return data;
}),
])
) as MethodThunks;
47 changes: 23 additions & 24 deletions devtools/client/src/redux/aliases.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import type {
AliasAction,
ConfigAction,
DataBindingAction,
ExpressionAction,
StartProfilerAction,
StopProfilerAction,
} from '@player-tools/devtools-common';
import type { AsyncRPCActions } from './actions';
import { Methods as Methods, RUNTIME_SOURCE } from '@player-tools/devtools-common';
import { MethodThunks } from './actions/methods';

export const GET_INFO_DETAILS = 'GET_INFO_DETAILS';
export const GET_CONFIG_DETAILS = 'GET_CONFIG_DETAILS';
Expand All @@ -16,6 +9,12 @@ export const GET_CONSOLE_EVAL = 'GET_CONSOLE_EVAL';
export const START_PROFILER = 'START_PROFILER';
export const STOP_PROFILER = 'STOP_PROFILER';


interface MethodAction<T extends Methods.MethodTypes> {
payload: Methods.ByType<T>['params'];
}

// Copied from webext redux library not allowed in flipper
const _alias = (aliases: any) => () => (next: any) => (action: any) => {
const alias = aliases[action.type];

Expand All @@ -26,20 +25,20 @@ const _alias = (aliases: any) => () => (next: any) => (action: any) => {
return next(action);
};

export const buildAliases = (actions: AsyncRPCActions) =>
/** Helper for building corresponding method action via supplied thunks */
const alias = <T extends Methods.MethodTypes>(type: T, methods: MethodThunks) => (action: MethodAction<T>) => methods[type]({
type,
params: action.payload,
source: RUNTIME_SOURCE,
} as Methods.ByType<T>)

export const buildAliases = (methods: MethodThunks) =>
_alias({
GET_INFO_DETAILS: (action: AliasAction) =>
actions['player-runtime-info-request'](action.payload),
GET_CONFIG_DETAILS: (action: ConfigAction) =>
actions['player-config-request'](action.payload),
GET_VIEW_DETAILS: (action: AliasAction) =>
actions['player-view-details-request'](action.payload),
GET_DATA_BINDING_DETAILS: (action: DataBindingAction) =>
actions['player-data-binding-details'](action.payload),
GET_CONSOLE_EVAL: (action: ExpressionAction) =>
actions['player-execute-expression'](action.payload),
START_PROFILER: (action: StartProfilerAction) =>
actions['player-start-profiler-request'](action.payload),
STOP_PROFILER: (action: StopProfilerAction) =>
actions['player-stop-profiler-request'](action.payload),
GET_INFO_DETAILS: alias('player-runtime-info-request', methods),
GET_CONFIG_DETAILS: alias('player-config-request', methods),
GET_VIEW_DETAILS: alias('player-view-details-request', methods),
GET_DATA_BINDING_DETAILS: alias('player-data-binding-details', methods),
GET_CONSOLE_EVAL: alias('player-execute-expression', methods),
START_PROFILER: alias('player-start-profiler-request', methods),
STOP_PROFILER: alias('player-stop-profiler-request', methods),
});
72 changes: 5 additions & 67 deletions devtools/client/src/redux/index.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,8 @@
import {
type Message,
clearStore,
playerFlowStartAction,
playerInitAction,
playerRemoveAction,
playerTimelineAction,
playerViewUpdateAction,
selectedPlayerAction,
} from '@player-tools/devtools-common';
import type { Store } from 'redux';
import { GET_DATA_BINDING_DETAILS } from './aliases';

export * from './actions';
export * from './aliases';
export * from './listeners';
export * from './middleware';
export * from './reducers';

export function handleMessage(store: Store, message: Message) {
switch (message.type) {
case 'runtime-init':
store.dispatch(clearStore());
break;
case 'player-init':
store.dispatch(playerInitAction(message));
store.dispatch(selectedPlayerAction());
break;
case 'player-removed':
store.dispatch(playerRemoveAction(message.playerID));
store.dispatch(selectedPlayerAction());
break;
case 'player-flow-start':
store.dispatch(playerFlowStartAction(message));
store.dispatch(playerTimelineAction(message));
store.dispatch({
type: GET_DATA_BINDING_DETAILS,
payload: { playerID: message.playerID, binding: '' },
});
break;
case 'player-log-event':
store.dispatch(playerTimelineAction(message));
break;
case 'player-view-update-event':
store.dispatch(playerViewUpdateAction(message));
break;
case 'player-data-change-event': {
const { players } = store.getState();

if (
players.activePlayers[message.playerID] &&
players.activePlayers[message.playerID].dataState.selectedBinding
) {
store.dispatch({
type: GET_DATA_BINDING_DETAILS,
payload: message,
});
}

store.dispatch({
type: GET_DATA_BINDING_DETAILS,
payload: { playerID: message.playerID, binding: '' },
});
store.dispatch(playerTimelineAction(message));
break;
}

default:
console.warn(`Unhandled event: ${JSON.stringify(message)}`);
break;
}
}
export * from './selectors';
export * from './state';
export * from './store';
8 changes: 8 additions & 0 deletions devtools/client/src/redux/listeners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Events } from "@player-tools/devtools-common";
import { Dispatch } from "redux";
import { EventActions } from "./actions";

/** Utility method to filter known events from a supplied message and dispatch the corresponding action */
export const dispatchEvents = (dispatch: Dispatch) => (message: any) => {
if (Events.isEvent(message)) dispatch(EventActions[message.type](message as any))
}
64 changes: 64 additions & 0 deletions devtools/client/src/redux/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { createListenerMiddleware, isAnyOf } from "@reduxjs/toolkit";
import { Actions, EventActions } from "./actions";
import { GET_DATA_BINDING_DETAILS } from "./aliases";
import { type StoreState } from './state';

/**
* Listener middleware that will be consumed by default when creating the devtools store.
* Exported such that clients can configure additional side effects.
*/
export const listenerMiddleware = createListenerMiddleware<StoreState>();

listenerMiddleware.startListening({
matcher: isAnyOf(
EventActions['player-data-change-event'],
EventActions['player-log-event'],
EventActions['player-flow-start'],
),
effect: (action, api) => {
api.dispatch(Actions['player-timeline-event'](action.payload));
},
});

listenerMiddleware.startListening({
actionCreator: EventActions['runtime-init'],
effect: (_, api) => {
api.dispatch(Actions["clear-store"]())
}
})

listenerMiddleware.startListening({
matcher: isAnyOf(
EventActions["player-init"],
EventActions["player-removed"],
),
effect: (_, api) => {
api.dispatch(Actions["selected-player"]())
}
});

listenerMiddleware.startListening({
matcher: isAnyOf(
EventActions["player-flow-start"],
EventActions["player-data-change-event"],
),
effect: (action, api) => {
const { players } = api.getState();
const { playerID } = action.payload;

if (
players.activePlayers[playerID] &&
players.activePlayers[playerID].dataState.selectedBinding
) {
api.dispatch({
type: GET_DATA_BINDING_DETAILS,
payload: { playerID, binding: players.activePlayers[playerID].dataState.selectedBinding },
});
}

api.dispatch({
type: GET_DATA_BINDING_DETAILS,
payload: { playerID, binding: '' },
})
}
})
Loading