dreaction-client-core 1.0.0

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.
Files changed (52) hide show
  1. package/lib/client-options.d.ts +77 -0
  2. package/lib/client-options.d.ts.map +1 -0
  3. package/lib/client-options.js +2 -0
  4. package/lib/index.d.ts +190 -0
  5. package/lib/index.d.ts.map +1 -0
  6. package/lib/index.js +401 -0
  7. package/lib/plugins/api-response.d.ts +13 -0
  8. package/lib/plugins/api-response.d.ts.map +1 -0
  9. package/lib/plugins/api-response.js +23 -0
  10. package/lib/plugins/benchmark.d.ts +15 -0
  11. package/lib/plugins/benchmark.d.ts.map +1 -0
  12. package/lib/plugins/benchmark.js +31 -0
  13. package/lib/plugins/clear.d.ts +11 -0
  14. package/lib/plugins/clear.d.ts.map +1 -0
  15. package/lib/plugins/clear.js +13 -0
  16. package/lib/plugins/image.d.ts +19 -0
  17. package/lib/plugins/image.d.ts.map +1 -0
  18. package/lib/plugins/image.js +24 -0
  19. package/lib/plugins/logger.d.ts +18 -0
  20. package/lib/plugins/logger.d.ts.map +1 -0
  21. package/lib/plugins/logger.js +44 -0
  22. package/lib/plugins/repl.d.ts +10 -0
  23. package/lib/plugins/repl.d.ts.map +1 -0
  24. package/lib/plugins/repl.js +55 -0
  25. package/lib/plugins/state-responses.d.ts +20 -0
  26. package/lib/plugins/state-responses.d.ts.map +1 -0
  27. package/lib/plugins/state-responses.js +38 -0
  28. package/lib/reactotron-core-client.d.ts +191 -0
  29. package/lib/reactotron-core-client.d.ts.map +1 -0
  30. package/lib/reactotron-core-client.js +400 -0
  31. package/lib/serialize.d.ts +20 -0
  32. package/lib/serialize.d.ts.map +1 -0
  33. package/lib/serialize.js +112 -0
  34. package/lib/stopwatch.d.ts +6 -0
  35. package/lib/stopwatch.d.ts.map +1 -0
  36. package/lib/stopwatch.js +45 -0
  37. package/lib/validate.d.ts +9 -0
  38. package/lib/validate.d.ts.map +1 -0
  39. package/lib/validate.js +26 -0
  40. package/package.json +29 -0
  41. package/src/client-options.ts +95 -0
  42. package/src/index.ts +654 -0
  43. package/src/plugins/api-response.ts +32 -0
  44. package/src/plugins/benchmark.ts +35 -0
  45. package/src/plugins/clear.ts +14 -0
  46. package/src/plugins/image.ts +34 -0
  47. package/src/plugins/logger.ts +59 -0
  48. package/src/plugins/repl.ts +63 -0
  49. package/src/plugins/state-responses.ts +75 -0
  50. package/src/serialize.ts +125 -0
  51. package/src/stopwatch.ts +50 -0
  52. package/src/validate.ts +38 -0
@@ -0,0 +1,14 @@
1
+ import type { DReactionCore, Plugin } from '../';
2
+
3
+ /**
4
+ * Clears the reactotron server.
5
+ */
6
+ const clear = () => (reactotron: DReactionCore) => {
7
+ return {
8
+ features: {
9
+ clear: () => reactotron.send('clear'),
10
+ },
11
+ } satisfies Plugin<DReactionCore>;
12
+ };
13
+
14
+ export default clear;
@@ -0,0 +1,34 @@
1
+ import type { DReactionCore, Plugin } from '../';
2
+
3
+ export interface ImagePayload {
4
+ uri: string;
5
+ preview: string;
6
+ caption?: string;
7
+ width?: number;
8
+ height?: number;
9
+ filename?: string;
10
+ }
11
+
12
+ /**
13
+ * Provides an image.
14
+ */
15
+ const image = () => (reactotron: DReactionCore) => {
16
+ return {
17
+ features: {
18
+ // expanded just to show the specs
19
+ image: (payload: ImagePayload) => {
20
+ const { uri, preview, filename, width, height, caption } = payload;
21
+ return reactotron.send('image', {
22
+ uri,
23
+ preview,
24
+ filename,
25
+ width,
26
+ height,
27
+ caption,
28
+ });
29
+ },
30
+ },
31
+ } satisfies Plugin<DReactionCore>;
32
+ };
33
+
34
+ export default image;
@@ -0,0 +1,59 @@
1
+ import type { DReactionCore, Plugin, InferFeatures } from '../';
2
+
3
+ /**
4
+ * Provides 4 features for logging. log & debug are the same.
5
+ */
6
+ const logger = () => (dreaction: DReactionCore) => {
7
+ return {
8
+ features: {
9
+ log: (...args) => {
10
+ const content = args && args.length === 1 ? args[0] : args;
11
+ dreaction.send('log', { level: 'debug', message: content }, false);
12
+ },
13
+ logImportant: (...args) => {
14
+ const content = args && args.length === 1 ? args[0] : args;
15
+ dreaction.send('log', { level: 'debug', message: content }, true);
16
+ },
17
+ debug: (message, important = false) =>
18
+ dreaction.send('log', { level: 'debug', message }, !!important),
19
+ warn: (message) =>
20
+ dreaction.send('log', { level: 'warn', message }, true),
21
+ error: (message, stack) =>
22
+ dreaction.send('log', { level: 'error', message, stack }, true),
23
+ },
24
+ } satisfies Plugin<DReactionCore>;
25
+ };
26
+
27
+ export default logger;
28
+
29
+ export type LoggerPlugin = ReturnType<typeof logger>;
30
+
31
+ export const hasLoggerPlugin = (
32
+ dreaction: DReactionCore
33
+ ): dreaction is DReactionCore &
34
+ InferFeatures<DReactionCore, ReturnType<typeof logger>> => {
35
+ return (
36
+ dreaction &&
37
+ 'log' in dreaction &&
38
+ typeof dreaction.log === 'function' &&
39
+ 'logImportant' in dreaction &&
40
+ typeof dreaction.logImportant === 'function' &&
41
+ 'debug' in dreaction &&
42
+ typeof dreaction.debug === 'function' &&
43
+ 'warn' in dreaction &&
44
+ typeof dreaction.warn === 'function' &&
45
+ 'error' in dreaction &&
46
+ typeof dreaction.error === 'function'
47
+ );
48
+ };
49
+
50
+ export const assertHasLoggerPlugin = (
51
+ dreaction: DReactionCore
52
+ ): asserts dreaction is DReactionCore &
53
+ InferFeatures<DReactionCore, ReturnType<typeof logger>> => {
54
+ if (!hasLoggerPlugin(dreaction)) {
55
+ throw new Error(
56
+ 'This Reactotron client has not had the logger plugin applied to it. Make sure that you add `use(logger())` before adding this plugin.'
57
+ );
58
+ }
59
+ };
@@ -0,0 +1,63 @@
1
+ import type { DReactionCore, Plugin } from '../';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/ban-types
4
+ export type AcceptableRepls = object | Function | string | number;
5
+
6
+ const repl = () => (reactotron: DReactionCore) => {
7
+ const myRepls: { [key: string]: AcceptableRepls } = {};
8
+ // let currentContext = null
9
+ return {
10
+ onCommand: ({ type, payload }) => {
11
+ if (type.substr(0, 5) !== 'repl.') return;
12
+
13
+ switch (type.substr(5)) {
14
+ case 'ls':
15
+ reactotron.send('repl.ls.response', Object.keys(myRepls));
16
+ break;
17
+ // case "cd":
18
+ // const changeTo = myRepls.find(r => r.name === payload)
19
+ // if (!changeTo) {
20
+ // reactotron.send("repl.cd.response", "That REPL does not exist")
21
+ // break
22
+ // }
23
+ // currentContext = payload
24
+ // reactotron.send("repl.cd.response", `Change REPL to "${payload}"`)
25
+ // break
26
+ case 'execute':
27
+ // if (!currentContext) {
28
+ // reactotron.send(
29
+ // "repl.execute.response",
30
+ // "You must first select the REPL to use. Try 'ls'"
31
+ // )
32
+ // break
33
+ // }
34
+ // const currentRepl = myRepls.find(r => r.name === currentContext)
35
+ // if (!currentRepl) {
36
+ // reactotron.send("repl.execute.response", "The selected REPL no longer exists.")
37
+ // break
38
+ // }
39
+ reactotron.send(
40
+ 'repl.execute.response',
41
+ function () {
42
+ return eval(payload); // eslint-disable-line no-eval
43
+ }.call(myRepls)
44
+ );
45
+ break;
46
+ }
47
+ },
48
+ features: {
49
+ repl: (name: string, value: AcceptableRepls) => {
50
+ if (!name) {
51
+ throw new Error('You must provide a name for your REPL');
52
+ }
53
+
54
+ if (myRepls[name]) {
55
+ throw new Error('You are already REPLing an item with that name');
56
+ }
57
+
58
+ myRepls[name] = value;
59
+ },
60
+ },
61
+ } satisfies Plugin<DReactionCore>;
62
+ };
63
+ export default repl;
@@ -0,0 +1,75 @@
1
+ import type {
2
+ StateActionCompletePayload,
3
+ StateBackupResponsePayload,
4
+ StateKeysResponsePayload,
5
+ StateValuesChangePayload,
6
+ StateValuesResponsePayload,
7
+ } from 'dreaction-protocol';
8
+ import type { DReactionCore, Plugin, InferFeatures } from '../';
9
+
10
+ /**
11
+ * Provides helper functions for send state responses.
12
+ */
13
+ const stateResponse = () => (reactotron: DReactionCore) => {
14
+ return {
15
+ features: {
16
+ stateActionComplete: (
17
+ name: StateActionCompletePayload['name'],
18
+ action: StateActionCompletePayload['action'],
19
+ important = false
20
+ ) =>
21
+ reactotron.send('state.action.complete', { name, action }, !!important),
22
+
23
+ stateValuesResponse: (
24
+ path: StateValuesResponsePayload['path'],
25
+ value: StateValuesResponsePayload['value'],
26
+ valid: StateValuesResponsePayload['value'] = true
27
+ ) => reactotron.send('state.values.response', { path, value, valid }),
28
+
29
+ stateKeysResponse: (
30
+ path: StateKeysResponsePayload['path'],
31
+ keys: StateKeysResponsePayload['keys'],
32
+ valid: StateKeysResponsePayload['valid'] = true
33
+ ) => reactotron.send('state.keys.response', { path, keys, valid }),
34
+
35
+ stateValuesChange: (changes: StateValuesChangePayload['changes']) =>
36
+ changes.length > 0 &&
37
+ reactotron.send('state.values.change', { changes }),
38
+
39
+ /** sends the state backup over to the server */
40
+ stateBackupResponse: (state: StateBackupResponsePayload['state']) =>
41
+ reactotron.send('state.backup.response', { state }),
42
+ },
43
+ } satisfies Plugin<DReactionCore>;
44
+ };
45
+
46
+ export type StateResponsePlugin = ReturnType<typeof stateResponse>;
47
+
48
+ export default stateResponse;
49
+
50
+ export const hasStateResponsePlugin = (
51
+ reactotron: DReactionCore
52
+ ): reactotron is DReactionCore &
53
+ InferFeatures<DReactionCore, ReturnType<typeof stateResponse>> =>
54
+ reactotron &&
55
+ 'stateActionComplete' in reactotron &&
56
+ typeof reactotron.stateActionComplete === 'function' &&
57
+ 'stateValuesResponse' in reactotron &&
58
+ typeof reactotron.stateValuesResponse === 'function' &&
59
+ 'stateKeysResponse' in reactotron &&
60
+ typeof reactotron.stateKeysResponse === 'function' &&
61
+ 'stateValuesChange' in reactotron &&
62
+ typeof reactotron.stateValuesChange === 'function' &&
63
+ 'stateBackupResponse' in reactotron &&
64
+ typeof reactotron.stateBackupResponse === 'function';
65
+
66
+ export const assertHasStateResponsePlugin = (
67
+ reactotron: DReactionCore
68
+ ): asserts reactotron is DReactionCore &
69
+ InferFeatures<DReactionCore, ReturnType<typeof stateResponse>> => {
70
+ if (!hasStateResponsePlugin(reactotron)) {
71
+ throw new Error(
72
+ 'This Reactotron client has not had the state responses plugin applied to it. Make sure that you add `use(stateResponse())` before adding this plugin.'
73
+ );
74
+ }
75
+ };
@@ -0,0 +1,125 @@
1
+ // JSON.stringify() doesn't support circular dependencies or keeping
2
+ // falsy values. This does.
3
+ //
4
+ // Mostly adapted from https://github.com/isaacs/json-stringify-safe
5
+
6
+ // replacement tokens
7
+ const UNDEFINED = '~~~ undefined ~~~';
8
+ const NULL = `~~~ null ~~~`;
9
+ const FALSE = `~~~ false ~~~`;
10
+ const ZERO = `~~~ zero ~~~`;
11
+ const EMPTY_STRING = `~~~ empty string ~~~`;
12
+ const CIRCULAR = '~~~ Circular Reference ~~~';
13
+ const ANONYMOUS = '~~~ anonymous function ~~~';
14
+ const INFINITY = '~~~ Infinity ~~~';
15
+ const NEGATIVE_INFINITY = '~~~ -Infinity ~~~';
16
+ // const NAN = '~~~ NaN ~~~'
17
+
18
+ /**
19
+ * Fix BigInt serialization
20
+ * BigInts are not supported by JSON.stringify in Hermes android.
21
+ * This is a workaround.
22
+ * https://github.com/GoogleChromeLabs/jsbi/issues/30#issuecomment-953187833
23
+ * https://github.com/infinitered/reactotron/issues/1436
24
+ */
25
+ declare global {
26
+ interface BigInt {
27
+ toJSON(): string;
28
+ }
29
+ }
30
+ if (typeof BigInt !== 'undefined') {
31
+ // eslint-disable-next-line no-extend-native
32
+ BigInt.prototype.toJSON = function () {
33
+ return this.toString();
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Attempts to give a name to a function.
39
+ *
40
+ * @param {Function} fn - The function to name.
41
+ */
42
+ function getFunctionName(fn: any): string {
43
+ const n = fn.name;
44
+ if (n === null || n === undefined || n === '') {
45
+ return ANONYMOUS;
46
+ } else {
47
+ return `~~~ ${n}() ~~~`;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Serializes an object to JSON.
53
+ *
54
+ * @param {any} source - The victim.
55
+ */
56
+ function serialize(source: any, proxyHack = false) {
57
+ const stack = [] as any[];
58
+ const keys = [] as string[];
59
+
60
+ /**
61
+ * Replace this object node with something potentially custom.
62
+ *
63
+ * @param {*} key - The key currently visited.
64
+ * @param {*} value - The value to replace.
65
+ */
66
+ function serializer(replacer: Function | null) {
67
+ return function (this: any, key: string, value: any) {
68
+ // slam dunks
69
+ if (value === true) return true;
70
+
71
+ // weird stuff
72
+ // if (Object.is(value, NaN)) return NAN // OK, apparently this is hard... leaving out for now
73
+ if (value === Infinity) return INFINITY;
74
+ if (value === -Infinity) return NEGATIVE_INFINITY;
75
+ if (value === 0) return ZERO;
76
+
77
+ // classic javascript
78
+ if (value === undefined) return UNDEFINED;
79
+ if (value === null) return NULL;
80
+ if (value === false) return FALSE;
81
+
82
+ // head shakers
83
+ if (value === -0) return ZERO; // eslint-disable-line
84
+ if (value === '') return EMPTY_STRING;
85
+
86
+ if (proxyHack && typeof value === 'object' && value.nativeEvent) {
87
+ return value.nativeEvent;
88
+ }
89
+
90
+ // known types that have easy resolving
91
+ switch (typeof value) {
92
+ case 'string':
93
+ return value;
94
+ case 'number':
95
+ return value;
96
+ case 'bigint':
97
+ return value.toString();
98
+ case 'function':
99
+ return getFunctionName(value);
100
+ }
101
+
102
+ // Tough things to crack
103
+ // If we have an iterator but are not an array (because arrays are easily seralizeable already)...
104
+ if (value[Symbol.iterator] && !Array.isArray(value)) {
105
+ // Convert to an array!
106
+ return [...value];
107
+ }
108
+
109
+ if (stack.length > 0) {
110
+ // check for prior existance
111
+ const thisPos = stack.indexOf(this);
112
+ ~thisPos ? stack.splice(thisPos + 1) : stack.push(this);
113
+ ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);
114
+ if (~stack.indexOf(value)) value = CIRCULAR;
115
+ } else {
116
+ stack.push(value);
117
+ }
118
+
119
+ return replacer == null ? value : replacer.call(this, key, value);
120
+ };
121
+ }
122
+ return JSON.stringify(source, serializer(null));
123
+ }
124
+
125
+ export default serialize;
@@ -0,0 +1,50 @@
1
+ /// <reference types="node" />
2
+
3
+ declare const global: any;
4
+
5
+ const hasHirezNodeTimer =
6
+ false &&
7
+ typeof process === 'object' &&
8
+ process &&
9
+ process.hrtime &&
10
+ typeof process.hrtime === 'function';
11
+
12
+ // the default timer
13
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
+ const defaultPerformanceNow = (started?: number) => Date.now();
15
+
16
+ // try to find the browser-based performance timer
17
+ const nativePerformance =
18
+ typeof window !== 'undefined' &&
19
+ window &&
20
+ (window.performance ||
21
+ (window as any).msPerformance ||
22
+ (window as any).webkitPerformance);
23
+
24
+ // the function we're trying to assign
25
+ let performanceNow = defaultPerformanceNow;
26
+
27
+ // accepts an already started time and returns the number of milliseconds
28
+ let delta = (started: number) => performanceNow() - started;
29
+
30
+ if (hasHirezNodeTimer) {
31
+ performanceNow = process.hrtime as any;
32
+ // delta = (started) => performanceNow(started)[1] / 1000000;
33
+ delta = (started) => performanceNow(started) / 1000000;
34
+ } else if (global.nativePerformanceNow) {
35
+ // react native 47
36
+ performanceNow = global.nativePerformanceNow;
37
+ } else if (nativePerformance) {
38
+ // browsers + safely check for react native < 47
39
+ performanceNow = () => nativePerformance.now && nativePerformance.now();
40
+ }
41
+
42
+ /**
43
+ * Starts a lame, low-res timer. Returns a function which when invoked,
44
+ * gives you the number of milliseconds since passing. ish.
45
+ */
46
+ export const start = () => {
47
+ // record the start time
48
+ const started = performanceNow();
49
+ return () => delta(started);
50
+ };
@@ -0,0 +1,38 @@
1
+ import type { ClientOptions } from './client-options';
2
+ import type { DReactionCore } from './';
3
+
4
+ const isCreateSocketValid = (
5
+ createSocket: unknown
6
+ ): createSocket is ClientOptions<DReactionCore>['createSocket'] =>
7
+ typeof createSocket !== 'undefined' && createSocket !== null;
8
+ const isHostValid = (host: string): boolean =>
9
+ (typeof host === 'string' && host && host !== '') as boolean;
10
+ const isPortValid = (port: number): boolean =>
11
+ typeof port === 'number' && port >= 1 && port <= 65535;
12
+ const onCommandValid = (fn: (cmd: string) => any) => typeof fn === 'function';
13
+
14
+ /**
15
+ * Ensures the options are sane to run this baby. Throw if not. These
16
+ * are basically sanity checks.
17
+ */
18
+ const validate = (options: ClientOptions<DReactionCore>) => {
19
+ const { createSocket, host, port, onCommand } = options;
20
+
21
+ if (!isCreateSocketValid(createSocket)) {
22
+ throw new Error('invalid createSocket function');
23
+ }
24
+
25
+ if (!isHostValid(host!)) {
26
+ throw new Error('invalid host');
27
+ }
28
+
29
+ if (!isPortValid(port!)) {
30
+ throw new Error('invalid port');
31
+ }
32
+
33
+ if (!onCommandValid(onCommand!)) {
34
+ throw new Error('invalid onCommand handler');
35
+ }
36
+ };
37
+
38
+ export default validate;