@rustwirebot/rustplus.js 2.5.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/LICENSE +22 -0
- package/README.md +351 -0
- package/camera.js +425 -0
- package/cli/index.js +308 -0
- package/cli/pair.html +49 -0
- package/docs/API.md +101 -0
- package/docs/DownloadBundle.md +7 -0
- package/docs/PairingFlow.md +19 -0
- package/donate.md +10 -0
- package/examples/10_shoot_autoturret.js +100 -0
- package/examples/1_send_team_chat.js +16 -0
- package/examples/2_turn_smart_switch_on.js +21 -0
- package/examples/3_turn_smart_switch_off.js +21 -0
- package/examples/4_entity_changed_broadcasts.js +58 -0
- package/examples/5_download_map_jpeg.js +22 -0
- package/examples/6_async_requests.js +35 -0
- package/examples/7_render_camera.js +34 -0
- package/examples/8_move_ptz_camera.js +56 -0
- package/examples/9_zoom_ptz_camera.js +37 -0
- package/package.json +48 -0
- package/rustplus.js +380 -0
- package/rustplus.js.svg +34 -0
- package/rustplus.proto +431 -0
- package/vendor/push-receiver/LICENSE +21 -0
- package/vendor/push-receiver/NOTICE.md +32 -0
- package/vendor/push-receiver/android/fcm.js +134 -0
- package/vendor/push-receiver/client.js +216 -0
- package/vendor/push-receiver/constants.js +53 -0
- package/vendor/push-receiver/fcm/index.js +52 -0
- package/vendor/push-receiver/fcm/server-key/index.js +67 -0
- package/vendor/push-receiver/gcm/android_checkin.proto +96 -0
- package/vendor/push-receiver/gcm/checkin.proto +155 -0
- package/vendor/push-receiver/gcm/index.js +128 -0
- package/vendor/push-receiver/index.js +17 -0
- package/vendor/push-receiver/mcs.proto +328 -0
- package/vendor/push-receiver/parser.js +276 -0
- package/vendor/push-receiver/register/index.js +18 -0
- package/vendor/push-receiver/utils/base64/index.js +15 -0
- package/vendor/push-receiver/utils/decrypt/index.js +23 -0
- package/vendor/push-receiver/utils/http-request/index.js +43 -0
- package/vendor/push-receiver/utils/request/index.js +27 -0
- package/vendor/push-receiver/utils/timeout/index.js +7 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { load, BufferReader } = require('protobufjs');
|
|
4
|
+
const {
|
|
5
|
+
MCS_VERSION_TAG_AND_SIZE,
|
|
6
|
+
MCS_TAG_AND_SIZE,
|
|
7
|
+
MCS_SIZE,
|
|
8
|
+
MCS_PROTO_BYTES,
|
|
9
|
+
|
|
10
|
+
kVersionPacketLen,
|
|
11
|
+
kTagPacketLen,
|
|
12
|
+
kSizePacketLenMin,
|
|
13
|
+
kMCSVersion,
|
|
14
|
+
|
|
15
|
+
kHeartbeatPingTag,
|
|
16
|
+
kHeartbeatAckTag,
|
|
17
|
+
kLoginRequestTag,
|
|
18
|
+
kLoginResponseTag,
|
|
19
|
+
kCloseTag,
|
|
20
|
+
kIqStanzaTag,
|
|
21
|
+
kDataMessageStanzaTag,
|
|
22
|
+
kStreamErrorStanzaTag,
|
|
23
|
+
} = require('./constants');
|
|
24
|
+
|
|
25
|
+
const DEBUG = () => {};
|
|
26
|
+
// uncomment the line below to output debug messages
|
|
27
|
+
// const DEBUG = console.log;
|
|
28
|
+
|
|
29
|
+
let proto = null;
|
|
30
|
+
|
|
31
|
+
// Parser parses wire data from gcm.
|
|
32
|
+
// This takes the role of WaitForData in the chromium connection handler.
|
|
33
|
+
//
|
|
34
|
+
// The main differences from the chromium implementation are:
|
|
35
|
+
// - Did not use a max packet length (kDefaultDataPacketLimit), instead we just
|
|
36
|
+
// buffer data in this._data
|
|
37
|
+
// - Error handling around protobufs
|
|
38
|
+
// - Setting timeouts while waiting for data
|
|
39
|
+
//
|
|
40
|
+
// ref: https://cs.chromium.org/chromium/src/google_apis/gcm/engine/connection_handler_impl.cc?rcl=dc7c41bc0ee5fee0ed269495dde6b8c40df43e40&l=178
|
|
41
|
+
module.exports = class Parser extends EventEmitter {
|
|
42
|
+
static async init() {
|
|
43
|
+
if (proto) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
proto = await load(path.resolve(__dirname, 'mcs.proto'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
constructor(socket) {
|
|
50
|
+
super();
|
|
51
|
+
this._socket = socket;
|
|
52
|
+
this._state = MCS_VERSION_TAG_AND_SIZE;
|
|
53
|
+
this._data = Buffer.alloc(0);
|
|
54
|
+
this._sizePacketSoFar = 0;
|
|
55
|
+
this._messageTag = 0;
|
|
56
|
+
this._messageSize = 0;
|
|
57
|
+
this._handshakeComplete = false;
|
|
58
|
+
this._isWaitingForData = true;
|
|
59
|
+
this._onData = this._onData.bind(this);
|
|
60
|
+
this._socket.on('data', this._onData);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
destroy() {
|
|
64
|
+
this._isWaitingForData = false;
|
|
65
|
+
this._socket.removeListener('data', this._onData);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_emitError(error) {
|
|
69
|
+
this.destroy();
|
|
70
|
+
this.emit('error', error);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_onData(buffer) {
|
|
74
|
+
DEBUG(`Got data: ${buffer.length}`);
|
|
75
|
+
this._data = Buffer.concat([this._data, buffer]);
|
|
76
|
+
if (this._isWaitingForData) {
|
|
77
|
+
this._isWaitingForData = false;
|
|
78
|
+
this._waitForData();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_waitForData() {
|
|
83
|
+
DEBUG(`waitForData state: ${this._state}`);
|
|
84
|
+
|
|
85
|
+
let minBytesNeeded = 0;
|
|
86
|
+
|
|
87
|
+
switch (this._state) {
|
|
88
|
+
case MCS_VERSION_TAG_AND_SIZE:
|
|
89
|
+
minBytesNeeded = kVersionPacketLen + kTagPacketLen + kSizePacketLenMin;
|
|
90
|
+
break;
|
|
91
|
+
case MCS_TAG_AND_SIZE:
|
|
92
|
+
minBytesNeeded = kTagPacketLen + kSizePacketLenMin;
|
|
93
|
+
break;
|
|
94
|
+
case MCS_SIZE:
|
|
95
|
+
minBytesNeeded = this._sizePacketSoFar + 1;
|
|
96
|
+
break;
|
|
97
|
+
case MCS_PROTO_BYTES:
|
|
98
|
+
minBytesNeeded = this._messageSize;
|
|
99
|
+
break;
|
|
100
|
+
default:
|
|
101
|
+
this._emitError(new Error(`Unexpected state: ${this._state}`));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (this._data.length < minBytesNeeded) {
|
|
106
|
+
// TODO(ibash) set a timeout and check for socket disconnect
|
|
107
|
+
DEBUG(
|
|
108
|
+
`Socket read finished prematurely. Waiting for ${minBytesNeeded -
|
|
109
|
+
this._data.length} more bytes`
|
|
110
|
+
);
|
|
111
|
+
this._isWaitingForData = true;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
DEBUG(`Processing MCS data: state == ${this._state}`);
|
|
116
|
+
|
|
117
|
+
switch (this._state) {
|
|
118
|
+
case MCS_VERSION_TAG_AND_SIZE:
|
|
119
|
+
this._onGotVersion();
|
|
120
|
+
break;
|
|
121
|
+
case MCS_TAG_AND_SIZE:
|
|
122
|
+
this._onGotMessageTag();
|
|
123
|
+
break;
|
|
124
|
+
case MCS_SIZE:
|
|
125
|
+
this._onGotMessageSize();
|
|
126
|
+
break;
|
|
127
|
+
case MCS_PROTO_BYTES:
|
|
128
|
+
this._onGotMessageBytes();
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
this._emitError(new Error(`Unexpected state: ${this._state}`));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_onGotVersion() {
|
|
137
|
+
const version = this._data.readInt8(0);
|
|
138
|
+
this._data = this._data.slice(1);
|
|
139
|
+
DEBUG(`VERSION IS ${version}`);
|
|
140
|
+
|
|
141
|
+
if (version < kMCSVersion && version !== 38) {
|
|
142
|
+
this._emitError(new Error(`Got wrong version: ${version}`));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Process the LoginResponse message tag.
|
|
147
|
+
this._onGotMessageTag();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_onGotMessageTag() {
|
|
151
|
+
this._messageTag = this._data.readInt8(0);
|
|
152
|
+
this._data = this._data.slice(1);
|
|
153
|
+
DEBUG(`RECEIVED PROTO OF TYPE ${this._messageTag}`);
|
|
154
|
+
|
|
155
|
+
this._onGotMessageSize();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_onGotMessageSize() {
|
|
159
|
+
let incompleteSizePacket = false;
|
|
160
|
+
const reader = new BufferReader(this._data);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
this._messageSize = reader.int32();
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error.message.startsWith('index out of range:')) {
|
|
166
|
+
incompleteSizePacket = true;
|
|
167
|
+
} else {
|
|
168
|
+
this._emitError(error);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// TODO(ibash) in chromium code there is an extra check here of:
|
|
174
|
+
// if prev_byte_count >= kSizePacketLenMax then something else went wrong
|
|
175
|
+
// NOTE(ibash) I could only test this case by manually cutting the buffer
|
|
176
|
+
// above to be mid-packet like: new BufferReader(this._data.slice(0, 1))
|
|
177
|
+
if (incompleteSizePacket) {
|
|
178
|
+
this._sizePacketSoFar = reader.pos;
|
|
179
|
+
this._state = MCS_SIZE;
|
|
180
|
+
this._waitForData();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this._data = this._data.slice(reader.pos);
|
|
185
|
+
|
|
186
|
+
DEBUG(`Proto size: ${this._messageSize}`);
|
|
187
|
+
this._sizePacketSoFar = 0;
|
|
188
|
+
|
|
189
|
+
if (this._messageSize > 0) {
|
|
190
|
+
this._state = MCS_PROTO_BYTES;
|
|
191
|
+
this._waitForData();
|
|
192
|
+
} else {
|
|
193
|
+
this._onGotMessageBytes();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_onGotMessageBytes() {
|
|
198
|
+
const protobuf = this._buildProtobufFromTag(this._messageTag);
|
|
199
|
+
if (!protobuf) {
|
|
200
|
+
this._emitError(new Error('Unknown tag'));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Messages with no content are valid; just use the default protobuf for
|
|
205
|
+
// that tag.
|
|
206
|
+
if (this._messageSize === 0) {
|
|
207
|
+
this.emit('message', {tag: this._messageTag, object: {}});
|
|
208
|
+
this._getNextMessage();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (this._data.length < this._messageSize) {
|
|
213
|
+
// Continue reading data.
|
|
214
|
+
DEBUG(
|
|
215
|
+
`Continuing data read. Buffer size is ${this._data.length}, expecting ${
|
|
216
|
+
this._messageSize
|
|
217
|
+
}`
|
|
218
|
+
);
|
|
219
|
+
this._state = MCS_PROTO_BYTES;
|
|
220
|
+
this._waitForData();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const buffer = this._data.slice(0, this._messageSize);
|
|
225
|
+
this._data = this._data.slice(this._messageSize);
|
|
226
|
+
const message = protobuf.decode(buffer);
|
|
227
|
+
const object = protobuf.toObject(message, {
|
|
228
|
+
longs : String,
|
|
229
|
+
enums : String,
|
|
230
|
+
bytes : Buffer,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
this.emit('message', {tag: this._messageTag, object: object});
|
|
234
|
+
|
|
235
|
+
if (this._messageTag === kLoginResponseTag) {
|
|
236
|
+
if (this._handshakeComplete) {
|
|
237
|
+
console.error('Unexpected login response');
|
|
238
|
+
} else {
|
|
239
|
+
this._handshakeComplete = true;
|
|
240
|
+
DEBUG('GCM Handshake complete.');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this._getNextMessage();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_getNextMessage() {
|
|
248
|
+
this._messageTag = 0;
|
|
249
|
+
this._messageSize = 0;
|
|
250
|
+
this._state = MCS_TAG_AND_SIZE;
|
|
251
|
+
this._waitForData();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_buildProtobufFromTag(tag) {
|
|
255
|
+
switch (tag) {
|
|
256
|
+
case kHeartbeatPingTag:
|
|
257
|
+
return proto.lookupType('mcs_proto.HeartbeatPing');
|
|
258
|
+
case kHeartbeatAckTag:
|
|
259
|
+
return proto.lookupType('mcs_proto.HeartbeatAck');
|
|
260
|
+
case kLoginRequestTag:
|
|
261
|
+
return proto.lookupType('mcs_proto.LoginRequest');
|
|
262
|
+
case kLoginResponseTag:
|
|
263
|
+
return proto.lookupType('mcs_proto.LoginResponse');
|
|
264
|
+
case kCloseTag:
|
|
265
|
+
return proto.lookupType('mcs_proto.Close');
|
|
266
|
+
case kIqStanzaTag:
|
|
267
|
+
return proto.lookupType('mcs_proto.IqStanza');
|
|
268
|
+
case kDataMessageStanzaTag:
|
|
269
|
+
return proto.lookupType('mcs_proto.DataMessageStanza');
|
|
270
|
+
case kStreamErrorStanzaTag:
|
|
271
|
+
return proto.lookupType('mcs_proto.StreamErrorStanza');
|
|
272
|
+
default:
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { v4: uuidv4 } = require('uuid');
|
|
2
|
+
const { register: registerGCM } = require('../gcm');
|
|
3
|
+
const registerFCM = require('../fcm');
|
|
4
|
+
|
|
5
|
+
module.exports = register;
|
|
6
|
+
|
|
7
|
+
async function register(senderId) {
|
|
8
|
+
// Should be unique by app - One GCM registration/token by app/appId
|
|
9
|
+
const appId = `wp:receiver.push.com#${uuidv4()}`;
|
|
10
|
+
const subscription = await registerGCM(appId);
|
|
11
|
+
const result = await registerFCM({
|
|
12
|
+
token : subscription.token,
|
|
13
|
+
senderId,
|
|
14
|
+
appId,
|
|
15
|
+
});
|
|
16
|
+
// Need to be saved by the client
|
|
17
|
+
return Object.assign({}, result, { gcm : subscription });
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const ece = require('http_ece');
|
|
3
|
+
|
|
4
|
+
module.exports = decrypt;
|
|
5
|
+
|
|
6
|
+
// https://tools.ietf.org/html/draft-ietf-webpush-encryption-03
|
|
7
|
+
function decrypt(object, keys) {
|
|
8
|
+
const cryptoKey = object.appData.find(item => item.key === 'crypto-key');
|
|
9
|
+
if (!cryptoKey) throw new Error('crypto-key is missing');
|
|
10
|
+
const salt = object.appData.find(item => item.key === 'encryption');
|
|
11
|
+
if (!salt) throw new Error('salt is missing');
|
|
12
|
+
const dh = crypto.createECDH('prime256v1');
|
|
13
|
+
dh.setPrivateKey(keys.privateKey, 'base64');
|
|
14
|
+
const params = {
|
|
15
|
+
version : 'aesgcm',
|
|
16
|
+
authSecret : keys.authSecret,
|
|
17
|
+
dh : cryptoKey.value.slice(3),
|
|
18
|
+
privateKey : dh,
|
|
19
|
+
salt : salt.value.slice(5),
|
|
20
|
+
};
|
|
21
|
+
const decrypted = ece.decrypt(object.rawData, params);
|
|
22
|
+
return JSON.parse(decrypted);
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Minimal request-promise-compatible HTTP client built on axios.
|
|
2
|
+
//
|
|
3
|
+
// This replaces the original `request`/`request-promise` dependency (both
|
|
4
|
+
// deprecated and flagged with known vulnerabilities) while preserving the
|
|
5
|
+
// exact call signature and return-value semantics the vendored
|
|
6
|
+
// push-receiver code was written against:
|
|
7
|
+
// - options: { url, method, headers, form, body, encoding }
|
|
8
|
+
// - `form` -> sent as application/x-www-form-urlencoded
|
|
9
|
+
// - `body` -> sent as-is (string or Buffer, e.g. protobuf bytes)
|
|
10
|
+
// - default return value: response body as a UTF-8 string
|
|
11
|
+
// - `encoding: null` -> return value is the raw response Buffer
|
|
12
|
+
// (used for binary/protobuf responses, e.g. the GCM checkin call)
|
|
13
|
+
// - non-2xx responses throw, same as request-promise's default behaviour
|
|
14
|
+
const axios = require('axios');
|
|
15
|
+
const querystring = require('querystring');
|
|
16
|
+
|
|
17
|
+
module.exports = async function httpRequest(options = {}) {
|
|
18
|
+
const { url, method = 'GET', headers = {}, form, body, encoding } = options;
|
|
19
|
+
|
|
20
|
+
let data = body;
|
|
21
|
+
const finalHeaders = { ...headers };
|
|
22
|
+
|
|
23
|
+
if (form) {
|
|
24
|
+
data = querystring.stringify(form);
|
|
25
|
+
if (!Object.keys(finalHeaders).some((h) => h.toLowerCase() === 'content-type')) {
|
|
26
|
+
finalHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const response = await axios.request({
|
|
31
|
+
url,
|
|
32
|
+
method,
|
|
33
|
+
headers: finalHeaders,
|
|
34
|
+
data,
|
|
35
|
+
responseType: 'arraybuffer',
|
|
36
|
+
// Prevent axios from trying to JSON-parse the body for us — callers
|
|
37
|
+
// expect a raw string/Buffer, exactly like request-promise returned.
|
|
38
|
+
transformResponse: [(d) => d],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const buffer = Buffer.from(response.data);
|
|
42
|
+
return encoding === null ? buffer : buffer.toString('utf8');
|
|
43
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const request = require('../http-request');
|
|
2
|
+
const { waitFor } = require('../timeout');
|
|
3
|
+
|
|
4
|
+
// In seconds
|
|
5
|
+
const MAX_RETRY_TIMEOUT = 15;
|
|
6
|
+
// Step in seconds
|
|
7
|
+
const RETRY_STEP = 5;
|
|
8
|
+
|
|
9
|
+
module.exports = requestWithRety;
|
|
10
|
+
|
|
11
|
+
function requestWithRety(...args) {
|
|
12
|
+
return retry(0, ...args);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function retry(retryCount = 0, ...args) {
|
|
16
|
+
try {
|
|
17
|
+
const result = await request(...args);
|
|
18
|
+
return result;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
const timeout = Math.min(retryCount * RETRY_STEP, MAX_RETRY_TIMEOUT);
|
|
21
|
+
console.error(`Request failed : ${e.message}`);
|
|
22
|
+
console.error(`Retrying in ${timeout} seconds`);
|
|
23
|
+
await waitFor(timeout * 1000);
|
|
24
|
+
const result = await retry(retryCount + 1, ...args);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
}
|