UNPKG

@snap/camera-kit

Version:
209 lines 11.7 kB
import { defineAction, defineActions, defineState, defineStates, dispatch, forActions, inStates, StateMachine, } from "@snap/state-management"; import { Injectable } from "@snap/ts-inject"; import { catchError, exhaustMap, forkJoin, from, map, merge, mergeMap, Observable, of, switchMap, take, takeUntil, tap, } from "rxjs"; import { lensRepositoryFactory } from "../lens/LensRepository"; import { lensPersistenceStoreFactory } from "../lens/LensPersistenceStore"; import { encodeLensLaunchData } from "../lens/LensLaunchData"; import { legalStateFactory } from "../legal/legalState"; import { lensAssetRepositoryFactory } from "../lens/assets/LensAssetRepository"; import { legalError, lensContentValidationError, lensError } from "../namedErrors"; import { getLogger } from "../logger/logger"; import { Timer } from "../metrics/operational/Timer"; import { unsubscribed } from "../observable-operators/unsubscribed"; import { assertUnreachable } from "../common/assertions"; import { metricsClientFactory } from "../clients/metricsClient"; import { lensCoreFactory } from "../lens-core-module/loader/lensCoreFactory"; import { remoteConfigurationFactory } from "../remote-configuration/remoteConfiguration"; import { watermarksLensGroup } from "../lens/fetchWatermarkLens"; import { userDataAccessResolverFactory } from "../lens/userDataAccessResolver"; const logger = getLogger("LensState"); const createLensState = () => { const actions = defineActions(defineAction("applyLens")(), defineAction("downloadComplete")(), defineAction("turnedOn")(), defineAction("resourcesLoaded")(), defineAction("firstFrameProcessed")(), defineAction("applyLensComplete")(), defineAction("applyLensFailed")(), defineAction("applyLensAborted")(), defineAction("removeLens")(), defineAction("turnedOff")(), defineAction("removeLensComplete")(), defineAction("removeLensFailed")()); const states = defineStates(defineState("noLensApplied")(), defineState("applyingLens")(), defineState("lensApplied")()); return new StateMachine(actions, states, states.noLensApplied(), (events) => merge(events.pipe(inStates("noLensApplied", "applyingLens", "lensApplied"), forActions("applyLens"), map(([a]) => states.applyingLens(a.data.lens))), events.pipe(inStates("applyingLens"), forActions("applyLensComplete"), map(([a]) => states.lensApplied(a.data))), events.pipe(inStates("applyingLens"), forActions("applyLensFailed"), map(() => states.noLensApplied())), events.pipe(inStates("lensApplied"), forActions("removeLensComplete"), map(() => states.noLensApplied())))); }; export const lensStateFactory = Injectable("lensState", [ lensCoreFactory.token, lensRepositoryFactory.token, lensAssetRepositoryFactory.token, lensPersistenceStoreFactory.token, legalStateFactory.token, metricsClientFactory.token, remoteConfigurationFactory.token, userDataAccessResolverFactory.token, ], (lensCore, lensRepository, lensAssetRepository, lensPersistence, legalState, metrics, remoteConfig, getUserDataAccess) => { const lensState = createLensState(); let firstLensApply = true; lensState.events .pipe(forActions("applyLens"), exhaustMap(([a]) => of(legalState.actions.requestLegalPrompt()).pipe(dispatch(legalState), inStates("accepted", "rejected"), take(1), map(([, { name }]) => { if (name === "accepted") return a; return lensState.actions.applyLensFailed({ error: legalError(`Failed to apply lens ${a.data.lens.id}. Required legal terms were not accepted.`), lens: a.data.lens, }); }))), switchMap((a) => { if (a.name === "applyLensFailed") return of(a); const { lens } = a.data; const dispatch = (action) => { lensState.dispatch(action, lens); }; const applyTimer = new Timer("lens").mark("apply", { first: `${firstLensApply}` }); firstLensApply = false; return forkJoin({ watermarkInput: remoteConfig.getInitializationConfig().pipe(mergeMap((config) => { if (!config.watermarkEnabled) return of(undefined); return from(lensRepository.loadLens("", watermarksLensGroup)).pipe(mergeMap((watermark) => from(lensRepository.getLensContent(watermark)).pipe(map(({ lensBuffer, lensChecksum }) => { return { lensId: watermark.id, lensDataBuffer: lensBuffer.slice(0), lensChecksum, launchData: new ArrayBuffer(0), }; })))); })), lensInput: of(a.data).pipe(mergeMap(({ lens, launchData }) => { return from(lensPersistence.retrieve(lens.id).catch(() => undefined)).pipe(map((persistentStore) => ({ lens, launchData, persistentStore }))); }), map(({ lens, launchData, persistentStore }) => { const lensDetails = lensRepository.getLensMetadata(lens.id); if (!lensDetails) { throw new Error(`Cannot apply lens ${lens.id}. It has not been loaded by the Lens ` + `repository. Use CameraKit.lensRepository.loadLens (or loadLensGroups) ` + `to load lens metadata before calling CameraKitSession.applyLens.`); } const { content, isThirdParty } = lensDetails; if (!content) { throw new Error(`Cannot apply lens ${lens.id}. Metadata retrieved for this lens does not ` + `include the lens content URL.`); } return { lens, launchData: encodeLensLaunchData(launchData !== null && launchData !== void 0 ? launchData : {}, persistentStore !== null && persistentStore !== void 0 ? persistentStore : new ArrayBuffer(0)), content, isThirdParty, }; }), mergeMap(({ lens, launchData, content, isThirdParty }) => { const networkTimer = applyTimer.mark("network"); return from(Promise.all([ lensRepository.getLensContent(lens).finally(() => networkTimer.measure("lens")), isThirdParty ? getUserDataAccess(lens) : undefined, content.assetManifest.length > 0 ? lensAssetRepository .cacheAssets(content.assetManifest, lens) .finally(() => networkTimer.measure("assets")) : Promise.resolve(), ])).pipe(tap(() => { networkTimer.measure(); lensState.dispatch("downloadComplete", lens); }), map(([{ lensBuffer, lensChecksum }, userDataAccess]) => { const lensDataBuffer = lensBuffer.slice(0); return { lensId: lens.id, lensDataBuffer, lensChecksum, launchData, apiVisibility: isThirdParty ? lensCore.LensApiVisibility.Public : lensCore.LensApiVisibility.Private, publicApiUserDataAccess: userDataAccess === "restricted" ? lensCore.UserDataAccess.Restricted : lensCore.UserDataAccess.Unrestricted, }; })); })), }).pipe(takeUntil(lensState.events.pipe(forActions("removeLens"))), mergeMap(({ lensInput, watermarkInput }) => new Observable((subscriber) => { const coreTimer = applyTimer.mark("core"); lensCore .replaceLenses({ lenses: [ Object.assign(Object.assign({}, lensInput), { onTurnOn: () => dispatch("turnedOn"), onResourcesLoaded: () => dispatch("resourcesLoaded"), onFirstFrameProcessed: () => { coreTimer.measure("first-frame"); applyTimer.measure("success"); applyTimer.stopAndReport(metrics); dispatch("firstFrameProcessed"); }, onTurnOff: () => dispatch("turnedOff") }), ...(watermarkInput ? [watermarkInput] : []), ], }) .then(() => { coreTimer.measure("success"); subscriber.next(lensState.actions.applyLensComplete(lens)); subscriber.complete(); }) .catch((lensCoreError) => { coreTimer.measure("failure"); applyTimer.measure("failure"); applyTimer.stopAndReport(metrics); const message = `Failed to apply lens ${lensInput.lensId}.`; const error = /validation failed/.test(lensCoreError.message) ? lensContentValidationError(message, lensCoreError) : lensError(message, lensCoreError); subscriber.next(lensState.actions.applyLensFailed({ error, lens })); subscriber.complete(); }); })), catchError((error) => { applyTimer.measure("failure"); applyTimer.stopAndReport(metrics); return of(lensState.actions.applyLensFailed({ error, lens })); }), unsubscribed(() => { applyTimer.measure("abort"); applyTimer.stopAndReport(metrics); })); }), dispatch(lensState)) .subscribe({ error: logger.error, }); lensState.events .pipe(inStates("lensApplied", "noLensApplied"), forActions("removeLens"), mergeMap(() => new Observable((subscriber) => { lensCore .clearAllLenses() .then(() => { subscriber.next(lensState.actions.removeLensComplete()); subscriber.complete(); }) .catch((lensCoreError) => { const error = new Error("Failed to remove lenses.", { cause: lensCoreError }); subscriber.next(lensState.actions.removeLensFailed(error)); subscriber.complete(); }); })), dispatch(lensState)) .subscribe({ error: logger.error, }); lensState.events .pipe(inStates("applyingLens"), forActions("removeLens"), switchMap(([a]) => lensState.events.pipe(inStates("lensApplied"), takeUntil(lensState.events.pipe(forActions("applyLens"))), map(() => a))), dispatch(lensState)) .subscribe({ error: logger.error, }); lensState.events.subscribe(([a, s]) => { const data = extractLoggableData(a); logger.debug(`Action: "${a.name}", state: "${s.name}"${data ? ", data: " + JSON.stringify(data) : ""}`); }); return lensState; }); function extractLoggableData(action) { switch (action.name) { case "applyLens": return { lensId: action.data.lens.id }; case "applyLensFailed": return { lensId: action.data.lens.id, error: action.data.error.message }; case "downloadComplete": case "turnedOn": case "resourcesLoaded": case "firstFrameProcessed": case "applyLensComplete": case "applyLensAborted": case "turnedOff": return { lensId: action.data.id }; case "removeLens": case "removeLensComplete": return undefined; case "removeLensFailed": return { error: action.data.message }; default: assertUnreachable(action); } } //# sourceMappingURL=lensState.js.map