@limrun/api 0.2.0 → 0.5.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +1 -1
  3. package/client.d.mts +3 -2
  4. package/client.d.mts.map +1 -1
  5. package/client.d.ts +3 -2
  6. package/client.d.ts.map +1 -1
  7. package/client.js +2 -2
  8. package/client.js.map +1 -1
  9. package/client.mjs +1 -1
  10. package/client.mjs.map +1 -1
  11. package/index.d.mts +1 -0
  12. package/index.d.ts +1 -0
  13. package/index.js +2 -0
  14. package/index.js.map +1 -1
  15. package/index.mjs +1 -0
  16. package/internal/tslib.js +22 -22
  17. package/package.json +4 -2
  18. package/resources/android-instances-helpers.d.mts +30 -0
  19. package/resources/android-instances-helpers.d.mts.map +1 -0
  20. package/resources/android-instances-helpers.d.ts +30 -0
  21. package/resources/android-instances-helpers.d.ts.map +1 -0
  22. package/resources/android-instances-helpers.js +129 -0
  23. package/resources/android-instances-helpers.js.map +1 -0
  24. package/resources/android-instances-helpers.mjs +123 -0
  25. package/resources/android-instances-helpers.mjs.map +1 -0
  26. package/resources/assets-helpers.d.mts +22 -0
  27. package/resources/assets-helpers.d.mts.map +1 -0
  28. package/resources/assets-helpers.d.ts +22 -0
  29. package/resources/assets-helpers.d.ts.map +1 -0
  30. package/resources/assets-helpers.js +41 -0
  31. package/resources/assets-helpers.js.map +1 -0
  32. package/resources/assets-helpers.mjs +37 -0
  33. package/resources/assets-helpers.mjs.map +1 -0
  34. package/resources/assets.d.mts +12 -2
  35. package/resources/assets.d.mts.map +1 -1
  36. package/resources/assets.d.ts +12 -2
  37. package/resources/assets.d.ts.map +1 -1
  38. package/resources/index.d.mts +2 -1
  39. package/resources/index.d.mts.map +1 -1
  40. package/resources/index.d.ts +2 -1
  41. package/resources/index.d.ts.map +1 -1
  42. package/resources/index.js +2 -0
  43. package/resources/index.js.map +1 -1
  44. package/resources/index.mjs +1 -0
  45. package/resources/index.mjs.map +1 -1
  46. package/src/client.ts +5 -1
  47. package/src/index.ts +2 -0
  48. package/src/resources/android-instances-helpers.ts +159 -0
  49. package/src/resources/assets-helpers.ts +63 -0
  50. package/src/resources/assets.ts +17 -1
  51. package/src/resources/index.ts +3 -0
  52. package/src/version.ts +1 -1
  53. package/version.d.mts +1 -1
  54. package/version.d.ts +1 -1
  55. package/version.js +1 -1
  56. package/version.mjs +1 -1
@@ -0,0 +1,159 @@
1
+ import { exec } from 'child_process';
2
+ import * as net from 'net';
3
+ import { WebSocket } from 'ws';
4
+
5
+ import { AndroidInstance } from './android-instances';
6
+
7
+ /**
8
+ * Opens a WebSocket TCP proxy for the ADB port and connects the local adb
9
+ * client to it.
10
+ */
11
+ export const startAdbTunnel = async (
12
+ androidInstance: AndroidInstance,
13
+ hostname?: string,
14
+ port?: number,
15
+ ): Promise<Proxy> => {
16
+ if (!androidInstance.status.adbWebSocketUrl) {
17
+ throw new Error('ADB WebSocket URL is not available');
18
+ }
19
+ const { address, close } = await startTcpProxy(
20
+ androidInstance.status.adbWebSocketUrl,
21
+ androidInstance.status.token,
22
+ hostname ?? '127.0.0.1',
23
+ port ?? 0,
24
+ );
25
+ try {
26
+ await new Promise<void>((resolve, reject) => {
27
+ exec(`adb connect ${address.address}:${address.port}`, (err) => {
28
+ if (err) return reject(err);
29
+ resolve();
30
+ });
31
+ });
32
+ } catch (err) {
33
+ close();
34
+ throw err;
35
+ }
36
+ return { address, close };
37
+ };
38
+
39
+ /** Returned by `startTcpProxy` – holds the chosen localhost port and a close callback. */
40
+ export interface Proxy {
41
+ address: net.AddressInfo;
42
+ close: () => void;
43
+ }
44
+
45
+ /**
46
+ * Starts a one-shot TCP → WebSocket proxy.
47
+ *
48
+ * The function creates a local TCP server that listens on an ephemeral port on
49
+ * 127.0.0.1. As soon as the **first** TCP client connects the server stops
50
+ * accepting further connections and forwards all traffic between that client
51
+ * and `remoteURL` through an authenticated WebSocket. If you need to proxy
52
+ * more than one TCP connection, call `startTcpProxy` again to create a new
53
+ * proxy instance.
54
+ *
55
+ * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance)
56
+ * @param token Bearer token sent as `Authorization` header
57
+ * @param hostname Optional IP address to listen on. Default is 127.0.0.1
58
+ * @param port Optional port number to listen on. Default is to ask Node.js
59
+ * to find an available non-privileged port.
60
+ */
61
+ export async function startTcpProxy(
62
+ remoteURL: string,
63
+ token: string,
64
+ hostname: string,
65
+ port: number,
66
+ ): Promise<Proxy> {
67
+ return new Promise((resolve, reject) => {
68
+ const server = net.createServer();
69
+
70
+ let ws: WebSocket | undefined;
71
+ let pingInterval: NodeJS.Timeout | undefined;
72
+
73
+ // close helper
74
+ const close = () => {
75
+ if (pingInterval) {
76
+ clearInterval(pingInterval);
77
+ pingInterval = undefined;
78
+ }
79
+ if (ws && ws.readyState === WebSocket.OPEN) {
80
+ ws.close(1000, 'close');
81
+ }
82
+ if (server.listening) {
83
+ server.close();
84
+ }
85
+ };
86
+
87
+ // No AbortController support – proxy can be closed via the returned handle
88
+
89
+ // TCP server error
90
+ server.once('error', (err) => {
91
+ close();
92
+ reject(new Error(`TCP server error: ${err.message}`));
93
+ });
94
+
95
+ // Listening
96
+ server.once('listening', () => {
97
+ const address = server.address();
98
+ if (!address || typeof address === 'string') {
99
+ close();
100
+ return reject(new Error('Failed to obtain listening address'));
101
+ }
102
+ resolve({ address, close });
103
+ });
104
+
105
+ // On first TCP connection
106
+ server.on('connection', (tcpSocket) => {
107
+ // Single-connection proxy
108
+ server.close();
109
+
110
+ ws = new WebSocket(remoteURL, {
111
+ headers: { Authorization: `Bearer ${token}` },
112
+ perMessageDeflate: false,
113
+ });
114
+
115
+ // WebSocket error
116
+ ws.once('error', (err) => {
117
+ console.error('WebSocket error:', err);
118
+ tcpSocket.destroy();
119
+ close();
120
+ });
121
+
122
+ ws.once('open', () => {
123
+ const socket = ws as WebSocket; // non-undefined after open
124
+
125
+ pingInterval = setInterval(() => {
126
+ if (socket.readyState === WebSocket.OPEN) {
127
+ (socket as any).ping();
128
+ }
129
+ }, 30_000);
130
+
131
+ // TCP → WS
132
+ tcpSocket.on('data', (chunk) => {
133
+ if (socket.readyState === WebSocket.OPEN) {
134
+ socket.send(chunk);
135
+ }
136
+ });
137
+
138
+ // WS → TCP
139
+ socket.on('message', (data) => {
140
+ if (!tcpSocket.destroyed) {
141
+ tcpSocket.write(data as Buffer);
142
+ }
143
+ });
144
+ });
145
+
146
+ // Mutual close
147
+ tcpSocket.on('close', close);
148
+ tcpSocket.on('error', (err) => {
149
+ console.error('TCP socket error:', err);
150
+ close();
151
+ });
152
+
153
+ ws.on('close', () => tcpSocket.destroy());
154
+ });
155
+
156
+ // Start listening
157
+ server.listen(port, hostname);
158
+ });
159
+ }
@@ -0,0 +1,63 @@
1
+ import { RequestOptions } from '../internal/request-options';
2
+ import { Assets as GeneratedAssets } from './assets';
3
+
4
+ export interface AssetGetOrUploadParams {
5
+ /**
6
+ * The path to the file to upload.
7
+ */
8
+ path: string;
9
+
10
+ /**
11
+ * The name for the asset. Defaults to the name of the file given in the filePath parameter.
12
+ */
13
+ name?: string;
14
+ }
15
+
16
+ export interface AssetGetOrUploadResponse {
17
+ id: string;
18
+ name: string;
19
+ signedDownloadUrl: string;
20
+ md5: string;
21
+ }
22
+
23
+ export class Assets extends GeneratedAssets {
24
+ async getOrUpload(
25
+ body: AssetGetOrUploadParams,
26
+ options?: RequestOptions,
27
+ ): Promise<AssetGetOrUploadResponse> {
28
+ const { crypto, fs, path } = globalThis as any;
29
+ const creationResponse = await this.getOrCreate(
30
+ {
31
+ name: body.name ?? path.basename(body.path),
32
+ },
33
+ options,
34
+ );
35
+ const data = await fs.promises.readFile(body.path);
36
+ const md5 = crypto.createHash('md5').update(data).digest('hex');
37
+ if (creationResponse.md5 && creationResponse.md5 === md5) {
38
+ return {
39
+ id: creationResponse.id,
40
+ name: creationResponse.name,
41
+ signedDownloadUrl: creationResponse.signedDownloadUrl,
42
+ md5: creationResponse.md5,
43
+ };
44
+ }
45
+ const uploadResponse = await fetch(creationResponse.signedUploadUrl, {
46
+ headers: {
47
+ 'Content-Length': data.length.toString(),
48
+ 'Content-Type': 'application/octet-stream',
49
+ },
50
+ method: 'PUT',
51
+ body: data,
52
+ });
53
+ if (uploadResponse.status !== 200) {
54
+ throw new Error(`Failed to upload asset: ${uploadResponse.status} ${await uploadResponse.text()}`);
55
+ }
56
+ return {
57
+ id: creationResponse.id,
58
+ name: creationResponse.name,
59
+ signedDownloadUrl: creationResponse.signedDownloadUrl,
60
+ md5,
61
+ };
62
+ }
63
+ }
@@ -35,7 +35,7 @@ export class Assets extends APIResource {
35
35
  * is no corresponding file in the storage so downloading it directly or using it
36
36
  * in instances will fail until you use the returned upload URL to submit the file.
37
37
  */
38
- getOrCreate(body: AssetGetOrCreateParams, options?: RequestOptions): APIPromise<Asset> {
38
+ getOrCreate(body: AssetGetOrCreateParams, options?: RequestOptions): APIPromise<AssetGetOrCreateResponse> {
39
39
  return this._client.put('/v1/assets', { body, ...options });
40
40
  }
41
41
  }
@@ -57,6 +57,21 @@ export interface Asset {
57
57
 
58
58
  export type AssetListResponse = Array<Asset>;
59
59
 
60
+ export interface AssetGetOrCreateResponse {
61
+ id: string;
62
+
63
+ name: string;
64
+
65
+ signedDownloadUrl: string;
66
+
67
+ signedUploadUrl: string;
68
+
69
+ /**
70
+ * Returned only if there is a corresponding file uploaded already.
71
+ */
72
+ md5?: string;
73
+ }
74
+
60
75
  export interface AssetListParams {
61
76
  /**
62
77
  * Toggles whether a download URL should be included in the response
@@ -99,6 +114,7 @@ export declare namespace Assets {
99
114
  export {
100
115
  type Asset as Asset,
101
116
  type AssetListResponse as AssetListResponse,
117
+ type AssetGetOrCreateResponse as AssetGetOrCreateResponse,
102
118
  type AssetListParams as AssetListParams,
103
119
  type AssetGetParams as AssetGetParams,
104
120
  type AssetGetOrCreateParams as AssetGetOrCreateParams,
@@ -12,7 +12,10 @@ export {
12
12
  Assets,
13
13
  type Asset,
14
14
  type AssetListResponse,
15
+ type AssetGetOrCreateResponse,
15
16
  type AssetListParams,
16
17
  type AssetGetParams,
17
18
  type AssetGetOrCreateParams,
18
19
  } from './assets';
20
+
21
+ export * from './assets-helpers';
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.0'; // x-release-please-version
1
+ export const VERSION = '0.5.0'; // x-release-please-version
package/version.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.2.0";
1
+ export declare const VERSION = "0.5.0";
2
2
  //# sourceMappingURL=version.d.mts.map
package/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.2.0";
1
+ export declare const VERSION = "0.5.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.2.0'; // x-release-please-version
4
+ exports.VERSION = '0.5.0'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.2.0'; // x-release-please-version
1
+ export const VERSION = '0.5.0'; // x-release-please-version
2
2
  //# sourceMappingURL=version.mjs.map