const MINUTE = 60 * 1000; export const BREAKPOINT_DEFAULT_LEASE_MS = 20 * MINUTE; // 20 minutes export const BREAKPOINT_RENEW_INCREMENT_MS = 10 * MINUTE; // 10 minutes export const BREAKPOINT_MAX_REMAINING_MS = 20 * MINUTE; // 20 minutes export type PhaseBreakpointId = | 'afterInstall' | 'afterPack' | 'afterSourcePrep' | 'afterUpload' | 'beforeFinalize' | 'beforeInstall' | 'beforePack' | 'beforeUpload'; export type HookBreakpointName = | 'todesktop:afterPack' | 'todesktop:beforeBuild' | 'todesktop:beforeInstall'; export type HookBreakpointPosition = 'after' | 'before'; interface PhaseBreakpointConfig { id: PhaseBreakpointId; type: 'phase'; } interface HookBreakpointConfig { id: HookBreakpointName; position: HookBreakpointPosition; type: 'hook'; } export type BreakpointConfig = HookBreakpointConfig | PhaseBreakpointConfig; export type BreakpointQueueEntry = { autoResumed?: boolean; hitAt?: string; skipped?: boolean; } & BreakpointConfig; export interface BreakpointRenewal { renewedAt: string; renewedByUserId: string; } export type CurrentBreakpointState = { createdAt: string; expiresAt: string; renewals?: BreakpointRenewal[]; resumedAt?: string; resumedByUserId?: string; skipped?: boolean; wasAutoResumed?: boolean; } & BreakpointConfig; export interface BreakpointPauseLease { createdAt: string; createdByUserId: string; defaultDurationMs: number; } export function isSameBreakpoint( a: | BreakpointConfig | BreakpointQueueEntry | CurrentBreakpointState | undefined, b: | BreakpointConfig | BreakpointQueueEntry | CurrentBreakpointState | undefined, ): boolean { if (!a || !b) { return false; } if (a.type !== b.type) { return false; } if (a.id !== b.id) { return false; } if (isHookBreakpointConfig(a) && isHookBreakpointConfig(b)) { return a.position === b.position; } return true; } function isHookBreakpointConfig( config: BreakpointConfig | BreakpointQueueEntry | CurrentBreakpointState, ): config is HookBreakpointConfig { return config.type === 'hook'; } export function cloneBreakpointQueue( queue?: null | ReadonlyArray, ): BreakpointQueueEntry[] { return Array.isArray(queue) ? queue.map((entry) => ({ ...entry })) : []; } export function markQueueSkipped( queue: ReadonlyArray, startIndex: number, options: { fallbackHitAt?: string } = {}, ): BreakpointQueueEntry[] { const updated = cloneBreakpointQueue(queue); const { fallbackHitAt } = options; if (startIndex < 0 || startIndex >= updated.length) { return updated; } const current = updated[startIndex]; const hitAt = current.hitAt ?? fallbackHitAt; updated[startIndex] = { ...current, skipped: true, ...(hitAt ? { hitAt } : {}), }; for (let i = startIndex + 1; i < updated.length; i += 1) { if (!updated[i].hitAt) { updated[i] = { ...updated[i], skipped: true, }; } } return updated; }