feat: add private profile media and realtime contracts
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Ephemeral webcam-face consent state, with no media or persistence capability.
|
||||
*
|
||||
* A future browser adapter may react to an accepted `start` transition by
|
||||
* calling `getUserMedia`; this module cannot do so, cannot fetch anything, and
|
||||
* deliberately has no encode/storage API. Every fresh controller starts off.
|
||||
*/
|
||||
|
||||
export type WebcamFaceConsentStatus =
|
||||
| "off"
|
||||
| "awaiting-consent"
|
||||
| "active"
|
||||
| "stopped"
|
||||
| "revoked";
|
||||
|
||||
export interface WebcamFaceConsentState {
|
||||
status: WebcamFaceConsentStatus;
|
||||
/** True only between an explicit accepted start and stop/revoke. */
|
||||
consentGranted: boolean;
|
||||
readonly ephemeral: true;
|
||||
readonly persistence: "none";
|
||||
readonly autoFetch: false;
|
||||
revision: number;
|
||||
}
|
||||
|
||||
export type WebcamFaceConsentEvent =
|
||||
| { type: "request-start" }
|
||||
| { type: "start"; explicitConsent: true }
|
||||
| { type: "stop" }
|
||||
| { type: "revoke" };
|
||||
|
||||
export type WebcamFaceConsentRejection =
|
||||
| "already-awaiting"
|
||||
| "already-active"
|
||||
| "already-stopped"
|
||||
| "already-revoked"
|
||||
| "consent-required";
|
||||
|
||||
export type WebcamFaceConsentTransition =
|
||||
| { accepted: true; state: WebcamFaceConsentState }
|
||||
| { accepted: false; state: WebcamFaceConsentState; reason: WebcamFaceConsentRejection };
|
||||
|
||||
export interface WebcamFaceConsentController {
|
||||
state(): WebcamFaceConsentState;
|
||||
requestStart(): WebcamFaceConsentTransition;
|
||||
/** Only valid after `requestStart`; the literal true prevents accidental implicit consent. */
|
||||
start(explicitConsent: true): WebcamFaceConsentTransition;
|
||||
stop(): WebcamFaceConsentTransition;
|
||||
revoke(): WebcamFaceConsentTransition;
|
||||
}
|
||||
|
||||
export function initialWebcamFaceConsent(): WebcamFaceConsentState {
|
||||
return {
|
||||
status: "off",
|
||||
consentGranted: false,
|
||||
ephemeral: true,
|
||||
persistence: "none",
|
||||
autoFetch: false,
|
||||
revision: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function copy(state: WebcamFaceConsentState): WebcamFaceConsentState {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
function reject(
|
||||
state: WebcamFaceConsentState,
|
||||
reason: WebcamFaceConsentRejection,
|
||||
): WebcamFaceConsentTransition {
|
||||
return { accepted: false, state: copy(state), reason };
|
||||
}
|
||||
|
||||
function accept(
|
||||
state: WebcamFaceConsentState,
|
||||
status: WebcamFaceConsentStatus,
|
||||
consentGranted: boolean,
|
||||
): WebcamFaceConsentTransition {
|
||||
return {
|
||||
accepted: true,
|
||||
state: { ...state, status, consentGranted, revision: state.revision + 1 },
|
||||
};
|
||||
}
|
||||
|
||||
/** Pure transition primitive for UI stores and deterministic tests. */
|
||||
export function transitionWebcamFaceConsent(
|
||||
state: WebcamFaceConsentState,
|
||||
event: WebcamFaceConsentEvent,
|
||||
): WebcamFaceConsentTransition {
|
||||
switch (event.type) {
|
||||
case "request-start":
|
||||
if (state.status === "awaiting-consent") return reject(state, "already-awaiting");
|
||||
if (state.status === "active") return reject(state, "already-active");
|
||||
return accept(state, "awaiting-consent", false);
|
||||
case "start":
|
||||
if (state.status === "active") return reject(state, "already-active");
|
||||
if (state.status !== "awaiting-consent" || event.explicitConsent !== true) {
|
||||
return reject(state, "consent-required");
|
||||
}
|
||||
return accept(state, "active", true);
|
||||
case "stop":
|
||||
if (state.status === "stopped" || state.status === "off") return reject(state, "already-stopped");
|
||||
if (state.status === "revoked") return reject(state, "already-revoked");
|
||||
return accept(state, "stopped", false);
|
||||
case "revoke":
|
||||
if (state.status === "revoked") return reject(state, "already-revoked");
|
||||
return accept(state, "revoked", false);
|
||||
}
|
||||
}
|
||||
|
||||
export function createWebcamFaceConsent(): WebcamFaceConsentController {
|
||||
let current = initialWebcamFaceConsent();
|
||||
function apply(event: WebcamFaceConsentEvent): WebcamFaceConsentTransition {
|
||||
const transition = transitionWebcamFaceConsent(current, event);
|
||||
if (transition.accepted) current = transition.state;
|
||||
return { ...transition, state: copy(transition.state) };
|
||||
}
|
||||
return {
|
||||
state: () => copy(current),
|
||||
requestStart: () => apply({ type: "request-start" }),
|
||||
start: (explicitConsent) => apply({ type: "start", explicitConsent }),
|
||||
stop: () => apply({ type: "stop" }),
|
||||
revoke: () => apply({ type: "revoke" }),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user