@tvlabs/wdio-service 0.1.2 → 0.1.4

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/esm/index.js CHANGED
@@ -1,3 +1,332 @@
1
- import TVLabsService from './service.js';
2
- export default TVLabsService;
3
- export * from './types.js';
1
+ import { SevereServiceError } from 'webdriverio';
2
+ import * as crypto from 'crypto';
3
+ import { WebSocket } from 'ws';
4
+ import { Socket } from 'phoenix';
5
+
6
+ const LOG_LEVELS = {
7
+ error: 0,
8
+ warn: 1,
9
+ info: 2,
10
+ debug: 3,
11
+ trace: 4,
12
+ silent: 5,
13
+ };
14
+ class Logger {
15
+ name;
16
+ logLevel;
17
+ constructor(name, logLevel = 'info') {
18
+ this.name = name;
19
+ this.logLevel = logLevel;
20
+ }
21
+ shouldLog(level) {
22
+ if (this.logLevel === 'silent') {
23
+ return false;
24
+ }
25
+ return LOG_LEVELS[level] <= LOG_LEVELS[this.logLevel];
26
+ }
27
+ formatMessage(level, ...args) {
28
+ const timestamp = new Date().toISOString();
29
+ return `${timestamp} ${level.toUpperCase()} ${this.name}: ${args
30
+ .map((arg) => typeof arg === 'object' ? JSON.stringify(arg) : String(arg))
31
+ .join(' ')}`;
32
+ }
33
+ debug(...args) {
34
+ if (this.shouldLog('debug')) {
35
+ console.log(this.formatMessage('debug', ...args));
36
+ }
37
+ }
38
+ info(...args) {
39
+ if (this.shouldLog('info')) {
40
+ console.log(this.formatMessage('info', ...args));
41
+ }
42
+ }
43
+ warn(...args) {
44
+ if (this.shouldLog('warn')) {
45
+ console.warn(this.formatMessage('warn', ...args));
46
+ }
47
+ }
48
+ error(...args) {
49
+ if (this.shouldLog('error')) {
50
+ console.error(this.formatMessage('error', ...args));
51
+ }
52
+ }
53
+ trace(...args) {
54
+ if (this.shouldLog('trace')) {
55
+ console.trace(this.formatMessage('trace', ...args));
56
+ }
57
+ }
58
+ }
59
+
60
+ var name = "@tvlabs/wdio-service";
61
+ var version = "0.1.4";
62
+ var packageJson = {
63
+ name: name,
64
+ version: version};
65
+
66
+ function getServiceInfo() {
67
+ return {
68
+ service_name: getServiceName(),
69
+ service_version: getServiceVersion(),
70
+ };
71
+ }
72
+ function getServiceVersion() {
73
+ return packageJson.version;
74
+ }
75
+ function getServiceName() {
76
+ return packageJson.name;
77
+ }
78
+
79
+ class TVLabsChannel {
80
+ endpoint;
81
+ maxReconnectRetries;
82
+ key;
83
+ logLevel;
84
+ socket;
85
+ lobbyTopic;
86
+ requestTopic;
87
+ log;
88
+ events = {
89
+ SESSION_READY: 'session:ready',
90
+ SESSION_FAILED: 'session:failed',
91
+ REQUEST_CANCELED: 'request:canceled',
92
+ REQUEST_FAILED: 'request:failed',
93
+ REQUEST_FILLED: 'request:filled',
94
+ REQUEST_MATCHING: 'request:matching',
95
+ };
96
+ constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
97
+ this.endpoint = endpoint;
98
+ this.maxReconnectRetries = maxReconnectRetries;
99
+ this.key = key;
100
+ this.logLevel = logLevel;
101
+ this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
102
+ this.socket = new Socket(this.endpoint, {
103
+ transport: WebSocket,
104
+ params: this.params(),
105
+ reconnectAfterMs: this.reconnectAfterMs.bind(this),
106
+ });
107
+ this.socket.onError((...args) => TVLabsChannel.logSocketError(this.log, ...args));
108
+ this.lobbyTopic = this.socket.channel('requests:lobby');
109
+ }
110
+ async disconnect() {
111
+ return new Promise((res, _rej) => {
112
+ this.lobbyTopic.leave();
113
+ this.requestTopic?.leave();
114
+ this.socket.disconnect(() => res());
115
+ });
116
+ }
117
+ async connect() {
118
+ try {
119
+ this.log.debug('Connecting to TV Labs...');
120
+ this.socket.connect();
121
+ await this.join(this.lobbyTopic);
122
+ this.log.debug('Connected to TV Labs!');
123
+ }
124
+ catch (error) {
125
+ this.log.error('Error connecting to TV Labs:', error);
126
+ throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
127
+ }
128
+ }
129
+ async newSession(capabilities, maxRetries, retry = 0) {
130
+ try {
131
+ const requestId = await this.requestSession(capabilities);
132
+ const sessionId = await this.observeRequest(requestId);
133
+ return sessionId;
134
+ }
135
+ catch {
136
+ return this.handleRetry(capabilities, maxRetries, retry);
137
+ }
138
+ }
139
+ async handleRetry(capabilities, maxRetries, retry) {
140
+ if (retry < maxRetries) {
141
+ this.log.warn(`Could not create a session, retrying (${retry + 1}/${maxRetries})`);
142
+ return this.newSession(capabilities, maxRetries, retry + 1);
143
+ }
144
+ else {
145
+ throw new SevereServiceError(`Could not create a session after ${maxRetries} attempts.`);
146
+ }
147
+ }
148
+ async observeRequest(requestId) {
149
+ const cleanup = () => this.unobserveRequest();
150
+ return new Promise((res, rej) => {
151
+ this.requestTopic = this.socket.channel(`requests:${requestId}`);
152
+ const eventHandlers = {
153
+ [this.events.REQUEST_MATCHING]: ({ request_id }) => {
154
+ this.log.info(`Session request ${request_id} matching...`);
155
+ },
156
+ [this.events.REQUEST_FILLED]: ({ session_id, request_id }) => {
157
+ this.log.info(`Session request ${request_id} filled: ${this.tvlabsSessionLink(session_id)}`);
158
+ this.log.info('Waiting for device to be ready...');
159
+ },
160
+ [this.events.SESSION_FAILED]: ({ session_id, reason }) => {
161
+ this.log.error(`Session ${session_id} failed, reason: ${reason}`);
162
+ rej(reason);
163
+ },
164
+ [this.events.REQUEST_CANCELED]: ({ request_id, reason }) => {
165
+ this.log.info(`Session request ${request_id} canceled, reason: ${reason}`);
166
+ rej(reason);
167
+ },
168
+ [this.events.REQUEST_FAILED]: ({ request_id, reason }) => {
169
+ this.log.info(`Session request ${request_id} failed, reason: ${reason}`);
170
+ rej(reason);
171
+ },
172
+ [this.events.SESSION_READY]: ({ session_id }) => {
173
+ this.log.info(`Session ${session_id} ready!`);
174
+ res(session_id);
175
+ },
176
+ };
177
+ Object.entries(eventHandlers).forEach(([event, handler]) => {
178
+ this.requestTopic?.on(event, handler);
179
+ });
180
+ this.join(this.requestTopic).catch((err) => {
181
+ rej(err);
182
+ });
183
+ }).finally(cleanup);
184
+ }
185
+ unobserveRequest() {
186
+ Object.values(this.events).forEach((event) => {
187
+ this.requestTopic?.off(event);
188
+ });
189
+ this.requestTopic?.leave();
190
+ this.requestTopic = undefined;
191
+ }
192
+ async requestSession(capabilities) {
193
+ this.log.info('Requesting TV Labs session');
194
+ try {
195
+ const response = await this.push(this.lobbyTopic, 'requests:create', { capabilities });
196
+ this.log.info(`Received session request ID: ${response.request_id}. Waiting for a match...`);
197
+ return response.request_id;
198
+ }
199
+ catch (error) {
200
+ this.log.error('Error requesting session:', error);
201
+ throw error;
202
+ }
203
+ }
204
+ async join(topic) {
205
+ return new Promise((res, rej) => {
206
+ topic
207
+ .join()
208
+ .receive('ok', (_resp) => {
209
+ res();
210
+ })
211
+ .receive('error', ({ response }) => {
212
+ rej('Failed to join topic: ' + response);
213
+ })
214
+ .receive('timeout', () => {
215
+ rej('timeout');
216
+ });
217
+ });
218
+ }
219
+ async push(topic, event, payload) {
220
+ return new Promise((res, rej) => {
221
+ topic
222
+ .push(event, payload)
223
+ .receive('ok', (msg) => {
224
+ res(msg);
225
+ })
226
+ .receive('error', (reason) => {
227
+ rej(reason);
228
+ })
229
+ .receive('timeout', () => {
230
+ rej('timeout');
231
+ });
232
+ });
233
+ }
234
+ params() {
235
+ const serviceInfo = getServiceInfo();
236
+ this.log.debug('Info:', serviceInfo);
237
+ return {
238
+ ...serviceInfo,
239
+ api_key: this.key,
240
+ };
241
+ }
242
+ reconnectAfterMs(tries) {
243
+ if (tries > this.maxReconnectRetries) {
244
+ throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
245
+ }
246
+ const wait = [0, 1000, 3000, 5000][tries] || 10000;
247
+ this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
248
+ return wait;
249
+ }
250
+ tvlabsSessionLink(sessionId) {
251
+ return `https://tvlabs.ai/app/sessions/${sessionId}`;
252
+ }
253
+ static logSocketError(log, event, _transport, _establishedConnections) {
254
+ const error = event.error;
255
+ log.error('Socket error:', error || event);
256
+ }
257
+ }
258
+
259
+ class TVLabsService {
260
+ _options;
261
+ _capabilities;
262
+ _config;
263
+ log;
264
+ constructor(_options, _capabilities, _config) {
265
+ this._options = _options;
266
+ this._capabilities = _capabilities;
267
+ this._config = _config;
268
+ this.log = new Logger('@tvlabs/wdio-server', this._config.logLevel);
269
+ if (this.attachRequestId()) {
270
+ this.setupRequestId();
271
+ }
272
+ }
273
+ onPrepare(_config, param) {
274
+ if (!Array.isArray(param)) {
275
+ throw new SevereServiceError('Multi-remote capabilities are not implemented. Contact TV Labs support if you are interested in this feature.');
276
+ }
277
+ }
278
+ async beforeSession(_config, capabilities, _specs, _cid) {
279
+ const channel = new TVLabsChannel(this.endpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
280
+ await channel.connect();
281
+ capabilities['tvlabs:session_id'] = await channel.newSession(capabilities, this.retries());
282
+ await channel.disconnect();
283
+ }
284
+ setupRequestId() {
285
+ const originalTransformRequest = this._config.transformRequest;
286
+ this._config.transformRequest = (requestOptions) => {
287
+ const requestId = crypto.randomUUID();
288
+ const originalRequestOptions = typeof originalTransformRequest === 'function'
289
+ ? originalTransformRequest(requestOptions)
290
+ : requestOptions;
291
+ if (typeof originalRequestOptions.headers === 'undefined') {
292
+ originalRequestOptions.headers = {};
293
+ }
294
+ this.setRequestHeader(originalRequestOptions.headers, 'x-request-id', requestId);
295
+ this.log.info('ATTACHED REQUEST ID', requestId);
296
+ return originalRequestOptions;
297
+ };
298
+ }
299
+ setRequestHeader(headers, header, value) {
300
+ if (headers instanceof Headers) {
301
+ headers.set(header, value);
302
+ }
303
+ else if (typeof headers === 'object') {
304
+ if (Array.isArray(headers)) {
305
+ headers.push([header, value]);
306
+ }
307
+ else {
308
+ headers[header] = value;
309
+ }
310
+ }
311
+ }
312
+ endpoint() {
313
+ return this._options.endpoint ?? 'wss://tvlabs.ai/appium';
314
+ }
315
+ retries() {
316
+ return this._options.retries ?? 3;
317
+ }
318
+ apiKey() {
319
+ return this._options.apiKey;
320
+ }
321
+ logLevel() {
322
+ return this._config.logLevel ?? 'info';
323
+ }
324
+ attachRequestId() {
325
+ return this._options.attachRequestId ?? true;
326
+ }
327
+ reconnectRetries() {
328
+ return this._options.reconnectRetries ?? 5;
329
+ }
330
+ }
331
+
332
+ export { TVLabsService, TVLabsService as default };
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAwB3C,qBAAa,MAAM;IAEf,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ;gBADR,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAiB;IAGrC,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,aAAa;IAUrB,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;CAKhC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAc3C,qBAAa,MAAM;IAEf,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ;gBADR,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAiB;IAGrC,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,aAAa;IASrB,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;CAKhC"}
package/esm/package.json CHANGED
@@ -1 +1,3 @@
1
- {"type": "module"}
1
+ {
2
+ "type": "module"
3
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,QAAQ,CAAC,eAAe;IAIlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,GAAG,CAAS;gBAGV,QAAQ,EAAE,oBAAoB,EAC9B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,WAAW;IAQtC,SAAS,CACP,OAAO,EAAE,OAAO,CAAC,UAAU,EAC3B,KAAK,EAAE,YAAY,CAAC,sBAAsB;IAStC,aAAa,CACjB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,cAAc,CAAC,EACjD,YAAY,EAAE,kBAAkB,EAChC,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,MAAM;IAmBd,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,gBAAgB;CAGzB"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,QAAQ,CAAC,eAAe;IAIlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,GAAG,CAAS;gBAGV,QAAQ,EAAE,oBAAoB,EAC9B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,WAAW;IAQtC,SAAS,CACP,OAAO,EAAE,OAAO,CAAC,UAAU,EAC3B,KAAK,EAAE,YAAY,CAAC,sBAAsB;IAStC,aAAa,CACjB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,cAAc,CAAC,EACjD,YAAY,EAAE,kBAAkB,EAChC,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,MAAM;IAmBd,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,gBAAgB;CAGzB"}
package/esm/types.d.ts CHANGED
@@ -31,7 +31,11 @@ export type TVLabsSessionRequestUpdate = {
31
31
  export type TVLabsSessionRequestResponse = {
32
32
  request_id: string;
33
33
  };
34
- export type TVLabsSessionChannelParams = {
34
+ export type TVLabsSocketParams = TVLabsServiceInfo & {
35
35
  api_key: string;
36
36
  };
37
+ export type TVLabsServiceInfo = {
38
+ service_version: string;
39
+ service_name: string;
40
+ };
37
41
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC5B,YAAY,CAAC,+BAA+B,GAAG;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,kCAAkC,CAAC,EAAE,MAAM,CAAC;QAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;KACjC,CAAC;IACF,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,CAAC;AAEJ,MAAM,MAAM,gCAAgC,GAAG,CAC7C,QAAQ,EAAE,0BAA0B,KACjC,IAAI,CAAC;AAEV,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC5B,YAAY,CAAC,+BAA+B,GAAG;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,kCAAkC,CAAC,EAAE,MAAM,CAAC;QAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;KACjC,CAAC;IACF,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,CAAC;AAEJ,MAAM,MAAM,gCAAgC,GAAG,CAC7C,QAAQ,EAAE,0BAA0B,KACjC,IAAI,CAAC;AAEV,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC"}
package/esm/utils.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { TVLabsServiceInfo } from './types.js';
2
+ export declare function getServiceInfo(): TVLabsServiceInfo;
3
+ export declare function getServiceVersion(): string;
4
+ export declare function getServiceName(): string;
5
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAG/C,wBAAgB,cAAc,IAAI,iBAAiB,CAKlD;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tvlabs/wdio-service",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "WebdriverIO service that provides a better integration into TV Labs",
5
5
  "author": "Regan Karlewicz <regan@tvlabs.ai>",
6
6
  "license": "Apache-2.0",
@@ -22,9 +22,7 @@
22
22
  "url": "https://github.com/tv-labs/wdio-tvlabs-service/issues"
23
23
  },
24
24
  "scripts": {
25
- "build": "npm run build:cjs && npm run build:esm",
26
- "build:cjs": "tsc -p src --module commonjs --moduleResolution node --outDir cjs && echo '{\"type\": \"commonjs\"}' > cjs/package.json",
27
- "build:esm": "tsc -p src --outDir esm && echo '{\"type\": \"module\"}' > esm/package.json",
25
+ "build": "npm run clean && rollup -c",
28
26
  "start": "ts-node src/index.ts",
29
27
  "dev": "ts-node --transpile-only src/index.ts",
30
28
  "clean": "rm -rf cjs && rm -rf esm",
@@ -44,6 +42,10 @@
44
42
  "typeScriptVersion": "5.8.2",
45
43
  "devDependencies": {
46
44
  "@eslint/js": "^9.22.0",
45
+ "@rollup/plugin-commonjs": "^28.0.6",
46
+ "@rollup/plugin-json": "^6.1.0",
47
+ "@rollup/plugin-node-resolve": "^16.0.1",
48
+ "@rollup/plugin-typescript": "^12.1.4",
47
49
  "@types/node": "^22.13.10",
48
50
  "@types/phoenix": "^1.6.6",
49
51
  "@types/ws": "^8.18.0",
@@ -56,13 +58,13 @@
56
58
  "globals": "^16.0.0",
57
59
  "jiti": "^2.4.2",
58
60
  "prettier": "^3.5.3",
61
+ "rollup": "^4.45.1",
59
62
  "typescript": "^5.8.2",
60
63
  "typescript-eslint": "^8.36.0",
61
64
  "vitest": "^3.0.9",
62
65
  "webdriverio": "^9.12.1"
63
66
  },
64
67
  "dependencies": {
65
- "chalk": "^5.1.2",
66
68
  "phoenix": "^1.7.20",
67
69
  "ws": "^8.18.3"
68
70
  }
@@ -0,0 +1,53 @@
1
+ import typescript from '@rollup/plugin-typescript';
2
+ import { nodeResolve } from '@rollup/plugin-node-resolve';
3
+ import commonjs from '@rollup/plugin-commonjs';
4
+ import json from '@rollup/plugin-json';
5
+ import { writeFileSync, mkdirSync } from 'fs';
6
+ import path from 'path';
7
+
8
+ const createPackageJson = (dir, type) => ({
9
+ name: `create-package-json-${type}`,
10
+ generateBundle() {
11
+ const content = JSON.stringify({ type }, null, 2);
12
+ mkdirSync(dir, { recursive: true });
13
+ writeFileSync(path.join(dir, 'package.json'), content);
14
+ },
15
+ });
16
+
17
+ const external = (id) => !id.startsWith('.') && !path.isAbsolute(id);
18
+
19
+ const plugins = (outDir) => [
20
+ nodeResolve({
21
+ preferBuiltins: true,
22
+ exportConditions: ['node'],
23
+ }),
24
+ json(),
25
+ commonjs(),
26
+ typescript({
27
+ tsconfig: 'src/tsconfig.json',
28
+ outDir,
29
+ }),
30
+ createPackageJson(outDir, outDir === 'esm' ? 'module' : 'commonjs'),
31
+ ];
32
+
33
+ export default [
34
+ {
35
+ input: './src/index.ts',
36
+ output: {
37
+ dir: 'esm',
38
+ format: 'esm',
39
+ },
40
+ external,
41
+ plugins: plugins('esm'),
42
+ },
43
+ {
44
+ input: './src/index.ts',
45
+ output: {
46
+ dir: 'cjs',
47
+ format: 'cjs',
48
+ exports: 'named',
49
+ },
50
+ external,
51
+ plugins: plugins('cjs'),
52
+ },
53
+ ];
package/src/channel.ts CHANGED
@@ -2,10 +2,11 @@ import { WebSocket } from 'ws';
2
2
  import { Socket, type Channel } from 'phoenix';
3
3
  import { SevereServiceError } from 'webdriverio';
4
4
  import { Logger } from './logger.js';
5
+ import { getServiceInfo } from './utils.js';
5
6
 
6
7
  import type {
7
8
  TVLabsCapabilities,
8
- TVLabsSessionChannelParams,
9
+ TVLabsSocketParams,
9
10
  TVLabsSessionRequestEventHandler,
10
11
  TVLabsSessionRequestResponse,
11
12
  LogLevel,
@@ -31,7 +32,7 @@ export class TVLabsChannel {
31
32
  private endpoint: string,
32
33
  private maxReconnectRetries: number,
33
34
  private key: string,
34
- private logLevel: LogLevel,
35
+ private logLevel: LogLevel = 'info',
35
36
  ) {
36
37
  this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
37
38
  this.socket = new Socket(this.endpoint, {
@@ -40,7 +41,9 @@ export class TVLabsChannel {
40
41
  reconnectAfterMs: this.reconnectAfterMs.bind(this),
41
42
  });
42
43
 
43
- this.socket.onError(this.logSocketError);
44
+ this.socket.onError((...args) =>
45
+ TVLabsChannel.logSocketError(this.log, ...args),
46
+ );
44
47
 
45
48
  this.lobbyTopic = this.socket.channel('requests:lobby');
46
49
  }
@@ -226,8 +229,13 @@ export class TVLabsChannel {
226
229
  });
227
230
  }
228
231
 
229
- private params(): TVLabsSessionChannelParams {
232
+ private params(): TVLabsSocketParams {
233
+ const serviceInfo = getServiceInfo();
234
+
235
+ this.log.debug('Info:', serviceInfo);
236
+
230
237
  return {
238
+ ...serviceInfo,
231
239
  api_key: this.key,
232
240
  };
233
241
  }
@@ -248,18 +256,18 @@ export class TVLabsChannel {
248
256
  return wait;
249
257
  }
250
258
 
251
- private logSocketError(
259
+ private tvlabsSessionLink(sessionId: string) {
260
+ return `https://tvlabs.ai/app/sessions/${sessionId}`;
261
+ }
262
+
263
+ private static logSocketError(
264
+ log: Logger,
252
265
  event: ErrorEvent,
253
266
  _transport: new (endpoint: string) => object,
254
267
  _establishedConnections: number,
255
268
  ) {
256
269
  const error = event.error;
257
- const code = error && error.code;
258
-
259
- this.log.error('Socket error:', code || error || event);
260
- }
261
270
 
262
- private tvlabsSessionLink(sessionId: string) {
263
- return `https://tvlabs.ai/app/sessions/${sessionId}`;
271
+ log.error('Socket error:', error || event);
264
272
  }
265
273
  }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import TVLabsService from './service.js';
2
2
 
3
+ export { TVLabsService };
3
4
  export default TVLabsService;
4
5
  export * from './types.js';
package/src/logger.ts CHANGED
@@ -1,18 +1,8 @@
1
1
  import type { LogLevel } from './types.js';
2
- import chalk from 'chalk';
3
2
 
4
3
  // TODO: Replace this with @wdio/logger
5
4
  // It is currently not compatible with CJS
6
5
 
7
- const LOG_LEVEL_COLORS: Record<LogLevel, typeof chalk> = {
8
- error: chalk.red,
9
- warn: chalk.yellow,
10
- info: chalk.cyanBright,
11
- debug: chalk.green,
12
- trace: chalk.cyan,
13
- silent: chalk.gray,
14
- };
15
-
16
6
  const LOG_LEVELS: Record<LogLevel, number> = {
17
7
  error: 0,
18
8
  warn: 1,
@@ -37,8 +27,7 @@ export class Logger {
37
27
 
38
28
  private formatMessage(level: LogLevel, ...args: unknown[]): string {
39
29
  const timestamp = new Date().toISOString();
40
- const levelColor = LOG_LEVEL_COLORS[level];
41
- return `${chalk.gray(timestamp)} ${levelColor(level.toUpperCase())} ${chalk.white(this.name)}: ${args
30
+ return `${timestamp} ${level.toUpperCase()} ${this.name}: ${args
42
31
  .map((arg) =>
43
32
  typeof arg === 'object' ? JSON.stringify(arg) : String(arg),
44
33
  )
package/src/service.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { SevereServiceError } from 'webdriverio';
2
2
  import * as crypto from 'crypto';
3
- import chalk from 'chalk';
4
3
 
5
4
  import { TVLabsChannel } from './channel.js';
6
5
  import { Logger } from './logger.js';
@@ -80,7 +79,7 @@ export default class TVLabsService implements Services.ServiceInstance {
80
79
  requestId,
81
80
  );
82
81
 
83
- this.log.info(chalk.blue('ATTACHED REQUEST ID'), requestId);
82
+ this.log.info('ATTACHED REQUEST ID', requestId);
84
83
 
85
84
  return originalRequestOptions;
86
85
  };
package/src/types.ts CHANGED
@@ -41,6 +41,11 @@ export type TVLabsSessionRequestResponse = {
41
41
  request_id: string;
42
42
  };
43
43
 
44
- export type TVLabsSessionChannelParams = {
44
+ export type TVLabsSocketParams = TVLabsServiceInfo & {
45
45
  api_key: string;
46
46
  };
47
+
48
+ export type TVLabsServiceInfo = {
49
+ service_version: string;
50
+ service_name: string;
51
+ };
package/src/utils.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { TVLabsServiceInfo } from './types.js';
2
+ import packageJson from '../package.json';
3
+
4
+ export function getServiceInfo(): TVLabsServiceInfo {
5
+ return {
6
+ service_name: getServiceName(),
7
+ service_version: getServiceVersion(),
8
+ };
9
+ }
10
+
11
+ export function getServiceVersion(): string {
12
+ return packageJson.version;
13
+ }
14
+
15
+ export function getServiceName(): string {
16
+ return packageJson.name;
17
+ }