@milaboratories/pl-client 2.4.10

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 (64) hide show
  1. package/README.md +52 -0
  2. package/dist/index.cjs +14527 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.js +14426 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +49 -0
  7. package/src/core/auth.ts +27 -0
  8. package/src/core/client.test.ts +47 -0
  9. package/src/core/client.ts +302 -0
  10. package/src/core/config.test.ts +19 -0
  11. package/src/core/config.ts +197 -0
  12. package/src/core/default_client.ts +161 -0
  13. package/src/core/driver.ts +30 -0
  14. package/src/core/error.test.ts +14 -0
  15. package/src/core/errors.ts +84 -0
  16. package/src/core/http.ts +178 -0
  17. package/src/core/ll_client.test.ts +111 -0
  18. package/src/core/ll_client.ts +228 -0
  19. package/src/core/ll_transaction.test.ts +152 -0
  20. package/src/core/ll_transaction.ts +333 -0
  21. package/src/core/transaction.test.ts +173 -0
  22. package/src/core/transaction.ts +730 -0
  23. package/src/core/type_conversion.ts +121 -0
  24. package/src/core/types.test.ts +22 -0
  25. package/src/core/types.ts +223 -0
  26. package/src/core/unauth_client.test.ts +21 -0
  27. package/src/core/unauth_client.ts +48 -0
  28. package/src/helpers/pl.ts +141 -0
  29. package/src/helpers/poll.ts +178 -0
  30. package/src/helpers/rich_resource_types.test.ts +22 -0
  31. package/src/helpers/rich_resource_types.ts +84 -0
  32. package/src/helpers/smart_accessors.ts +146 -0
  33. package/src/helpers/state_helpers.ts +5 -0
  34. package/src/helpers/tx_helpers.ts +24 -0
  35. package/src/index.ts +14 -0
  36. package/src/proto/github.com/googleapis/googleapis/google/rpc/status.ts +125 -0
  37. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts +45 -0
  38. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts +271 -0
  39. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.ts +51 -0
  40. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts +380 -0
  41. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts +59 -0
  42. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts +450 -0
  43. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.ts +148 -0
  44. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts +706 -0
  45. package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +406 -0
  46. package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/api.ts +12636 -0
  47. package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts +1384 -0
  48. package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/base_types.ts +181 -0
  49. package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/import.ts +251 -0
  50. package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/resource_types.ts +693 -0
  51. package/src/proto/google/api/http.ts +687 -0
  52. package/src/proto/google/protobuf/any.ts +326 -0
  53. package/src/proto/google/protobuf/descriptor.ts +4502 -0
  54. package/src/proto/google/protobuf/duration.ts +230 -0
  55. package/src/proto/google/protobuf/empty.ts +81 -0
  56. package/src/proto/google/protobuf/struct.ts +482 -0
  57. package/src/proto/google/protobuf/timestamp.ts +287 -0
  58. package/src/proto/google/protobuf/wrappers.ts +751 -0
  59. package/src/test/test_config.test.ts +6 -0
  60. package/src/test/test_config.ts +166 -0
  61. package/src/util/branding.ts +4 -0
  62. package/src/util/pl.ts +11 -0
  63. package/src/util/util.test.ts +10 -0
  64. package/src/util/util.ts +9 -0
@@ -0,0 +1,161 @@
1
+ import fs from 'node:fs';
2
+ import { AuthInformation, plAddressToConfig, PlClientConfig } from './config';
3
+ import canonicalize from 'canonicalize';
4
+ import YAML from 'yaml';
5
+ import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import { notEmpty } from '@milaboratories/ts-helpers';
8
+ import { UnauthenticatedPlClient } from './unauth_client';
9
+ import { PlClient } from './client';
10
+ import { createHash } from 'crypto';
11
+ import { inferAuthRefreshTime } from './auth';
12
+
13
+ const CONFIG_FILE_LOCAL_JSON = 'pl.json';
14
+ const CONFIG_FILE_USER_JSON = path.join(os.homedir(), '.pl.json');
15
+ const CONFIG_FILE_LOCAL_YAML = 'pl.yaml';
16
+ const CONFIG_FILE_USER_YAML = path.join(os.homedir(), '.pl.yaml');
17
+ const CONF_FILE_SEQUENCE = [
18
+ CONFIG_FILE_LOCAL_JSON,
19
+ CONFIG_FILE_LOCAL_YAML,
20
+ CONFIG_FILE_USER_JSON,
21
+ CONFIG_FILE_USER_YAML
22
+ ];
23
+
24
+ const AUTH_DATA_FILE = '.pl_auth.json';
25
+
26
+ type FileConfigOverrideFields =
27
+ | 'grpcProxy'
28
+ | 'httpProxy'
29
+ | 'user'
30
+ | 'password'
31
+ | 'alternativeRoot'
32
+ | 'defaultTransactionTimeout'
33
+ | 'defaultRequestTimeout'
34
+ | 'authTTLSeconds'
35
+ | 'authMaxRefreshSeconds';
36
+ const FILE_CONFIG_OVERRIDE_FIELDS: FileConfigOverrideFields[] = [
37
+ 'grpcProxy',
38
+ 'httpProxy',
39
+ 'user',
40
+ 'password',
41
+ 'alternativeRoot',
42
+ 'defaultTransactionTimeout',
43
+ 'defaultRequestTimeout',
44
+ 'authTTLSeconds',
45
+ 'authMaxRefreshSeconds'
46
+ ];
47
+
48
+ type PlConfigFile = {
49
+ address: string;
50
+ } & Partial<Pick<PlClientConfig, FileConfigOverrideFields>>;
51
+
52
+ interface AuthCache {
53
+ /** To check if config changed */
54
+ confHash: string;
55
+ expiration: number;
56
+ authInformation: AuthInformation;
57
+ }
58
+
59
+ export function tryGetFileConfig(): [PlConfigFile, string] | undefined {
60
+ for (const confPath of CONF_FILE_SEQUENCE)
61
+ if (fs.existsSync(confPath)) {
62
+ const fileContent = fs.readFileSync(confPath, { encoding: 'utf-8' });
63
+ if (confPath.endsWith('json')) return [JSON.parse(fileContent) as PlConfigFile, confPath];
64
+ else return [YAML.parse(fileContent) as PlConfigFile, confPath];
65
+ }
66
+ return undefined;
67
+ }
68
+
69
+ function saveAuthInfoCallback(
70
+ confHash: string,
71
+ authMaxRefreshSeconds: number
72
+ ): (newAuthInfo: AuthInformation) => void {
73
+ return (newAuthInfo) => {
74
+ fs.writeFileSync(
75
+ AUTH_DATA_FILE,
76
+ Buffer.from(
77
+ JSON.stringify({
78
+ confHash,
79
+ authInformation: newAuthInfo,
80
+ expiration: inferAuthRefreshTime(newAuthInfo, authMaxRefreshSeconds)
81
+ } as AuthCache)
82
+ ),
83
+ 'utf8'
84
+ );
85
+ };
86
+ }
87
+
88
+ const cleanAuthInfoCallback = () => {
89
+ fs.rmSync(AUTH_DATA_FILE);
90
+ };
91
+
92
+ /** Uses default algorithm to construct a pl client from the environment */
93
+ export async function defaultPlClient(): Promise<PlClient> {
94
+ let config: PlClientConfig | undefined = undefined;
95
+ if (process.env.PL_ADDRESS !== undefined) {
96
+ config = plAddressToConfig(process.env.PL_ADDRESS);
97
+ } else {
98
+ const fromFile = tryGetFileConfig();
99
+ if (fromFile !== undefined) {
100
+ const [fileConfig, configPath] = fromFile;
101
+ const address = notEmpty(fileConfig.address, `no pl address in file: ${configPath}`);
102
+ config = plAddressToConfig(address);
103
+ // applying overrides
104
+ for (const field of FILE_CONFIG_OVERRIDE_FIELDS)
105
+ if (fileConfig[field] !== undefined) (config as any)[field] = fileConfig[field];
106
+ }
107
+ }
108
+
109
+ if (config === undefined)
110
+ throw new Error("Can't find configuration to create default platform client.");
111
+
112
+ if (process.env.PL_USER !== undefined) config.user = process.env.PL_USER;
113
+
114
+ if (process.env.PL_PASSWORD !== undefined) config.user = process.env.PL_PASSWORD;
115
+
116
+ const confHash = createHash('sha256')
117
+ .update(Buffer.from(canonicalize(config)!))
118
+ .digest('base64');
119
+
120
+ let authInformation: AuthInformation | undefined = undefined;
121
+
122
+ // try recover auth information from cache
123
+ if (fs.existsSync(AUTH_DATA_FILE)) {
124
+ const cache: AuthCache = JSON.parse(fs.readFileSync(AUTH_DATA_FILE, { encoding: 'utf-8' }));
125
+ if (cache.confHash === confHash && cache.expiration > Date.now())
126
+ authInformation = cache.authInformation;
127
+ }
128
+
129
+ if (authInformation === undefined) {
130
+ const client = new UnauthenticatedPlClient(config);
131
+
132
+ if (await client.requireAuth()) {
133
+ if (config.user === undefined || config.password === undefined)
134
+ throw new Error(`No auth information for found to authenticate with PL server.`);
135
+ authInformation = await client.login(config.user, config.password);
136
+ } else {
137
+ // No authorization is required
138
+ authInformation = {};
139
+ }
140
+
141
+ // saving cache
142
+ fs.writeFileSync(
143
+ AUTH_DATA_FILE,
144
+ Buffer.from(
145
+ JSON.stringify({
146
+ confHash,
147
+ authInformation,
148
+ expiration: inferAuthRefreshTime(authInformation, config.authMaxRefreshSeconds)
149
+ } as AuthCache)
150
+ ),
151
+ 'utf8'
152
+ );
153
+ }
154
+
155
+ return await PlClient.init(config, {
156
+ authInformation,
157
+ onUpdate: (newAuthInfo) => saveAuthInfoCallback(confHash, config!.authMaxRefreshSeconds),
158
+ onUpdateError: cleanAuthInfoCallback,
159
+ onAuthError: cleanAuthInfoCallback
160
+ });
161
+ }
@@ -0,0 +1,30 @@
1
+ import { PlClient } from './client';
2
+ import { GrpcTransport } from '@protobuf-ts/grpc-transport';
3
+ import type { RpcOptions } from '@protobuf-ts/runtime-rpc';
4
+ import { Dispatcher } from 'undici';
5
+ import { ResourceType } from './types';
6
+
7
+ /** Drivers must implement this interface */
8
+ export interface PlDriver {
9
+ close(): void;
10
+ }
11
+
12
+ /** Definition to use driver via {@link PlClient} */
13
+ export interface PlDriverDefinition<Drv extends PlDriver> {
14
+ /** Used as key to only once instantiate specific drivers */
15
+ readonly name: string;
16
+
17
+ /** Initialization routine, will be executed only once for each driver in a specific client */
18
+ init(pl: PlClient, grpcTransport: GrpcTransport, httpDispatcher: Dispatcher): Drv;
19
+ }
20
+
21
+ // addRTypeToMetadata adds a metadata with resource type
22
+ // for every RPC call. It is necessary for the platform core
23
+ // to proxy the call to the proper controller.
24
+ export function addRTypeToMetadata(rType: ResourceType, options?: RpcOptions) {
25
+ options = options ?? {};
26
+ options.meta = options.meta ?? {};
27
+ options.meta['resourceType'] = `${rType.name}:${rType.version}`;
28
+
29
+ return options;
30
+ }
@@ -0,0 +1,14 @@
1
+ import * as tp from 'node:timers/promises';
2
+ import { isTimeoutOrCancelError } from './errors';
3
+
4
+ test('timeout of sleep error type detection', async () => {
5
+ let noError = false;
6
+ try {
7
+ await tp.setTimeout(1000, undefined, { signal: AbortSignal.timeout(10) });
8
+ noError = true;
9
+ } catch (err: unknown) {
10
+ expect((err as any).code).toStrictEqual('ABORT_ERR');
11
+ expect(isTimeoutOrCancelError(err)).toEqual(true);
12
+ }
13
+ expect(noError).toBe(false);
14
+ });
@@ -0,0 +1,84 @@
1
+ import { Status } from '../proto/github.com/googleapis/googleapis/google/rpc/status';
2
+ import { Aborted } from '@milaboratories/ts-helpers';
3
+
4
+ export function isConnectionProblem(err: unknown, nested: boolean = false): boolean {
5
+ if (err instanceof DisconnectedError) return true;
6
+ if ((err as any).name == 'RpcError' && (err as any).code == 'UNAVAILABLE') return true;
7
+ if ((err as any).cause !== undefined && !nested)
8
+ // nested limits the depth of search
9
+ return isConnectionProblem((err as any).cause, true);
10
+ return false;
11
+ }
12
+
13
+ export function isUnauthenticated(err: unknown, nested: boolean = false): boolean {
14
+ if (err instanceof UnauthenticatedError) return true;
15
+ if ((err as any).name == 'RpcError' && (err as any).code == 'UNAUTHENTICATED') return true;
16
+ if ((err as any).cause !== undefined && !nested)
17
+ // nested limits the depth of search
18
+ return isUnauthenticated((err as any).cause, true);
19
+ return false;
20
+ }
21
+
22
+ export function isTimeoutOrCancelError(err: unknown, nested: boolean = false): boolean {
23
+ if (err instanceof Aborted || (err as any).name == 'AbortError') return true;
24
+ if ((err as any).code == 'ABORT_ERR') return true;
25
+ if (
26
+ (err as any).name == 'RpcError' &&
27
+ ((err as any).code == 'CANCELLED' || (err as any).code == 'DEADLINE_EXCEEDED')
28
+ )
29
+ return true;
30
+ if ((err as any).cause !== undefined && !nested)
31
+ // nested limits the depth of search
32
+ return isTimeoutOrCancelError((err as any).cause, true);
33
+ return false;
34
+ }
35
+
36
+ export const PlErrorCodeNotFound = 5;
37
+
38
+ export class PlError extends Error {
39
+ constructor(public readonly status: Status) {
40
+ super(`code=${status.code} ${status.message}`);
41
+ }
42
+ }
43
+
44
+ export function throwPlNotFoundError(message: string): never {
45
+ throw new RecoverablePlError({ code: PlErrorCodeNotFound, message, details: [] });
46
+ }
47
+
48
+ export class RecoverablePlError extends PlError {
49
+ constructor(status: Status) {
50
+ super(status);
51
+ }
52
+ }
53
+
54
+ export class UnrecoverablePlError extends PlError {
55
+ constructor(status: Status) {
56
+ super(status);
57
+ }
58
+ }
59
+
60
+ export function isNotFoundError(err: unknown, nested: boolean = false): boolean {
61
+ if ((err as any).name == 'RpcError' && (err as any).code == 'NOT_FOUND') return true;
62
+ if ((err as any).cause !== undefined && !nested) return isNotFoundError((err as any).cause, true);
63
+ return err instanceof RecoverablePlError && err.status.code === PlErrorCodeNotFound;
64
+ }
65
+
66
+ export class UnauthenticatedError extends Error {
67
+ constructor(message: string) {
68
+ super('LoginFailed: ' + message);
69
+ }
70
+ }
71
+
72
+ export class DisconnectedError extends Error {
73
+ constructor(message: string) {
74
+ super('Disconnected: ' + message);
75
+ }
76
+ }
77
+
78
+ export function rethrowMeaningfulError(error: any, wrapIfUnknown: boolean = false): never {
79
+ if (isUnauthenticated(error)) throw new UnauthenticatedError(error.message);
80
+ if (isConnectionProblem(error)) throw new DisconnectedError(error.message);
81
+ if (isTimeoutOrCancelError(error)) throw new Aborted(error);
82
+ if (wrapIfUnknown) throw new Error(error.message, { cause: error });
83
+ else throw error;
84
+ }
@@ -0,0 +1,178 @@
1
+ // import * as util from 'util';
2
+ // import http from 'node:http';
3
+ // import https from 'node:https';
4
+ // import { Readable, finished } from 'stream';
5
+ //
6
+ // type RequestOptions = https.RequestOptions;
7
+ //
8
+ // export const finishedP = util.promisify(finished);
9
+ //
10
+ // export const readableToBuffer = (
11
+ // stream: http.IncomingMessage
12
+ // ): Promise<Buffer> => {
13
+ // if (stream.destroyed) {
14
+ // return Promise.reject(stream.errored);
15
+ // }
16
+ //
17
+ // return new Promise((resolve, reject) => {
18
+ // const chunks: Buffer[] = [];
19
+ // stream.on('data', chunk => chunks.push(chunk));
20
+ // stream.on('error', reject);
21
+ // stream.on('end', () => {
22
+ // resolve(Buffer.concat(chunks));
23
+ // });
24
+ // });
25
+ // };
26
+ //
27
+ // class Body {
28
+ // private buff: Buffer | undefined;
29
+ //
30
+ // public constructor(private message: http.IncomingMessage) {}
31
+ //
32
+ // async redable() {
33
+ // return this.buffer().then(b => Readable.from(b));
34
+ // }
35
+ //
36
+ // async buffer(): Promise<Buffer> {
37
+ // if (!this.buff) {
38
+ // this.buff = await readableToBuffer(this.message).catch(e => {
39
+ // throw e;
40
+ // });
41
+ // }
42
+ //
43
+ // return this.buff;
44
+ // }
45
+ //
46
+ // async string() {
47
+ // return this.buffer().then(b => b.toString());
48
+ // }
49
+ //
50
+ // async json() {
51
+ // return this.string().then(s => JSON.parse(s));
52
+ // }
53
+ // }
54
+ //
55
+ // export class HttpClient {
56
+ // constructor(private options: RequestOptions) {
57
+ // }
58
+ //
59
+ // assingOptions(options: RequestOptions) {
60
+ // Object.assign(this.options, options);
61
+ // }
62
+ //
63
+ // request(
64
+ // url: URL | string,
65
+ // conf: {
66
+ // data?: Buffer | string;
67
+ // } & RequestOptions
68
+ // ) {
69
+ // url = typeof url === 'string' ? new URL(url) : url;
70
+ //
71
+ // const data = conf.data ?? Buffer.from('');
72
+ //
73
+ // delete conf.data;
74
+ //
75
+ // const options: RequestOptions = Object.assign(
76
+ // {
77
+ // hostname: url.hostname,
78
+ // path: url.pathname + url.search,
79
+ // port: url.port,
80
+ // } as RequestOptions,
81
+ // this.options,
82
+ // conf,
83
+ // );
84
+ //
85
+ // if (!options.method) {
86
+ // options.method = 'GET';
87
+ // }
88
+ //
89
+ // if (!options.headers) {
90
+ // options.headers = {};
91
+ // }
92
+ //
93
+ // options.headers['Content-Length'] = Buffer.byteLength(data);
94
+ //
95
+ // // console.log('options', JSON.stringify(options, null, 2));
96
+ //
97
+ // const isTls = url.protocol === 'https:';
98
+ //
99
+ // const lib = isTls ? https : http;
100
+ //
101
+ // return new Promise<{
102
+ // statusCode: number;
103
+ // ok: boolean;
104
+ // headers: http.IncomingHttpHeaders;
105
+ // body: Body;
106
+ // }>((resolve, reject) => {
107
+ // const timeout = options.timeout ?? 30000;
108
+ //
109
+ // const t = setTimeout(() => {
110
+ // req.end();
111
+ // reject(Error(`Timeout ${timeout} exceeded`));
112
+ // }, timeout);
113
+ //
114
+ // resolve = wrapFunction(resolve, () => clearTimeout(t)); // @TODO PromiseTimeout in utils
115
+ // reject = wrapFunction(reject, () => clearTimeout(t));
116
+ //
117
+ // const req = lib.request(options, message => {
118
+ // const statusCode = message.statusCode ?? 500;
119
+ // resolve({
120
+ // statusCode,
121
+ // headers: message.headers,
122
+ // ok: statusCode >= 200 && statusCode < 300,
123
+ // body: new Body(message),
124
+ // });
125
+ // });
126
+ //
127
+ // req.on('connect', (message, socket) => {
128
+ // const statusCode = message.statusCode ?? 500;
129
+ // socket.end();
130
+ // socket.on('error', () => {});
131
+ // resolve({
132
+ // statusCode,
133
+ // ok: statusCode >= 200 && statusCode < 300,
134
+ // headers: message.headers,
135
+ // body: new Body(message),
136
+ // });
137
+ // });
138
+ //
139
+ // req.on('error', e => {
140
+ // reject(e);
141
+ // });
142
+ //
143
+ // if (data.length) {
144
+ // req.write(data);
145
+ // }
146
+ //
147
+ // req.end();
148
+ // });
149
+ // }
150
+ //
151
+ // async connectProxy(proxy: URL | string) {
152
+ // proxy = new URL(proxy);
153
+ //
154
+ // const headers = {} as http.OutgoingHttpHeaders;
155
+ //
156
+ // if (proxy.username && proxy.password) {
157
+ // headers['Proxy-Authorization'] = `Basic ${Buffer.from(proxy.username + ':' + proxy.password).toString('base64')}`;
158
+ // }
159
+ //
160
+ // return this.request(proxy, {
161
+ // method: 'CONNECT',
162
+ // path: 'www.google.com:80', // @TODO temp (it seems that we need a target in order to get 200 from the proxy)
163
+ // headers,
164
+ // agent: undefined
165
+ // }).then(r => ({ok: r.ok, statusCode: r.statusCode})).catch(() => ({ok: false, statusCode: 500}));
166
+ // }
167
+ // }
168
+ //
169
+ // export const wrapFunction = <T extends unknown[], U>(
170
+ // fn: (...args: T) => U,
171
+ // before: () => void
172
+ // ) => {
173
+ // return (...args: T): U => {
174
+ // before();
175
+ // return fn(...args);
176
+ // };
177
+ // };
178
+ //
@@ -0,0 +1,111 @@
1
+ import { LLPlClient } from './ll_client';
2
+ import { getTestConfig, getTestLLClient, getTestClientConf } from '../test/test_config';
3
+ import { TxAPI_Open_Request_WritableTx } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';
4
+ import { request } from 'undici';
5
+ import * as tp from 'node:timers/promises';
6
+
7
+ import { UnauthenticatedError } from './errors';
8
+
9
+ test('authenticated instance test', async () => {
10
+ const client = await getTestLLClient();
11
+ const tx = client.createTx();
12
+ const response = await tx.send(
13
+ {
14
+ oneofKind: 'txOpen',
15
+ txOpen: { name: 'test', writable: TxAPI_Open_Request_WritableTx.WRITABLE }
16
+ },
17
+ false
18
+ );
19
+ expect(response.txOpen.tx?.isValid).toBeTruthy();
20
+ await tx.complete();
21
+ await tx.await();
22
+ });
23
+
24
+ test('unauthenticated status change', async () => {
25
+ const cfg = getTestConfig();
26
+ if (cfg.test_password === undefined) {
27
+ console.log("skipping test because target server doesn't support authentication");
28
+ return;
29
+ }
30
+
31
+ const client = new LLPlClient(cfg.address);
32
+ expect(client.status).toBe('OK');
33
+
34
+ const tx = client.createTx();
35
+
36
+ await expect(async () => {
37
+ await tx.send(
38
+ {
39
+ oneofKind: 'txOpen',
40
+ txOpen: { name: 'test', writable: TxAPI_Open_Request_WritableTx.WRITABLE }
41
+ },
42
+ false
43
+ );
44
+ }).rejects.toThrow(UnauthenticatedError);
45
+
46
+ await expect(async () => {
47
+ await tx.await();
48
+ }).rejects.toThrow(UnauthenticatedError);
49
+
50
+ await tp.setImmediate();
51
+
52
+ expect(client.status).toEqual('Unauthenticated');
53
+ });
54
+
55
+ test('automatic token update', async () => {
56
+ const { conf, auth } = await getTestClientConf();
57
+ conf.authMaxRefreshSeconds = 1;
58
+ let numberOfAuthUpdates = 0;
59
+ const client = new LLPlClient(conf, {
60
+ auth: {
61
+ authInformation: auth.authInformation,
62
+ onUpdate: (auth) => {
63
+ console.log(auth);
64
+ ++numberOfAuthUpdates;
65
+ }
66
+ }
67
+ });
68
+
69
+ for (let i = 0; i < 6; i++) {
70
+ const tx = client.createTx();
71
+ const response = await tx.send(
72
+ {
73
+ oneofKind: 'txOpen',
74
+ txOpen: { name: 'test', writable: TxAPI_Open_Request_WritableTx.WRITABLE }
75
+ },
76
+ false
77
+ );
78
+ expect(response.txOpen.tx?.isValid).toBeTruthy();
79
+ await tx.complete();
80
+ await tx.await();
81
+
82
+ if (numberOfAuthUpdates > 1) {
83
+ return;
84
+ }
85
+
86
+ await tp.setTimeout(1000);
87
+ }
88
+ }, 5000);
89
+
90
+ test('test simple https call', async () => {
91
+ const client = await getTestLLClient();
92
+ const response = await request('https://cdn.milaboratory.com/ping', {
93
+ dispatcher: client.httpDispatcher
94
+ });
95
+ const text = await response.body.text();
96
+ expect(text).toEqual('pong');
97
+ });
98
+
99
+ test('test https call via proxy', async () => {
100
+ const testConfig = getTestConfig();
101
+ if (testConfig.test_proxy === undefined) {
102
+ console.log('skipped');
103
+ return;
104
+ }
105
+ const client = await getTestLLClient({ httpProxy: testConfig.test_proxy });
106
+ const response = await request('https://cdn.milaboratory.com/ping', {
107
+ dispatcher: client.httpDispatcher
108
+ });
109
+ const text = await response.body.text();
110
+ expect(text).toEqual('pong');
111
+ });