@aperture-browser/api-client 0.0.0-init.0 → 0.0.0-nightly.82.2e4f6368108f
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/README.md +108 -10
- package/dist/index.d.ts +386 -392
- package/dist/index.js +367 -30
- package/dist/index.js.map +1 -1
- package/dist/passkeys.d.ts +30 -0
- package/dist/passkeys.js +54 -0
- package/dist/passkeys.js.map +1 -0
- package/dist/service-DhkerN7u.d.ts +296 -0
- package/dist/service-riqZPQHq.js +8 -0
- package/dist/service-riqZPQHq.js.map +1 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -6,30 +6,128 @@ Effect services for the [Aperture](https://github.com/tarik02/aperture) API.
|
|
|
6
6
|
npm install @aperture-browser/api-client effect
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Each API area is a service: `AuthApi`, `SessionsApi`, `SnapshotsApi`, `TenantsApi`, `UsersApi`, `TokensApi` and `
|
|
9
|
+
Each API area is a service: `AuthApi`, `SessionsApi`, `SnapshotsApi`, `TenantsApi`, `UsersApi`, `TokensApi`, `EventsApi` and `HealthApi`. `apiClientLayer` provides all of them over the `HttpClient` your app supplies. Their methods take the caller's credentials first. Failures are `ApiRequestError`s carrying the server's error code and HTTP status.
|
|
10
|
+
|
|
11
|
+
A caller that always acts with the same credentials, such as a server holding one API token, can bind them once: `apertureClientLayer(credentials)` provides `ApertureClient`, which exposes the same methods without the credentials argument, plus `auth.getAuthMe()` and `health.getHealth()`.
|
|
10
12
|
|
|
11
13
|
```ts
|
|
12
14
|
import * as Effect from "effect/Effect";
|
|
13
15
|
import * as Layer from "effect/Layer";
|
|
16
|
+
import * as Option from "effect/Option";
|
|
17
|
+
import * as Stream from "effect/Stream";
|
|
14
18
|
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
15
|
-
import {
|
|
19
|
+
import { ApertureClient, apertureClientLayer, baseUrlLayer } from "@aperture-browser/api-client";
|
|
16
20
|
|
|
17
|
-
const
|
|
21
|
+
const layer = apertureClientLayer({
|
|
18
22
|
kind: "bearer",
|
|
19
23
|
token: process.env.APERTURE_TOKEN!,
|
|
20
24
|
authorityType: "tenant",
|
|
21
|
-
tenantId:
|
|
25
|
+
tenantId: null,
|
|
22
26
|
selectedTenantId: null,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const program = SessionsApi.use((sessions) => sessions.listSessions(credentials, { limit: 20 }));
|
|
26
|
-
|
|
27
|
-
const layer = apiClientLayer.pipe(
|
|
27
|
+
}).pipe(
|
|
28
28
|
Layer.provide(baseUrlLayer("https://aperture.example.com")),
|
|
29
29
|
Layer.provide(FetchHttpClient.layer),
|
|
30
30
|
);
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const program = Effect.gen(function* () {
|
|
33
|
+
const client = yield* ApertureClient;
|
|
34
|
+
const base = yield* client.snapshots.getSnapshotByName("signed-in-base");
|
|
35
|
+
const { session } = yield* client.sessions.createSession({
|
|
36
|
+
baseSnapshotName: Option.isSome(base) ? base.value.name : null,
|
|
37
|
+
browser: { channel: "chromium" },
|
|
38
|
+
proxy: {
|
|
39
|
+
upstreams: { office: { url: "socks5://user:pass@proxy.example.com:1080" } },
|
|
40
|
+
rules: [{ match: "*", via: "office" }],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
yield* client.sessions.updateSessionProxy(session.id, { rules: [], drain: true });
|
|
44
|
+
return yield* client.sessions.streamSessions({ status: "running" }).pipe(Stream.runCount);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await Effect.runPromise(program.pipe(Effect.provide(layer)));
|
|
33
48
|
```
|
|
34
49
|
|
|
50
|
+
`withCredentials(service, credentials)` binds credentials to a single service the same way.
|
|
51
|
+
|
|
52
|
+
## Pagination
|
|
53
|
+
|
|
54
|
+
Every paginated list (sessions, snapshots, tenants, users, admin and tenant tokens, events, audit events) has the same three calls, derived from one generic so they stay aligned:
|
|
55
|
+
|
|
56
|
+
- `listX(params)` fetches one page; pass the previous page's `meta.nextCursor` as `cursor`.
|
|
57
|
+
- `streamX(filter)` is a `Stream` of items across pages, fetching the next page only when pulled.
|
|
58
|
+
- `listAllX(filter)` collects every page into an array.
|
|
59
|
+
|
|
60
|
+
The filter is `listX`'s params without `cursor`; its `limit` is the page size.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const page = yield* client.sessions.listSessions({ status: "running", limit: 20 });
|
|
64
|
+
const next = yield* client.sessions.listSessions({ status: "running", limit: 20, cursor: page.meta.nextCursor });
|
|
65
|
+
const all = yield* client.sessions.listAllSessions({ status: "running", limit: 100 });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Session files
|
|
69
|
+
|
|
70
|
+
`listSessionFiles(sessionId)` lists a session's files and directories (downloads, recordings, uploads, Playwright output, and folders users created), each tagged with `type`. `sessionFileTree(entries)` arranges that listing into directory and file nodes for a tree view, and `sessionFileRootDirectories` names the top-level directories that cannot be deleted, moved or renamed. These calls manage files in any retained state:
|
|
71
|
+
|
|
72
|
+
- `uploadSessionFiles(sessionId, files, { directory })` stores files, in `uploads` unless `directory` says otherwise.
|
|
73
|
+
- `createSessionDirectory(sessionId, relativePath)` creates a folder.
|
|
74
|
+
- `deleteSessionFile(sessionId, relativePath, { recursive })` deletes a file or folder; a non-empty folder needs `recursive`.
|
|
75
|
+
- `moveSessionFile(sessionId, { from, to })` moves or renames a file or folder.
|
|
76
|
+
- `createSessionFileDownloadURL(sessionId, { relativePath, disposition: "inline" })` returns a signed URL a browser can show, for example as an `<img>` source.
|
|
77
|
+
|
|
78
|
+
A failed move or delete carries a stable code, such as `session_file_exists`, `session_file_busy`, `session_directory_not_empty` or `session_directory_protected`.
|
|
79
|
+
|
|
80
|
+
Upload contents may be a `Blob`, a `Uint8Array` or a `Stream` of bytes. Blobs and byte arrays are sent as `FormData`; any stream makes the whole body a streamed request, which browsers other than Chromium cannot send. Every file carries `relativePath`, which works with `browser_file_upload`, and `sandboxPath`, which works with CDP `DOM.setFileInputFiles`.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const program = Effect.gen(function* () {
|
|
84
|
+
const client = yield* ApertureClient;
|
|
85
|
+
const tree = sessionFileTree(yield* client.sessions.listSessionFiles(sessionId));
|
|
86
|
+
yield* client.sessions.createSessionDirectory(sessionId, "uploads/invoices");
|
|
87
|
+
yield* client.sessions.uploadSessionFiles(sessionId, [{ name: file.name, content: file }], {
|
|
88
|
+
directory: "uploads/invoices",
|
|
89
|
+
});
|
|
90
|
+
yield* client.sessions.deleteSessionFile(sessionId, "uploads/old", { recursive: true });
|
|
91
|
+
yield* client.sessions.moveSessionFile(sessionId, {
|
|
92
|
+
from: "downloads/report.pdf",
|
|
93
|
+
to: "uploads/invoices/report.pdf",
|
|
94
|
+
});
|
|
95
|
+
const preview = yield* client.sessions.createSessionFileDownloadURL(sessionId, {
|
|
96
|
+
relativePath: "outputs/page.png",
|
|
97
|
+
disposition: "inline",
|
|
98
|
+
});
|
|
99
|
+
return { tree, previewUrl: preview.url };
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## The live session
|
|
104
|
+
|
|
105
|
+
The live-session calls reach the running session directly and take an optional `sessionToken` in place of the credentials: `uploadLiveSessionFiles` stores files in its `uploads` directory, `setSessionViewport` resizes a target, and `streamSessionRecording` streams a recording without buffering it (`downloadSessionRecording` returns a `Blob`). A failure inside the running session is an `ApiRequestError` with code `live_session_error`, its HTTP status, and the session's message.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
109
|
+
import * as FileSystem from "effect/FileSystem";
|
|
110
|
+
|
|
111
|
+
const program = Effect.gen(function* () {
|
|
112
|
+
const client = yield* ApertureClient;
|
|
113
|
+
const fs = yield* FileSystem.FileSystem;
|
|
114
|
+
yield* client.sessions.setSessionViewport(sessionId, { targetId, width: 1280, height: 720 });
|
|
115
|
+
yield* client.sessions
|
|
116
|
+
.streamSessionRecording(sessionId, recordingId)
|
|
117
|
+
.pipe(Stream.run(fs.sink("./recording.webm")));
|
|
118
|
+
}).pipe(Effect.provide(NodeFileSystem.layer));
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Passkeys
|
|
122
|
+
|
|
123
|
+
Browser passkey sign-in and registration live in `@aperture-browser/api-client/passkeys`, so the main entry does not depend on `@simplewebauthn/browser`. Install it when you use that entry:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
npm install @simplewebauthn/browser
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`loginWithPasskey()` and `registerPasskey(name)` run the whole WebAuthn ceremony against `AuthApi`. They fail with `PasskeyCeremonyError` when the browser does not complete it; its `reason` is `cancelled` when the user dismissed the prompt or it timed out, and `failed` otherwise.
|
|
130
|
+
|
|
35
131
|
Requires `effect` 4.
|
|
132
|
+
|
|
133
|
+
Documentation: https://aperture-browser-docs.pages.dev/docs/packages/api-client
|