1 | # reactotron-core-client
|
2 |
|
3 | This provides the core functionality of the clients allowing it talk to talk to the server.
|
4 |
|
5 | It is used by `reactotron-react-dom` and `reactotron-react-native`.
|
6 |
|
7 | # Usage
|
8 |
|
9 | ```js
|
10 | import { createClient } from "reactotron-core-client"
|
11 |
|
12 | // setup a reactotron client
|
13 | const client = createClient({
|
14 | // injected in for compatibility
|
15 | createSocket: (path) => new WebSocket(path),
|
16 |
|
17 | host: "localhost",
|
18 | port: 9090,
|
19 | name: "I am a client!",
|
20 |
|
21 | // fires when we get connected to a server
|
22 | onConnect: () => console.log("hi"),
|
23 |
|
24 | // fires when we get disconnected from the server
|
25 | onDisconnect: () => console.log("bye"),
|
26 |
|
27 | // fires when the server is telling us something
|
28 | onCommand: ({ type, payload }) => {
|
29 | switch (type) {
|
30 | case "server.intro":
|
31 | const { name, version } = payload
|
32 | break
|
33 |
|
34 | case "state.values.request":
|
35 | const { path } = payload // the path to the state
|
36 | break
|
37 |
|
38 | case "state.keys.request":
|
39 | const { path } = payload // the path to the state
|
40 | break
|
41 |
|
42 | case "state.values.subscribe":
|
43 | const { paths } = payload // string array of state paths
|
44 | break
|
45 |
|
46 | case "state.action.dispatch":
|
47 | const { action } = payload // an object to be dispatch through the state plugin
|
48 | break
|
49 | }
|
50 | console.log(`I just received a ${type} command`)
|
51 | console.log(payload)
|
52 | },
|
53 | })
|
54 |
|
55 | // connect to the server
|
56 | client.connect()
|
57 |
|
58 | // send a log message as a string
|
59 | client.send("log", { level: "debug", message: "hello!" })
|
60 |
|
61 | // send a log message as a string that's important
|
62 | client.send("log", { level: "debug", message: "hello!" }, true)
|
63 |
|
64 | // sending an object log message
|
65 | client.send("log", {
|
66 | level: "debug",
|
67 | message: {
|
68 | nested: [1, 2, { hello: "there" }],
|
69 | fun: true,
|
70 | },
|
71 | })
|
72 |
|
73 | // send a warning
|
74 | client.send("log", { level: "warn", message: "oops" })
|
75 |
|
76 | // send an error with an optional stack trace
|
77 | client.send("log", {
|
78 | level: "error",
|
79 | message: "crap",
|
80 | stackTrace: [],
|
81 | })
|
82 |
|
83 | // report that an action is complete
|
84 | client.send("state.action.complete", {
|
85 | name: "LOGIN_REQUEST",
|
86 | action: {
|
87 | type: "LOGIN_REQUEST",
|
88 | email: "steve@kellock.ca",
|
89 | password: "secret...shhh....",
|
90 | },
|
91 | })
|
92 |
|
93 | // report that values have changed
|
94 | client.send("state.values.response", {
|
95 | path: "user.givenName",
|
96 | value: "Steve",
|
97 | valid: true,
|
98 | })
|
99 |
|
100 | // list the keys at a given path in state
|
101 | client.send("state.keys.response", {
|
102 | path: "user",
|
103 | keys: ["givenName", "familyName"],
|
104 | valid: true,
|
105 | })
|
106 |
|
107 | // let the server know the state values they're subscribed to have changed
|
108 | client.send("state.values.change", {
|
109 | changes: [
|
110 | { path: "user.givenName", value: "Steve" },
|
111 | {
|
112 | path: "user",
|
113 | value: { givenName: "Steve", familyName: "Kellock" },
|
114 | },
|
115 | ],
|
116 | })
|
117 |
|
118 | // report any API activity
|
119 | client.send("api.response", {
|
120 | request: {
|
121 | url: "https://api.example.com/v1/people",
|
122 | method: "POST",
|
123 | data: {
|
124 | user: { givenName: "Steve", familyName: "Kellock" },
|
125 | },
|
126 | headers: {
|
127 | Accept: "application/json",
|
128 | Cookie: "__ispy=mylittleye; __something=blue",
|
129 | },
|
130 | },
|
131 | response: {
|
132 | body: { result: "ok" },
|
133 | status: 200,
|
134 | headers: {
|
135 | Connection: "keep-alive",
|
136 | Server: "cloudflare-nginx",
|
137 | },
|
138 | },
|
139 | duration: 150.0,
|
140 | })
|
141 |
|
142 | // send a benchmark report up to the server
|
143 | client.send("bench.report", {
|
144 | title: "My Fast Algorithmz",
|
145 | steps: [
|
146 | { title: "Step 1", time: 0 },
|
147 | { title: "Step 2", time: 123 },
|
148 | { title: "Step 3", time: 1024 },
|
149 | ],
|
150 | })
|
151 |
|
152 | // a utility to time things
|
153 | const elapsed = client.startTimer()
|
154 | // do something you want to time
|
155 | const ms = elapsed() // the number of ms it took. ish.
|
156 |
|
157 | // display a custom event
|
158 | client.display({
|
159 | name: "MY EVENT",
|
160 | value: { color: "green", vegetable: "spinach", variant: "baby", salad: true },
|
161 | important: true,
|
162 | preview: "What's in my appetizer?",
|
163 | })
|
164 | ```
|
165 |
|
166 | # Why are we passing createSocket down?
|
167 |
|
168 | # Messages
|
169 |
|
170 | ### client.intro
|
171 |
|
172 | The client sends this message to the server when it first connects. It contains
|
173 | all the configuration information used to configure the client.
|
174 |
|
175 | For example:
|
176 |
|
177 | ```js
|
178 | {
|
179 | "host": "localhost", // the server we're connecting to
|
180 | "port": 9090, // the server's port
|
181 | "name": "My Fantastic App", // the name of our app
|
182 | "userAgent": "Internet Explorer 3.0", // the user agent
|
183 | "reactotronVersion": "0.99.1", // the version of reactotron
|
184 | "environment": "development" // our environment
|
185 | }
|
186 | ```
|
187 |
|
188 | ### server.intro
|
189 |
|
190 | The client receives this message from the server once connected. It contains
|
191 | configuration information used by the server.
|
192 |
|
193 | Right now the payload is empty because I haven't even created the server!
|
194 |
|
195 | It'll probably have things like directory, version... I really don't know yet.
|
196 |
|
197 | ```json
|
198 | {
|
199 | "name": "I Am Server. Roar.",
|
200 | "version": "0.99.1"
|
201 | }
|
202 | ```
|
203 |
|
204 | ### log
|
205 |
|
206 | The client sends this to the server to log a message, warning or error. For
|
207 | warnings and errors, we pass through an optional stackTrace array.
|
208 |
|
209 | Log:
|
210 |
|
211 | ```json
|
212 | {
|
213 | "value": "hello!",
|
214 | "level": "debug"
|
215 | }
|
216 | ```
|
217 |
|
218 | Warn:
|
219 |
|
220 | ```json
|
221 | {
|
222 | "value": "hello!",
|
223 | "level": "warn",
|
224 | "stackTrace": null
|
225 | }
|
226 | ```
|
227 |
|
228 | Error:
|
229 |
|
230 | ```json
|
231 | {
|
232 | "value": "hello!",
|
233 | "level": "error",
|
234 | "stackTrace": [{ "lineNo": 1, "file": "foo.js" }]
|
235 | }
|
236 | ```
|
237 |
|
238 | TBD: The actual stack trace format. I've seen a couple of formats unfortunately
|
239 | and I need to research what these will look like.
|
240 |
|
241 | Also, how is source maps going to factor in?
|
242 |
|
243 | ### image
|
244 |
|
245 | Send from the client to the server to pass an image. The uri field is required
|
246 | and is a `data-uri`. This means, an ordinary http link will work, but as will embedding the image inline.
|
247 |
|
248 | ```json
|
249 | {
|
250 | "uri": "http://placekitten.com/g/400/400",
|
251 | "preview": "placekitten.com!",
|
252 | "filename": "cat.jpg",
|
253 | "width": 400,
|
254 | "height": 400,
|
255 | "caption": "D'awwwwwww"
|
256 | }
|
257 | ```
|
258 |
|
259 | ### clear
|
260 |
|
261 | An instruction sent from the client to the server to clear the history on the server.
|
262 |
|
263 | ### state.action.complete
|
264 |
|
265 | Sent from the client to the server when an action is complete. It's up to you
|
266 | to decide what an action is. For Redux, these are actions dispatched. For MobX,
|
267 | these are the results of `spy`.
|
268 |
|
269 | ```json
|
270 | {
|
271 | "name": "MY_ACTION",
|
272 | "value": {}
|
273 | }
|
274 | ```
|
275 |
|
276 | ### state.action.dispatch
|
277 |
|
278 | Sent from the client to the server in order to dispatch this action through the
|
279 | state system.
|
280 |
|
281 | ```json
|
282 | {
|
283 | "action": { "type": "LOGIN_REQUEST", "password": "s3cr3t@g3ntm@n" }
|
284 | }
|
285 | ```
|
286 |
|
287 | ### state.values.request
|
288 |
|
289 | Sent from the server to the client to ask for the values of state.
|
290 |
|
291 | ```json
|
292 | {
|
293 | "path": "account"
|
294 | }
|
295 | ```
|
296 |
|
297 | ### state.values.response
|
298 |
|
299 | Sent from the client to the server in response to `state.values.request`.
|
300 |
|
301 | ```json
|
302 | {
|
303 | "path": "account",
|
304 | "valid": true,
|
305 | "value": {
|
306 | "givenName": "Steve",
|
307 | "familyName": "Kellock"
|
308 | }
|
309 | }
|
310 | ```
|
311 |
|
312 | ### state.values.subscribe
|
313 |
|
314 | Sent from the server to the client to ask for notification when something
|
315 | in the state changes.
|
316 |
|
317 | ```json
|
318 | {
|
319 | "paths": ["account", "cart.total"]
|
320 | }
|
321 | ```
|
322 |
|
323 | ### state.values.change
|
324 |
|
325 | Sent from the client to the server when one of the subscriptions found in
|
326 | `state.values.subscribe` has changed.
|
327 |
|
328 | ```json
|
329 | {
|
330 | "changes": [
|
331 | {
|
332 | "path": "account",
|
333 | "value": {
|
334 | "email": "steve@kellock.ca"
|
335 | }
|
336 | },
|
337 | {
|
338 | "path": "cart.total",
|
339 | "value": 100.01
|
340 | }
|
341 | ]
|
342 | }
|
343 | ```
|
344 |
|
345 | ### state.keys.request
|
346 |
|
347 | Sent from the server to the client to enumerate the keys inside state.
|
348 |
|
349 | ```json
|
350 | {
|
351 | "path": "account"
|
352 | }
|
353 | ```
|
354 |
|
355 | ### state.keys.response
|
356 |
|
357 | Sent from the client to server in response to `state.keys.request`.
|
358 |
|
359 | ```json
|
360 | {
|
361 | "path": "account",
|
362 | "valid": true,
|
363 | "keys": ["givenName", "familyName"]
|
364 | }
|
365 | ```
|
366 |
|
367 | ### api.response
|
368 |
|
369 | Sent from the client to server when an API has finished a request.
|
370 |
|
371 | ```json
|
372 | {
|
373 | "request": {
|
374 | "url": "https://api.example.com/people/1",
|
375 | "method": "PUT",
|
376 | "data": {
|
377 | "firstName": "Steve",
|
378 | "lastName": "Kellock"
|
379 | },
|
380 | "headers": {
|
381 | "Accept": "application/json",
|
382 | "Cookie": "__ispy=mylittleye; __something=blue"
|
383 | }
|
384 | },
|
385 | "response": {
|
386 | "body": {},
|
387 | "status": 200,
|
388 | "headers": {
|
389 | "Connection": "keep-alive",
|
390 | "Server": "cloudflare-nginx"
|
391 | }
|
392 | },
|
393 | "duration": 120.0
|
394 | }
|
395 | ```
|
396 |
|
397 | ### bench.report
|
398 |
|
399 | Sent from the client to server when it's time to report some performance details.
|
400 |
|
401 | ```json
|
402 | {
|
403 | "title": "My Sorting Algorithm",
|
404 | "steps": [
|
405 | { "title": "start", "time": 0 },
|
406 | { "title": "lookup tables", "time": 123 },
|
407 | { "title": "randomize", "time": 422 }
|
408 | ]
|
409 | }
|
410 | ```
|
411 |
|
412 | ### display
|
413 |
|
414 | Sent from the client to the server to provide a way to show "custom" commands.
|
415 |
|
416 | ```json
|
417 | {
|
418 | "name": "MY EVENT",
|
419 | "value": {
|
420 | "color": "green",
|
421 | "vegetable": "spinach",
|
422 | "variant": "baby",
|
423 | "salad": true
|
424 | },
|
425 | "image": {
|
426 | "uri": "http://placekitten.com/g/400/400"
|
427 | },
|
428 | "important": true,
|
429 | "preview": "What's in my appetizer?"
|
430 | }
|
431 | ```
|
432 |
|
433 | # Plugins
|
434 |
|
435 | Reactotron is extensible via plugins. You add plugins by calling the `use`
|
436 | function on the the client.
|
437 |
|
438 | A plugin looks like this:
|
439 |
|
440 | ```js
|
441 | export default () => (reactotron) => {}
|
442 | ```
|
443 |
|
444 | - A function that:
|
445 | - returns a function with 1 parameter (reactotron) that:
|
446 | - returns an object
|
447 |
|
448 | ### The 1st Function
|
449 |
|
450 | You use the first function to configure your plugin. If you don't have any
|
451 | configuration required for your plugin, just leave it empty like above.
|
452 |
|
453 | ### The 2nd function
|
454 |
|
455 | The 2nd function gets called with the reactotron object. Among other things,
|
456 | it contains (most importantly) a function called `send()`.
|
457 |
|
458 | ### The return object
|
459 |
|
460 | This contains hooks into reactotron. By naming the keys certain things, you're
|
461 | able to hook into guts to do stuff. Most importantly `onCommand` to receive
|
462 | events from the server and `features` to define extra functions on reactotron.
|
463 |
|
464 | ```js
|
465 | // counter-plugin.js
|
466 | export default () => (reactotron) => {
|
467 | let commandCounter = 0
|
468 | return {
|
469 | onCommand: (command) => {
|
470 | commandCounter++
|
471 | if (commandCounter === 69) console.log("tee hee")
|
472 | },
|
473 | }
|
474 | }
|
475 | ```
|
476 |
|
477 | Here's what a plugin can do.
|
478 |
|
479 | ```js
|
480 | {
|
481 | // Fires whenever a command is received from the server.
|
482 | //
|
483 | // command is an object with:
|
484 | // .type - String - the name of the command
|
485 | // .payload - anything - maybe null, maybe a string, maybe an object, not a function
|
486 | // its whatever the server sent.
|
487 | onCommand: command => {
|
488 | const { type, payload } = command
|
489 | },
|
490 |
|
491 | // Fires when we connect to the server. Will only be called if the plugin
|
492 | // is setup before connecting to the server.
|
493 | onConnect: () => {},
|
494 |
|
495 | // Fires when we disconnect from the server.
|
496 | onDisconnect: () => {},
|
497 |
|
498 | // fires when the plugin is attached (this only happens once at initialization)
|
499 | onPlugin: reactotron => console.log('I have been attached to ', reactotron),
|
500 |
|
501 | // This is an object (not a function). The keys are strings. The values are functions.
|
502 | // Every entry in here will become a method on the Reactotron client object.
|
503 | // Collisions are handled on a first-come first-serve basis.
|
504 | //
|
505 | // These names are reserved:
|
506 | // connect, configure, send, use, options, connected, plugins, and socket.
|
507 | //
|
508 | // Sorry.
|
509 | //
|
510 | // I went with this mixin approach because the interface feels nice from the
|
511 | // calling code point-of-view.
|
512 | features: {
|
513 | // Reactotron.log('hello!')
|
514 | log: (message) => send('log', { level: 'debug', message } ),
|
515 |
|
516 | // Reactotron.warn('look out! falling rocks!')
|
517 | warn: (message) => send('log', { level: 'warn', message } ),
|
518 | }
|
519 |
|
520 | }
|
521 | ```
|