Skip to content

Commit

Permalink
app: Implement service object for recording all events for debugging
Browse files Browse the repository at this point in the history
  • Loading branch information
evanpurkhiser committed Oct 20, 2020
1 parent a227be1 commit e46b700
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 12 deletions.
7 changes: 6 additions & 1 deletion package.json
Expand Up @@ -78,6 +78,8 @@
"@types/lodash": "^4.14.161",
"@types/module-alias": "^2.0.0",
"@types/node": "14.6.4",
"@types/node-fetch": "^2.5.7",
"@types/node-gzip": "^1.1.0",
"@types/node-static": "^0.7.5",
"@types/object-path": "^0.11.0",
"@types/react": "^16.9.49",
Expand All @@ -103,6 +105,7 @@
"file-loader": "^6.0.0",
"filesize": "^6.1.0",
"fork-ts-checker-webpack-plugin": "^5.1.0",
"form-data": "^3.0.0",
"framer-motion": "^1.11.1",
"html-webpack-plugin": "^4.3.0",
"http-proxy": "^1.18.1",
Expand All @@ -113,9 +116,11 @@
"mobx-react": "^6.2.2",
"mobx-utils": "^5.5.7",
"module-alias": "^2.2.2",
"node-fetch": "^2.6.1",
"node-gzip": "^1.1.2",
"node-static": "^0.7.11",
"noop2": "^2.0.0",
"prolink-connect": "0.2.0-prerelease.11",
"prolink-connect": "0.2.0-prerelease.12",
"public-ip": "^4.0.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
Expand Down
160 changes: 160 additions & 0 deletions src/main/debugEvents.ts
@@ -0,0 +1,160 @@
import * as Sentry from '@sentry/node';
import fetch from 'node-fetch';
import FormData from 'form-data';
import {
CDJStatus,
ConnectedProlinkNetwork,
Device,
ProlinkNetwork,
} from 'prolink-connect';
import {autorun} from 'mobx';
import {gzip} from 'node-gzip';

import store from 'src/shared/store';

type Events =
| {
type: 'status';
event: CDJStatus.State;
}
| {
type: 'nowPlaying';
event: CDJStatus.State;
}
| {
type: 'deviceAdded';
event: Device;
}
| {
type: 'deviceRemoved';
event: Device;
};

type DebugEvent = {ts: number} & Events;

async function uploadEvents(eventId: string, data: Buffer) {
const dsn = Sentry.getCurrentHub().getClient()?.getDsn();

if (dsn === undefined) {
return;
}

// Determine the sentry attachment upload URL
const {host, path, projectId, port, protocol, user} = dsn;
const portString = port !== '' ? `:${port}` : '';
const pathString = path !== '' ? `/${path}` : '';

const endpoint = `${protocol}://${host}${portString}${pathString}/api/${projectId}/events/${eventId}/attachments/?sentry_key=${user}&sentry_version=7&sentry_client=custom-javascript`;

// Transform our event history into uploadable FormData
const formData = new FormData();

formData.append('my-attachment', await gzip(data), {
filename: 'events.json.gz',
contentType: 'application/json',
});

// Upload debug events blob to Sentry
await fetch(endpoint, {method: 'POST', body: formData});
}

class DebugEventsService {
#network: ConnectedProlinkNetwork;
/**
* Is the service currently recording and reporting?
*/
#active = false;
/**
* Records events for the current DJ set
*/
#eventHistory: DebugEvent[] = [];

constructor(network: ConnectedProlinkNetwork) {
this.#network = network;
}

toggle(enabled: boolean) {
return enabled ? this.enable() : this.disable();
}

/**
* Start capturing events for debugging
*/
enable() {
if (this.#active) {
return;
}

this.#network.statusEmitter.on('status', this.#handleStatus);
this.#network.mixstatus.on('setStarted', this.#handleSetStarted);
this.#network.mixstatus.on('setEnded', this.#handleSetEnded);
this.#network.mixstatus.on('nowPlaying', this.#handleNowPlaying);
this.#network.deviceManager.on('connected', this.#handleNewDevice);
this.#network.deviceManager.on('disconnected', this.#handleRemovedDevice);

this.#active = true;
}

/**
* Stop capturing events for debugging
*/
disable() {
if (!this.#active) {
return;
}

this.#network.statusEmitter.off('status', this.#handleStatus);
this.#network.mixstatus.off('setStarted', this.#handleSetStarted);
this.#network.mixstatus.off('setEnded', this.#handleSetEnded);
this.#network.mixstatus.off('nowPlaying', this.#handleNowPlaying);
this.#network.deviceManager.off('connected', this.#handleNewDevice);
this.#network.deviceManager.off('disconnected', this.#handleRemovedDevice);

this.#eventHistory = [];
this.#active = false;
}

#handleNewDevice = (event: Device) => {
this.#eventHistory.push({ts: Date.now(), type: 'deviceAdded', event});
};

#handleRemovedDevice = (event: Device) => {
this.#eventHistory.push({ts: Date.now(), type: 'deviceRemoved', event});
};

#handleNowPlaying = (event: CDJStatus.State) => {
this.#eventHistory.push({ts: Date.now(), type: 'nowPlaying', event});
};

#handleStatus = (event: CDJStatus.State) => {
this.#eventHistory.push({ts: Date.now(), type: 'status', event});
};

// Control events. These events will control the lifecycle of capturing debug
// events. Including uploading the events list to Sentry.

#handleSetStarted = () => {
this.#eventHistory = [];
};

#handleSetEnded = () => {
const eventId = Sentry.captureMessage('Debug events uploaded', Sentry.Severity.Debug);
const data = Buffer.from(JSON.stringify(this.#eventHistory));

uploadEvents(eventId, data);
};
}

/**
* Registers availability to capture and upload debug events from the network
* to Sentry. The service will be activated and deactivated reactively to the
* reportDebugEvents store configuration.
*/
export function registerDebuggingEventsService(network: ProlinkNetwork) {
if (!network.isConnected()) {
return;
}

const service = new DebugEventsService(network);
autorun(() => service.toggle(store.config.reportDebugEvents));
}
2 changes: 2 additions & 0 deletions src/main/main.ts
Expand Up @@ -11,6 +11,7 @@ import {startOverlayServer} from 'main/overlayServer';
import {registerMainIpc, observeStore, loadMainConfig} from 'src/shared/store/ipc';
import connectNetworkStore from 'src/shared/store/network';
import store from 'src/shared/store';
import {registerDebuggingEventsService} from './debugEvents';

// Intialize the store for the main thread immedaitely.
store.isInitalized = true;
Expand Down Expand Up @@ -99,6 +100,7 @@ app.on('ready', async () => {
await startOverlayServer();

connectNetworkStore(network);
registerDebuggingEventsService(network);
});

app.on('window-all-closed', () => {
Expand Down
1 change: 1 addition & 0 deletions src/overlay/index.tsx
@@ -1,4 +1,5 @@
import taggedNowPlaying from './overlays/taggedNowPlaying';
import classicMetadata from './overlays/classicMetadata';

type OverlayType = typeof registeredOverlays[number]['type'];

Expand Down
6 changes: 6 additions & 0 deletions src/shared/store/index.ts
Expand Up @@ -142,6 +142,12 @@ export class AppConfig {
@serializable(list(rawJS))
@observable
overlays = observable.array<OverlayInstance>();
/**
* Should debug events be enabled to be stored and uploaded?
*/
@serializable
@observable
reportDebugEvents = false;
}

export class AppStore {
Expand Down
11 changes: 5 additions & 6 deletions src/shared/store/network.ts
Expand Up @@ -231,11 +231,11 @@ const connectLocaldbHydrateDone = (network: ConnectedProlinkNetwork) =>
})
);

const connectMixstatus = (network: ConnectedProlinkNetwork) => {
const mixstatus = new MixstatusProcessor();
network.statusEmitter.on('status', s => mixstatus.handleState(s));

mixstatus.on(
/**
* Configure listeners for the mixstatus processor
*/
const connectMixstatus = (network: ConnectedProlinkNetwork) =>
network.mixstatus.on(
'nowPlaying',
action(async state => {
const playedAt = new Date();
Expand Down Expand Up @@ -268,4 +268,3 @@ const connectMixstatus = (network: ConnectedProlinkNetwork) => {
store.mixstatus.trackHistory.push(played);
})
);
};
39 changes: 34 additions & 5 deletions yarn.lock
Expand Up @@ -1796,6 +1796,21 @@
resolved "https://registry.yarnpkg.com/@types/module-alias/-/module-alias-2.0.0.tgz#882668f8b8cdbda44812c3b592c590909e18849e"
integrity sha512-e3sW4oEH0qS1QxSfX7PT6xIi5qk/YSMsrB9Lq8EtkhQBZB+bKyfkP+jpLJRySanvBhAQPSv2PEBe81M8Iy/7yg==

"@types/node-fetch@^2.5.7":
version "2.5.7"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"

"@types/node-gzip@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@types/node-gzip/-/node-gzip-1.1.0.tgz#99a7dfab7c0eec545658f3d736e8d6939ed7161e"
integrity sha512-j7cGb6HIOZbDx3sqe9/9VAPeSvyt143yu5k35gzRXE3mxEgK6BOZ6BAiJ3ToXBcJqLzL9Cr53dav21jlp3f9gw==
dependencies:
"@types/node" "*"

"@types/node-static@^0.7.5":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@types/node-static/-/node-static-0.7.5.tgz#9ebf105804b5b2aab896b783718ebf1a145d27b4"
Expand Down Expand Up @@ -3686,7 +3701,7 @@ colors@>=0.6.0:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==

combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
Expand Down Expand Up @@ -5622,6 +5637,15 @@ fork-ts-checker-webpack-plugin@^5.1.0:
semver "^7.3.2"
tapable "^1.0.0"

form-data@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"

form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
Expand Down Expand Up @@ -8646,6 +8670,11 @@ node-forge@^0.10.0:
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==

node-gzip@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/node-gzip/-/node-gzip-1.1.2.tgz#245bd171b31ce7c7f50fc4cd0ca7195534359afb"
integrity sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw==

node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
Expand Down Expand Up @@ -9564,10 +9593,10 @@ progress@^2.0.1, progress@^2.0.3:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==

prolink-connect@0.2.0-prerelease.11:
version "0.2.0-prerelease.11"
resolved "https://registry.yarnpkg.com/prolink-connect/-/prolink-connect-0.2.0-prerelease.11.tgz#049574eb7a085fd19de2ad095a09926c0a7eac6e"
integrity sha512-6+eYAJ0ht6ahDOBkyM7lJ4UAUcbjiqxNVKLNAUK36Pic6CM7072/3DF14WrRRZb9FCRke+rkEnmVZvMOCyn7/w==
prolink-connect@0.2.0-prerelease.12:
version "0.2.0-prerelease.12"
resolved "https://registry.yarnpkg.com/prolink-connect/-/prolink-connect-0.2.0-prerelease.12.tgz#7d5f9a42d8a005dccc7360071810be5ca8f01c8e"
integrity sha512-NpPrmJ6Ocx7iqgfmQstAD9E5u/vGv1HPqXi0Eug4bdw6S1uPZVa0MN/To4LkpiuP6htrf9NZgcJEVwtfXxYdgw==
dependencies:
"@sentry/node" "^5.16.1"
"@sentry/tracing" "^5.25.0"
Expand Down

1 comment on commit e46b700

@vercel
Copy link

@vercel vercel bot commented on e46b700 Oct 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.