@aperture-browser/session-react 0.0.0-nightly.87.b249cb4c5698 → 0.0.0-nightly.88.6472b75ebdd9
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 +6 -2
- package/dist/{headless-BctsPMrj.js → headless-Dz39dQkW.js} +88 -80
- package/dist/headless-Dz39dQkW.js.map +1 -0
- package/dist/headless.d.ts +22 -38
- package/dist/headless.js +2 -2
- package/dist/index.d.ts +9 -11
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/headless-BctsPMrj.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,13 +7,15 @@ npm install @aperture-browser/session-react effect react react-dom
|
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
|
-
import {
|
|
10
|
+
import { useMemo } from "react";
|
|
11
|
+
import { ApertureSession, shareSessionAccess } from "@aperture-browser/session-react";
|
|
11
12
|
import "@aperture-browser/session-react/styles.css";
|
|
12
13
|
|
|
13
14
|
export function SharedBrowser({ token }: { token: string }) {
|
|
15
|
+
const access = useMemo(() => shareSessionAccess(token, "https://aperture.example"), [token]);
|
|
14
16
|
return (
|
|
15
17
|
<div style={{ height: 600 }}>
|
|
16
|
-
<ApertureSession
|
|
18
|
+
<ApertureSession access={access} />
|
|
17
19
|
</div>
|
|
18
20
|
);
|
|
19
21
|
}
|
|
@@ -22,3 +24,5 @@ export function SharedBrowser({ token }: { token: string }) {
|
|
|
22
24
|
`@aperture-browser/session-react/headless` has the same session without any UI, for apps that build their own controls.
|
|
23
25
|
|
|
24
26
|
Documentation: https://aperture-browser-docs.pages.dev/docs/packages/session-react
|
|
27
|
+
|
|
28
|
+
For consumer-authenticated access, pass a stable `access` object with `{ kind: "relay", baseUrl: "/aperture", sessionId }`. Your backend owns authentication through `@aperture-browser/session-relay`; the browser sends no Aperture credentials. `useSession({ access })` exposes the same connection headlessly.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Stream from "effect/Stream";
|
|
4
|
-
import { DEFAULT_VIEWPORT, LiveSessionConnection, LiveSessionError, LiveSessionError as LiveSessionError$1, computeRenderMetrics, createViewportPreset, evdevKeycodeByCode, keyboardInputMessage, keyboardModifiers, shouldForwardBrowserShortcut, strictParseOptions, windowsVirtualKeyCodeForCodeOrKey } from "@aperture-browser/live-session";
|
|
4
|
+
import { DEFAULT_VIEWPORT, LiveSessionConnection, LiveSessionError, LiveSessionError as LiveSessionError$1, computeRenderMetrics, createViewportPreset, downloadSessionRecording, evdevKeycodeByCode, getSessionStatus, keyboardInputMessage, keyboardModifiers, shouldForwardBrowserShortcut, strictParseOptions, windowsVirtualKeyCodeForCodeOrKey } from "@aperture-browser/live-session";
|
|
5
5
|
import * as Option from "effect/Option";
|
|
6
6
|
import * as PubSub from "effect/PubSub";
|
|
7
7
|
import * as Schema from "effect/Schema";
|
|
8
8
|
import * as SubscriptionRef from "effect/SubscriptionRef";
|
|
9
9
|
import * as Data from "effect/Data";
|
|
10
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
import * as Redacted from "effect/Redacted";
|
|
12
|
-
import { SessionsApi, apiClientLayer, baseUrlLayer } from "@aperture-browser/api-client";
|
|
13
11
|
import * as Layer from "effect/Layer";
|
|
14
12
|
import * as ManagedRuntime from "effect/ManagedRuntime";
|
|
15
13
|
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
16
14
|
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
15
|
+
import { apiClientLayer, baseUrlLayer } from "@aperture-browser/api-client";
|
|
16
|
+
import * as Redacted from "effect/Redacted";
|
|
17
17
|
//#region src/effect.tsx
|
|
18
18
|
const RuntimeContext = createContext(null);
|
|
19
19
|
/** A hook below needed the app runtime, but no RuntimeProvider is above the component. */
|
|
@@ -95,10 +95,10 @@ const pointerButtonCode = {
|
|
|
95
95
|
right: 273,
|
|
96
96
|
middle: 274
|
|
97
97
|
};
|
|
98
|
-
function useLiveSession({
|
|
99
|
-
const identity = useMemo(() => collaborationIdentity(
|
|
98
|
+
function useLiveSession({ access, displayName = null, enabled, webrtcSupported, iceServers, autoSize }) {
|
|
99
|
+
const identity = useMemo(() => collaborationIdentity(displayName), [displayName]);
|
|
100
|
+
const [role, setRole] = useState("viewer");
|
|
100
101
|
const runtime = useRuntime();
|
|
101
|
-
const baseUrl = useBaseUrl();
|
|
102
102
|
const frameRef = useMemo(() => runtime.runSync(SubscriptionRef.make(null)), [runtime]);
|
|
103
103
|
const paintPubSub = useMemo(() => runtime.runSync(PubSub.unbounded()), [runtime]);
|
|
104
104
|
const frames = useMemo(() => SubscriptionRef.changes(frameRef), [frameRef]);
|
|
@@ -141,6 +141,7 @@ function useLiveSession({ sessionId, displayName = null, credentials, sessionTok
|
|
|
141
141
|
const handleMessage = useCallback((message) => {
|
|
142
142
|
switch (message.type) {
|
|
143
143
|
case "session.snapshot":
|
|
144
|
+
setRole(message.role);
|
|
144
145
|
clientIdRef.current = message.clientId;
|
|
145
146
|
setClientId(message.clientId);
|
|
146
147
|
holderClientIdRef.current = message.holderClientId ?? null;
|
|
@@ -232,9 +233,10 @@ function useLiveSession({ sessionId, displayName = null, credentials, sessionTok
|
|
|
232
233
|
}
|
|
233
234
|
}, [publishPaint]);
|
|
234
235
|
useFork(() => {
|
|
235
|
-
if (!enabled ||
|
|
236
|
+
if (!enabled || access === null) {
|
|
236
237
|
connectionRef.current = null;
|
|
237
238
|
setPhase("idle");
|
|
239
|
+
setRole("viewer");
|
|
238
240
|
setTargets([]);
|
|
239
241
|
setActiveTargetId(null);
|
|
240
242
|
setMediaStream(null);
|
|
@@ -251,12 +253,10 @@ function useLiveSession({ sessionId, displayName = null, credentials, sessionTok
|
|
|
251
253
|
publishPaint({ type: "clear" });
|
|
252
254
|
return;
|
|
253
255
|
}
|
|
256
|
+
setRole("viewer");
|
|
254
257
|
return Effect.gen(function* () {
|
|
255
258
|
const connection = yield* LiveSessionConnection.make({
|
|
256
|
-
|
|
257
|
-
sessionId,
|
|
258
|
-
credentials,
|
|
259
|
-
sessionToken,
|
|
259
|
+
access,
|
|
260
260
|
identity,
|
|
261
261
|
autoSize,
|
|
262
262
|
iceServers,
|
|
@@ -284,16 +284,13 @@ function useLiveSession({ sessionId, displayName = null, credentials, sessionTok
|
|
|
284
284
|
})));
|
|
285
285
|
}, [
|
|
286
286
|
autoSize,
|
|
287
|
-
|
|
288
|
-
credentials,
|
|
287
|
+
access,
|
|
289
288
|
enabled,
|
|
290
289
|
handleMessage,
|
|
291
290
|
iceServers,
|
|
292
291
|
identity,
|
|
293
292
|
publishFrame,
|
|
294
293
|
publishPaint,
|
|
295
|
-
sessionId,
|
|
296
|
-
sessionToken,
|
|
297
294
|
webrtcSupported
|
|
298
295
|
]);
|
|
299
296
|
const sendReliable = useCallback((message) => connectionRef.current?.sendReliable(message) ?? false, []);
|
|
@@ -580,9 +577,9 @@ const StoredIdentity = Schema.Struct({
|
|
|
580
577
|
avatarHash: Schema.String.check(Schema.isPattern(/^[0-9a-f]{32}$/))
|
|
581
578
|
});
|
|
582
579
|
const decodeStoredIdentity = Schema.decodeUnknownOption(Schema.fromJsonString(StoredIdentity), strictParseOptions);
|
|
583
|
-
function collaborationIdentity(
|
|
580
|
+
function collaborationIdentity(accountName) {
|
|
584
581
|
const anonymous = loadAnonymousIdentity();
|
|
585
|
-
if (
|
|
582
|
+
if (!accountName?.trim()) return anonymous;
|
|
586
583
|
return {
|
|
587
584
|
...anonymous,
|
|
588
585
|
name: Array.from(accountName.trim()).slice(0, 48).join("")
|
|
@@ -1766,7 +1763,7 @@ function focusedElement(node) {
|
|
|
1766
1763
|
//#region src/hooks/use-browser-control.ts
|
|
1767
1764
|
const emptyIceServers$1 = [];
|
|
1768
1765
|
const AUTO_SIZE_DEFAULT_STORAGE_KEY = "aperture.viewport.auto-size-default";
|
|
1769
|
-
function useBrowserControl({
|
|
1766
|
+
function useBrowserControl({ access, displayName, enabled = true, webrtcProducerSupported = false, webrtcIceServers = emptyIceServers$1, onNotice }) {
|
|
1770
1767
|
const runtime = useRuntime();
|
|
1771
1768
|
const onNoticeRef = useRef(onNotice);
|
|
1772
1769
|
onNoticeRef.current = onNotice;
|
|
@@ -1775,21 +1772,19 @@ function useBrowserControl({ sessionId, credentials, displayName, sessionToken,
|
|
|
1775
1772
|
message
|
|
1776
1773
|
}));
|
|
1777
1774
|
const [viewportAutoSizeDefault, setViewportAutoSizeDefaultState] = useState(loadAutoSizeDefault);
|
|
1778
|
-
const [viewportAutoSync, setViewportAutoSyncState] = useState(() =>
|
|
1775
|
+
const [viewportAutoSync, setViewportAutoSyncState] = useState(() => viewportAutoSizeDefault);
|
|
1779
1776
|
const viewportAutoSyncRef = useRef(viewportAutoSync);
|
|
1780
1777
|
viewportAutoSyncRef.current = viewportAutoSync;
|
|
1781
1778
|
const autoSizePreference = useCallback(() => viewportAutoSyncRef.current, []);
|
|
1782
1779
|
const live = useLiveSession({
|
|
1783
|
-
|
|
1780
|
+
access,
|
|
1784
1781
|
displayName,
|
|
1785
|
-
|
|
1786
|
-
sessionToken,
|
|
1787
|
-
role: collaborationRole,
|
|
1788
|
-
enabled: Boolean(enabled && sessionId && credentials),
|
|
1782
|
+
enabled: enabled && access !== null,
|
|
1789
1783
|
webrtcSupported: webrtcProducerSupported,
|
|
1790
1784
|
iceServers: webrtcIceServers,
|
|
1791
1785
|
autoSize: autoSizePreference
|
|
1792
1786
|
});
|
|
1787
|
+
const collaborationRole = live.collaboration.role;
|
|
1793
1788
|
const [targetOrder, setTargetOrder] = useState([]);
|
|
1794
1789
|
const targets = useMemo(() => mergeTargetsInCurrentOrder(targetOrder, live.targets), [live.targets, targetOrder]);
|
|
1795
1790
|
const liveTargetsRef = useRef(live.targets);
|
|
@@ -1818,13 +1813,12 @@ function useBrowserControl({ sessionId, credentials, displayName, sessionToken,
|
|
|
1818
1813
|
setViewportState(createBrowserViewport(live.mediaSize, live.mediaSize.deviceScaleFactor));
|
|
1819
1814
|
}, [live.mediaSize]);
|
|
1820
1815
|
const sendInput = useCallback((message) => {
|
|
1821
|
-
if (!enabled ||
|
|
1816
|
+
if (!enabled || access === null) return false;
|
|
1822
1817
|
return live.sendBrowserInput(message, inputDimensionsRef.current);
|
|
1823
1818
|
}, [
|
|
1824
|
-
|
|
1819
|
+
access,
|
|
1825
1820
|
enabled,
|
|
1826
|
-
live
|
|
1827
|
-
sessionId
|
|
1821
|
+
live
|
|
1828
1822
|
]);
|
|
1829
1823
|
const activateTarget = useCallback((targetId) => {
|
|
1830
1824
|
live.selectTarget(targetId);
|
|
@@ -1972,19 +1966,18 @@ function useBrowserControl({ sessionId, credentials, displayName, sessionToken,
|
|
|
1972
1966
|
recordingBusy,
|
|
1973
1967
|
runStartRecording
|
|
1974
1968
|
]);
|
|
1975
|
-
const runStopRecording = useEffectCallback((
|
|
1969
|
+
const runStopRecording = useEffectCallback((access, recordingId) => settleRecording(live.request("recording.stop", { recordingId }).pipe(Effect.andThen(downloadSessionRecording(access, recordingId)), Effect.flatMap(({ blob, filename }) => {
|
|
1976
1970
|
const recording = live.recordings.find((candidate) => candidate.recordingId === recordingId);
|
|
1977
|
-
return downloadBlob(blob, filename ?? `${sessionId}-${recording?.targetId ?? "target"}-${recordingId}.webm`);
|
|
1978
|
-
}), Effect.andThen(notify("success", "Recording saved"))), "Recording failed to stop"), [live
|
|
1971
|
+
return downloadBlob(blob, filename ?? `${access.sessionId}-${recording?.targetId ?? "target"}-${recordingId}.webm`);
|
|
1972
|
+
}), Effect.andThen(notify("success", "Recording saved"))), "Recording failed to stop"), [live]);
|
|
1979
1973
|
const stopRecording = useCallback((recordingId) => {
|
|
1980
|
-
if (
|
|
1974
|
+
if (access === null || recordingBusy) return;
|
|
1981
1975
|
setRecordingBusy(true);
|
|
1982
|
-
runStopRecording(
|
|
1976
|
+
runStopRecording(access, recordingId);
|
|
1983
1977
|
}, [
|
|
1984
|
-
|
|
1978
|
+
access,
|
|
1985
1979
|
recordingBusy,
|
|
1986
|
-
runStopRecording
|
|
1987
|
-
sessionId
|
|
1980
|
+
runStopRecording
|
|
1988
1981
|
]);
|
|
1989
1982
|
const runCancelRecording = useEffectCallback((recordingId) => settleRecording(live.request("recording.cancel", { recordingId }).pipe(Effect.andThen(notify("success", "Recording stopped"))), "Recording failed to stop"), [live]);
|
|
1990
1983
|
const cancelRecording = useCallback((recordingId) => {
|
|
@@ -1994,23 +1987,18 @@ function useBrowserControl({ sessionId, credentials, displayName, sessionToken,
|
|
|
1994
1987
|
}, [recordingBusy, runCancelRecording]);
|
|
1995
1988
|
const runSetRemoteCursor = useEffectCallback((visible) => live.request("presentation.cursor.set", { visible }).pipe(Effect.catch((error) => notify("error", errorMessage(error, "Remote cursor could not be updated")))), [live]);
|
|
1996
1989
|
const setRemoteCursorEnabled = useCallback((visible) => {
|
|
1997
|
-
if (
|
|
1998
|
-
}, [
|
|
1999
|
-
credentials,
|
|
2000
|
-
runSetRemoteCursor,
|
|
2001
|
-
sessionId
|
|
2002
|
-
]);
|
|
1990
|
+
if (access !== null) runSetRemoteCursor(visible);
|
|
1991
|
+
}, [access, runSetRemoteCursor]);
|
|
2003
1992
|
const runSelectPresentation = useEffectCallback((selection) => live.selectPresentation(selection).pipe(Effect.catch((error) => notify("error", errorMessage(error, "Presentation could not be updated")))), [live]);
|
|
2004
1993
|
const selectMediaStream = useCallback((selection) => {
|
|
2005
|
-
if (!enabled ||
|
|
1994
|
+
if (!enabled || access === null || live.mediaSwitching) return false;
|
|
2006
1995
|
runSelectPresentation(selection);
|
|
2007
1996
|
return true;
|
|
2008
1997
|
}, [
|
|
2009
|
-
|
|
1998
|
+
access,
|
|
2010
1999
|
enabled,
|
|
2011
2000
|
live.mediaSwitching,
|
|
2012
|
-
runSelectPresentation
|
|
2013
|
-
sessionId
|
|
2001
|
+
runSelectPresentation
|
|
2014
2002
|
]);
|
|
2015
2003
|
const setWebRTCStreamSettings = useCallback((settings) => selectMediaStream({
|
|
2016
2004
|
kind: "webrtc",
|
|
@@ -2132,7 +2120,7 @@ function errorMessage(cause, fallback) {
|
|
|
2132
2120
|
}
|
|
2133
2121
|
//#endregion
|
|
2134
2122
|
//#region src/runtime.ts
|
|
2135
|
-
const makeApertureRuntime = (options = {}) => ManagedRuntime.make(Layer.mergeAll(apiClientLayer.pipe(Layer.provide(options.baseUrl ? baseUrlLayer(options.baseUrl) : Layer.empty), Layer.provide(FetchHttpClient.layer)), Layer.succeed(HttpClient.TracerPropagationEnabled, false)));
|
|
2123
|
+
const makeApertureRuntime = (options = {}) => ManagedRuntime.make(Layer.mergeAll(FetchHttpClient.layer, apiClientLayer.pipe(Layer.provide(options.baseUrl ? baseUrlLayer(options.baseUrl) : Layer.empty), Layer.provide(FetchHttpClient.layer)), Layer.succeed(HttpClient.TracerPropagationEnabled, false)));
|
|
2136
2124
|
//#endregion
|
|
2137
2125
|
//#region src/provider.tsx
|
|
2138
2126
|
function ApertureProvider({ baseUrl, children }) {
|
|
@@ -2152,55 +2140,75 @@ function ApertureProvider({ baseUrl, children }) {
|
|
|
2152
2140
|
}) : null;
|
|
2153
2141
|
}
|
|
2154
2142
|
//#endregion
|
|
2155
|
-
//#region src/
|
|
2156
|
-
|
|
2143
|
+
//#region src/session.ts
|
|
2144
|
+
/** Convert an editor/viewer share link into direct session access. */
|
|
2145
|
+
function shareSessionAccess(token, baseUrl) {
|
|
2157
2146
|
const sessionId = token.slice(4, 40);
|
|
2158
|
-
|
|
2159
|
-
if (!role || token[40] !== "_" || !sessionId || !token.slice(41)) return null;
|
|
2147
|
+
if (!(token.startsWith("ape_") || token.startsWith("apv_")) || token[40] !== "_" || token.length <= 41) return null;
|
|
2160
2148
|
return {
|
|
2161
|
-
|
|
2149
|
+
kind: "direct",
|
|
2150
|
+
baseUrl,
|
|
2162
2151
|
sessionId,
|
|
2163
|
-
|
|
2152
|
+
credentials: {
|
|
2153
|
+
kind: "bearer",
|
|
2154
|
+
token: Redacted.make(token),
|
|
2155
|
+
authorityType: null,
|
|
2156
|
+
tenantId: null,
|
|
2157
|
+
selectedTenantId: null
|
|
2158
|
+
}
|
|
2164
2159
|
};
|
|
2165
2160
|
}
|
|
2166
2161
|
const emptyIceServers = [];
|
|
2167
|
-
function
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2162
|
+
function failedStatus(httpStatus) {
|
|
2163
|
+
if (httpStatus === 401 || httpStatus === 403) return "denied";
|
|
2164
|
+
if (httpStatus === 410) return "expired";
|
|
2165
|
+
return "unavailable";
|
|
2166
|
+
}
|
|
2167
|
+
function useSession({ access, displayName, onNotice }) {
|
|
2168
|
+
const [state, setState] = useState({
|
|
2169
|
+
access: null,
|
|
2170
|
+
status: "loading",
|
|
2171
|
+
browser: null
|
|
2172
|
+
});
|
|
2173
|
+
useFork(() => {
|
|
2174
|
+
if (access === null) return;
|
|
2175
|
+
setState({
|
|
2176
|
+
access,
|
|
2177
|
+
status: "loading",
|
|
2178
|
+
browser: null
|
|
2179
|
+
});
|
|
2180
|
+
return getSessionStatus(access).pipe(Effect.match({
|
|
2181
|
+
onFailure: (error) => setState({
|
|
2182
|
+
access,
|
|
2183
|
+
status: failedStatus(error.status),
|
|
2184
|
+
browser: null
|
|
2185
|
+
}),
|
|
2186
|
+
onSuccess: (browser) => setState(access.kind === "direct" && browser.sessionId !== access.sessionId ? {
|
|
2187
|
+
access,
|
|
2188
|
+
status: "unavailable",
|
|
2189
|
+
browser: null
|
|
2190
|
+
} : {
|
|
2191
|
+
access,
|
|
2192
|
+
status: "ready",
|
|
2193
|
+
browser
|
|
2194
|
+
})
|
|
2195
|
+
}));
|
|
2196
|
+
}, [access]);
|
|
2197
|
+
const browser = state.access === access ? state.browser : null;
|
|
2185
2198
|
const control = useBrowserControl({
|
|
2186
|
-
|
|
2187
|
-
credentials: browser ? credentials : null,
|
|
2199
|
+
access: browser === null ? null : access,
|
|
2188
2200
|
displayName,
|
|
2189
|
-
sessionToken: share?.token,
|
|
2190
|
-
collaborationRole: share?.role ?? "viewer",
|
|
2191
|
-
enabled: browser !== null,
|
|
2192
2201
|
webrtcProducerSupported: browser?.media.mode === "auto" && browser.media.webrtcProducer,
|
|
2193
2202
|
webrtcIceServers: browser?.media.iceServers ?? emptyIceServers,
|
|
2194
2203
|
onNotice
|
|
2195
2204
|
});
|
|
2196
2205
|
return {
|
|
2197
|
-
status:
|
|
2198
|
-
share,
|
|
2206
|
+
status: access === null ? "invalid" : state.access !== access ? "loading" : state.status,
|
|
2199
2207
|
browser,
|
|
2200
2208
|
control
|
|
2201
2209
|
};
|
|
2202
2210
|
}
|
|
2203
2211
|
//#endregion
|
|
2204
|
-
export { makeApertureRuntime as a, collaborationPaintLifetimeMs as c, RuntimeProviderMissingError as d, useBaseUrl as f, useRuntime as g, useRunApi as h, ApertureProvider as i, useLiveSession as l, useFork as m,
|
|
2212
|
+
export { makeApertureRuntime as a, collaborationPaintLifetimeMs as c, RuntimeProviderMissingError as d, useBaseUrl as f, useRuntime as g, useRunApi as h, ApertureProvider as i, useLiveSession as l, useFork as m, shareSessionAccess as n, useBrowserControl as o, useEffectCallback as p, useSession as r, SessionViewport as s, LiveSessionError$1 as t, RuntimeProvider as u };
|
|
2205
2213
|
|
|
2206
|
-
//# sourceMappingURL=headless-
|
|
2214
|
+
//# sourceMappingURL=headless-Dz39dQkW.js.map
|