blob: 51b1880233e75c0cc5820fb62c901489a8a925de [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export const DESKTOP_TRANSCRIPT_FRAGMENT_MAX_BYTES = 128 * 1024;
export const DESKTOP_TRANSCRIPT_RANGE_MAX_BYTES = 512 * 1024;
export const DESKTOP_TRANSCRIPT_SESSION_CACHE_MAX_BYTES = 20 * 1024 * 1024;
export const DESKTOP_TRANSCRIPT_OVERLAY_CACHE_MAX_BYTES = 16 * 1024 * 1024;
export const DESKTOP_TRANSCRIPT_GLOBAL_CACHE_MAX_BYTES = 64 * 1024 * 1024;
export const DESKTOP_TRANSCRIPT_MESSAGE_MAX_BYTES = 16 * 1024 * 1024;
export interface DesktopTranscriptFragment {
readonly source: 'durable' | 'overlay';
readonly identity: number | string;
readonly order: number | null;
readonly byteOffset: number;
readonly totalBytes: number;
readonly data: Uint8Array;
}
export interface DesktopTranscriptBatchPayload {
readonly sessionId: string;
readonly generation: string;
readonly hostEpoch: string;
readonly durableThrough: number | null;
readonly fragments: readonly DesktopTranscriptFragment[];
readonly evictedDurableSequences: readonly number[];
readonly completedOverlayMessageIds: readonly string[];
readonly hasOlder: boolean;
readonly hasNewer: boolean;
readonly reset: boolean;
readonly ready: boolean;
}
export interface DesktopTranscriptBatch extends DesktopTranscriptBatchPayload {
readonly deliverySequence: number;
}
export interface DesktopTranscriptOpenResult {
readonly sessionId: string;
readonly generation: string;
readonly hostEpoch: string;
readonly readThroughMessageId: string | null;
}
export interface DesktopTranscriptRangeRequest {
readonly consumerId: string;
readonly sessionId: string;
readonly hostEpoch: string;
readonly anchorSequence: number | null;
readonly maxBytes: number;
}
export interface DesktopTranscriptHandle extends DesktopTranscriptOpenResult {
loadBefore(anchorSequence: number | null, maxBytes?: number): Promise<void>;
loadAround(sequence: number, maxBytes?: number): Promise<void>;
close(): Promise<void>;
}
export function assertDesktopTranscriptBatch(value: unknown): DesktopTranscriptBatch {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error('Invalid Desktop transcript batch');
}
const batch = value as Record<string, unknown>;
if (
typeof batch.sessionId !== 'string' ||
!isSequence(batch.deliverySequence) ||
typeof batch.generation !== 'string' ||
typeof batch.hostEpoch !== 'string' ||
(batch.durableThrough !== null && !isSequence(batch.durableThrough)) ||
!Array.isArray(batch.fragments) ||
!Array.isArray(batch.evictedDurableSequences) ||
!batch.evictedDurableSequences.every(isSequence) ||
batch.evictedDurableSequences.length > 256 ||
!Array.isArray(batch.completedOverlayMessageIds) ||
!batch.completedOverlayMessageIds.every(
(messageId) => typeof messageId === 'string' && messageId.length > 0 && messageId.length <= 256,
) ||
batch.completedOverlayMessageIds.length > 256 ||
typeof batch.hasOlder !== 'boolean' ||
typeof batch.hasNewer !== 'boolean' ||
typeof batch.reset !== 'boolean' ||
typeof batch.ready !== 'boolean'
) {
throw new Error('Invalid Desktop transcript batch');
}
let rawBytes = 0;
for (const value of batch.fragments) {
const fragment = value as Record<string, unknown>;
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
(fragment.source !== 'durable' && fragment.source !== 'overlay') ||
(fragment.source === 'durable'
? !isSequence(fragment.identity)
: typeof fragment.identity !== 'string' || fragment.identity.length === 0) ||
(fragment.source === 'overlay'
? !isSequence(fragment.order)
: fragment.order !== null) ||
!isSequence(fragment.byteOffset) ||
!isSequence(fragment.totalBytes) ||
(fragment.totalBytes as number) < 1 ||
!(fragment.data instanceof Uint8Array) ||
fragment.data.byteLength > DESKTOP_TRANSCRIPT_FRAGMENT_MAX_BYTES
) {
throw new Error('Invalid Desktop transcript fragment');
}
const bytes = fragment.data.byteLength;
if (
bytes < 1 ||
(fragment.byteOffset as number) + bytes > (fragment.totalBytes as number)
) {
throw new Error('Invalid Desktop transcript fragment bounds');
}
rawBytes += bytes;
}
if (rawBytes > DESKTOP_TRANSCRIPT_FRAGMENT_MAX_BYTES) {
throw new Error('Desktop transcript batch exceeds its byte limit');
}
return value as DesktopTranscriptBatch;
}
function isSequence(value: unknown): value is number {
return Number.isSafeInteger(value) && (value as number) >= 0;
}