@revyl/verify 0.1.0 → 0.3.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.
- package/CHANGELOG.md +48 -3
- package/README.md +128 -61
- package/dist/api/client.d.ts +6 -4
- package/dist/api/client.js +64 -14
- package/dist/api/types.d.ts +7 -0
- package/dist/cli/init.js +2 -3
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/install.js +64 -0
- package/dist/cli/login.d.ts +6 -0
- package/dist/cli/login.js +200 -0
- package/dist/cli/main.js +48 -14
- package/dist/computer-session.d.ts +21 -5
- package/dist/computer-session.js +214 -35
- package/dist/config.d.ts +8 -1
- package/dist/config.js +16 -2
- package/dist/errors.d.ts +6 -1
- package/dist/errors.js +4 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/open.d.ts +11 -3
- package/dist/open.js +37 -11
- package/dist/screen-readiness.d.ts +1 -0
- package/dist/screen-readiness.js +27 -0
- package/dist/session.d.ts +30 -2
- package/dist/session.js +165 -12
- package/docs/README.md +2 -2
- package/docs/agents.md +14 -9
- package/docs/api.md +86 -12
- package/docs/ci.md +1 -1
- package/docs/computers.md +28 -17
- package/docs/configuration.md +14 -12
- package/docs/end-to-end.md +105 -0
- package/docs/errors.md +31 -5
- package/docs/getting-started.md +49 -26
- package/docs/llms.txt +6 -6
- package/docs/mobile.md +6 -0
- package/docs/web.md +1 -7
- package/package.json +7 -4
- package/docs/linux-computers-contract.md +0 -107
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { chmod, lstat, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
|
|
4
|
+
import { homedir, hostname } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
|
+
import { appUrl, RevylClient } from '../api/client.js';
|
|
8
|
+
import { missingCredentialsMessage, resolveCredentials } from '../auth.js';
|
|
9
|
+
import { isVerifyError, VerifyError } from '../errors.js';
|
|
10
|
+
const AUTH_PATH = '/api/v1/entity/users/cli-device-authorizations';
|
|
11
|
+
export async function signIn(options = {}) {
|
|
12
|
+
const existing = options.force ? undefined : await resolveCredentials();
|
|
13
|
+
if (existing)
|
|
14
|
+
return existing;
|
|
15
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY || process.env.CI)
|
|
16
|
+
throw new VerifyError('AUTH_MISSING', `${missingCredentialsMessage()} Set the REVYL_API_KEY secret for non-interactive runs.`);
|
|
17
|
+
const backendUrl = new RevylClient({ token: '', backendUrl: options.backendUrl }).backendUrl;
|
|
18
|
+
const viewerOrigin = new URL(appUrl(options.appUrl)).origin;
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
const cancel = () => controller.abort();
|
|
21
|
+
process.once('SIGINT', cancel);
|
|
22
|
+
process.once('SIGTERM', cancel);
|
|
23
|
+
const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(600_000)]);
|
|
24
|
+
try {
|
|
25
|
+
const authorization = await post(backendUrl, AUTH_PATH, { client_instance_id: await clientInstanceId(), device_label: hostname().trim().replace(/\s+/g, ' ').slice(0, 80) || 'Revyl CLI' }, signal);
|
|
26
|
+
const deviceCode = requiredString(authorization, 'device_code');
|
|
27
|
+
const userCode = requiredString(authorization, 'user_code');
|
|
28
|
+
const rawUrl = requiredString(authorization, 'verification_uri_complete');
|
|
29
|
+
let url;
|
|
30
|
+
try {
|
|
31
|
+
url = new URL(rawUrl);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw invalidResponse();
|
|
35
|
+
}
|
|
36
|
+
if (url.origin !== viewerOrigin || url.pathname !== '/cli/device' || url.username || url.password)
|
|
37
|
+
throw invalidResponse();
|
|
38
|
+
const expiresIn = authorization.expires_in;
|
|
39
|
+
const firstInterval = authorization.interval;
|
|
40
|
+
if (!Number.isInteger(expiresIn) || typeof expiresIn !== 'number' || expiresIn <= 0 || !Number.isInteger(firstInterval) || typeof firstInterval !== 'number' || firstInterval < 0)
|
|
41
|
+
throw invalidResponse();
|
|
42
|
+
const deadline = Date.now() + Math.min(expiresIn * 1000, 600_000);
|
|
43
|
+
let intervalMs = firstInterval > 0 ? firstInterval * 1000 : 5000;
|
|
44
|
+
process.stderr.write(`Open this URL to approve, then confirm the code below:\n${url.href}\nCode: ${userCode}\nWaiting for approval (Ctrl+C to cancel)...\n`);
|
|
45
|
+
openBrowser(url.href);
|
|
46
|
+
while (Date.now() < deadline) {
|
|
47
|
+
await delay(Math.min(intervalMs, deadline - Date.now()), undefined, { signal });
|
|
48
|
+
if (Date.now() >= deadline)
|
|
49
|
+
break;
|
|
50
|
+
const credential = await post(backendUrl, `${AUTH_PATH}/credentials`, { device_code: deviceCode }, signal);
|
|
51
|
+
if (credential.state === 'pending') {
|
|
52
|
+
intervalMs = Math.min(intervalMs * 1.5, 15_000);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (credential.state === 'denied')
|
|
56
|
+
throw new VerifyError('AUTH_REJECTED', 'The Revyl login request was denied. Run your script again to sign in, or use revyl auth login.');
|
|
57
|
+
if (credential.state !== 'approved')
|
|
58
|
+
throw invalidResponse();
|
|
59
|
+
const token = requiredString(credential, 'api_key_token');
|
|
60
|
+
const who = await new RevylClient({ token, backendUrl }).whoAmI();
|
|
61
|
+
const concurrent = await resolveCredentials();
|
|
62
|
+
if (concurrent) {
|
|
63
|
+
try {
|
|
64
|
+
await new RevylClient({ token: concurrent.token, backendUrl }).whoAmI();
|
|
65
|
+
return concurrent;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (!isVerifyError(error) || error.code !== 'AUTH_REJECTED')
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const keyId = optionalString(credential, 'api_key_id');
|
|
73
|
+
const stored = {
|
|
74
|
+
...(keyId ? { api_key: token, api_key_id: keyId, auth_method: 'browser_api_key' } : { access_token: token, expires_at: tokenExpiry(token), auth_method: 'browser' }),
|
|
75
|
+
...(who.email || optionalString(credential, 'email') ? { email: who.email || optionalString(credential, 'email') } : {}),
|
|
76
|
+
...(who.org_id || optionalString(credential, 'org_id') ? { org_id: who.org_id || optionalString(credential, 'org_id') } : {}),
|
|
77
|
+
...(who.user_id || optionalString(credential, 'user_id') ? { user_id: who.user_id || optionalString(credential, 'user_id') } : {}),
|
|
78
|
+
...(process.env.REVYL_API_KEY?.trim() ? { local_auth_override: true } : {}),
|
|
79
|
+
};
|
|
80
|
+
await writePrivateState('credentials.json', stored);
|
|
81
|
+
return { token, source: 'revyl-cli-login' };
|
|
82
|
+
}
|
|
83
|
+
throw new VerifyError('AUTH_MISSING', 'The Revyl login request expired. Run your script again to sign in, or use revyl auth login.');
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (signal.aborted)
|
|
87
|
+
throw new VerifyError('AUTH_MISSING', 'Revyl sign-in was cancelled or timed out. Run your script again to sign in, or use revyl auth login.');
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
process.removeListener('SIGINT', cancel);
|
|
92
|
+
process.removeListener('SIGTERM', cancel);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function post(backendUrl, path, body, signal) {
|
|
96
|
+
let response;
|
|
97
|
+
try {
|
|
98
|
+
response = await fetch(new URL(path, backendUrl), { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify(body), redirect: 'error', signal: AbortSignal.any([signal, AbortSignal.timeout(15_000)]) });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new VerifyError('AUTH_MISSING', 'Could not reach Revyl to sign in. Run your script again, use revyl auth login, or set REVYL_API_KEY.');
|
|
102
|
+
}
|
|
103
|
+
if (response.status === 404)
|
|
104
|
+
throw new VerifyError('AUTH_MISSING', 'The Revyl login request is unavailable or expired. Run your script again, or use revyl auth login.');
|
|
105
|
+
if (!response.ok)
|
|
106
|
+
throw new VerifyError('AUTH_REJECTED', `Revyl sign-in failed (HTTP ${response.status}). Run your script again, use revyl auth login, or set REVYL_API_KEY.`);
|
|
107
|
+
let value;
|
|
108
|
+
try {
|
|
109
|
+
value = await response.json();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw invalidResponse();
|
|
113
|
+
}
|
|
114
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
115
|
+
throw invalidResponse();
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
function requiredString(value, key) {
|
|
119
|
+
const text = optionalString(value, key);
|
|
120
|
+
if (!text)
|
|
121
|
+
throw invalidResponse();
|
|
122
|
+
return text;
|
|
123
|
+
}
|
|
124
|
+
function optionalString(value, key) {
|
|
125
|
+
const text = value[key];
|
|
126
|
+
if (text === null || text === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
if (typeof text !== 'string' || text.length > 8192 || /[\x00-\x1f\x7f]/.test(text))
|
|
129
|
+
throw invalidResponse();
|
|
130
|
+
return text || undefined;
|
|
131
|
+
}
|
|
132
|
+
function invalidResponse() {
|
|
133
|
+
return new VerifyError('AUTH_REJECTED', 'Revyl returned an invalid sign-in response. Run your script again, use revyl auth login, or set REVYL_API_KEY.');
|
|
134
|
+
}
|
|
135
|
+
function tokenExpiry(token) {
|
|
136
|
+
try {
|
|
137
|
+
const payload = JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8'));
|
|
138
|
+
if (typeof payload.exp === 'number' && Number.isFinite(payload.exp) && payload.exp > 0)
|
|
139
|
+
return new Date(Math.trunc(payload.exp) * 1000).toISOString();
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
return new Date(Date.now() + 30 * 60_000).toISOString();
|
|
143
|
+
}
|
|
144
|
+
async function clientInstanceId() {
|
|
145
|
+
const path = join(homedir(), '.revyl', 'client-identity.json');
|
|
146
|
+
try {
|
|
147
|
+
const identity = JSON.parse(await readFile(path, 'utf8'));
|
|
148
|
+
if (identity && typeof identity === 'object' && 'client_instance_id' in identity && typeof identity.client_instance_id === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(identity.client_instance_id))
|
|
149
|
+
return identity.client_instance_id;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (!(error instanceof SyntaxError) && error.code !== 'ENOENT')
|
|
153
|
+
throw new VerifyError('AUTH_MISSING', 'Could not read the Revyl client identity. Check ~/.revyl permissions.');
|
|
154
|
+
}
|
|
155
|
+
const id = randomBytes(16).toString('hex');
|
|
156
|
+
await writePrivateState('client-identity.json', { client_instance_id: id });
|
|
157
|
+
return id;
|
|
158
|
+
}
|
|
159
|
+
async function writePrivateState(name, value) {
|
|
160
|
+
const directory = join(homedir(), '.revyl');
|
|
161
|
+
const destination = join(directory, name);
|
|
162
|
+
const temporary = join(directory, `.${name}.${randomBytes(16).toString('hex')}`);
|
|
163
|
+
try {
|
|
164
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
165
|
+
if (!(await lstat(directory)).isDirectory() || (await lstat(directory)).isSymbolicLink())
|
|
166
|
+
throw new Error();
|
|
167
|
+
if (process.platform !== 'win32')
|
|
168
|
+
await chmod(directory, 0o700);
|
|
169
|
+
try {
|
|
170
|
+
if (!(await lstat(destination)).isFile())
|
|
171
|
+
throw new Error();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error.code !== 'ENOENT')
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
const file = await open(temporary, 'wx', 0o600);
|
|
178
|
+
try {
|
|
179
|
+
await file.writeFile(`${JSON.stringify(value, null, 2)}\n`);
|
|
180
|
+
await file.sync();
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
await file.close();
|
|
184
|
+
}
|
|
185
|
+
await rename(temporary, destination);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
throw new VerifyError('AUTH_MISSING', 'Could not save Revyl sign-in state. Use a writable, owner-only ~/.revyl directory.');
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await rm(temporary, { force: true });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function openBrowser(url) {
|
|
195
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'rundll32' : 'xdg-open';
|
|
196
|
+
const args = process.platform === 'win32' ? ['url.dll,FileProtocolHandler', url] : [url];
|
|
197
|
+
const child = spawn(command, args, { stdio: 'ignore', timeout: 10_000 });
|
|
198
|
+
child.once('error', () => process.stderr.write('Open the URL above in your browser to finish signing in.\n'));
|
|
199
|
+
child.unref();
|
|
200
|
+
}
|
package/dist/cli/main.js
CHANGED
|
@@ -6,18 +6,20 @@ import { missingCredentialsMessage, resolveCredentials } from '../auth.js';
|
|
|
6
6
|
import { VerifyError, errorMessage, isVerifyError } from '../errors.js';
|
|
7
7
|
import { PACKAGE_NAME, VERSION } from '../version.js';
|
|
8
8
|
import { initProject, validateInitAnswers } from './init.js';
|
|
9
|
-
|
|
9
|
+
import { installVerify, packageHasVerify } from './install.js';
|
|
10
|
+
import { signIn } from './login.js';
|
|
11
|
+
const HELP = `verify ${VERSION}: open Revyl cloud browsers, devices, and computers from your code
|
|
10
12
|
|
|
11
13
|
Usage
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
npx ${PACKAGE_NAME} init [--platform web|ios|android|mac] [--app-id <id>] [--url <https-url>] [--force] [--yes] [--no-install]
|
|
15
|
+
Install Verify, write verify.config.ts and an example script, then check authentication
|
|
16
|
+
npx ${PACKAGE_NAME} whoami
|
|
15
17
|
Check which Revyl account your credentials belong to
|
|
16
18
|
|
|
17
19
|
Authentication
|
|
18
20
|
REVYL_API_KEY, or a login from the Revyl CLI ('revyl auth login').
|
|
19
21
|
|
|
20
|
-
Docs: https://github.com/RevylAI/verify/tree/
|
|
22
|
+
Docs: https://github.com/RevylAI/verify/tree/v${VERSION}/packages/verify/docs
|
|
21
23
|
`;
|
|
22
24
|
const USAGE_CODES = new Set(['INVALID_ARGUMENT', 'AUTH_MISSING', 'AUTH_REJECTED']);
|
|
23
25
|
export async function main(argv) {
|
|
@@ -50,8 +52,11 @@ async function initCommand(args) {
|
|
|
50
52
|
'app-id': { type: 'string' },
|
|
51
53
|
url: { type: 'string' },
|
|
52
54
|
force: { type: 'boolean' },
|
|
55
|
+
yes: { type: 'boolean' },
|
|
56
|
+
'no-install': { type: 'boolean' },
|
|
53
57
|
},
|
|
54
58
|
});
|
|
59
|
+
const interactive = Boolean(process.stdin.isTTY) && !process.env.CI;
|
|
55
60
|
let answers;
|
|
56
61
|
if (values.platform) {
|
|
57
62
|
answers = validateInitAnswers({
|
|
@@ -60,23 +65,52 @@ async function initCommand(args) {
|
|
|
60
65
|
...(values.url ? { url: values.url } : {}),
|
|
61
66
|
});
|
|
62
67
|
}
|
|
63
|
-
else if (
|
|
64
|
-
answers = await askInitQuestions(values['app-id'], values.url);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
68
|
+
else if (!interactive) {
|
|
67
69
|
throw new VerifyError('INVALID_ARGUMENT', 'Pass --platform web, ios, android, or mac when not running in a terminal.');
|
|
68
70
|
}
|
|
71
|
+
if (!values['no-install'] && !(await packageHasVerify(process.cwd()))) {
|
|
72
|
+
let installApproved = !interactive || Boolean(values.yes);
|
|
73
|
+
if (interactive && !values.yes) {
|
|
74
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
75
|
+
try {
|
|
76
|
+
const answer = (await readline.question(`Install ${PACKAGE_NAME} as a dev dependency? (Y/n) `)).trim().toLowerCase();
|
|
77
|
+
installApproved = answer === '' || answer === 'y' || answer === 'yes';
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
readline.close();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (installApproved)
|
|
84
|
+
await installVerify(process.cwd());
|
|
85
|
+
}
|
|
86
|
+
answers ??= await askInitQuestions(values['app-id'], values.url);
|
|
69
87
|
const result = await initProject(process.cwd(), answers, { force: values.force ?? false });
|
|
88
|
+
process.stdout.write(`Created ${result.created.join(' and ')}${result.gitignoreUpdated ? ', and added .verify/ to .gitignore' : ''}.\n`);
|
|
89
|
+
const credentials = await resolveCredentials();
|
|
90
|
+
if (!credentials) {
|
|
91
|
+
if (interactive) {
|
|
92
|
+
await signIn();
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
throw new VerifyError('AUTH_MISSING', `${missingCredentialsMessage()} In non-interactive environments, set the REVYL_API_KEY secret.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await whoamiCommand();
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (!interactive || !isVerifyError(error) || error.code !== 'AUTH_REJECTED')
|
|
103
|
+
throw error;
|
|
104
|
+
await signIn({ force: true });
|
|
105
|
+
await whoamiCommand();
|
|
106
|
+
}
|
|
70
107
|
const steps = [
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
...(answers.platform === 'mac' ? ['Mac computers are not yet available in Revyl production. Early access works only on Revyl staging for enabled organizations; use the optional REVYL_BACKEND_URL override with staging credentials. Ask support@revyl.ai for access. See https://github.com/RevylAI/verify/blob/main/packages/verify/docs/computers.md#availability-and-errors'] : []),
|
|
108
|
+
'Configure the generated verify.config.ts if needed',
|
|
109
|
+
...(answers.platform === 'mac' ? ['Mac computers are early access. Ask support@revyl.ai about access.'] : []),
|
|
74
110
|
...(answers.platform === 'mac' || answers.appId ? [] : ['Put your Revyl app id in verify.config.ts (or set REVYL_APP_ID)']),
|
|
75
111
|
'node verify/example.ts',
|
|
76
112
|
];
|
|
77
113
|
process.stdout.write([
|
|
78
|
-
`Created ${result.created.join(' and ')}${result.gitignoreUpdated ? ', and added .verify/ to .gitignore' : ''}.`,
|
|
79
|
-
'',
|
|
80
114
|
'Next:',
|
|
81
115
|
...steps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
82
116
|
'',
|
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
import { RevylClient } from './api/client.js';
|
|
2
|
-
import type {
|
|
3
|
-
import { type
|
|
2
|
+
import type { ComputerPlatform } from './api/types.js';
|
|
3
|
+
import { type HostedTargetConfig, type ResolvedComputerTarget } from './config.js';
|
|
4
|
+
import { type ComputerProcess, type ComputerRecording, type ExecOptions, type ExecResult, type FileStat, type OpenedSession, type OperationOptions, type Session, type SessionOptions, type StepResult } from './session.js';
|
|
4
5
|
export declare class ComputerSession implements Session {
|
|
5
6
|
private readonly client;
|
|
6
7
|
private readonly options;
|
|
7
|
-
readonly
|
|
8
|
+
private readonly stopOnClose;
|
|
9
|
+
readonly platform: ComputerPlatform;
|
|
8
10
|
readonly target: string;
|
|
9
|
-
readonly id:
|
|
11
|
+
readonly id: string;
|
|
10
12
|
readonly viewerUrl: string;
|
|
11
13
|
readonly steps: StepResult[];
|
|
12
14
|
private readonly lifetime;
|
|
13
15
|
private closePromise;
|
|
16
|
+
private expiresAtMs;
|
|
17
|
+
private screenReadyUntilMs;
|
|
18
|
+
private readonly children;
|
|
19
|
+
private readonly childStarts;
|
|
20
|
+
private closing;
|
|
14
21
|
private constructor();
|
|
15
22
|
static start(client: RevylClient, target: ResolvedComputerTarget, options: SessionOptions): Promise<ComputerSession>;
|
|
16
23
|
get closed(): boolean;
|
|
24
|
+
static attach(client: RevylClient, computerId: string, options: SessionOptions & {
|
|
25
|
+
stopOnClose?: boolean;
|
|
26
|
+
}): Promise<ComputerSession>;
|
|
27
|
+
open(target: HostedTargetConfig): Promise<OpenedSession>;
|
|
28
|
+
report(): Promise<Record<string, unknown>>;
|
|
29
|
+
navigate(): Promise<never>;
|
|
30
|
+
get expiresAt(): Date | undefined;
|
|
31
|
+
recording(): Promise<ComputerRecording | null>;
|
|
17
32
|
instruction(): Promise<never>;
|
|
18
33
|
validation(): Promise<never>;
|
|
19
34
|
screenshot(options?: {
|
|
@@ -29,8 +44,9 @@ export declare class ComputerSession implements Session {
|
|
|
29
44
|
[Symbol.asyncDispose](): Promise<void>;
|
|
30
45
|
private operation;
|
|
31
46
|
private request;
|
|
32
|
-
private
|
|
47
|
+
private parseReport;
|
|
33
48
|
private assertOpen;
|
|
49
|
+
private observationSignal;
|
|
34
50
|
private unavailable;
|
|
35
51
|
private log;
|
|
36
52
|
}
|