Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c09d184
initial ota sku support
adamshiervani Jan 27, 2026
25944c9
Fix tests
adamshiervani Jan 27, 2026
e61ed12
Backwards compatible sku ota
adamshiervani Jan 27, 2026
b62ae13
Add SKU handling tests for legacy and supported versions in releases
adamshiervani Jan 27, 2026
2eb2b97
Explicit test setup for byspassing pre-release tests
adamshiervani Jan 27, 2026
0ef25d6
Refactor hash verification and add device rollout bucket function
adamshiervani Jan 27, 2026
1bf90f9
Update SKU handling tests to pin app and system versions for consiste…
adamshiervani Jan 27, 2026
5969011
Add new SKU update suppport to latest system and app endpoints
adamshiervani Jan 27, 2026
4060056
Remove unused mock function for S3 file with hash and update test to …
adamshiervani Jan 27, 2026
882a74a
Add instructions to run tests in README.md
adamshiervani Jan 27, 2026
73308ef
Refactor device request handlers to enforce parameter typing for Retr…
adamshiervani Jan 27, 2026
071615b
Update Node.js version in package.json and GitHub Actions workflow to…
adamshiervani Jan 27, 2026
50f6fe3
Add PostgreSQL service to GitHub Actions workflow and run Prisma migr…
adamshiervani Jan 27, 2026
df05e79
Add DATABASE_URL environment variable to GitHub Actions workflow for …
adamshiervani Jan 27, 2026
724675f
Update default SKU to jetkvm-v2 to align with build config
adamshiervani Jan 27, 2026
2a17bde
Add Zod validation for query parameters in release endpoints and upda…
adamshiervani Jan 28, 2026
39ea9e2
Refactor SKU handling in release endpoints to use a dedicated query s…
adamshiervani Jan 28, 2026
0658b43
Add caching behavior tests for RetrieveLatestApp and RetrieveLatestSy…
adamshiervani Jan 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,29 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
DATABASE_URL: postgresql://jetkvm:jetkvm@localhost:5432/jetkvm?schema=public
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: jetkvm
POSTGRES_PASSWORD: jetkvm
POSTGRES_DB: jetkvm
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U jetkvm -d jetkvm"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: v21.1.0
node-version: v22.21.0
cache: 'npm'
cache-dependency-path: '**/package-lock.json'

Expand All @@ -35,6 +51,9 @@ jobs:
env:
CI: true

- name: Run Prisma Migrations
run: npx prisma migrate deploy

- name: Run Tests
run: npm test

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ npx prisma migrate deploy

# Start the production server on port 3000
npm run dev

# Run tests
npm test
```

## Production
Expand Down
14 changes: 12 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:coverage": "vitest run --coverage"
},
"engines": {
"node": "21.1.0"
"node": "22.21.0"
},
"keywords": [],
"author": "JetKVM",
Expand All @@ -42,7 +42,8 @@
"semver": "^7.6.3",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"ws": "^8.17.1"
"ws": "^8.17.1",
"zod": "^4.3.6"
},
"optionalDependencies": {
"bufferutil": "^4.0.8"
Expand Down
15 changes: 12 additions & 3 deletions src/devices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export const List = async (req: express.Request, res: express.Response) => {
}
};

export const Retrieve = async (req: express.Request, res: express.Response) => {
export const Retrieve = async (
req: express.Request<{ id: string }>,
res: express.Response
) => {
const idToken = req.session?.id_token;
const { sub } = jose.decodeJwt(idToken);
const { id } = req.params;
Expand All @@ -55,7 +58,10 @@ export const Retrieve = async (req: express.Request, res: express.Response) => {
return res.status(200).json({ device });
};

export const Update = async (req: express.Request, res: express.Response) => {
export const Update = async (
req: express.Request<{ id: string }>,
res: express.Response
) => {
const idToken = req.session?.id_token;
const { sub } = jose.decodeJwt(idToken);
if (!sub) throw new UnauthorizedError("Missing sub in token");
Expand Down Expand Up @@ -94,7 +100,10 @@ export const Token = async (req: express.Request, res: express.Response) => {
return res.json({ secretToken });
};

export const Delete = async (req: express.Request, res: express.Response) => {
export const Delete = async (
req: express.Request<{ id: string }>,
res: express.Response
) => {
if (req.headers.authorization?.startsWith("Bearer ")) {
const secretToken = req.headers.authorization.split("Bearer ")[1];

Expand Down
14 changes: 13 additions & 1 deletion src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export async function verifyHash(
): Promise<boolean> {
const content = await streamToBuffer(file.Body);
const remoteHash = await streamToString(hashFile.Body);
const localHash = createHash("sha256").update(content).digest("hex");
const localHash = createHash("sha256")
.update(new Uint8Array(content))
.digest("hex");

const matches = remoteHash.trim() === localHash;
if (!matches && exception) {
Expand All @@ -44,4 +46,14 @@ export async function verifyHash(
export function toSemverRange(range?: string) {
if (!range) return "*";
return validRange(range) || "*";
}

/**
* Computes a deterministic rollout bucket (0-99) for a device ID.
* Used to decide if a device is eligible for a staged rollout.
*/
export function getDeviceRolloutBucket(deviceId: string): number {
const hash = createHash("md5").update(deviceId).digest("hex");
const hashPrefix = hash.substring(0, 8);
return parseInt(hashPrefix, 16) % 100;
}
Loading