UNPKG

@snap/camera-kit

Version:
159 lines 7.97 kB
import { Subject, catchError, filter, map, merge, retry, takeUntil, tap } from "rxjs"; import { forActions } from "@snap/state-management"; import { Injectable } from "../dependency-injection/Injectable"; import { RemoteApiInfo } from "../generated-proto/pb_schema/camera_kit/v3/features/remote_api_info"; import { ResponseCode, responseCodeToNumber } from "../generated-proto/pb_schema/lenses/remote_api/remote_api_service"; import { getLogger } from "../logger/logger"; import { Count } from "../metrics/operational/Count"; import { joinMetricNames } from "../metrics/operational/Metric"; import { extractSchemeAndRoute } from "./UriHandlers"; const logger = getLogger("RemoteApiServices"); const uriResponseOkCode = 200; const apiResponseStatusHeader = ":sc_lens_api_status"; const apiBinaryContentType = "application/octet-stream"; const remoteApiInfoProtobufTypeUrl = "type.googleapis.com/com.snap.camerakit.v3.features.RemoteApiInfo"; const remoteApiUploadImageSpecId = "af3f69c8-2e62-441f-8b1c-d3956f7b336c"; const statusToResponseCodeMap = { success: ResponseCode.SUCCESS, redirected: ResponseCode.REDIRECTED, badRequest: ResponseCode.BAD_REQUEST, accessDenied: ResponseCode.ACCESS_DENIED, notFound: ResponseCode.NOT_FOUND, timeout: ResponseCode.TIMEOUT, requestTooLarge: ResponseCode.REQUEST_TOO_LARGE, serverError: ResponseCode.SERVER_ERROR, cancelled: ResponseCode.CANCELLED, proxyError: ResponseCode.PROXY_ERROR, }; function callCancellationHandler(cancellationHandlers, ...keys) { var _a; for (const key of keys) { (_a = cancellationHandlers.get(key)) === null || _a === void 0 ? void 0 : _a(); cancellationHandlers.delete(key); } } function handleLensApplicationEnd(lensRequestState, ...lensIds) { for (const lensId of lensIds) { const state = lensRequestState.get(lensId); if (state) { callCancellationHandler(state.cancellationHandlers, ...state.cancellationHandlers.keys()); lensRequestState.delete(lensId); } } } export const remoteApiServicesFactory = Injectable("remoteApiServices", () => { const remoteApiServices = []; return remoteApiServices; }); export function getRemoteApiUriHandler(registeredServices, sessionState, lensState, lensRepository, metrics) { const registeredServiceMap = new Map(); for (const service of registeredServices) { const existingServices = registeredServiceMap.get(service.apiSpecId) || []; registeredServiceMap.set(service.apiSpecId, [...existingServices, service]); } const uriRequests = new Subject(); const uriCancelRequests = new Subject(); const lensRequestState = new Map(); const lensTurnOffEvents = lensState.events.pipe(forActions("turnedOff"), tap(([action]) => handleLensApplicationEnd(lensRequestState, action.data.id))); const uriRequestEvents = uriRequests.pipe(map((uriRequest) => { var _a, _b; const lensId = uriRequest.lens.id; if (!lensRequestState.has(lensId)) { lensRequestState.set(lensId, { cancellationHandlers: new Map(), supportedSpecIds: new Set([ ...((_b = (_a = lensRepository.getLensMetadata(lensId)) === null || _a === void 0 ? void 0 : _a.featureMetadata) !== null && _b !== void 0 ? _b : []) .filter((feature) => feature.typeUrl === remoteApiInfoProtobufTypeUrl) .flatMap((any) => RemoteApiInfo.decode(any.value).apiSpecIds), remoteApiUploadImageSpecId, ]), }); } const requestState = lensRequestState.get(lensId); const { route } = extractSchemeAndRoute(uriRequest.request.uri); const [specId, endpointIdWithQuery] = route.split("/").slice(2); const [endpointId] = endpointIdWithQuery.split("?"); return { uriRequest, specId, endpointId, requestState }; }), filter(({ specId, requestState }) => requestState.supportedSpecIds.has(specId)), filter(({ specId }) => registeredServiceMap.has(specId)), map(({ uriRequest, specId, endpointId, requestState }) => { var _a; const dimensions = { specId }; const reportSingleCount = (name) => { metrics.setOperationalMetrics(Count.count(joinMetricNames(["lens", "remote-api", name]), 1, dimensions)); }; reportSingleCount("requests"); const remoteApiRequest = { apiSpecId: specId, body: uriRequest.request.data, endpointId, parameters: uriRequest.request.metadata, }; for (const service of (_a = registeredServiceMap.get(specId)) !== null && _a !== void 0 ? _a : []) { let requestHandler = undefined; try { requestHandler = service.getRequestHandler(remoteApiRequest, uriRequest.lens); } catch (error) { logger.warn("Client's Remote API request handler factory threw an error.", error); } if (requestHandler) { reportSingleCount("handled-requests"); let cancellationHandler = undefined; try { cancellationHandler = requestHandler((response) => { var _a; reportSingleCount("responses"); const responseCode = (_a = statusToResponseCodeMap[response.status]) !== null && _a !== void 0 ? _a : ResponseCode.UNRECOGNIZED; const uriResponse = { code: uriResponseOkCode, description: "", contentType: apiBinaryContentType, data: response.body, metadata: Object.assign(Object.assign({}, response.metadata), { [apiResponseStatusHeader]: responseCodeToNumber(responseCode).toString() }), }; uriRequest.reply(uriResponse); }); } catch (error) { logger.warn("Client's Remote API request handler threw an error.", error); } if (typeof cancellationHandler === "function") { requestState.cancellationHandlers.set(uriRequest.request.identifier, () => { try { cancellationHandler(); } catch (error) { logger.warn("Client's Remote API request cancellation handler threw an error.", error); } }); } break; } } })); const uriCancelRequestEvents = uriCancelRequests.pipe(tap((uriRequest) => { var _a; const cancellationHandlers = (_a = lensRequestState.get(uriRequest.lens.id)) === null || _a === void 0 ? void 0 : _a.cancellationHandlers; if (cancellationHandlers) { callCancellationHandler(cancellationHandlers, uriRequest.request.requestId); } })); merge(lensTurnOffEvents, uriRequestEvents, uriCancelRequestEvents) .pipe(catchError((error, sourcePipe) => { logger.error(error); metrics.setOperationalMetrics(Count.count(joinMetricNames(["lens", "remote-api", "errors"]), 1)); return sourcePipe; }), retry(), takeUntil(sessionState.events.pipe(forActions("destroy")))) .subscribe({ complete: () => handleLensApplicationEnd(lensRequestState, ...lensRequestState.keys()), }); return { uri: "app://remote-api/performApiRequest", handleRequest(request, reply, lens) { uriRequests.next({ request, reply, lens }); }, cancelRequest(request, lens) { uriCancelRequests.next({ request, lens }); }, }; } //# sourceMappingURL=RemoteApiServices.js.map