Skip to content

Commit

Permalink
Merge pull request #1 from mallpopstar/capture-errors
Browse files Browse the repository at this point in the history
added capture errors handler
  • Loading branch information
mallpopstar committed Aug 11, 2023
2 parents cd75c48 + acdda55 commit 84c54f4
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 4 deletions.
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Remote Control

Remote Control is a JavaScript library that allows you to perform actions on a website from a remote location. It can be used to automate repetitive tasks, query and modify DOM, or interact with a website in an automated fashion. All from a remote source. It is a light-weight alternative to heavier solutions that use Chromium browsers on the server.
Remote Control is a JavaScript library that allows you to perform actions on a website from a remote location. It's a light-weight alternative to heavier solutions libraries that use Chromium on the server. It can be used to automate repetitive tasks, query and modify DOM, or interact with a website in an automated fashion from the server or admin panel.

### What can I do?

Expand All @@ -12,8 +12,7 @@ Many things that you can do using JavaScript on a website can be done remotely w
- Intercept and modify network requests
- Read and write to the DOM
- Event listening on DOM elements
- Take screenshots of websites (planned)
- Capture errors and exceptions (planned)
- Capture errors and exceptions

### Use cases

Expand All @@ -33,6 +32,8 @@ Remote Control is not a web scraping framework. Instead, it can be used in conju

Remote Control is not a browser automation framework. Instead, it can be used in conjunction with browser automation libraries to automate browsers, such as [Playwright](https://playwright.dev/) or [Puppeteer](https://pptr.dev/).

Remote Control does not provide security out of the box. It is only the communications layer. You will be responsible for implementing security measures to prevent malicious actors from sending requests to all users.

### How does it work?

Remote Control is a collection of modules that perform actions on your website. Depending on your use cases, you may need to only to use some of the modules in Remote Control. Unused modules will not be included in your bundle.
Expand Down Expand Up @@ -135,10 +136,30 @@ npm install
npm run dev
```

## Q&A

### How can I connect to users on my website?

Remote Control provides the hooks for you to do this but you are responsible for implementing the communication channel using (ex, BroadcastChannel, WebSocket, WebRTC, HTTP polling, etc).

> ⚠️ **Warning:** You will be responsible for implementing security measures to prevent malicious actors from sending requests to all users.
**BroadcastChannel**

Broadcast doesn't require a server. If you are using BroadcastChannel, you can use the [BroadcastChannel API](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel) to send messages to all users, where the channel name is the same for all users.

**WebSocket**

If you are using WebSocket, you can use the [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) to send messages to all users, where the WebSocket URL is the same for all users.

**WebRTC**

WebRT is a peer-to-peer protocol. If you are using WebRTC, you will need to implement a signaling server to connect users. You can use the [WebRTC API](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API) to send messages to all users.

## Roadmap

- [ ] Better Documentation (right now, use examples in [src/main.ts](src/main.ts))
- [ ] Intercept errors and reporting them (Think [LogRocket](https://logrocket.com/))
- [X] Intercept errors and reporting them (think [LogRocket](https://logrocket.com/))
- [X] Example using WebSocket (see [examples/websocket](examples/websocket))
- [ ] Example using WebRTC
- [ ] Example using Custom Channel
Expand Down
65 changes: 65 additions & 0 deletions src/error/receiver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { IReceiver } from '@mallpopstar/partyline'

class ErrorReceiver {
#receiver: IReceiver
#handlers: Map<string, any> = new Map()
#initialized = false

#errorHandler = (event: ErrorEvent) => {
const errorData = {
message: event.error?.message,
stack: event.error?.stack,
url: location.href
}

this.#handlers.forEach(handler => handler(errorData))
}

#rejectionHandler = (event: PromiseRejectionEvent) => {
const errorData = {
message: event.reason?.message,
stack: event.reason?.stack,
url: location.href
}

this.#handlers.forEach(handler => handler(errorData))
}

constructor(receiver: IReceiver) {
this.#receiver = receiver
}

start() {
this.stop()

if (!this.#initialized) {
this.#initialized = true
window.addEventListener('error', this.#errorHandler)
window.addEventListener('unhandledrejection', this.#rejectionHandler)
}

this.#receiver.onSubscribe('error', async (req, res) => {
const handler = (error: any) => {
res.send(error)
}

this.#handlers.set(req.id, handler)
})
}

stop() {
try {
window.removeEventListener('error', this.#errorHandler)
window.removeEventListener('unhandledrejection', this.#rejectionHandler)
this.#handlers.forEach(handler => handler())
this.#handlers.clear()
this.#receiver.removeAllHandlers(/\berror\b/)
} catch (e: any) {
console.warn('cleanup error', e.message)
}
}
}

export const createErrorReceiver = (receiver: IReceiver) => {
return new ErrorReceiver(receiver)
}
13 changes: 13 additions & 0 deletions src/error/sender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ISender } from "@mallpopstar/partyline"

class ErrorSender {
constructor(private sender: ISender) {}

subscribe(callback: (error: { message: string, stack: string }) => void) {
return this.sender.subscribe('error', callback)
}
}

export const createErrorSender = (sender: ISender) => {
return new ErrorSender(sender)
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export { createFetchSender } from './network/fetch/sender'
// page
export { createPageReceiver } from './page/receiver'
export { createPageSender } from './page/sender'
// error
export { createErrorReceiver } from './error/receiver'
export { createErrorSender } from './error/sender'
// storage
export { createCookieReceiver } from './storage/cookie/receiver'
export { createCookieSender } from './storage/cookie/sender'
Expand Down
18 changes: 18 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { createCookieStoreReceiver } from './storage/cookie-store/receiver'
import { createCookieStoreSender } from './storage/cookie-store/sender'
import { createDocumentReceiver } from './document/receiver'
import { createDocumentSender } from './document/sender'
import { createErrorReceiver } from './error/receiver'
import { createErrorSender } from './error/sender'
import { createFetchReceiver } from './network/fetch/receiver'
import { createFetchSender } from './network/fetch/sender'
import { createLocalStorageReceiver } from './storage/local-storage/receiver'
Expand Down Expand Up @@ -53,6 +55,7 @@ async function main() {
runDocument()
runPage()
runDocumentChange()
runError()
// runWorker()
}

Expand Down Expand Up @@ -218,4 +221,19 @@ function runPage() {
})
}

function runError() {
createErrorReceiver(receiver).start()

const errorSender = createErrorSender(sender)
const unsub = errorSender.subscribe((req: any) => {
console.log('error captured:', req)
console.log('error stack:', req.body.stack)
unsub()
})

setTimeout(() => {
throw new Error('this is an error')
}, 1000)
}

main()

0 comments on commit 84c54f4

Please sign in to comment.