gjrequest.js 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.
- package/README.md +88 -0
- package/index.js +93 -0
- package/modules/authentication.js +54 -0
- package/modules/endpoint-httprequest.js +36 -0
- package/modules/gjp.js +60 -0
- package/modules/message.js +94 -0
- package/modules/scores.js +53 -0
- package/package.json +18 -0
- package/test.js +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# gjrequest.js
|
|
2
|
+
|
|
3
|
+
> A lightweight Node.js SDK for interacting with the **Geometry Dash API** (GDPlatform-powered).
|
|
4
|
+
> Handles login, reading/sending messages, and fetching level scores using `gjp2`.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
* Login with `accountID` + password (generates `gjp2`)
|
|
11
|
+
* Read inbox or sent messages
|
|
12
|
+
* Send messages (Base64-encoded)
|
|
13
|
+
* Fetch level scores
|
|
14
|
+
* Generic endpoint requests via `requestEndpoint()`
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install gjrequest.js
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> Or copy the module locally if not published.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
require("dotenv").config();
|
|
32
|
+
const { Client } = require("gjrequest.js");
|
|
33
|
+
|
|
34
|
+
(async () => {
|
|
35
|
+
const client = new Client();
|
|
36
|
+
|
|
37
|
+
// Login securely via environment variables
|
|
38
|
+
await client.login({
|
|
39
|
+
accountID: process.env.GD_ACCOUNT_ID,
|
|
40
|
+
password: process.env.GD_PASSWORD
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Read inbox messages
|
|
44
|
+
const inbox = await client.readMessages();
|
|
45
|
+
console.log(inbox);
|
|
46
|
+
|
|
47
|
+
// Send a message
|
|
48
|
+
const sent = await client.sendMessage(
|
|
49
|
+
29294657,
|
|
50
|
+
"Hello World",
|
|
51
|
+
"This is a test message!"
|
|
52
|
+
);
|
|
53
|
+
console.log(sent);
|
|
54
|
+
|
|
55
|
+
// Get level scores
|
|
56
|
+
const scores = await client.getLevelScores(1234567);
|
|
57
|
+
console.log(scores);
|
|
58
|
+
|
|
59
|
+
// Generic endpoint request
|
|
60
|
+
const response = await client.requestEndpoint("uploadGJMessage20.php", {
|
|
61
|
+
toAccountID: 29294657,
|
|
62
|
+
subject: Buffer.from("Test").toString("base64"),
|
|
63
|
+
body: Buffer.from("Hello!").toString("base64")
|
|
64
|
+
});
|
|
65
|
+
console.log(response);
|
|
66
|
+
})();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Security Notes
|
|
72
|
+
|
|
73
|
+
* **Do NOT hardcode your GD password** in public repositories.
|
|
74
|
+
* Use environment variables (`.env`) or other secure storage.
|
|
75
|
+
* `gjp2` is generated locally; no real login is sent except the hashed payload.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## References
|
|
80
|
+
|
|
81
|
+
* [GD Docs by Wyliemaster](https://wyliemaster.github.io/gddocs/)
|
|
82
|
+
* [Geometry Dash API Endpoints](https://www.boomlings.com/database/)
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT © SkunkPlatform
|
package/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const { generateGjp2 } = require("./modules/gjp");
|
|
2
|
+
const { messages, read, upload } = require("./modules/message");
|
|
3
|
+
const { getScores } = require("./modules/scores"); // new module we defined
|
|
4
|
+
const { doPost } = require("./modules/endpoint-httprequest");
|
|
5
|
+
|
|
6
|
+
class Client {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.accountID = null;
|
|
9
|
+
this.gjp2 = null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* "Login" by providing your account ID and password
|
|
14
|
+
* for APIs that accept gjp2 (no actual login call needed)
|
|
15
|
+
*/
|
|
16
|
+
async login({ accountID, password }) {
|
|
17
|
+
if (!accountID || !password) {
|
|
18
|
+
throw new Error("AccountID and password are required.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.accountID = accountID;
|
|
22
|
+
this.gjp2 = generateGjp2(password);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
accountID: this.accountID,
|
|
26
|
+
gjp2: this.gjp2
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getLevelScores(levelID) {
|
|
31
|
+
this._requireLogin();
|
|
32
|
+
return getScores({
|
|
33
|
+
accountID: this.accountID,
|
|
34
|
+
gjp2: this.gjp2,
|
|
35
|
+
levelID
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async sendMessage(toAccountID, subject = "untitled", body = "") {
|
|
40
|
+
this._requireLogin();
|
|
41
|
+
|
|
42
|
+
// Helper to match the Python base64.b64encode(b"...").decode() logic
|
|
43
|
+
const encode = (str) => Buffer.from(str).toString('base64');
|
|
44
|
+
|
|
45
|
+
return upload({
|
|
46
|
+
accountID: this.accountID,
|
|
47
|
+
gjp2: this.gjp2,
|
|
48
|
+
toAccountID: toAccountID,
|
|
49
|
+
subject: encode(subject), // Encodes "You're dumb lol"
|
|
50
|
+
body: encode(body) // Encodes "Mhm yep..."
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async readMessage(messageID) {
|
|
55
|
+
this._requireLogin();
|
|
56
|
+
|
|
57
|
+
return read({
|
|
58
|
+
accountID: this.accountID,
|
|
59
|
+
gjp: this.gjp2,
|
|
60
|
+
messageID
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async readMessages({ page = 0, sent = false } = {}) {
|
|
65
|
+
this._requireLogin();
|
|
66
|
+
|
|
67
|
+
return messages({
|
|
68
|
+
accountID: this.accountID,
|
|
69
|
+
gjp2: this.gjp2,
|
|
70
|
+
page,
|
|
71
|
+
sent
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async requestEndpoint(endpoint, params = {}, secret = "Wmfd2893gb7") {
|
|
76
|
+
this._requireLogin();
|
|
77
|
+
|
|
78
|
+
// Always include accountID and gjp2
|
|
79
|
+
params.accountID = this.accountID;
|
|
80
|
+
params.gjp2 = this.gjp2;
|
|
81
|
+
params.secret = secret;
|
|
82
|
+
|
|
83
|
+
return doPost(`/database/${endpoint}`, params);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_requireLogin() {
|
|
87
|
+
if (!this.accountID || !this.gjp2) {
|
|
88
|
+
throw new Error("Client not logged in. Call login() first.");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { Client };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const https = require("https");
|
|
2
|
+
const querystring = require("querystring");
|
|
3
|
+
|
|
4
|
+
const GD_SERVER = "www.boomlings.com";
|
|
5
|
+
const SECRET = "Wmfd2893gb7"; // standard for user/score APIs
|
|
6
|
+
|
|
7
|
+
function doPost(path, params) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const postData = querystring.stringify(params);
|
|
10
|
+
|
|
11
|
+
const options = {
|
|
12
|
+
hostname: GD_SERVER,
|
|
13
|
+
path,
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
17
|
+
"Content-Length": Buffer.byteLength(postData),
|
|
18
|
+
"User-Agent": "GeometryDash/2.11"
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const req = https.request(options, (res) => {
|
|
23
|
+
let data = "";
|
|
24
|
+
res.on("data", chunk => data += chunk);
|
|
25
|
+
res.on("end", () => resolve(data));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
req.on("error", reject);
|
|
29
|
+
req.write(postData);
|
|
30
|
+
req.end();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get scores for a level
|
|
36
|
+
* @param {Object} opts
|
|
37
|
+
* Required fields:
|
|
38
|
+
* - accountID
|
|
39
|
+
* - gjp2 (encrypted password)
|
|
40
|
+
* - levelID
|
|
41
|
+
* - secret (optional, defaults to GD_SERVER secret)
|
|
42
|
+
*/
|
|
43
|
+
async function getScores({ accountID, gjp2, levelID }) {
|
|
44
|
+
const params = {
|
|
45
|
+
accountID,
|
|
46
|
+
gjp2,
|
|
47
|
+
levelID,
|
|
48
|
+
secret: SECRET
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return doPost("/database/getGJScores20.php", params);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { getScores };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const https = require("https");
|
|
2
|
+
const querystring = require("querystring");
|
|
3
|
+
|
|
4
|
+
const GD_SERVER = "www.boomlings.com";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generic POST request helper for Geometry Dash endpoints
|
|
8
|
+
*/
|
|
9
|
+
function doPost(path, params, userAgent = "GeometryDash/2.11") {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const postData = querystring.stringify(params);
|
|
12
|
+
|
|
13
|
+
const options = {
|
|
14
|
+
hostname: GD_SERVER,
|
|
15
|
+
path,
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
19
|
+
"Content-Length": Buffer.byteLength(postData),
|
|
20
|
+
"User-Agent": userAgent
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const req = https.request(options, (res) => {
|
|
25
|
+
let data = "";
|
|
26
|
+
res.on("data", chunk => data += chunk);
|
|
27
|
+
res.on("end", () => resolve(data));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
req.on("error", reject);
|
|
31
|
+
req.write(postData);
|
|
32
|
+
req.end();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { doPost };
|
package/modules/gjp.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
|
|
3
|
+
const XOR_KEY = "37526";
|
|
4
|
+
const SALT = "mI29fmAnxgTs";
|
|
5
|
+
|
|
6
|
+
// XOR cipher
|
|
7
|
+
function xorCipher(input, key = XOR_KEY) {
|
|
8
|
+
const inputBuffer = Buffer.from(input, "utf8");
|
|
9
|
+
const keyBuffer = Buffer.from(key, "utf8");
|
|
10
|
+
|
|
11
|
+
const output = Buffer.alloc(inputBuffer.length);
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < inputBuffer.length; i++) {
|
|
14
|
+
output[i] = inputBuffer[i] ^ keyBuffer[i % keyBuffer.length];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return output.toString("utf8");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// SHA1(password + salt)
|
|
21
|
+
function generateGjp2(password = "", salt = SALT) {
|
|
22
|
+
return crypto
|
|
23
|
+
.createHash("sha1")
|
|
24
|
+
.update(password + salt)
|
|
25
|
+
.digest("hex");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// XOR + Base64 (URL-safe)
|
|
29
|
+
function encodeGjp(password) {
|
|
30
|
+
const xored = xorCipher(password);
|
|
31
|
+
|
|
32
|
+
return Buffer.from(xored, "utf8")
|
|
33
|
+
.toString("base64")
|
|
34
|
+
.replace(/\+/g, "-")
|
|
35
|
+
.replace(/\//g, "_")
|
|
36
|
+
.replace(/=+$/, ""); // remove padding (GD-safe)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Reverse URL-safe Base64 + XOR
|
|
40
|
+
function decodeGjp(gjp) {
|
|
41
|
+
// Restore URL-safe chars
|
|
42
|
+
let normalized = gjp
|
|
43
|
+
.replace(/-/g, "+")
|
|
44
|
+
.replace(/_/g, "/");
|
|
45
|
+
|
|
46
|
+
// Restore padding
|
|
47
|
+
while (normalized.length % 4) {
|
|
48
|
+
normalized += "=";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const decoded = Buffer.from(normalized, "base64").toString("utf8");
|
|
52
|
+
|
|
53
|
+
return xorCipher(decoded);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
generateGjp2,
|
|
58
|
+
encodeGjp,
|
|
59
|
+
decodeGjp
|
|
60
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const https = require("https");
|
|
2
|
+
const querystring = require("querystring");
|
|
3
|
+
|
|
4
|
+
const GD_SERVER = "www.boomlings.com"; // primary Geometry Dash API server
|
|
5
|
+
const SECRET = "Wmfd2893gb7"; // the secret param required for most GD API requests
|
|
6
|
+
|
|
7
|
+
function doPost(path, params) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const postData = querystring.stringify(params);
|
|
10
|
+
|
|
11
|
+
const options = {
|
|
12
|
+
hostname: GD_SERVER,
|
|
13
|
+
path,
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
"User-Agent": "",
|
|
17
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
18
|
+
"Content-Length": Buffer.byteLength(postData)
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const req = https.request(options, (res) => {
|
|
23
|
+
let data = "";
|
|
24
|
+
res.on("data", (chunk) => {
|
|
25
|
+
data += chunk;
|
|
26
|
+
});
|
|
27
|
+
res.on("end", () => {
|
|
28
|
+
resolve(data);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
req.on("error", reject);
|
|
33
|
+
req.write(postData);
|
|
34
|
+
req.end();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Upload (send) a private message
|
|
40
|
+
* @param {Object} opts
|
|
41
|
+
* Required fields typically include:
|
|
42
|
+
* - accountID
|
|
43
|
+
* - gjp (GJP2 hash of password)
|
|
44
|
+
* - message (text)
|
|
45
|
+
* - toAccountID (the user you’re messaging)
|
|
46
|
+
*/
|
|
47
|
+
async function upload(opts) {
|
|
48
|
+
const params = {
|
|
49
|
+
secret: SECRET,
|
|
50
|
+
accountID: opts.accountID,
|
|
51
|
+
gjp2: opts.gjp2, // Use gjp2 for modern 2.2+ auth
|
|
52
|
+
toAccountID: opts.toAccountID,
|
|
53
|
+
subject: opts.subject, // API expects 'subject'
|
|
54
|
+
body: opts.body, // API expects 'body'
|
|
55
|
+
gameVersion: 22,
|
|
56
|
+
binaryVersion: 42,
|
|
57
|
+
gdw: 0
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return doPost("/database/uploadGJMessage20.php", params);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Read (download) a private message
|
|
65
|
+
* @param {Object} opts
|
|
66
|
+
* Required fields typically include:
|
|
67
|
+
* - accountID
|
|
68
|
+
* - gjp
|
|
69
|
+
* - messageID (the ID of the message to download)
|
|
70
|
+
*/
|
|
71
|
+
async function read(opts) {
|
|
72
|
+
const params = {
|
|
73
|
+
secret: SECRET,
|
|
74
|
+
accountID: opts.accountID,
|
|
75
|
+
gjp: opts.gjp,
|
|
76
|
+
messageID: opts.messageID,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return doPost("/database/downloadGJMessage20.php", params);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function messages(opts) {
|
|
83
|
+
const params = {
|
|
84
|
+
secret: SECRET,
|
|
85
|
+
accountID: opts.accountID,
|
|
86
|
+
gjp2: opts.gjp2, // SHA1(password + "mI29fmAnxgTs")
|
|
87
|
+
page: opts.page || 0, // optional (default 0)
|
|
88
|
+
getSent: opts.sent ? 1 : 0 // optional (0 = inbox, 1 = sent)
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return doPost("/database/getGJMessages20.php", params);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { upload, read, messages };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const https = require("https");
|
|
2
|
+
const querystring = require("querystring");
|
|
3
|
+
|
|
4
|
+
const GD_SERVER = "www.boomlings.com";
|
|
5
|
+
const SECRET = "Wmfd2893gb7"; // standard for user/score APIs
|
|
6
|
+
|
|
7
|
+
function doPost(path, params) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const postData = querystring.stringify(params);
|
|
10
|
+
|
|
11
|
+
const options = {
|
|
12
|
+
hostname: GD_SERVER,
|
|
13
|
+
path,
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
17
|
+
"Content-Length": Buffer.byteLength(postData),
|
|
18
|
+
"User-Agent": "GeometryDash/2.11"
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const req = https.request(options, (res) => {
|
|
23
|
+
let data = "";
|
|
24
|
+
res.on("data", chunk => data += chunk);
|
|
25
|
+
res.on("end", () => resolve(data));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
req.on("error", reject);
|
|
29
|
+
req.write(postData);
|
|
30
|
+
req.end();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get scores for a level
|
|
36
|
+
* @param {Object} opts
|
|
37
|
+
* Required fields:
|
|
38
|
+
* - accountID
|
|
39
|
+
* - gjp2 (SHA1 hash of your password)
|
|
40
|
+
* - levelID
|
|
41
|
+
*/
|
|
42
|
+
async function getScores({ accountID, gjp2, levelID }) {
|
|
43
|
+
const params = {
|
|
44
|
+
accountID,
|
|
45
|
+
gjp2,
|
|
46
|
+
levelID,
|
|
47
|
+
secret: SECRET
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return doPost("/database/getGJScores20.php", params);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { getScores };
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gjrequest.js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Geometry Dash API - Powered by GDPlatform.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"geometry dash",
|
|
11
|
+
"gd",
|
|
12
|
+
"api",
|
|
13
|
+
"gjrequest"
|
|
14
|
+
],
|
|
15
|
+
"author": "SkunkPlatform",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "commonjs"
|
|
18
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { Client } = require("./index");
|
|
2
|
+
|
|
3
|
+
(async () => {
|
|
4
|
+
const client = new Client();
|
|
5
|
+
|
|
6
|
+
await client.login({
|
|
7
|
+
accountID: 1234567890, // AccountID
|
|
8
|
+
password: "insert your gd password" // Use your Geometry Dash Password on other account. ACTION REQUIRED: Environment Variables, DO NOT SHARE GD PASSWORD TO YOUR PUBLIC REPOSITORY THAT WILL POSSIBLE CAUSE RISKS. DO NOT ATTEMP TO SHARE.
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const messages = await client.readMessages();
|
|
12
|
+
console.log(messages);
|
|
13
|
+
|
|
14
|
+
const submitted = await client.sendMessage(
|
|
15
|
+
29294657, // Send to an Account ID
|
|
16
|
+
"Subject", // this subject will be base64.
|
|
17
|
+
"Body" // this body will be base64.
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
console.log(submitted);
|
|
21
|
+
})();
|