@snap/camera-kit
Version:
Camera Kit Web
159 lines • 7.97 kB
JavaScript
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