Skip to content
Snippets Groups Projects
index.ts 6.89 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { extractCommand, makeBuffer, search, searchParent } from "./util";
    import {
      CommandType,
      CallFn,
      RecipeObject,
      OnFn,
      DecodeResponse,
    
      Proto,
    
      DecodeOptions,
    
      USPVersion,
    
      CommandObject,
    
    } from "../types";
    
    import resolve from "./recipes/resolve";
    import operateRecipe from "./recipes/operate";
    import subscribe from "./recipes/subscribe";
    
    
    import v10 from "./1.0,1.1";
    import v12 from "./1.2";
    
    import common from "./common";
    
    // Note: version files are empty for now, they used to be necessary but were later made obsolete. I still keep them around in case version specific changes crop up again.
    
    const versionSpecificCommands = {
    
      "1.0": v10,
      "1.1": v10,
      "1.2": v12,
    
    const getCommand = (
      version: USPVersion,
      commandType: CommandType
    ): CommandObject | null =>
    
      version in versionSpecificCommands &&
      commandType in versionSpecificCommands[version]
        ? versionSpecificCommands[version][commandType]
        : common[commandType] || null;
    
    const recipes: RecipeObject[] = [resolve as any, operateRecipe, subscribe];
    
    
    export const makeRecipes = (call: CallFn, on: OnFn): any =>
      recipes.reduce(
        (acc, { make, name }) => ({ ...acc, [name]: make(call, on) }),
        {}
      );
    
    
    export const decodeId = (data: any) => String(data);
    
    const unkownErr = (
      msg: Record<string, string>
    ): [string, string, Record<string, any>] => ["error", "", msg];
    
    
    export const decodeRecord = (proto: Proto, data: any): Record<string, any> => {
    
      const record = proto.rootRecord.lookupType("usp_record.Record");
    
      const decodedRecord = record.decode(
    
        "binaryData" in data ? data.binaryData : data
      );
    
      return JSON.parse(JSON.stringify(decodedRecord));
    };
    
    export const readMsg = (
      proto: Proto,
      data: any
    ): [Record<string, any>, unknown, boolean] => {
    
      const record = proto.rootRecord.lookupType("usp_record.Record");
      const decodedRecord = record.decode(
        "binaryData" in data ? data.binaryData : data
      );
    
      const convertedDecodedRecord = JSON.parse(JSON.stringify(decodedRecord))
      if ("disconnect" in convertedDecodedRecord)
        return [{}, convertedDecodedRecord.disconnect, true];
    
      try {
        const msg = proto.rootMsg.lookupType("usp.Msg");
        const decodedMsg = msg.decode(
          decodedRecord.noSessionContext?.payload ||
            decodedRecord.sessionContext?.payload[0]
        );
    
        // forces conversion
        return [JSON.parse(JSON.stringify(decodedMsg)), null, false];
      } catch (err) {
        return [{}, err, false];
      }
    
    type DecodeFn = (parsedMsg: any, version: USPVersion) => DecodeResponse;
    
    export const decode: DecodeFn = (parsedMsg, version: USPVersion) => {
    
      const err = searchParent(parsedMsg, "errMsg") || null;
      const command = extractCommand(parsedMsg);
    
      const foundId = search(parsedMsg, "msgId");
    
      // if id is formatted by me (command@) then use, otherwise check for sub id
    
      const id = foundId.includes("@")
    
        ? foundId
        : search(parsedMsg, "subscriptionId") || null;
    
      // if command is unkown
    
      if (!command) return unkownErr(parsedMsg);
    
      if (err) return [id, null, err, command];
    
      const cmd: CommandObject | null = getCommand(version, command);
    
      if (!cmd) return unkownErr(parsedMsg);
    
    
      const [decodedData, decodedId, decodedErr] = cmd.decode(parsedMsg);
      return [decodedId || id, decodedData, decodedErr || err, command];
    };
    
    
    type DecodeWithOptionsFn = (
    
      parsedMsg: any,
      cmdType: CommandType | undefined,
    
      options: DecodeOptions,
      version: USPVersion
    ) => any;
    
    export const decodeWithOptions: DecodeWithOptionsFn = (
      parsedMsg,
      cmdType,
      options,
      version: USPVersion
    
    ) => {
    
      const cmd: CommandObject | null = getCommand(version, cmdType as CommandType);
    
      if (!cmd) return unkownErr(parsedMsg);
    
    
      const [decodedData] = cmd.decode(parsedMsg, options);
      return decodedData;
    
    export const makeEmptySessionMessage = (
      proto: Proto,
      bufferOptions: Record<string, string>,
      sessionOptions: USPSession,
      version: USPVersion,
      useSession: boolean
    ) => {
      const converted = _convert(proto, {});
      if (isError(converted)) return null;
      const encoded = proto.rootMsg
        .lookupType("usp.Msg")
        .encode(converted)
        .finish();
      const buffer = makeBuffer(
        proto.rootRecord,
        encoded,
        version,
        bufferOptions,
        sessionOptions,
        useSession
      );
      return buffer;
    };
    
    
      (proto: Proto, bufferOptions: Record<string, string>, useSession: boolean) =>
    
        version: USPVersion,
    
        args: Record<string, any>,
        sessionOptions: USPSession
    
      ): [string, any, string | null] => {
    
        const cmd = getCommand(version, command);
    
        if (!cmd) return ["error", null, `Uknown command: ${command}`];
    
    
        const msg = cmd.encode(args);
        const id = msg.header.msgId;
        msg.header.msgType = proto.header.MsgType[msg.header.msgType];
        const converted = _convert(proto, msg);
        if (isError(converted)) return [id, null, converted];
        const encoded = proto.rootMsg
          .lookupType("usp.Msg")
          .encode(converted)
          .finish();
        const buffer = makeBuffer(
          proto.rootRecord,
          encoded,
          version,
          bufferOptions,
          sessionOptions,
          useSession
        );
        return [id, buffer, null];
    
    export const makeSession = (sessionId: number | null): USPSession => ({
    
    export const hasSessionDisconnectError = (msg: Record<string, any>) =>
      msg?.disconnect?.reasonCode === 7105;
    
    export const msgLacksPayload = (msg: Record<string, any>) =>
      msg?.sessionContext?.payload[0] === "";
    
    
    const isError = (o: any): o is string => typeof o == "string";
    
    const internalKeys = ["lookup"];
    const isInternal = (key: string) => internalKeys.includes(key);
    const makePayload = (items: [string, any][], isArr: boolean) =>
      items
        .filter(([k]) => !isInternal(k))
        .reduce(
          (acc: any, [k, v]) =>
            isArr
              ? [...acc, v]
              : {
                  ...acc,
                  [k]: v,
                },
          isArr ? [] : {}
        );
    
    const isStringArray = (obj: any) =>
      Array.isArray(obj) && obj.every((v) => typeof v === "string");
    const needsConversion = (v: any) => typeof v === "object" && !isStringArray(v);
    
    
    const _convert = (proto: Proto, value: any): string | any => {
    
      const skip = value.lookup === undefined;
      const lookup = "usp." + value.lookup;
    
      const item = skip ? null : proto.rootMsg.lookupType(lookup);
    
    
      const simpleValues: any[] = Object.entries(value).filter(
        ([, v]) => !needsConversion(v)
      );
      const toConvert = Object.entries(value).filter(([, v]) => needsConversion(v));
      const converted: [string, any][] = toConvert.map(([k, v]) => [
        k,
    
        _convert(proto, v),
    
      ]);
    
      const err = converted.find(([, v]) => isError(v));
      if (err) return err[1];
    
      const total = converted.concat(simpleValues);
      const payload = makePayload(total, Array.isArray(value));
      const payloadErr = item?.verify(payload);
      if (payloadErr) return payloadErr;
    
      return item ? item.create(payload) : payload;
    };