connect-the-dots / cloud-js / IncrementNumCompletion.js
IncrementNumCompletion.js
Raw
const { DataApi } = require("@unity-services/cloud-save-1.4");

module.exports = async ({ params, context, logger }) => {
  const { projectId } = context;
  const levelId = String(params.levelId);
  const cloudSave = new DataApi(context);

  const COLLECTION = "level-stats";
  const KEY = "level-completion";
  const MAX_RETRIES = 5;
  const BASE_DELAY_MS = 50;

  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
    try {
      const read = await cloudSave.getPrivateCustomItems(projectId, COLLECTION, [KEY]);
      const record = read.data.results[0] ?? { value: {}, metadata: {} };

      const stats = record.value;
      const old = stats[levelId]?.NumCompletions || 0;
      const updated = old + 1;
      stats[levelId] = { NumCompletions: updated };

      await cloudSave.setPrivateCustomItemBatch(projectId, COLLECTION, {
        data: [{
          key: KEY,
          value: stats,
          writeLock: record.metadata?.etag
        }]
      });

      return { NumCompletions: updated };
    } catch (err) {
      if (err.response?.status === 412 && attempt < MAX_RETRIES) {
        const backoff = BASE_DELAY_MS * 2 ** (attempt - 1) + Math.random() * BASE_DELAY_MS;
        logger.warn(`ETag conflict (try ${attempt}/${MAX_RETRIES}) – retrying in ${Math.round(backoff)} ms`);
        await new Promise(r => setTimeout(r, backoff));
        continue;
      }
      logger.error("IncrementNumCompletion failed", { message: err.message });
      throw err;
    }
  }
  throw new Error(`Failed to increment after ${MAX_RETRIES} retries`);
};