Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,4 @@
}
return _createElement(tagName, options);
});
freezeAndDelete(window, "RTCPeerConnection");
})();
3 changes: 0 additions & 3 deletions js/container/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,4 @@ freezeValue(document, 'createElement', (tagName: string, options?: ElementCreati
return _createElement(tagName, options);
});

// --- WebRTC: no permission path in TrUAPI mode ---
freezeAndDelete(window, 'RTCPeerConnection');

export {};
3 changes: 3 additions & 0 deletions playground/src/lib/auto-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { runExample, type LogEntry, type RunResult } from "./example-runner";
import { getClientSync } from "@parity/truapi/sandbox";
import type { MethodInfo, ServiceInfo } from "./services";
import { WEBRTC_METHOD_NAME, WEBRTC_SERVICE_NAME } from "./webrtc-check";
import type { DiagnosisStatus } from "@/shared/diagnosis";

export const DIAGNOSIS_ID = "__diagnosis__";
Expand Down Expand Up @@ -47,6 +48,8 @@ const METHOD_TIMEOUT_MS = new Map<string, number>([
["Statement Store/create_proof_authorized", LIVE_ALLOCATION_TIMEOUT_MS],
["Statement Store/submit", LIVE_ALLOCATION_TIMEOUT_MS],
["Statement Store/subscribe", LIVE_ALLOCATION_TIMEOUT_MS],
// Two permission prompts (camera, microphone) may need the user.
[`${WEBRTC_SERVICE_NAME}/${WEBRTC_METHOD_NAME}`, SSO_TIMEOUT_MS],
]);

type RunOneOpts = {
Expand Down
11 changes: 7 additions & 4 deletions playground/src/lib/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import type {
ProductExecutionKind,
ServiceInfo,
} from "@parity/truapi/playground/services-types";
import { WEBRTC_SERVICE } from "./webrtc-check";

export type { MethodInfo, ProductExecutionKind, ServiceInfo };
export { servicesForExecution };

export const services: ServiceInfo[] = servicesForExecution(
generatedServices,
"Spa",
);
// Generated SPA-compatible services plus the synthetic WebRTC browser-capability
// method, which is exercised the same way (an example) but is not a wire method.
export const services: ServiceInfo[] = [
...servicesForExecution(generatedServices, "Spa"),
WEBRTC_SERVICE,
];
81 changes: 81 additions & 0 deletions playground/src/lib/webrtc-check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { ServiceInfo } from "@parity/truapi/playground/services-types";

// A first-class playground method backed by an example that combines TrUAPI
// permission calls with the browser WebRTC APIs. It runs through the same
// example runner as every other method (`truapi` and `assert`/`console` are
// injected; globals like `navigator`/`RTCPeerConnection` are reachable in the
// runner's function scope), so it shows up in the method browser, ⌘K, and the
// diagnosis alike.
//
// The example follows the product model — request every device permission via
// TrUAPI first, then use the capability:
// 1. requestDevicePermission("Camera") and ("Microphone") — device permissions;
// 2. only once granted, getUserMedia + RTCPeerConnection.createOffer.

export const WEBRTC_SERVICE_NAME = "WebRTC";
export const WEBRTC_METHOD_NAME = "peer_connection";

const WEBRTC_EXAMPLE_SOURCE = `// Fail fast: WebRTC media capture requires a secure context, so bail before
// prompting for any permission if the origin isn't secure.
console.log("secure context:", window.isSecureContext);
assert(
window.isSecureContext,
"Not a secure context — WebRTC media capture requires HTTPS or a localhost/loopback origin.",
);

// Permission phase — request the camera and microphone through TrUAPI up front.
const camera = await truapi.permissions.requestDevicePermission("Camera");
assert(camera.isOk(), "camera permission request failed:", camera);
assert(camera.value.granted, "camera permission denied");
console.log("camera granted");

const microphone = await truapi.permissions.requestDevicePermission("Microphone");
assert(microphone.isOk(), "microphone permission request failed:", microphone);
assert(microphone.value.granted, "microphone permission denied");
console.log("microphone granted");

// Capability phase — permissions granted, now access the capability.
assert(
typeof RTCPeerConnection !== "undefined",
"RTCPeerConnection is unavailable — the host has not wired the WebRTC bridge (fail-closed).",
);
assert(
typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia,
"navigator.mediaDevices.getUserMedia is unavailable in this host.",
);

const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
console.log(
"media captured:",
stream.getTracks().map((t) => t.kind).join(", "),
);

const pc = new RTCPeerConnection();
try {
for (const track of stream.getTracks()) pc.addTrack(track, stream);
const offer = await pc.createOffer();
assert(!!offer.sdp, "createOffer returned an empty SDP");
console.log("offer created:", offer.type, offer.sdp.length + " bytes of SDP");
} finally {
pc.close();
stream.getTracks().forEach((t) => t.stop());
}`;

/** Synthetic WebRTC method — browsable, runnable, and part of the diagnosis. */
export const WEBRTC_SERVICE: ServiceInfo = {
name: WEBRTC_SERVICE_NAME,
methods: [
{
name: WEBRTC_METHOD_NAME,
type: "unary",
description:
"Requests camera + microphone (device permissions) through TrUAPI, " +
"then — once granted — opens an RTCPeerConnection and creates an " +
"offer with the captured media.",
exampleSource: WEBRTC_EXAMPLE_SOURCE,
},
],
};