Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 609203e5 authored by Paula's avatar Paula
Browse files

Feature/#update components

parent 5e537a0b
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
FROM node:23-alpine AS builder
FROM node:24-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

FROM nginx:1.27.3
FROM nginx:1.29.8

COPY --from=builder /app/dist /usr/share/nginx/html
+678 −1272

File changed.

Preview size limit exceeded, changes collapsed.

+12 −11
Original line number Diff line number Diff line
@@ -7,25 +7,26 @@
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "check": "npm run check:json-schema && npm run check:lint",
    "check": "npm run check:json-schema && npm run check:types && npm run check:lint",
    "check:types": "npx tsc --noEmit",
    "check:lint": "npx eslint . && npx prettier . --check",
    "check:json-schema": "npx ajv validate --spec=draft2020 -s public/schemas/devices.schema.json -d \"public/resources/*.json\"",
    "check:json-schema": "node scripts/validate-json-schema.mjs",
    "format": "npx prettier . --write"
  },
  "devDependencies": {
    "ajv-cli": "^5.0.0",
    "@eslint/js": "^9.17.0",
    "@eslint/js": "^10.0.1",
    "@types/w3c-web-usb": "^1.0.10",
    "eslint": "^9.17.0",
    "globals": "^15.14.0",
    "prettier": "3.4.2",
    "typescript": "^5.7.0",
    "vite": "^6.0.5"
    "ajv": "^8.18.0",
    "eslint": "^10.2.1",
    "globals": "^17.5.0",
    "prettier": "^3.8.3",
    "typescript": "^6.0.3",
    "vite": "^8.0.9"
  },
  "dependencies": {
    "@e/fastboot": "2.0.0",
    "@zip.js/zip.js": "^2.7.54",
    "@zip.js/zip.js": "^2.8.26",
    "hash-wasm": "^4.11.0",
    "ky": "^1.7.4"
    "ky": "^2.0.2"
  }
}
+103 −0
Original line number Diff line number Diff line
import { readFile } from "node:fs/promises";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";

import Ajv2020 from "ajv/dist/2020.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.resolve(__dirname, "..");
const SCHEMA_PATH = path.join(
  ROOT_DIR,
  "public",
  "schemas",
  "devices.schema.json",
);
const RESOURCES_DIR = path.join(ROOT_DIR, "public", "resources");

/**
 * Read and parse a JSON file.
 * @param {string} filePath Absolute path to the JSON file.
 * @returns {Promise<unknown>} Parsed JSON content.
 */
async function readJsonFile(filePath) {
  const rawContent = await readFile(filePath, "utf8");
  return JSON.parse(rawContent);
}

/**
 * Build a sorted list of resource JSON files to validate.
 * @returns {Promise<string[]>} Absolute file paths of resource JSON files.
 */
async function listResourceJsonFiles() {
  const { readdir } = await import("node:fs/promises");
  const entries = await readdir(RESOURCES_DIR, { withFileTypes: true });

  return entries
    .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
    .map((entry) => path.join(RESOURCES_DIR, entry.name))
    .sort((a, b) => a.localeCompare(b));
}

/**
 * Format AJV errors in a readable way.
 * @param {import("ajv").ErrorObject[] | null | undefined} errors AJV validation errors.
 * @returns {string} Human-readable error lines.
 */
function formatAjvErrors(errors) {
  if (!errors || errors.length === 0) {
    return "Unknown validation error";
  }

  return errors
    .map((error) => {
      const location = error.instancePath || "/";
      return `- ${location}: ${error.message}`;
    })
    .join("\n");
}

/**
 * Validate all resource files against the device schema.
 * @returns {Promise<void>}
 */
async function main() {
  const schema = await readJsonFile(SCHEMA_PATH);
  const jsonFiles = await listResourceJsonFiles();

  const ajv = new Ajv2020({ allErrors: true, strict: false });
  const validate = ajv.compile(schema);

  let hasValidationError = false;
  let invalidFileCount = 0;

  for (const filePath of jsonFiles) {
    const document = await readJsonFile(filePath);
    const valid = validate(document);

    if (!valid) {
      hasValidationError = true;
      invalidFileCount += 1;
      const displayPath = path.relative(ROOT_DIR, filePath);
      console.error(`\nValidation failed for ${displayPath}`);
      console.error(formatAjvErrors(validate.errors));
    }
  }

  if (hasValidationError) {
    throw new Error(
      `Schema validation failed for ${invalidFileCount} resource file(s).`,
    );
  }

  console.log(
    `Validated ${jsonFiles.length} resource file(s) against devices schema.`,
  );
}

main().catch((error) => {
  console.error("Schema validation script failed.");
  console.error(error);
  process.exit(1);
});
+19 −12
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ export class ControllerManager {
   * @returns {Promise<void>} Resolves when the next step is started.
   */
  async next() {
    let current = this.steps[this.currentIndex];
    let next = this.steps[this.currentIndex + 1];

    DebugManager.log("Controller Manager Next", next);
@@ -88,7 +87,7 @@ export class ControllerManager {
        }
      }
      this.currentIndex++;
      current = this.steps[this.currentIndex];
      let current = this.steps[this.currentIndex];
      DebugManager.log(
        `next() advancing to step="${current.name}", needUserGesture=${current.needUserGesture}`,
      );
@@ -137,6 +136,7 @@ export class ControllerManager {
      } catch (e) {
        throw new Error(
          `Cannot execute command ${this_command.command} <br/> ${e.message || e}`,
          { cause: e },
        );
      }
    } else {
@@ -220,6 +220,7 @@ export class ControllerManager {
      const proposal = "Proposal: Retry by refreshing this page.";
      throw new Error(
        `Cannot download <br/> ${e.message || e} <br/> ${proposal}`,
        { cause: e },
      );
    }
  }
@@ -235,7 +236,9 @@ export class ControllerManager {
      await this.deviceManager.reboot(cmd.mode);
      return true;
    } catch (e) {
      throw new Error(`Reboot to ${cmd.mode} failed: ${e.message || e}`);
      throw new Error(`Reboot to ${cmd.mode} failed: ${e.message || e}`, {
        cause: e,
      });
    }
  }

@@ -259,6 +262,7 @@ export class ControllerManager {
    } catch (e) {
      throw new Error(
        `The device is not connected ${e.message || e} <br/> ${proposal}`,
        { cause: e },
      );
    }
  }
@@ -315,7 +319,9 @@ export class ControllerManager {
          DebugManager.log("device already unlocked");
        } else if (e.bootloaderMessage?.includes("not allowed")) {
          DebugManager.log("device unlock is not allowed");
          throw new Error(`Unlock not allowed: ${e.message || e}`);
          throw new Error(`Unlock not allowed: ${e.message || e}`, {
            cause: e,
          });
        } else {
          throw e;
        }
@@ -352,13 +358,11 @@ export class ControllerManager {
    if (!isLocked) {
      try {
        await this.deviceManager.lock(cmd.command);
        isLocked = true;
      } catch (e) {
        if (e.bootloaderMessage?.includes("already")) {
          DebugManager.log("device already locked");
          isLocked = true;
        } else {
          throw new Error(`Lock failed: ${e.message || e}`);
          throw new Error(`Lock failed: ${e.message || e}`, { cause: e });
        }
      }
    }
@@ -377,7 +381,9 @@ export class ControllerManager {
      await this.deviceManager.sideload(cmd.file);
      return true;
    } catch (e) {
      throw new Error(`Sideload ${cmd.file} failed: ${e.message || e}`);
      throw new Error(`Sideload ${cmd.file} failed: ${e.message || e}`, {
        cause: e,
      });
    }
  }

@@ -391,7 +397,9 @@ export class ControllerManager {
    try {
      return this.deviceManager.format(cmd.partition);
    } catch (e) {
      throw new Error(`Format ${cmd.partition} failed: ${e.message || e}`);
      throw new Error(`Format ${cmd.partition} failed: ${e.message || e}`, {
        cause: e,
      });
    }
  }

@@ -469,7 +477,7 @@ export class ControllerManager {
   * @returns {Promise<object>} Resource object or throws device-model-not-supported.
   */
  async getResources() {
    let resources = null;
    let resources;
    try {
      let current_security_path_level = null;
      try {
@@ -538,9 +546,8 @@ export class ControllerManager {
        }
      }
    } catch (e) {
      resources = null;
      DebugManager.log(`getResources Error: ${e}`);
      throw Error("device-model-not-supported");
      throw new Error("device-model-not-supported", { cause: e });
    }

    return resources;
Loading