Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.3.14
bun-version: 1.4.2

- name: Install dependencies
run: bun install --frozen-lockfile
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.3.14
bun-version: 1.4.2

- name: Resolve build metadata
id: build-info
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
FROM --platform=$BUILDPLATFORM oven/bun:1.3.14-slim AS builder
FROM --platform=$BUILDPLATFORM oven/bun:1.4.2-slim AS builder
WORKDIR /app
COPY package.json bun.lock ./
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
RUN bun install --frozen-lockfile
COPY src/ ./src/
RUN bun build src/index.ts --outfile dist/index.js --target bun --external playwright

FROM oven/bun:1.3.14-slim AS prod-deps
FROM oven/bun:1.4.2-slim AS prod-deps
WORKDIR /app
COPY package.json bun.lock ./
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
Expand All @@ -22,7 +22,7 @@ RUN apt-get update \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* /usr/lib/node_modules
WORKDIR /app
COPY --from=oven/bun:1.3.14-slim /usr/local/bin/bun /usr/local/bin/bun
COPY --from=oven/bun:1.4.2-slim /usr/local/bin/bun /usr/local/bin/bun
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
"$schema": "https://biomejs.dev/schemas/2.5.12/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
38 changes: 18 additions & 20 deletions bun.lock

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

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typetype-token",
"version": "1.8.0",
"version": "1.8.1",
"private": true,
"license": "MIT",
"scripts": {
Expand All @@ -16,12 +16,12 @@
"dependencies": {
"bgutils-js": "^4.0.3",
"googlevideo": "^4.1.1",
"playwright": "^1.62.1",
"youtubei.js": "^17.2.0"
"playwright": "^1.63.0",
"youtubei.js": "^18.0.0"
},
"devDependencies": {
"@biomejs/biome": "^2.5.10",
"bun-types": "^1.3.14",
"@biomejs/biome": "^2.5.12",
"bun-types": "^1.4.2",
"typescript": "~7.0.2"
},
"trustedDependencies": [
Expand Down
73 changes: 73 additions & 0 deletions src/remote-login-input-queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { RemoteLoginPage } from "./remote-login-browser.ts";
import { applyRemoteLoginInput } from "./remote-login-input.ts";
import type { RemoteLoginInput } from "./remote-login-messages.ts";

const MAX_INPUT_QUEUE = 128;

export class RemoteLoginInputQueue {
private readonly pending: RemoteLoginInput[] = [];
private page: RemoteLoginPage["page"] | null = null;
private draining = false;
private closed = false;

constructor(private readonly fail: (message: string) => void) {}

async attach(page: RemoteLoginPage["page"]): Promise<void> {
if (this.closed) return;
this.page = page;
await this.drain();
}

enqueue(message: RemoteLoginInput): void {
if (this.closed) return;
if (message.type === "cancel") {
this.close();
this.fail("Session cancelled");
return;
}
const last = this.pending.at(-1);
if (
(message.type === "resize" && last?.type === "resize") ||
(message.type === "pointer" &&
message.event === "move" &&
last?.type === "pointer" &&
last.event === "move")
) {
this.pending[this.pending.length - 1] = message;
} else {
if (this.pending.length >= MAX_INPUT_QUEUE) {
this.close();
this.fail("Remote browser input queue exceeded");
return;
}
this.pending.push(message);
}
void this.drain();
}

close(): void {
this.closed = true;
this.pending.length = 0;
this.page = null;
}

private async drain(): Promise<void> {
const page = this.page;
if (this.closed || this.draining || !page) return;
this.draining = true;
try {
while (!this.closed) {
const message = this.pending.shift();
if (!message) return;
await applyRemoteLoginInput(page, message);
}
} catch {
if (!this.closed) {
this.close();
this.fail("Remote browser input failed");
}
} finally {
this.draining = false;
}
}
}
57 changes: 7 additions & 50 deletions src/remote-login-session.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { RemoteLoginPage } from "./remote-login-browser.ts";
import { sendRemoteLoginCompletion } from "./remote-login-callback.ts";
import type { RemoteLoginConfig } from "./remote-login-config.ts";
import { applyRemoteLoginInput } from "./remote-login-input.ts";
import { RemoteLoginInputQueue } from "./remote-login-input-queue.ts";
import {
errorMessage,
parseRemoteLoginInput,
type RemoteLoginInput,
type RemoteLoginPhase,
statusMessage,
} from "./remote-login-messages.ts";
Expand Down Expand Up @@ -33,8 +32,7 @@ export class RemoteLoginSession {
private expiryTimer: ReturnType<typeof setTimeout>;
private frameTimer: ReturnType<typeof setTimeout> | null = null;
private loginTimer: ReturnType<typeof setTimeout> | null = null;
private readonly inputQueue: RemoteLoginInput[] = [];
private inputDrainRunning = false;
private readonly inputQueue = new RemoteLoginInputQueue((message) => this.fail(message));
constructor(options: RemoteLoginSessionOptions) {
this.sessionId = options.sessionId;
this.userId = options.userId;
Expand All @@ -54,6 +52,8 @@ export class RemoteLoginSession {
return;
}
this.page = page;
await this.inputQueue.attach(page.page);
if (this.closed) return;
this.setPhase("awaiting_login");
this.scheduleFrames();
this.scheduleLoginCheck();
Expand All @@ -71,56 +71,14 @@ export class RemoteLoginSession {
if (this.closed || typeof raw !== "string") return;
const message = parseRemoteLoginInput(raw);
if (!message) return;
this.enqueueInput(message);
this.inputQueue.enqueue(message);
}
disconnect(): void {
this.fail("WebSocket disconnected");
}
cancel(): void {
this.fail("Session cancelled");
}
private async applyInput(message: ReturnType<typeof parseRemoteLoginInput>): Promise<void> {
if (!message) return;
const page = this.page?.page;
if (!page) return;
if ((await applyRemoteLoginInput(page, message)) === "cancelled") this.cancel();
}
private enqueueInput(message: RemoteLoginInput): void {
if (message.type === "cancel") {
this.inputQueue.length = 0;
this.inputQueue.unshift(message);
} else if (message.type === "pointer" && message.event === "move") {
const last = this.inputQueue.at(-1);
if (last?.type === "pointer" && last.event === "move") {
this.inputQueue[this.inputQueue.length - 1] = message;
} else if (this.inputQueue.length < MAX_INPUT_QUEUE) {
this.inputQueue.push(message);
}
} else {
if (this.inputQueue.length >= MAX_INPUT_QUEUE) {
const moveIndex = this.inputQueue.findIndex(
(input) => input.type === "pointer" && input.event === "move",
);
if (moveIndex >= 0) this.inputQueue.splice(moveIndex, 1);
}
if (this.inputQueue.length < MAX_INPUT_QUEUE) this.inputQueue.push(message);
}
void this.drainInputQueue();
}
private async drainInputQueue(): Promise<void> {
if (this.inputDrainRunning) return;
this.inputDrainRunning = true;
try {
while (!this.closed) {
const message = this.inputQueue.shift();
if (!message) return;
await this.applyInput(message);
}
} finally {
this.inputDrainRunning = false;
if (!this.closed && this.inputQueue.length > 0) void this.drainInputQueue();
}
}
private scheduleLoginCheck(): void {
if (this.closed || this.captureStarted) return;
this.loginTimer = setTimeout(() => void this.checkLogin(), 1000);
Expand Down Expand Up @@ -164,6 +122,7 @@ export class RemoteLoginSession {
setTimeout(() => this.finish(1000, "Connected"), 50);
}
private scheduleFrames(): void {
if (this.phase === "opening") return;
if (this.closed || !this.connection || !this.page || this.frameTimer) return;
this.frameTimer = setTimeout(() => void this.sendFrame(), this.config.frameIntervalMs);
}
Expand Down Expand Up @@ -196,7 +155,7 @@ export class RemoteLoginSession {
private finish(code: number, reason: string): void {
if (this.closed) return;
this.closed = true;
this.inputQueue.length = 0;
this.inputQueue.close();
clearTimeout(this.expiryTimer);
if (this.frameTimer) clearTimeout(this.frameTimer);
if (this.loginTimer) clearTimeout(this.loginTimer);
Expand All @@ -205,5 +164,3 @@ export class RemoteLoginSession {
this.onDone(this.sessionId, this.userId);
}
}

const MAX_INPUT_QUEUE = 128;
Loading