leonitousconforti/tinyburg

View on GitHub
packages/nucleus/src/endpoints/send-item.ts

Summary

Maintainability
A
1 hr
Test Coverage
import type { ILogger } from "../logger.js";
import type { IConfig } from "../config.js";
import type { DecompressedSave } from "../decompress-save.js";
import type { INimblebitResponse } from "./nimblebit-response.js";
import type { SyncItemType } from "../parsing-structs/sync-item.js";
import type { INimblebitJsonSave } from "../parsing-structs/blocks.js";

import { DebugLogger } from "../logger.js";
import { cryptoSalt } from "../crypto-salt.js";
import { blocks } from "../parsing-structs/blocks.js";
import { isValidPlayerId } from "../validation/player-id.js";
import { serverEndpoints, postNetworkRequest } from "../contact-server.js";
import { concatenationSubRoutine } from "../parsing-structs/parsing-subroutines.js";

// Debug logger (will default to using this if no other logger is supplied).
const loggingNamespace: string = "tinyburg:endpoints:send_item";
const debug: ILogger = new DebugLogger(loggingNamespace);

// Send item function params
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type SendItemParameters = {
    /** Type of the item to send. */
    itemType: SyncItemType;

    /** Friend/cloud id of the player to send the item to. */
    sendTo: string;

    /** Item to send. Either as a decompressed save or as a json save. */
    item: DecompressedSave | INimblebitJsonSave;
};

// Nimblebit api send item response type.
export interface ISendItem extends INimblebitResponse {
    success?: "Sent" | "NotSent";
}

// Sends an item to a player. One use case for this endpoint is when visiting players or when
// sending bitizens the players. You can send items to yourself and it will proxy the requests
// through a burn bot.
export const sendItem = async (
    config: IConfig,
    { itemType, sendTo, item }: SendItemParameters,
    logger: ILogger = debug
): Promise<ISendItem> => {
    // Setup logging
    const passLogger = logger === debug ? undefined : logger;
    logger.info("Sending item(%s) to %s...", itemType.toString(), sendTo);

    // Player must be authenticated
    if (!config.authenticated) {
        return logger.fatal(new Error("Player not authenticated"));
    }

    // If you want to send something to yourself, we will use the burn bot to 'proxy' the request
    let sendFrom = { id: config.player.playerId, ss: config.player.playerSs };
    if (sendTo === sendFrom.id) {
        sendFrom = { id: config.burnBot.playerId, ss: config.burnBot.playerSs };
    }

    // SendTo should be a valid player id
    if (!isValidPlayerId(sendTo)) {
        return logger.fatal(new Error("Invalid send to address"));
    }

    // Convert the item to a string if needed
    let itemString: DecompressedSave = item as DecompressedSave;
    if (typeof item !== "string") {
        itemString = concatenationSubRoutine(item as INimblebitJsonSave, blocks);
    }

    // The send item request follows the same authentication process as most other endpoints. The
    // endpoint url will be https://sync.nimblebit.com/send_item/tt/{itemType}/{sendFromId}/{sendTo}/{salt}/{hash} where
    //
    // sendFromId is the senders cloud/friend id
    // sendTo is the receivers cloud/friend id
    // salt is a random 32bit signed integer, [-2147483648 to 2147483647]
    // and hash is the md5 hash of tt/{itemType}/{sendFromId}/{sendTo}/{salt} + {itemStr} + {sendFromSs} + {secretSalt}
    const salt = cryptoSalt(passLogger);
    const hash = `tt/${itemType.toString()}/${sendFrom.id}/${sendTo}/${salt}${itemString.toString()}${sendFrom.ss}`;
    const endpoint = serverEndpoints.sendItem + itemType.toString() + "/" + sendFrom.id + "/" + sendTo + "/" + salt;
    const serverResponse = await postNetworkRequest<ISendItem>({
        config,
        endpoint,
        hash,
        postData: { itemStr: itemString },
        log: passLogger,
    });

    // Bad response
    if (serverResponse.error) {
        return logger.fatal(new Error(`Authentication error: ${serverResponse.error}`));
    }

    // Good response
    if (serverResponse.success === "Sent") {
        logger.info("Success, item(%s) sent!", itemType.toString());
        return serverResponse;
    }

    return logger.fatal(new Error("Bad server response"));
};