UNPKG

6.11 kBJavaScriptView Raw
1/* @flow */
2
3declare var __VERSION__: string;
4
5import {create as createDefered} from '../defered';
6import type {Defered} from "../defered";
7
8import {debugInOut} from '../debug-decorator';
9
10type TrezorDeviceInfo = {path: string};
11
12export default class ChromeUdpPlugin {
13 name: string = `ChromeUdpPlugin`;
14
15 waiting: {[id: string]: Defered<ArrayBuffer>} = {};
16 buffered: {[id: string]: Array<ArrayBuffer>} = {};
17
18 infos: {[id: string]: {address: string, port: number}} = {};
19
20 portDiff: number;
21
22 version: string = __VERSION__;
23 debug: boolean = false;
24
25 constructor(portDiff?: ?number) {
26 if (portDiff == null) {
27 this.portDiff = 3;
28 } else {
29 this.portDiff = portDiff;
30 }
31 }
32
33 @debugInOut
34 init(debug: ?boolean): Promise<void> {
35 this.debug = !!debug;
36 try {
37 chrome.sockets.udp.onReceive.addListener(({socketId, data}) => {
38 this._udpListener(socketId, data);
39 });
40 return Promise.resolve();
41 } catch (e) {
42 // if not Chrome, not sockets etc, this will reject
43 return Promise.reject(e);
44 }
45 }
46
47 ports: Array<number> = [];
48
49 setPorts(ports: Array<number>) {
50 if (ports.length > this.portDiff) {
51 throw new Error(`Too many ports. Max ${this.portDiff} allowed.`);
52 }
53 this.ports = ports;
54 }
55
56 enumerate(): Promise<Array<TrezorDeviceInfo>> {
57 const devices = this.ports.map(port => {
58 return {
59 path: port.toString(),
60 };
61 });
62 return Promise.resolve(devices);
63 }
64
65 send(device: string, session: string, data: ArrayBuffer): Promise<void> {
66 const socket = parseInt(session);
67 if (isNaN(socket)) {
68 return Promise.reject(new Error(`Session not a number`));
69 }
70 return this._udpSend(socket, data);
71 }
72
73 receive(device: string, session: string): Promise<ArrayBuffer> {
74 const socket = parseInt(session);
75 if (isNaN(socket)) {
76 return Promise.reject(new Error(`Session not a number`));
77 }
78 return this._udpReceive(socket);
79 }
80
81 @debugInOut
82 connect(device: string): Promise<string> {
83 const port = parseInt(device);
84 if (isNaN(port)) {
85 return Promise.reject(new Error(`Device not a number`));
86 }
87 return this._udpConnect(port).then(n => n.toString());
88 }
89
90 @debugInOut
91 disconnect(path: string, session: string): Promise<void> {
92 const socket = parseInt(session);
93 if (isNaN(socket)) {
94 return Promise.reject(new Error(`Session not a number`));
95 }
96 return this._udpDisconnect(socket);
97 }
98
99 _udpDisconnect(socketId: number): Promise<void> {
100 return new Promise((resolve, reject) => {
101 try {
102 chrome.sockets.udp.close(socketId, () => {
103 if (chrome.runtime.lastError) {
104 reject(chrome.runtime.lastError);
105 } else {
106 delete this.infos[socketId.toString()];
107 resolve();
108 }
109 });
110 } catch (e) {
111 reject(e);
112 }
113 });
114 }
115
116 _udpConnect(
117 port: number
118 ): Promise<number> {
119 const address = `127.0.0.1`;
120 return new Promise((resolve, reject) => {
121 try {
122 chrome.sockets.udp.create({}, ({socketId}) => {
123 if (chrome.runtime.lastError) {
124 reject(chrome.runtime.lastError);
125 } else {
126 try {
127 chrome.sockets.udp.bind(socketId, `127.0.0.1`, port + this.portDiff, (result: number) => {
128 if (chrome.runtime.lastError) {
129 reject(chrome.runtime.lastError);
130 } else {
131 if (result >= 0) {
132 this.infos[socketId.toString()] = {address: address, port: port};
133 resolve(socketId);
134 } else {
135 reject(`Cannot create socket, error: ${result}`);
136 }
137 }
138 });
139 } catch (e) {
140 reject(e);
141 }
142 }
143 });
144 } catch (e) {
145 reject(e);
146 }
147 });
148 }
149
150 _udpReceive(socketId: number): Promise<ArrayBuffer> {
151 return this._udpReceiveUnsliced(socketId).then(data => {
152 const dataView: Uint8Array = new Uint8Array(data);
153 if (dataView[0] !== 63) {
154 throw new Error(`Invalid data; first byte should be 63, is ${dataView[0]}`);
155 }
156 return data.slice(1);
157 });
158 }
159
160 _udpReceiveUnsliced(
161 socketId: number
162 ): Promise<ArrayBuffer> {
163 const id = socketId.toString();
164
165 if (this.buffered[id] != null) {
166 const res = this.buffered[id].shift();
167 if (this.buffered[id].length === 0) {
168 delete this.buffered[id];
169 }
170 return Promise.resolve(res);
171 }
172
173 if (this.waiting[id] != null) {
174 return Promise.reject(`Something else already listening on socketId ${socketId}`);
175 }
176 const d = createDefered();
177 this.waiting[id] = d;
178 return d.promise;
179 }
180
181 _udpSend(socketId: number, data: ArrayBuffer): Promise<void> {
182 const id = socketId.toString();
183 const info = this.infos[id];
184 if (info == null) {
185 return Promise.reject(`Socket ${socketId} does not exist`);
186 }
187
188 const sendDataV: Uint8Array = new Uint8Array(64);
189 sendDataV[0] = 63;
190 sendDataV.set(new Uint8Array(data), 1);
191 const sendData = sendDataV.buffer;
192
193 return new Promise((resolve, reject) => {
194 try {
195 chrome.sockets.udp.send(socketId, sendData, info.address, info.port, ({resultCode}) => {
196 if (chrome.runtime.lastError) {
197 reject(chrome.runtime.lastError);
198 } else {
199 if (resultCode >= 0) {
200 resolve();
201 } else {
202 reject(`Cannot send, error: ${resultCode}`);
203 }
204 }
205 });
206 } catch (e) {
207 reject(e);
208 }
209 });
210 }
211
212 _udpListener(socketId: number, data: ArrayBuffer) {
213 const id = socketId.toString();
214 const d: ?Defered<ArrayBuffer> = this.waiting[id];
215 if (d != null) {
216 d.resolve(data);
217 delete this.waiting[id];
218 } else {
219 if (this.infos[id] != null) {
220 if (this.buffered[id] == null) {
221 this.buffered[id] = [];
222 }
223 this.buffered[id].pop(data);
224 }
225 }
226 }
227
228}
229