Authenticate browser surface relay peers

This commit is contained in:
Timothy J. Aveni
2026-06-21 21:06:03 -07:00
parent 2cdd578848
commit 990c18f00b
10 changed files with 54 additions and 18 deletions
+2 -1
View File
@@ -60,7 +60,8 @@ type ProducerHandlers = {
export declare class BrowserSurfaceRelayProducerClient {
#private;
readonly relayUrl: string;
constructor(relayUrl?: string);
readonly producerToken: string;
constructor(relayUrl?: string, producerToken?: string);
connect(): Promise<void>;
close(): void;
attach(stateContextId: string, surfaceId: string, handlers?: ProducerHandlers): Promise<void>;
+1 -1
View File
File diff suppressed because one or more lines are too long
+5 -2
View File
@@ -126,11 +126,13 @@ const parseProducerMessage = (value) => {
};
export class BrowserSurfaceRelayProducerClient {
relayUrl;
producerToken;
#socket = null;
#ready = null;
#handlers = new Map();
constructor(relayUrl = DEFAULT_SURFACE_RELAY_URL) {
constructor(relayUrl = DEFAULT_SURFACE_RELAY_URL, producerToken = "") {
this.relayUrl = relayUrl;
this.producerToken = producerToken;
}
async connect() {
if (this.#ready) {
@@ -216,6 +218,7 @@ export class BrowserSurfaceRelayProducerClient {
type: "browser-surface-producer-attach",
stateContextId,
surfaceId,
producerToken: this.producerToken,
});
}
async publishProps(stateContextId, surfaceId, props) {
@@ -301,7 +304,7 @@ export const createBrowserSurfaceController = ({ relaySchema, surfaceId, bundleD
bundleDir,
entryPoint,
}));
const producer = new BrowserSurfaceRelayProducerClient(registration.relayUrl);
const producer = new BrowserSurfaceRelayProducerClient(registration.relayUrl, registration.producerToken);
const instance = {
ctx,
stateContextId: ctx.stateContext,
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -10,6 +10,7 @@ type BrowserSurfaceSchemaLike = PackageSchema & {
type EmbeddedSurfaceHostProps = {
surface: EmbeddedSurfaceRef;
relayUrl?: string;
relaySessionToken?: string;
className?: string;
style?: CSSProperties;
fallback?: ReactNode;
@@ -19,15 +20,16 @@ export type EmbeddedSurfaceComponentProps<SurfaceId extends string = string> = {
stateContextId: string;
surfaceId?: SurfaceId;
relayUrl?: string;
relaySessionToken?: string;
className?: string;
style?: CSSProperties;
fallback?: ReactNode;
onError?: (error: Error) => void;
};
export declare const EmbeddedSurface: ({ surface, relayUrl, className, style, fallback, onError, }: EmbeddedSurfaceHostProps) => import("react/jsx-runtime").JSX.Element;
export declare const EmbeddedSurface: ({ surface, relayUrl, relaySessionToken, className, style, fallback, onError, }: EmbeddedSurfaceHostProps) => import("react/jsx-runtime").JSX.Element;
export declare const createEmbeddedSurfaceComponent: <Target extends string | BrowserSurfaceSchemaLike, SurfaceId extends string = string>(target: Target, options?: {
surfaceId?: SurfaceId;
relayUrl?: string;
}) => ({ stateContextId, surfaceId, relayUrl, className, style, fallback, onError, }: EmbeddedSurfaceComponentProps<SurfaceId>) => import("react/jsx-runtime").JSX.Element;
}) => ({ stateContextId, surfaceId, relayUrl, relaySessionToken, className, style, fallback, onError, }: EmbeddedSurfaceComponentProps<SurfaceId>) => import("react/jsx-runtime").JSX.Element;
export {};
//# sourceMappingURL=browserSurfaceEmbedded.d.ts.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"browserSurfaceEmbedded.d.ts","sourceRoot":"","sources":["../../src/browserSurfaceEmbedded.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAC;AAMnC,KAAK,wBAAwB,GAAG,aAAa,GAAG;IAC9C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;CACH,CAAC;AA6HF,KAAK,wBAAwB,GAAG;IAC9B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,6BAA6B,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAmUF,eAAO,MAAM,eAAe,GAAI,6DAO7B,wBAAwB,4CA6L1B,CAAC;AAEF,eAAO,MAAM,8BAA8B,GACzC,MAAM,SAAS,MAAM,GAAG,wBAAwB,EAChD,SAAS,SAAS,MAAM,GAAG,MAAM,EAEjC,QAAQ,MAAM,EACd,UAAU;IACR,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,MAOO,+EAQH,6BAA6B,CAAC,SAAS,CAAC,4CAa9C,CAAC"}
{"version":3,"file":"browserSurfaceEmbedded.d.ts","sourceRoot":"","sources":["../../src/browserSurfaceEmbedded.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAC;AAMnC,KAAK,wBAAwB,GAAG,aAAa,GAAG;IAC9C,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;CACH,CAAC;AA8HF,KAAK,wBAAwB,GAAG;IAC9B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,6BAA6B,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAqUF,eAAO,MAAM,eAAe,GAAI,gFAQ7B,wBAAwB,4CAoM1B,CAAC;AAEF,eAAO,MAAM,8BAA8B,GACzC,MAAM,SAAS,MAAM,GAAG,wBAAwB,EAChD,SAAS,SAAS,MAAM,GAAG,MAAM,EAEjC,QAAQ,MAAM,EACd,UAAU;IACR,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,MAOO,kGASH,6BAA6B,CAAC,SAAS,CAAC,4CAc9C,CAAC"}
+13 -6
View File
@@ -188,7 +188,7 @@ class BrowserSurfaceRelayClient {
}
return null;
}
async attach(stateContextId, surfaceId, hostSessionId, handler) {
async attach(stateContextId, surfaceId, hostSessionId, sessionToken, handler) {
const key = attachmentKey(stateContextId, surfaceId, hostSessionId);
const existing = this.#findAttachment(stateContextId, surfaceId);
if (existing) {
@@ -221,6 +221,7 @@ class BrowserSurfaceRelayClient {
stateContextId,
surfaceId,
hostSessionId,
sessionToken,
});
return async () => {
this.#attachments.delete(key);
@@ -282,7 +283,7 @@ const releaseSharedBrowserSurfaceRelayClient = (relayUrl) => {
entry.client.close();
sharedBrowserSurfaceRelayClients.delete(relayUrl);
};
export const EmbeddedSurface = ({ surface, relayUrl, className, style, fallback = null, onError, }) => {
export const EmbeddedSurface = ({ surface, relayUrl, relaySessionToken = "", className, style, fallback = null, onError, }) => {
const hostRef = useRef(null);
const unsubscribeRef = useRef(null);
const mountedHandleRef = useRef(null);
@@ -396,7 +397,7 @@ export const EmbeddedSurface = ({ surface, relayUrl, className, style, fallback
const hostSessionId = createBrowserSurfaceHostSessionId();
hostSessionIdRef.current = hostSessionId;
void relayClient
.attach(surface.stateContextId, surface.surfaceId, hostSessionId, async (message) => {
.attach(surface.stateContextId, surface.surfaceId, hostSessionId, relaySessionToken, async (message) => {
try {
await handleRelayMessage(message);
}
@@ -423,7 +424,13 @@ export const EmbeddedSurface = ({ surface, relayUrl, className, style, fallback
hostSessionIdRef.current = "";
clearMountedSurface();
};
}, [relayClient, surface.packageName, surface.stateContextId, surface.surfaceId]);
}, [
relayClient,
relaySessionToken,
surface.packageName,
surface.stateContextId,
surface.surfaceId,
]);
useEffect(() => {
return () => {
releaseSharedBrowserSurfaceRelayClient(resolvedRelayUrl);
@@ -436,9 +443,9 @@ export const EmbeddedSurface = ({ surface, relayUrl, className, style, fallback
};
export const createEmbeddedSurfaceComponent = (target, options) => {
const defaultSurfaceId = getEmbeddedSurfaceId(target, options?.surfaceId);
return ({ stateContextId, surfaceId, relayUrl, className, style, fallback, onError, }) => (_jsx(EmbeddedSurface, { surface: createEmbeddedSurfaceRef(target, {
return ({ stateContextId, surfaceId, relayUrl, relaySessionToken, className, style, fallback, onError, }) => (_jsx(EmbeddedSurface, { surface: createEmbeddedSurfaceRef(target, {
stateContextId,
surfaceId: surfaceId ?? defaultSurfaceId,
}), relayUrl: relayUrl ?? options?.relayUrl, className: className, style: style, fallback: fallback, onError: onError }));
}), relayUrl: relayUrl ?? options?.relayUrl, relaySessionToken: relaySessionToken, className: className, style: style, fallback: fallback, onError: onError }));
};
//# sourceMappingURL=browserSurfaceEmbedded.js.map
File diff suppressed because one or more lines are too long
+10 -2
View File
@@ -278,6 +278,7 @@ type ProducerClientMessage =
type: "browser-surface-producer-attach";
stateContextId: string;
surfaceId: string;
producerToken: string;
}
| {
type: "browser-surface-producer-detach";
@@ -319,13 +320,15 @@ const parseProducerMessage = (value: unknown): ProducerMessage | null => {
export class BrowserSurfaceRelayProducerClient {
readonly relayUrl: string;
readonly producerToken: string;
#socket: WebSocket | null = null;
#ready: Promise<void> | null = null;
#handlers = new Map<string, ProducerHandlers>();
constructor(relayUrl = DEFAULT_SURFACE_RELAY_URL) {
constructor(relayUrl = DEFAULT_SURFACE_RELAY_URL, producerToken = "") {
this.relayUrl = relayUrl;
this.producerToken = producerToken;
}
async connect() {
@@ -426,6 +429,7 @@ export class BrowserSurfaceRelayProducerClient {
type: "browser-surface-producer-attach",
stateContextId,
surfaceId,
producerToken: this.producerToken,
});
}
@@ -482,6 +486,7 @@ export type BrowserSurfaceInstance<State> = Omit<
type RelayRegistration = {
relayUrl: string;
producerToken: string;
};
export type BrowserSurfaceHostError = {
@@ -645,7 +650,10 @@ export const createBrowserSurfaceController = <State, Props, Action>({
bundleDir,
entryPoint,
})) as RelayRegistration;
const producer = new BrowserSurfaceRelayProducerClient(registration.relayUrl);
const producer = new BrowserSurfaceRelayProducerClient(
registration.relayUrl,
registration.producerToken,
);
const instance: BrowserSurfaceInstanceInternal<State> = {
ctx,
+16 -1
View File
@@ -113,6 +113,7 @@ type BrowserSurfaceRelayClientMessage =
stateContextId: string;
surfaceId: string;
hostSessionId: string;
sessionToken: string;
}
| {
type: "browser-surface-host-detach";
@@ -151,6 +152,7 @@ type SharedBrowserSurfaceRelayClientEntry = {
type EmbeddedSurfaceHostProps = {
surface: EmbeddedSurfaceRef;
relayUrl?: string;
relaySessionToken?: string;
className?: string;
style?: CSSProperties;
fallback?: ReactNode;
@@ -161,6 +163,7 @@ export type EmbeddedSurfaceComponentProps<SurfaceId extends string = string> = {
stateContextId: string;
surfaceId?: SurfaceId;
relayUrl?: string;
relaySessionToken?: string;
className?: string;
style?: CSSProperties;
fallback?: ReactNode;
@@ -375,6 +378,7 @@ class BrowserSurfaceRelayClient {
stateContextId: string,
surfaceId: string,
hostSessionId: string,
sessionToken: string,
handler: (message: BrowserSurfaceRelayServerMessage) => void | Promise<void>,
) {
const key = attachmentKey(stateContextId, surfaceId, hostSessionId);
@@ -409,6 +413,7 @@ class BrowserSurfaceRelayClient {
stateContextId,
surfaceId,
hostSessionId,
sessionToken,
});
return async () => {
this.#attachments.delete(key);
@@ -491,6 +496,7 @@ const releaseSharedBrowserSurfaceRelayClient = (relayUrl: string) => {
export const EmbeddedSurface = ({
surface,
relayUrl,
relaySessionToken = "",
className,
style,
fallback = null,
@@ -643,6 +649,7 @@ export const EmbeddedSurface = ({
surface.stateContextId,
surface.surfaceId,
hostSessionId,
relaySessionToken,
async (message) => {
try {
await handleRelayMessage(message);
@@ -671,7 +678,13 @@ export const EmbeddedSurface = ({
hostSessionIdRef.current = "";
clearMountedSurface();
};
}, [relayClient, surface.packageName, surface.stateContextId, surface.surfaceId]);
}, [
relayClient,
relaySessionToken,
surface.packageName,
surface.stateContextId,
surface.surfaceId,
]);
useEffect(() => {
return () => {
@@ -705,6 +718,7 @@ export const createEmbeddedSurfaceComponent = <
stateContextId,
surfaceId,
relayUrl,
relaySessionToken,
className,
style,
fallback,
@@ -716,6 +730,7 @@ export const createEmbeddedSurfaceComponent = <
surfaceId: surfaceId ?? defaultSurfaceId,
})}
relayUrl={relayUrl ?? options?.relayUrl}
relaySessionToken={relaySessionToken}
className={className}
style={style}
fallback={fallback}