Skip to content

Commit 3de7c83

Browse files
Merge pull request #48 from telerik/vladimirov/fix-debug-ios
fix: messages transformed with message-unpack-stream are incorrect in some cases
2 parents 0e657b9 + c4ee9f2 commit 3de7c83

File tree

11 files changed

+543
-324
lines changed

11 files changed

+543
-324
lines changed

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,12 @@ xcuserdata/
275275
.DS_Store
276276

277277
### VS Code
278-
.vscode
278+
.vscode/**/*
279+
!.vscode/launch.json
279280

280281
### The following folder is present because of a workaround for MobileDevice.framework and Xcode 9.0
281282
.frameworks/*
283+
284+
### Node.js files
285+
coverage/
286+
*.tgz

.npmignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ DerivedData
1111
**/*.ipdb
1212
**/*.iobj
1313
.frameworks/*
14+
15+
### Test files
16+
test/
17+
coverage/
18+
*.tgz

.vscode/launch.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Launch Tests",
11+
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
12+
// modify timeout, so we can debug tests without failing with timeout error
13+
// real value can be found in mocha.opts file
14+
"args": ["--timeout", "1500000"]
15+
}
16+
]
17+
}

index.js

Lines changed: 3 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,176 +1,7 @@
11
"use strict";
22

3-
const uuid = require('node-uuid');
4-
const EventEmitter = require("events");
5-
6-
const Constants = require("./constants");
7-
8-
const IOSDeviceLibStdioHandler = require("./ios-device-lib-stdio-handler").IOSDeviceLibStdioHandler;
9-
10-
const MethodNames = {
11-
install: "install",
12-
uninstall: "uninstall",
13-
list: "list",
14-
log: "log",
15-
upload: "upload",
16-
download: "download",
17-
read: "read",
18-
delete: "delete",
19-
postNotification: "postNotification",
20-
awaitNotificationResponse: "awaitNotificationResponse",
21-
start: "start",
22-
stop: "stop",
23-
apps: "apps",
24-
connectToPort: "connectToPort"
25-
};
26-
27-
const Events = {
28-
deviceLogData: "deviceLogData"
29-
};
30-
31-
class IOSDeviceLib extends EventEmitter {
32-
constructor(onDeviceFound, onDeviceLost, options) {
33-
super();
34-
this._options = options || {};
35-
this._iosDeviceLibStdioHandler = new IOSDeviceLibStdioHandler(this._options);
36-
this._iosDeviceLibStdioHandler.startReadingData();
37-
this._iosDeviceLibStdioHandler.on(Constants.DeviceFoundEventName, onDeviceFound);
38-
this._iosDeviceLibStdioHandler.on(Constants.DeviceLostEventName, onDeviceLost);
39-
}
40-
41-
install(ipaPath, deviceIdentifiers) {
42-
return deviceIdentifiers.map(di => this._getPromise(MethodNames.install, [ipaPath, [di]]));
43-
}
44-
45-
uninstall(appId, deviceIdentifiers) {
46-
return deviceIdentifiers.map(di => this._getPromise(MethodNames.uninstall, [appId, [di]]));
47-
}
48-
49-
list(listArray) {
50-
return listArray.map(listObject => this._getPromise(MethodNames.list, [listObject]));
51-
}
52-
53-
upload(uploadArray) {
54-
return uploadArray.map(uploadObject => this._getPromise(MethodNames.upload, [uploadObject]));
55-
}
56-
57-
download(downloadArray) {
58-
return downloadArray.map(downloadObject => this._getPromise(MethodNames.download, [downloadObject]));
59-
}
60-
61-
read(readArray) {
62-
return readArray.map(readObject => this._getPromise(MethodNames.read, [readObject]));
63-
}
64-
65-
delete(deleteArray) {
66-
return deleteArray.map(deleteObject => this._getPromise(MethodNames.delete, [deleteObject]));
67-
}
68-
69-
postNotification(postNotificationArray) {
70-
return postNotificationArray.map(notificationObject => this._getPromise(MethodNames.postNotification, [notificationObject]));
71-
}
72-
73-
awaitNotificationResponse(awaitNotificationResponseArray) {
74-
return awaitNotificationResponseArray.map(awaitNotificationObject => this._getPromise(MethodNames.awaitNotificationResponse, [awaitNotificationObject]));
75-
}
76-
77-
apps(deviceIdentifiers) {
78-
return deviceIdentifiers.map(di => this._getPromise(MethodNames.apps, [di]));
79-
}
80-
81-
start(startArray) {
82-
return startArray.map(startObject => this._getPromise(MethodNames.start, [startObject]));
83-
}
84-
85-
stop(stopArray) {
86-
return stopArray.map(stopObject => this._getPromise(MethodNames.stop, [stopObject]));
87-
}
88-
89-
startDeviceLog(deviceIdentifiers) {
90-
this._getPromise(MethodNames.log, deviceIdentifiers, { shouldEmit: true, disregardTimeout: true, doNotFailOnDeviceLost: true });
91-
}
92-
93-
connectToPort(connectToPortArray) {
94-
return connectToPortArray.map(connectToPortObject => this._getPromise(MethodNames.connectToPort, [connectToPortObject]));
95-
}
96-
97-
dispose(signal) {
98-
this.removeAllListeners();
99-
this._iosDeviceLibStdioHandler.dispose(signal);
100-
}
101-
102-
_getPromise(methodName, args, options = {}) {
103-
return new Promise((resolve, reject) => {
104-
if (!args || !args.length) {
105-
return reject(new Error("No arguments provided"));
106-
}
107-
108-
let timer = null;
109-
let eventHandler = null;
110-
let deviceLostHandler = null;
111-
const id = uuid.v4();
112-
const removeListeners = () => {
113-
if (eventHandler) {
114-
this._iosDeviceLibStdioHandler.removeListener(Constants.DataEventName, eventHandler);
115-
}
116-
117-
if (deviceLostHandler) {
118-
this._iosDeviceLibStdioHandler.removeListener(Constants.DeviceLostEventName, deviceLostHandler);
119-
}
120-
};
121-
122-
// In case device is lost during waiting for operation to complete
123-
// or in case we do not execute operation in the specified timeout
124-
// remove all handlers and reject the promise.
125-
// NOTE: This is not applicable for device logs, where the Promise is not awaited
126-
// Rejecting it results in Unhandled Rejection
127-
const handleMessage = (message) => {
128-
removeListeners();
129-
message.error ? reject(message.error) : resolve(message);
130-
};
131-
132-
deviceLostHandler = (device) => {
133-
let message = `Device ${device.deviceId} lost during operation ${methodName} for message ${id}`;
134-
135-
if (!options.doNotFailOnDeviceLost) {
136-
message = { error: new Error(message) };
137-
}
138-
139-
handleMessage(message);
140-
};
141-
142-
eventHandler = (message) => {
143-
if (message && message.id === id) {
144-
if (timer) {
145-
clearTimeout(timer);
146-
}
147-
148-
delete message.id;
149-
if (options && options.shouldEmit) {
150-
this.emit(Events.deviceLogData, message);
151-
} else {
152-
handleMessage(message);
153-
}
154-
}
155-
};
156-
157-
if (this._options.timeout && !options.disregardTimeout) {
158-
// TODO: Check if we should clear the timers when dispose is called.
159-
timer = setTimeout(() => {
160-
handleMessage({ error: new Error(`Timeout waiting for ${methodName} response from ios-device-lib, message id: ${id}.`) });
161-
}, this._options.timeout);
162-
}
163-
164-
this._iosDeviceLibStdioHandler.on(Constants.DataEventName, eventHandler);
165-
this._iosDeviceLibStdioHandler.on(Constants.DeviceLostEventName, deviceLostHandler);
166-
167-
this._iosDeviceLibStdioHandler.writeData(this._getMessage(id, methodName, args));
168-
});
169-
}
170-
171-
_getMessage(id, name, args) {
172-
return JSON.stringify({ methods: [{ id: id, name: name, args: args }] }) + '\n';
173-
}
174-
}
3+
const IOSDeviceLib = require("./ios-device-lib").IOSDeviceLib;
4+
const MessageUnpackStream = require("./message-unpack-stream").MessageUnpackStream;
1755

1766
exports.IOSDeviceLib = IOSDeviceLib;
7+
exports.MessageUnpackStream = MessageUnpackStream;

ios-device-lib.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
const uuid = require('node-uuid');
2+
const EventEmitter = require("events");
3+
4+
const Constants = require("./constants");
5+
6+
const IOSDeviceLibStdioHandler = require("./ios-device-lib-stdio-handler").IOSDeviceLibStdioHandler;
7+
8+
const MethodNames = {
9+
install: "install",
10+
uninstall: "uninstall",
11+
list: "list",
12+
log: "log",
13+
upload: "upload",
14+
download: "download",
15+
read: "read",
16+
delete: "delete",
17+
postNotification: "postNotification",
18+
awaitNotificationResponse: "awaitNotificationResponse",
19+
start: "start",
20+
stop: "stop",
21+
apps: "apps",
22+
connectToPort: "connectToPort"
23+
};
24+
25+
const Events = {
26+
deviceLogData: "deviceLogData"
27+
};
28+
29+
class IOSDeviceLib extends EventEmitter {
30+
constructor(onDeviceFound, onDeviceLost, options) {
31+
super();
32+
this._options = options || {};
33+
this._iosDeviceLibStdioHandler = new IOSDeviceLibStdioHandler(this._options);
34+
this._iosDeviceLibStdioHandler.startReadingData();
35+
this._iosDeviceLibStdioHandler.on(Constants.DeviceFoundEventName, onDeviceFound);
36+
this._iosDeviceLibStdioHandler.on(Constants.DeviceLostEventName, onDeviceLost);
37+
}
38+
39+
install(ipaPath, deviceIdentifiers) {
40+
return deviceIdentifiers.map(di => this._getPromise(MethodNames.install, [ipaPath, [di]]));
41+
}
42+
43+
uninstall(appId, deviceIdentifiers) {
44+
return deviceIdentifiers.map(di => this._getPromise(MethodNames.uninstall, [appId, [di]]));
45+
}
46+
47+
list(listArray) {
48+
return listArray.map(listObject => this._getPromise(MethodNames.list, [listObject]));
49+
}
50+
51+
upload(uploadArray) {
52+
return uploadArray.map(uploadObject => this._getPromise(MethodNames.upload, [uploadObject]));
53+
}
54+
55+
download(downloadArray) {
56+
return downloadArray.map(downloadObject => this._getPromise(MethodNames.download, [downloadObject]));
57+
}
58+
59+
read(readArray) {
60+
return readArray.map(readObject => this._getPromise(MethodNames.read, [readObject]));
61+
}
62+
63+
delete(deleteArray) {
64+
return deleteArray.map(deleteObject => this._getPromise(MethodNames.delete, [deleteObject]));
65+
}
66+
67+
postNotification(postNotificationArray) {
68+
return postNotificationArray.map(notificationObject => this._getPromise(MethodNames.postNotification, [notificationObject]));
69+
}
70+
71+
awaitNotificationResponse(awaitNotificationResponseArray) {
72+
return awaitNotificationResponseArray.map(awaitNotificationObject => this._getPromise(MethodNames.awaitNotificationResponse, [awaitNotificationObject]));
73+
}
74+
75+
apps(deviceIdentifiers) {
76+
return deviceIdentifiers.map(di => this._getPromise(MethodNames.apps, [di]));
77+
}
78+
79+
start(startArray) {
80+
return startArray.map(startObject => this._getPromise(MethodNames.start, [startObject]));
81+
}
82+
83+
stop(stopArray) {
84+
return stopArray.map(stopObject => this._getPromise(MethodNames.stop, [stopObject]));
85+
}
86+
87+
startDeviceLog(deviceIdentifiers) {
88+
this._getPromise(MethodNames.log, deviceIdentifiers, { shouldEmit: true, disregardTimeout: true, doNotFailOnDeviceLost: true });
89+
}
90+
91+
connectToPort(connectToPortArray) {
92+
return connectToPortArray.map(connectToPortObject => this._getPromise(MethodNames.connectToPort, [connectToPortObject]));
93+
}
94+
95+
dispose(signal) {
96+
this.removeAllListeners();
97+
this._iosDeviceLibStdioHandler.dispose(signal);
98+
}
99+
100+
_getPromise(methodName, args, options = {}) {
101+
return new Promise((resolve, reject) => {
102+
if (!args || !args.length) {
103+
return reject(new Error("No arguments provided"));
104+
}
105+
106+
let timer = null;
107+
let eventHandler = null;
108+
let deviceLostHandler = null;
109+
const id = uuid.v4();
110+
const removeListeners = () => {
111+
if (eventHandler) {
112+
this._iosDeviceLibStdioHandler.removeListener(Constants.DataEventName, eventHandler);
113+
}
114+
115+
if (deviceLostHandler) {
116+
this._iosDeviceLibStdioHandler.removeListener(Constants.DeviceLostEventName, deviceLostHandler);
117+
}
118+
};
119+
120+
// In case device is lost during waiting for operation to complete
121+
// or in case we do not execute operation in the specified timeout
122+
// remove all handlers and reject the promise.
123+
// NOTE: This is not applicable for device logs, where the Promise is not awaited
124+
// Rejecting it results in Unhandled Rejection
125+
const handleMessage = (message) => {
126+
removeListeners();
127+
message.error ? reject(message.error) : resolve(message);
128+
};
129+
130+
deviceLostHandler = (device) => {
131+
let message = `Device ${device.deviceId} lost during operation ${methodName} for message ${id}`;
132+
133+
if (!options.doNotFailOnDeviceLost) {
134+
message = { error: new Error(message) };
135+
}
136+
137+
handleMessage(message);
138+
};
139+
140+
eventHandler = (message) => {
141+
if (message && message.id === id) {
142+
if (timer) {
143+
clearTimeout(timer);
144+
}
145+
146+
delete message.id;
147+
if (options && options.shouldEmit) {
148+
this.emit(Events.deviceLogData, message);
149+
} else {
150+
handleMessage(message);
151+
}
152+
}
153+
};
154+
155+
if (this._options.timeout && !options.disregardTimeout) {
156+
// TODO: Check if we should clear the timers when dispose is called.
157+
timer = setTimeout(() => {
158+
handleMessage({ error: new Error(`Timeout waiting for ${methodName} response from ios-device-lib, message id: ${id}.`) });
159+
}, this._options.timeout);
160+
}
161+
162+
this._iosDeviceLibStdioHandler.on(Constants.DataEventName, eventHandler);
163+
this._iosDeviceLibStdioHandler.on(Constants.DeviceLostEventName, deviceLostHandler);
164+
165+
this._iosDeviceLibStdioHandler.writeData(this._getMessage(id, methodName, args));
166+
});
167+
}
168+
169+
_getMessage(id, name, args) {
170+
return JSON.stringify({ methods: [{ id: id, name: name, args: args }] }) + '\n';
171+
}
172+
}
173+
174+
exports.IOSDeviceLib = IOSDeviceLib;

0 commit comments

Comments
 (0)