1 |
|
2 |
|
3 | declare var __VERSION__: string;
|
4 |
|
5 | import {create as createDefered} from '../defered';
|
6 | import type {Defered} from "../defered";
|
7 |
|
8 | import {debugInOut} from '../debug-decorator';
|
9 |
|
10 | type TrezorDeviceInfo = {path: string};
|
11 |
|
12 | export 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 |
|
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 |
|