Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

Commit

Permalink
Add basically everything
Browse files Browse the repository at this point in the history
  • Loading branch information
kognise committed Feb 26, 2019
1 parent 8885e77 commit 043750e
Show file tree
Hide file tree
Showing 5 changed files with 407 additions and 1 deletion.
116 changes: 115 additions & 1 deletion README.md
@@ -1 +1,115 @@
# repl.it-button
# Repl.it API

*A Node.js client for creating projects and executing code on [Repl.it](https://repl.it/).*

## Installation

With Yarn:

```
$ yarn add repl.it-api
```

With NPM:

```
$ npm install repl.it-api
```

## Documentation

All of the asyncronous code in the documentation below will be expressed async/await syntax, it works equally well with Promises.

### Instantiate a Client

Before doing anything else you have to import `repl.it-api` and create a Repl.it client.

```javascript
const ReplitClient = require('repl.it-api')
const client = new ReplitClient()
```

`ReplitClient`'s constructor takes one argument: a number (in milliseconds) which is the timeout for code execution. The default is `3000`.

### Create a Project & Connect

You have to create a project, and connect to Repl.it's websocket to execute code and write files in.

```javascript
await client.create()
await client.connect()
```

`client#create` takes one argument: a string that should be a valid language. The default is `nodejs`.

### Write to a File

```javascript
await client.write('file.js', 'console.log("Hello, world!")')
```

You can also write to the main file, which is the file that is executed by Repl.it.

```javascript
await client.writeMain('console.log("Hello from the main file")')
```

`client#write` takes two arguments:

1. A string that should be the file name or path to the file. Please don't include a slash or `./` at the beginning.
2. A string for the actial file content.

`client#writeMain` only takes one argument, which is the same as the second argument to `client#write`.

### Run the Project

Now you probably want to actually run your project!

```javascript
const result = await client.run({
output: (output) => console.log('Output:', output.trim()),
timedOut: () => console.log('Timed out!'),
installStart: () => console.log('Install start'),
installOutput: (output) => console.log('Install output:', output.trim()),
installEnd: () => console.log('Install end'),
listen: (port) => console.log('Listening on port', port)
})
```

`client#run` takes one argument that should be an object with a bunch event listeners, all documented below. They are all optional.

- `output`: fired when the program outputs text, has one argument: a string containing the output. You may want to trim the whitespace.
- `timedOut`: fired when the program execution times out. This timeout period **does not include** package installation time.
- `installStart`: fired when package installation begins. This will only happen when there are packages that need to be installed.
- `installOutput`: fired when package installation outputs text, has one argument: a string containing the output. You may want to trim the whitespace.
- `installEnd`: fired when package installation finishes.
- `installOutput`: fired when program listens on a port, has one argument: a number containing the port. You may want to trim the whitespace.

`client#run` will also resolve with the output of the program which will most likely be `'undefined'`. Also, note that when the project either times out or listens on a port it resolves immediately.

### Close the Connection

We **super ultra very much recommend** doing this before exiting your program. It's as simple as the below code.

```javascript
await client.close()
```

### Get Project Info

```javascript
const info = await client.getInfo()
```

`info` will be an object in the following format:

```javascript
{
id: '7ab11c71-5efd-4b31-9136-686573e2455d',
url: 'https://repl.it/repls/FastFlakyProblems',
slug: 'FastFlakyProblems',
language: 'nodejs'
}
```

Of course, your values will be different.
25 changes: 25 additions & 0 deletions example.js
@@ -0,0 +1,25 @@
const ReplitClient = require('./lib')
const client = new ReplitClient()

client.create().then(() => {
console.log('Created')
return client.connect()
}).then(() => {
console.log('Connected')
return client.writeMain('require(\'express\')().listen()')
}).then(() => {
console.log('Wrote')
return client.run({
output: (output) => console.log('Output:', output.trim()),
timedOut: () => console.log('Timed out!'),
installStart: () => console.log('Install start'),
installOutput: (output) => console.log('Install output:', output.trim()),
installEnd: () => console.log('Install end'),
listen: (port) => console.log('Listening on port', port)
})
}).then((result) => {
console.log('Result:', result)
return client.close()
}).then(() => {
console.log(client.getInfo())
})
151 changes: 151 additions & 0 deletions lib.js
@@ -0,0 +1,151 @@
const { CookieJar } = require('tough-cookie')
const nodeFetch = require('node-fetch')
const fetchCookie = require('fetch-cookie')
const WebSocketClient = require('websocket').client

const parseJson = (response) => response.json()
const sendJson = (connection, json) => connection.sendUTF(JSON.stringify(json))
const headers = {
'Referer': 'https://repl.it/languages/nodejs',
'Content-Type': 'application/json'
}

module.exports = class {
constructor(timeout = 5000) {
this.got = {}
this.fetch = fetchCookie(nodeFetch, new CookieJar())
this.timeout = timeout
}

async create(language = 'nodejs') {
const { id, url, fileNames, slug } = await this.fetch('https://repl.it/data/repls/new', {
method: 'POST',
body: JSON.stringify({ language }),
headers
}).then(parseJson)
this.got.id = id
this.got.url = url
this.got.slug = slug
this.got.language = language
this.got.mainFile = fileNames[0]

this.got.token = await this.fetch(`https://repl.it/data/repls/${id}/gen_repl_token`, {
method: 'POST',
body: JSON.stringify({
liveCodingToken: null,
polygott: false
}),
headers
}).then(parseJson)
}

async connect() {
const connection = await new Promise((resolve, reject) => {
const client = new WebSocketClient()

client.on('connectFailed', (error) => {
reject(error)
})

client.on('connect', (connection) => {
resolve(connection)
})

client.connect('wss://eval.repl.it/ws')
})
await new Promise((resolve) => {
sendJson(connection, {
command: 'auth',
data: this.got.token
})
connection.on('message', ({ type, utf8Data }) => {
if (type !== 'utf8') return
const { command } = JSON.parse(utf8Data)
if (command === 'ready') resolve()
})
})
this.got.connection = connection
}

async write(name, content) {
const json = await this.fetch(`https://repl.it/data/repls/signed_urls/${this.got.id}/${encodeURIComponent(name)}?d=${Date.now()}`).then(parseJson)
const writeUrl = json.urls_by_action.write
await this.fetch(writeUrl, {
method: 'PUT',
body: content,
headers: {
'Content-Type': ''
}
})
}

writeMain(content) {
return this.write(this.got.mainFile, content)
}

run(listeners = {}) {
const { output, timedOut, listen, installStart, installOutput, installDone } = listeners
let alreadyLeft = false
let timeout

return new Promise((resolve) => {
const timeoutAmount = this.timeout
function setTheTimeout() {
timeout = setTimeout(() => {
if (alreadyLeft) return
alreadyLeft = true
timedOut && timedOut()
resolve()
}, timeoutAmount)
}

sendJson(this.got.connection, {
command: 'runProject',
data: '[]'
})
setTheTimeout()
this.got.connection.on('message', ({ type, utf8Data }) => {
if (type !== 'utf8') return
const { command, data } = JSON.parse(utf8Data)

if (command === 'event:packageInstallStart') {
clearTimeout(timeout)
installStart && installStart()
} else if (installOutput && command === 'event:packageInstallOutput') {
installOutput(data)
} else if (command === 'event:packageInstallEnd') {
setTheTimeout()
installDone && installDone()
} else if (output && command === 'output') {
output(data)
} else if (command === 'result' && !alreadyLeft) {
alreadyLeft = true
resolve(data)
} else if (command === 'event:portOpen') {
const { port } = JSON.parse(data)
alreadyLeft = true
listen && listen(port)
resolve()
}
})
})
}

close() {
return new Promise((resolve) => {
this.got.connection.close()
this.got.connection.on('close', () => {
resolve()
})
})
}

getInfo() {
return {
id: this.got.id,
url: `https://repl.it${this.got.url}`,
slug: this.got.slug,
language: this.got.language
}
}
}
19 changes: 19 additions & 0 deletions package.json
@@ -0,0 +1,19 @@
{
"name": "repl.it-api",
"version": "0.0.1",
"description": "A Node.js client for creating projects and executing code on Repl.it.",
"main": "lib.js",
"repository": {
"type": "git",
"url": "https://github.com/arch-lord/repl.it-api.git"
},
"author": "Felix Mattick <felix.mattick@gmail.com>",
"license": "MIT",
"private": false,
"dependencies": {
"fetch-cookie": "^0.7.2",
"node-fetch": "^2.3.0",
"tough-cookie": "^3.0.1",
"websocket": "^1.0.28"
}
}

0 comments on commit 043750e

Please sign in to comment.