-
-
Notifications
You must be signed in to change notification settings - Fork 171
/
create-websocket-target.js
167 lines (143 loc) Β· 4.66 KB
/
create-websocket-target.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
const debug = require('debug')('loki:websocket');
const WebSocket = require('ws');
const { NativeError, withTimeout, withRetries } = require('@loki/core');
const createMessageQueue = require('./create-message-queue');
const MESSAGE_PREFIX = 'loki:';
const NATIVE_ERROR_TYPE = `${MESSAGE_PREFIX}error`;
const sanitize = string => {
return (
string
.toLowerCase()
// eslint-disable-next-line no-useless-escape
.replace(/[ βββββ²ΒΏ'`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '-')
.replace(/-+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, '')
);
};
const sanitizeSafe = (string, part) => {
const sanitized = sanitize(string);
if (sanitized === '') {
throw new Error(
`Invalid ${part} '${string}', must include alphanumeric characters`
);
}
return sanitized;
};
const toId = (kind, name) =>
`${sanitizeSafe(kind, 'kind')}--${sanitizeSafe(name, 'name')}`;
function createWebsocketTarget(socketUri, platform, saveScreenshotToFile) {
let socket;
const messageQueue = createMessageQueue(NATIVE_ERROR_TYPE);
const send = (type, ...args) => {
debug(`Sending message ${type} with args ${JSON.stringify(args, null, 2)}`);
socket.send(JSON.stringify({ type, args }));
};
const waitForLokiMessage = async (type, timeout = 2000) => {
const prefixedType = `${MESSAGE_PREFIX}${type}`;
const matchesPlatform = data => data && data.platform === platform;
try {
const message = await withTimeout(timeout)(
messageQueue.waitFor(prefixedType, matchesPlatform)
);
return message;
} catch (err) {
messageQueue.rejectAllOfType(prefixedType);
throw err;
}
};
const sendLokiCommand = (type, params = {}) =>
send(`${MESSAGE_PREFIX}${type}`, Object.assign({ platform }, params));
const connect = uri =>
new Promise((resolve, reject) => {
debug(`Connecting to ${uri}`);
const ws = new WebSocket(uri, {
perMessageDeflate: false,
});
const timeout = setTimeout(() => {
const err = new Error('Timed out connecting to storybook web socket');
reject(err);
messageQueue.rejectAll(err);
}, 10000);
const onMessage = data => {
const { type, args } = JSON.parse(data);
debug(
`Received message ${type} with args ${JSON.stringify(args, null, 2)}`
);
messageQueue.receiveMessage(type, args);
};
const onError = err => {
debug('Connection failed', err);
clearTimeout(timeout);
reject(err);
messageQueue.rejectAll(err);
};
const onOpen = () => {
debug('Connected');
clearTimeout(timeout);
// TODO: remove other listeners
resolve(ws);
ws.on('message', onMessage);
};
const onClose = () => {
debug('Connection closed');
clearTimeout(timeout);
};
ws.on('open', onOpen);
ws.on('close', onClose);
ws.on('error', onError);
});
const prepare = withRetries(5)(async () => {
sendLokiCommand('prepare');
await waitForLokiMessage('didPrepare');
});
async function start() {
try {
socket = await connect(socketUri);
} catch (err) {
throw new Error(
'Failed connecting to storybook server. Start it with `yarn storybook` and review --react-native-port and --host arguments.'
);
}
try {
await prepare();
} catch (err) {
throw new Error(
'Failed preparing for loki. Make sure the app is configured and running in storybook mode.'
);
}
}
async function stop() {
sendLokiCommand('restore');
await waitForLokiMessage('didRestore');
socket.close();
}
async function getStorybook() {
sendLokiCommand('getStories');
const { stories } = await waitForLokiMessage('setStories');
return stories;
}
let lastStoryCrashed = false;
async function captureScreenshotForStory(kind, story, outputPath) {
if (lastStoryCrashed) {
// Try to recover from previous crash. App should automatically restart after 1000 ms
await messageQueue.waitFor('setStories');
await prepare();
lastStoryCrashed = false;
}
const storyId = toId(kind, story);
debug('captureScreenshotForStory', kind, story, storyId);
send('setCurrentStory', { kind, story, storyId });
try {
await waitForLokiMessage('ready', 30000);
} catch (error) {
if (error instanceof NativeError) {
lastStoryCrashed = error.isFatal;
}
throw error;
}
await withTimeout(10000)(saveScreenshotToFile(outputPath));
}
return { start, stop, getStorybook, captureScreenshotForStory };
}
module.exports = createWebsocketTarget;