import { Buffer } from 'node:buffer'; import { createHash, randomUUID } from 'node:crypto'; import { WORKS_SQUARE_CONFIG } from '../api/works-config'; import { getValidWorksSquareAccessToken, getWorksSquareAccountBinding, subscribeWorksSquareSession, } from '../services/works-square-session'; import { proxyAwareFetch, runWithDeadline } from '../utils/proxy-fetch'; import { AccountPluginCache, type AccountBinding, type MarketplaceChannel, type MarketplaceLibraryEntry, type MarketplaceLibrarySnapshot, type MarketplaceResolveItem, type MarketplaceResolveSnapshot, } from './account-plugin-cache'; import { isValidSemVer } from './release-descriptor'; export type { AccountBinding, MarketplaceChannel, MarketplaceLibraryEntry, MarketplaceLibrarySnapshot, MarketplaceResolveAction, MarketplaceResolveItem, MarketplaceResolveSnapshot, } from './account-plugin-cache'; const DEFAULT_REQUEST_TIMEOUT_MS = 10_000; const DEFAULT_RESPONSE_BYTES = 2 * 1024 * 1024; const DEFAULT_REQUEST_BYTES = 512 * 1024; const DEFAULT_ARTIFACT_BYTES = 16 * 1024 * 1024; const MAX_PLUGIN_ID = 128; const MAX_RELEASE_ID = 128; const MAX_TEXT = 32_000; const MAX_CURSOR = 512; const MAX_TAGS = 64; const MAX_OPERATIONS = 256; const MAX_PERMISSIONS = 128; const MAX_RESOLVE_ITEMS = 256; const PLUGIN_ID_PATTERN = /^[a-z][a-z0-9.-]{0,127}$/u; const SHA256_PATTERN = /^[a-f0-9]{64}$/u; const RELEASE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u; const REQUEST_ID_PATTERN = /^[\x21-\x7e]{1,128}$/u; const ETAG_PATTERN = /^(?:W\/)?"plugins-(\d+)-tp-([A-Za-z0-9._-]{1,128})"$/u; type UnknownRecord = Record; type FetchImplementation = (input: string | URL, init?: RequestInit) => Promise; export interface CatalogQuery { readonly query?: string; readonly category?: string; readonly featured?: boolean; readonly limit?: number; readonly cursor?: string; } export interface MarketplaceBilling { readonly mode: 'included' | 'platform_metered'; readonly status: 'billing_unavailable' | null; readonly notice: string; readonly entitlementScope: string | null; readonly unitName: string | null; readonly unitSize: number | null; readonly ratePoints: string | null; readonly minimumChargePoints: string | null; readonly roundingMode: 'ceil' | null; readonly pricingVersion: string | null; } export interface MarketplaceOperation { readonly capabilityId: string; readonly operation: string; readonly executionMode: 'synchronous' | 'job'; readonly billing: MarketplaceBilling; readonly enabled: boolean; } export interface MarketplaceRelease { readonly releaseId: string; readonly pluginId: string; readonly version: string; readonly packageSchemaVersion: number; readonly contractVersion: number; readonly minMakeloreVersion: string; readonly maxMakeloreVersion: string | null; readonly deliveryKind: 'bundled' | 'artifact'; readonly artifactSha256: string | null; readonly artifactSizeBytes: number | null; readonly signingKeyId: string | null; readonly descriptorSignature: string | null; readonly publishedAt: string | null; } export interface MarketplaceCatalogItem { readonly pluginId: string; readonly title: string; readonly summary: string; readonly category: string; readonly tags: readonly string[]; readonly providerDisplayName: string; readonly runtimeKind: 'skill_only' | 'bundled_typed' | 'platform_hosted'; readonly runtimeStatus: 'enabled' | 'suspended'; readonly acquisition: 'free' | 'system_included'; readonly usageBilling: 'included' | 'token_point' | 'mixed'; readonly includedOperationCount: number; readonly meteredOperationCount: number; readonly stableVersion: string | null; readonly betaVersion: string | null; } export interface CatalogPage { readonly items: readonly MarketplaceCatalogItem[]; readonly nextCursor: string | null; readonly total: number; readonly catalogGeneration: number; readonly etag: string; readonly pricingVersionId: string | null; readonly stale: boolean; readonly fetchedAt: number; } export interface PluginDetail extends MarketplaceCatalogItem { readonly descriptionMarkdown: string; readonly permissions: readonly string[]; readonly operations: readonly MarketplaceOperation[]; readonly stableRelease: MarketplaceRelease | null; readonly betaRelease: MarketplaceRelease | null; readonly etag: string; readonly pricingVersionId: string | null; readonly stale: boolean; readonly fetchedAt: number; } export interface InstalledReleaseInput { readonly pluginId: string; readonly releaseId: string; readonly sha256: string; } export interface ResolveRequest { /** Persisted by a caller when a long-lived sync operation is resumed. */ readonly resolveRequestId?: string; /** Alias accepted for callers that use the server terminology directly. */ readonly requestId?: string; readonly resolveRequestDigest?: string; readonly makeloreVersion: string; readonly channel?: MarketplaceChannel; readonly installed: readonly InstalledReleaseInput[]; } export interface DownloadRequest { readonly releaseId: string; readonly releaseAdmissionId: string; } export interface DownloadGrant { readonly releaseAdmissionId: string; readonly releaseId: string; readonly pluginId: string; readonly version: string; readonly packageSchemaVersion: number; readonly contractVersion: number; readonly minMakeloreVersion: string; readonly maxMakeloreVersion: string | null; readonly sizeBytes: number; readonly sha256: string; readonly signingKeyId: string; readonly descriptorSignature: string; readonly expiresAt: string; readonly contentUrl: string; } export interface MarketplaceSessionPort { getAccessToken(options?: { readonly forceRefresh?: boolean }): Promise; getAccountBinding(): AccountBinding | null; subscribe?(listener: () => void): () => void; } export interface MarketplaceClientOptions { readonly fetchImpl?: FetchImplementation; readonly apiBaseUrl?: string; readonly requestTimeoutMs?: number; readonly maxResponseBytes?: number; readonly maxRequestBytes?: number; readonly maxArtifactBytes?: number; readonly now?: () => number; readonly session?: MarketplaceSessionPort; readonly getAccessToken?: MarketplaceSessionPort['getAccessToken']; readonly getAccountBinding?: MarketplaceSessionPort['getAccountBinding']; readonly subscribeSession?: (listener: () => void) => () => void; readonly accountCache?: AccountPluginCache; } export type MarketplaceErrorCode = | 'marketplace_auth_required' | 'marketplace_account_changed' | 'marketplace_request_invalid' | 'marketplace_request_failed' | 'marketplace_response_invalid' | 'marketplace_response_too_large' | 'marketplace_download_invalid' | 'marketplace_beta_selection_required' | 'plugin_auth_required' | 'plugin_account_changed' | 'plugin_library_required' | 'plugin_release_not_ready' | 'plugin_release_yanked' | 'plugin_incompatible_client' | 'plugin_signature_invalid' | 'plugin_artifact_invalid' | 'plugin_runtime_suspended' | 'plugin_backend_unavailable'; export class MarketplaceClientError extends Error { constructor( readonly code: MarketplaceErrorCode, readonly status = 0, message: string = code, ) { super(message); this.name = 'MarketplaceClientError'; } } interface CacheMetadata { readonly etag: string; readonly generation: number; readonly pricingVersionId: string | null; } interface RequestResult { readonly status: number; readonly headers: Headers; readonly value: T | null; readonly notModified: boolean; } function isRecord(value: unknown): value is UnknownRecord { return value !== null && typeof value === 'object' && !Array.isArray(value); } function fail(code: MarketplaceErrorCode, message: string = code, status = 0): never { throw new MarketplaceClientError(code, status, message); } const SERVER_ERROR_CODES = new Set([ 'plugin_auth_required', 'plugin_account_changed', 'plugin_library_required', 'plugin_release_not_ready', 'plugin_release_yanked', 'plugin_incompatible_client', 'plugin_signature_invalid', 'plugin_artifact_invalid', 'plugin_runtime_suspended', 'plugin_backend_unavailable', ]); function exactKeys( value: UnknownRecord, required: readonly string[], optional: readonly string[] = [], ): void { const allowed = new Set([...required, ...optional]); for (const key of Object.keys(value)) { if (!allowed.has(key)) fail('marketplace_response_invalid', `unknown field: ${key.slice(0, 128)}`); } for (const key of required) { if (!(key in value)) fail('marketplace_response_invalid', `missing field: ${key}`); } } function stringValue(value: unknown, field: string, max = MAX_TEXT): string { if (typeof value !== 'string' || value.length === 0 || value.length > max) { fail('marketplace_response_invalid', `invalid ${field}`); } return value; } function nullableString(value: unknown, field: string, max = MAX_TEXT): string | null { return value === null ? null : stringValue(value, field, max); } function integerValue(value: unknown, field: string, minimum = 0, maximum = Number.MAX_SAFE_INTEGER): number { if (!Number.isSafeInteger(value) || (value as number) < minimum || (value as number) > maximum) { fail('marketplace_response_invalid', `invalid ${field}`); } return value as number; } function booleanValue(value: unknown, field: string): boolean { if (typeof value !== 'boolean') fail('marketplace_response_invalid', `invalid ${field}`); return value; } function idValue(value: unknown, field: string): string { const result = stringValue(value, field, MAX_PLUGIN_ID); if (!PLUGIN_ID_PATTERN.test(result)) fail('marketplace_response_invalid', `invalid ${field}`); return result; } function releaseIdValue(value: unknown, field: string, max = MAX_RELEASE_ID): string { const result = stringValue(value, field, max); if (!RELEASE_ID_PATTERN.test(result)) fail('marketplace_response_invalid', `invalid ${field}`); return result; } function shaValue(value: unknown, field: string): string { const result = stringValue(value, field, 64); if (!SHA256_PATTERN.test(result)) fail('marketplace_response_invalid', `invalid ${field}`); return result; } function dateValue(value: unknown, field: string): string | null { if (value === null) return null; const result = stringValue(value, field, 80); if (!Number.isFinite(Date.parse(result))) fail('marketplace_response_invalid', `invalid ${field}`); return result; } function semverValue(value: unknown, field: string): string { const result = stringValue(value, field, 64); if (!isValidSemVer(result)) fail('marketplace_response_invalid', `invalid ${field}`); return result; } function clone(value: T): T { return structuredClone(value); } function normalizedBinding(value: AccountBinding | null): AccountBinding | null { if (!value || typeof value.accountKey !== 'string' || value.accountKey.length === 0 || value.accountKey.length > 512 || !Number.isSafeInteger(value.epoch) || value.epoch < 0) { return null; } return { accountKey: value.accountKey, epoch: value.epoch }; } function parseBilling(value: unknown): MarketplaceBilling { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid billing'); exactKeys(record, ['mode', 'status', 'notice', 'entitlement_scope', 'unit_name', 'unit_size', 'rate_points', 'minimum_charge_points', 'rounding_mode', 'pricing_version']); if (record.mode !== 'included' && record.mode !== 'platform_metered') fail('marketplace_response_invalid', 'invalid billing mode'); if (record.status !== null && record.status !== 'billing_unavailable') fail('marketplace_response_invalid', 'invalid billing status'); if (record.rounding_mode !== null && record.rounding_mode !== 'ceil') fail('marketplace_response_invalid', 'invalid billing rounding'); const unitSize = record.unit_size === null ? null : integerValue(record.unit_size, 'unit_size', 1); return { mode: record.mode, status: record.status, notice: stringValue(record.notice, 'notice', 160), entitlementScope: nullableString(record.entitlement_scope, 'entitlement_scope', 64), unitName: nullableString(record.unit_name, 'unit_name', 80), unitSize, ratePoints: nullableString(record.rate_points, 'rate_points', 32), minimumChargePoints: nullableString(record.minimum_charge_points, 'minimum_charge_points', 32), roundingMode: record.rounding_mode, pricingVersion: nullableString(record.pricing_version, 'pricing_version', 36), }; } function parseOperation(value: unknown): MarketplaceOperation { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid operation'); exactKeys(record, ['capability_id', 'operation', 'execution_mode', 'billing', 'enabled']); if (record.execution_mode !== 'synchronous' && record.execution_mode !== 'job') fail('marketplace_response_invalid', 'invalid execution mode'); return { capabilityId: stringValue(record.capability_id, 'capability_id', 128), operation: stringValue(record.operation, 'operation', 128), executionMode: record.execution_mode, billing: parseBilling(record.billing), enabled: booleanValue(record.enabled, 'enabled'), }; } function parseRelease(value: unknown): MarketplaceRelease { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid release'); exactKeys(record, [ 'release_id', 'plugin_id', 'version', 'package_schema_version', 'contract_version', 'min_makelore_version', 'max_makelore_version', 'delivery_kind', 'artifact_sha256', 'artifact_size_bytes', 'signing_key_id', 'descriptor_signature', 'published_at', ]); if (record.delivery_kind !== 'bundled' && record.delivery_kind !== 'artifact') fail('marketplace_response_invalid', 'invalid delivery kind'); const artifactSha256 = record.artifact_sha256 === null ? null : shaValue(record.artifact_sha256, 'artifact_sha256'); const artifactSizeBytes = record.artifact_size_bytes === null ? null : integerValue(record.artifact_size_bytes, 'artifact_size_bytes', 1, DEFAULT_ARTIFACT_BYTES); if (record.delivery_kind === 'artifact' && (artifactSha256 === null || artifactSizeBytes === null)) fail('marketplace_response_invalid', 'artifact release is missing artifact metadata'); return { releaseId: releaseIdValue(record.release_id, 'release_id'), pluginId: idValue(record.plugin_id, 'plugin_id'), version: semverValue(record.version, 'version'), packageSchemaVersion: integerValue(record.package_schema_version, 'package_schema_version', 1), contractVersion: integerValue(record.contract_version, 'contract_version', 1), minMakeloreVersion: semverValue(record.min_makelore_version, 'min_makelore_version'), maxMakeloreVersion: record.max_makelore_version === null ? null : semverValue(record.max_makelore_version, 'max_makelore_version'), deliveryKind: record.delivery_kind, artifactSha256, artifactSizeBytes, signingKeyId: nullableString(record.signing_key_id, 'signing_key_id', 128), descriptorSignature: nullableString(record.descriptor_signature, 'descriptor_signature', 256), publishedAt: dateValue(record.published_at, 'published_at'), }; } function parseCatalogItem(value: unknown, validateKeys = true): MarketplaceCatalogItem { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid catalog item'); if (validateKeys) { exactKeys(record, [ 'plugin_id', 'title', 'summary', 'category', 'tags', 'provider_display_name', 'runtime_kind', 'runtime_status', 'acquisition', 'usage_billing', 'included_operation_count', 'metered_operation_count', 'stable_version', 'beta_version', ]); } if (!Array.isArray(record.tags) || record.tags.length > MAX_TAGS) fail('marketplace_response_invalid', 'invalid tags'); if (record.runtime_kind !== 'skill_only' && record.runtime_kind !== 'bundled_typed' && record.runtime_kind !== 'platform_hosted') fail('marketplace_response_invalid', 'invalid runtime kind'); if (record.runtime_status !== 'enabled' && record.runtime_status !== 'suspended') fail('marketplace_response_invalid', 'invalid runtime status'); if (record.acquisition !== 'free' && record.acquisition !== 'system_included') fail('marketplace_response_invalid', 'invalid acquisition'); if (record.usage_billing !== 'included' && record.usage_billing !== 'token_point' && record.usage_billing !== 'mixed') fail('marketplace_response_invalid', 'invalid usage billing'); return { pluginId: idValue(record.plugin_id, 'plugin_id'), title: stringValue(record.title, 'title', 255), summary: stringValue(record.summary, 'summary', 4_000), category: stringValue(record.category, 'category', 128), tags: record.tags.map((tag, index) => stringValue(tag, `tags[${index}]`, 128)), providerDisplayName: stringValue(record.provider_display_name, 'provider_display_name', 128), runtimeKind: record.runtime_kind, runtimeStatus: record.runtime_status, acquisition: record.acquisition, usageBilling: record.usage_billing, includedOperationCount: integerValue(record.included_operation_count, 'included_operation_count'), meteredOperationCount: integerValue(record.metered_operation_count, 'metered_operation_count'), stableVersion: record.stable_version === null ? null : semverValue(record.stable_version, 'stable_version'), betaVersion: record.beta_version === null ? null : semverValue(record.beta_version, 'beta_version'), }; } function parseCatalogPage(value: unknown): Omit { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid catalog page'); exactKeys(record, ['items', 'next_cursor', 'total', 'catalog_generation']); if (!Array.isArray(record.items) || record.items.length > 100) fail('marketplace_response_invalid', 'invalid catalog items'); return { items: record.items.map(parseCatalogItem), nextCursor: nullableString(record.next_cursor, 'next_cursor', MAX_CURSOR), total: integerValue(record.total, 'total'), catalogGeneration: integerValue(record.catalog_generation, 'catalog_generation', 1), }; } function parsePluginDetail(value: unknown): Omit { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid plugin detail'); exactKeys(record, [ 'plugin_id', 'title', 'summary', 'category', 'tags', 'provider_display_name', 'runtime_kind', 'runtime_status', 'acquisition', 'usage_billing', 'included_operation_count', 'metered_operation_count', 'stable_version', 'beta_version', 'description_markdown', 'permissions', 'operations', 'stable_release', 'beta_release', ]); const base = parseCatalogItem(record, false); if (!Array.isArray(record.permissions) || record.permissions.length > MAX_PERMISSIONS) fail('marketplace_response_invalid', 'invalid permissions'); if (!Array.isArray(record.operations) || record.operations.length > MAX_OPERATIONS) fail('marketplace_response_invalid', 'invalid operations'); return { ...base, descriptionMarkdown: stringValue(record.description_markdown, 'description_markdown', 32_000), permissions: record.permissions.map((permission, index) => stringValue(permission, `permissions[${index}]`, 128)), operations: record.operations.map(parseOperation), stableRelease: record.stable_release === null ? null : parseRelease(record.stable_release), betaRelease: record.beta_release === null ? null : parseRelease(record.beta_release), }; } function parseLibraryEntry(value: unknown): MarketplaceLibraryEntry { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid Library entry'); exactKeys(record, [ 'plugin_id', 'title', 'summary', 'category', 'acquisition', 'acquisition_mode', 'catalog_status', 'runtime_status', 'acquired_at', 'removed_at', 'stable_version', 'beta_version', ]); if (record.acquisition !== 'free' && record.acquisition !== 'system_included') fail('marketplace_response_invalid', 'invalid Library acquisition'); if (record.acquisition_mode !== 'system_included' && record.acquisition_mode !== 'user_acquired') fail('marketplace_response_invalid', 'invalid Library acquisition mode'); if (record.catalog_status !== 'active' && record.catalog_status !== 'retired') fail('marketplace_response_invalid', 'invalid catalog status'); if (record.runtime_status !== 'enabled' && record.runtime_status !== 'suspended') fail('marketplace_response_invalid', 'invalid Library runtime status'); return { pluginId: idValue(record.plugin_id, 'plugin_id'), title: stringValue(record.title, 'title', 255), summary: stringValue(record.summary, 'summary', 4_000), category: stringValue(record.category, 'category', 128), acquisition: record.acquisition, acquisitionMode: record.acquisition_mode, catalogStatus: record.catalog_status, runtimeStatus: record.runtime_status, acquiredAt: dateValue(record.acquired_at, 'acquired_at'), removedAt: dateValue(record.removed_at, 'removed_at'), stableVersion: record.stable_version === null ? null : semverValue(record.stable_version, 'stable_version'), betaVersion: record.beta_version === null ? null : semverValue(record.beta_version, 'beta_version'), }; } function parseLibrary(value: unknown): Omit { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid Library snapshot'); exactKeys(record, ['items', 'total']); if (!Array.isArray(record.items) || record.items.length > 256) fail('marketplace_response_invalid', 'invalid Library items'); return { items: record.items.map(parseLibraryEntry), total: integerValue(record.total, 'total') }; } function parseResolveItem(value: unknown): MarketplaceResolveItem { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid resolve item'); exactKeys(record, ['plugin_id', 'action', 'release_id', 'version', 'sha256', 'size_bytes', 'release_admission_id', 'expires_at', 'channel', 'reason']); if (record.action !== 'keep' && record.action !== 'install' && record.action !== 'update' && record.action !== 'unavailable') fail('marketplace_response_invalid', 'invalid resolve action'); if (record.channel !== null && record.channel !== 'stable' && record.channel !== 'beta') fail('marketplace_response_invalid', 'invalid resolve channel'); return { pluginId: idValue(record.plugin_id, 'plugin_id'), action: record.action, releaseId: record.release_id === null ? null : releaseIdValue(record.release_id, 'release_id'), version: record.version === null ? null : semverValue(record.version, 'version'), sha256: record.sha256 === null ? null : shaValue(record.sha256, 'sha256'), sizeBytes: record.size_bytes === null ? null : integerValue(record.size_bytes, 'size_bytes', 1, DEFAULT_ARTIFACT_BYTES), releaseAdmissionId: record.release_admission_id === null ? null : releaseIdValue(record.release_admission_id, 'release_admission_id'), expiresAt: dateValue(record.expires_at, 'expires_at'), channel: record.channel, reason: nullableString(record.reason, 'reason', 160), }; } function parseResolve(value: unknown): Omit { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid resolve snapshot'); exactKeys(record, ['resolve_request_id', 'resolve_request_digest', 'items', 'catalog_generation']); if (!REQUEST_ID_PATTERN.test(stringValue(record.resolve_request_id, 'resolve_request_id', 128))) fail('marketplace_response_invalid', 'invalid resolve request ID'); if (!Array.isArray(record.items) || record.items.length > MAX_RESOLVE_ITEMS) fail('marketplace_response_invalid', 'invalid resolve items'); const requestId = stringValue(record.resolve_request_id, 'resolve_request_id', 128); if (!REQUEST_ID_PATTERN.test(requestId)) fail('marketplace_response_invalid', 'invalid resolve request ID'); return { resolveRequestId: requestId, resolveRequestDigest: shaValue(record.resolve_request_digest, 'resolve_request_digest'), items: record.items.map(parseResolveItem), catalogGeneration: integerValue(record.catalog_generation, 'catalog_generation', 1), }; } function parseDownloadGrant(value: unknown): DownloadGrant { const record = isRecord(value) ? value : fail('marketplace_response_invalid', 'invalid download grant'); exactKeys(record, [ 'release_admission_id', 'release_id', 'plugin_id', 'version', 'package_schema_version', 'contract_version', 'min_makelore_version', 'max_makelore_version', 'size_bytes', 'sha256', 'signing_key_id', 'descriptor_signature', 'expires_at', 'content_url', ]); return { releaseAdmissionId: releaseIdValue(record.release_admission_id, 'release_admission_id'), releaseId: releaseIdValue(record.release_id, 'release_id'), pluginId: idValue(record.plugin_id, 'plugin_id'), version: semverValue(record.version, 'version'), packageSchemaVersion: integerValue(record.package_schema_version, 'package_schema_version', 1), contractVersion: integerValue(record.contract_version, 'contract_version', 1), minMakeloreVersion: semverValue(record.min_makelore_version, 'min_makelore_version'), maxMakeloreVersion: record.max_makelore_version === null ? null : semverValue(record.max_makelore_version, 'max_makelore_version'), sizeBytes: integerValue(record.size_bytes, 'size_bytes', 1, DEFAULT_ARTIFACT_BYTES), sha256: shaValue(record.sha256, 'sha256'), signingKeyId: stringValue(record.signing_key_id, 'signing_key_id', 128), descriptorSignature: stringValue(record.descriptor_signature, 'descriptor_signature', 256), expiresAt: dateValue(record.expires_at, 'expires_at') ?? fail('marketplace_response_invalid', 'grant expiry is required'), contentUrl: stringValue(record.content_url, 'content_url', 512), }; } function parseCacheMetadata(headers: Headers, fallbackGeneration: number): CacheMetadata { const rawEtag = headers.get('etag'); const generationHeader = headers.get('x-plugin-catalog-generation'); const pricingHeader = headers.get('x-token-point-pricing-version'); const generation = generationHeader === null ? fallbackGeneration : integerValue(Number(generationHeader), 'X-Plugin-Catalog-Generation', 1); const pricingVersionId = pricingHeader === null || pricingHeader === 'none' ? null : stringValue(pricingHeader, 'X-Token-Point-Pricing-Version', 128); const etag = rawEtag ?? `"plugins-${generation}-tp-${pricingVersionId ?? 'none'}"`; const match = ETAG_PATTERN.exec(etag); if (!match || Number(match[1]) !== generation || (pricingVersionId === null ? match[2] !== 'none' : match[2] !== pricingVersionId)) { fail('marketplace_response_invalid', 'invalid composite Marketplace ETag'); } return { etag, generation, pricingVersionId }; } async function readBoundedBytes(response: Response, maximum: number): Promise { const declared = response.headers.get('content-length'); if (declared !== null && /^\d+$/u.test(declared) && Number(declared) > maximum) { await response.body?.cancel().catch(() => undefined); fail('marketplace_response_too_large', 'Marketplace response exceeds its bound'); } if (!response.body) { const bytes = new Uint8Array(await response.arrayBuffer()); if (bytes.byteLength > maximum) fail('marketplace_response_too_large', 'Marketplace response exceeds its bound'); return bytes; } const reader = response.body.getReader(); const chunks: Uint8Array[] = []; let size = 0; try { while (true) { const { done, value } = await reader.read(); if (done) break; if (!value) continue; size += value.byteLength; if (size > maximum) { await reader.cancel().catch(() => undefined); fail('marketplace_response_too_large', 'Marketplace response exceeds its bound'); } chunks.push(value); } } finally { reader.releaseLock(); } return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))); } function parseJsonBytes(bytes: Uint8Array): unknown { let source: string; try { source = new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { fail('marketplace_response_invalid', 'Marketplace response is not UTF-8'); } if (!source.trim()) fail('marketplace_response_invalid', 'Marketplace response is empty'); try { return JSON.parse(source) as unknown; } catch { fail('marketplace_response_invalid', 'Marketplace response is not JSON'); } } function failForResponse( response: Response, bytes: Uint8Array | null, fallbackMessage: string, ): never { let code: MarketplaceErrorCode | null = null; let responseMessage = fallbackMessage; if (bytes && bytes.byteLength > 0) { try { const payload = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)) as unknown; if (isRecord(payload)) { if (typeof payload.code === 'string' && SERVER_ERROR_CODES.has(payload.code as MarketplaceErrorCode)) { code = payload.code as MarketplaceErrorCode; } if (typeof payload.error === 'string' && payload.error.length > 0 && payload.error.length <= 256) { responseMessage = payload.error; } } } catch { // A malformed error body must stay a bounded generic client failure. } } fail(code ?? 'marketplace_request_failed', responseMessage, response.status); } async function fetchResponseWithBodyDeadline( fetchImpl: FetchImplementation, input: string | URL, init: RequestInit, timeoutMs: number, maximum: number, ): Promise<{ response: Response; bytes: Uint8Array | null }> { return runWithDeadline(async (signal) => { const response = await fetchImpl(input, { ...init, signal }); if (response.status >= 300 && response.status < 400) { await response.body?.cancel().catch(() => undefined); return { response, bytes: null }; } return { response, bytes: await readBoundedBytes(response, maximum) }; }, timeoutMs, init.signal); } function normalizedBase(value: string): string { try { const url = new URL(value); if (url.protocol !== 'http:' && url.protocol !== 'https:') throw new Error('unsupported protocol'); return url.toString().replace(/\/+$/u, ''); } catch { throw new TypeError('Marketplace API base URL is invalid'); } } function queryValue(value: string | undefined, field: string, max: number): string | undefined { if (value === undefined) return undefined; if (value.length > max) fail('marketplace_request_invalid', `invalid ${field}`); return value; } function validateRequestId(value: string, field = 'resolveRequestId'): string { if (!REQUEST_ID_PATTERN.test(value)) fail('marketplace_request_invalid', `invalid ${field}`); return value; } function compareStableText(left: string, right: string): number { if (left === right) return 0; return left < right ? -1 : 1; } function canonicalResolveInput( input: Pick, ): string { const installed = [...input.installed].map((item) => ({ pluginId: item.pluginId, releaseId: item.releaseId, sha256: item.sha256, })).sort((left, right) => compareStableText(left.pluginId, right.pluginId) || compareStableText(left.releaseId, right.releaseId) || compareStableText(left.sha256, right.sha256)); return JSON.stringify({ makeloreVersion: input.makeloreVersion, channel: input.channel ?? 'stable', installed, }); } function prepareResolveInput(input: ResolveRequest): { requestId: string; digest: string; channel: MarketplaceChannel; payload: Record; cacheKey: string; } { if (!isValidSemVer(input.makeloreVersion)) fail('marketplace_request_invalid', 'makeloreVersion must be SemVer'); const channel = input.channel ?? 'stable'; if (channel !== 'stable' && channel !== 'beta') fail('marketplace_request_invalid', 'invalid channel'); if (!Array.isArray(input.installed) || input.installed.length > MAX_RESOLVE_ITEMS) fail('marketplace_request_invalid', 'invalid installed releases'); const validatedInstalled = input.installed.map((item, index) => { if (!isRecord(item)) fail('marketplace_request_invalid', `invalid installed[${index}]`); return { pluginId: idValue(item.pluginId, `installed[${index}].pluginId`), releaseId: releaseIdValue(item.releaseId, `installed[${index}].releaseId`), sha256: shaValue(item.sha256, `installed[${index}].sha256`), }; }); const canonical = canonicalResolveInput({ ...input, installed: validatedInstalled }); const derivedDigest = createHash('sha256').update(canonical, 'utf8').digest('hex'); const digest = input.resolveRequestDigest ?? derivedDigest; if (!SHA256_PATTERN.test(digest)) fail('marketplace_request_invalid', 'invalid resolveRequestDigest'); const requestedId = input.resolveRequestId ?? input.requestId; // The digest identifies request content; the ID identifies one logical sync. // Deriving both from content would replay an expired Admission forever. const requestId = validateRequestId(requestedId ?? `makelore-resolve-${randomUUID()}`); return { requestId, digest, channel, payload: { resolve_request_id: requestId, resolve_request_digest: digest, makelore_version: input.makeloreVersion, channel, installed: validatedInstalled.map((item) => ({ plugin_id: item.pluginId, release_id: item.releaseId, sha256: item.sha256, })), }, cacheKey: `${requestId}\u0000${digest}`, }; } function relativeContentUrl( contentUrl: string, apiBaseUrl: string, releaseId: string, admissionId: string, ): URL { if (!contentUrl.startsWith('/')) fail('marketplace_response_invalid', 'content_url must be relative'); let parsed: URL; try { parsed = new URL(contentUrl, apiBaseUrl); } catch { fail('marketplace_response_invalid', 'content_url is invalid'); } const base = new URL(apiBaseUrl); const expectedPath = `/api/plugin-marketplace/v1/releases/${encodeURIComponent(releaseId)}/content`; if (parsed.origin !== base.origin || parsed.pathname !== expectedPath || parsed.hash) { fail('marketplace_response_invalid', 'content_url escapes the Marketplace content route'); } const entries = [...parsed.searchParams.entries()]; if (entries.length !== 1 || entries[0]?.[0] !== 'release_admission_id' || entries[0][1] !== admissionId) { fail('marketplace_response_invalid', 'content_url admission does not match the grant'); } return parsed; } export interface MarketplaceClient { readCatalog(input: CatalogQuery): Promise; readDetail(pluginId: string): Promise; readLibrary(): Promise; acquire(pluginId: string): Promise; remove(pluginId: string): Promise; resolve(input: ResolveRequest): Promise; issueDownload(input: DownloadRequest): Promise; downloadContent(grant: DownloadGrant): Promise; getCurrentAccountBinding(): AccountBinding | null; dispose(): void; } class MarketplaceClientImpl implements MarketplaceClient { private readonly fetchImpl: FetchImplementation; private readonly apiBaseUrl: string; private readonly requestTimeoutMs: number; private readonly maxResponseBytes: number; private readonly maxRequestBytes: number; private readonly maxArtifactBytes: number; private readonly now: () => number; private readonly getAccessTokenImpl: MarketplaceSessionPort['getAccessToken']; private readonly getAccountBindingImpl: MarketplaceSessionPort['getAccountBinding']; private readonly accountCache: AccountPluginCache; private readonly catalogCache = new Map(); private readonly detailCache = new Map(); private readonly unsubscribeSession: (() => void) | null; private disposed = false; constructor(options: MarketplaceClientOptions = {}) { this.fetchImpl = options.fetchImpl ?? proxyAwareFetch; this.apiBaseUrl = normalizedBase(options.apiBaseUrl ?? WORKS_SQUARE_CONFIG.apiBaseUrl); this.requestTimeoutMs = positiveOption(options.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS, 'requestTimeoutMs'); this.maxResponseBytes = positiveOption(options.maxResponseBytes, DEFAULT_RESPONSE_BYTES, 'maxResponseBytes'); this.maxRequestBytes = positiveOption(options.maxRequestBytes, DEFAULT_REQUEST_BYTES, 'maxRequestBytes'); this.maxArtifactBytes = positiveOption(options.maxArtifactBytes, DEFAULT_ARTIFACT_BYTES, 'maxArtifactBytes'); this.now = options.now ?? (() => Date.now()); this.accountCache = options.accountCache ?? new AccountPluginCache(); const session = options.session; this.getAccessTokenImpl = options.getAccessToken ?? (session ? session.getAccessToken.bind(session) : undefined) ?? (async (refreshOptions) => getValidWorksSquareAccessToken({ forceRefresh: refreshOptions?.forceRefresh })); this.getAccountBindingImpl = options.getAccountBinding ?? (session ? session.getAccountBinding.bind(session) : undefined) ?? (() => { const binding = getWorksSquareAccountBinding(); return binding ? { accountKey: binding.accountKey, epoch: binding.epoch } : null; }); const subscribe = options.subscribeSession ?? (session?.subscribe ? session.subscribe.bind(session) : undefined) ?? ((listener: () => void) => subscribeWorksSquareSession(() => listener())); this.unsubscribeSession = subscribe(() => { this.accountCache.invalidateAll(); }); } getCurrentAccountBinding(): AccountBinding | null { return normalizedBinding(this.getAccountBindingImpl()); } dispose(): void { if (this.disposed) return; this.disposed = true; this.unsubscribeSession?.(); this.accountCache.invalidateAll(); } async readCatalog(input: CatalogQuery = {}): Promise { const query = { query: queryValue(input.query, 'query', 120), category: queryValue(input.category, 'category', 128), featured: input.featured ?? false, limit: input.limit ?? 24, cursor: queryValue(input.cursor, 'cursor', MAX_CURSOR), }; if (typeof query.featured !== 'boolean' || !Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > 100) { fail('marketplace_request_invalid', 'invalid catalog query'); } const key = JSON.stringify(query); const previous = this.catalogCache.get(key); const params = new URLSearchParams(); if (query.query !== undefined) params.set('query', query.query); if (query.category !== undefined) params.set('category', query.category); if (query.featured) params.set('featured', 'true'); params.set('limit', String(query.limit)); if (query.cursor !== undefined) params.set('cursor', query.cursor); try { const result = await this.requestJson( `/api/plugin-marketplace/v1/catalog?${params.toString()}`, { auth: 'optional', etag: previous?.etag }, parseCatalogPage, ); if (result.notModified) { if (!previous) fail('marketplace_response_invalid', '304 received without a catalog snapshot'); const fresh = { ...previous, stale: false, fetchedAt: this.now() }; this.catalogCache.set(key, fresh); return clone(fresh); } const parsed = result.value!; const metadata = parseCacheMetadata(result.headers, parsed.catalogGeneration); if (metadata.generation !== parsed.catalogGeneration) fail('marketplace_response_invalid', 'catalog generation header does not match body'); const page: CatalogPage = { ...parsed, ...metadata, stale: false, fetchedAt: this.now() }; this.catalogCache.set(key, page); return clone(page); } catch (error) { if (previous && canServeStale(error)) { const stale = { ...previous, stale: true, fetchedAt: previous.fetchedAt }; this.catalogCache.set(key, stale); return clone(stale); } throw normalizeError(error); } } async readDetail(pluginId: string): Promise { const validated = idValue(pluginId, 'pluginId'); const previous = this.detailCache.get(validated); try { const result = await this.requestJson( `/api/plugin-marketplace/v1/plugins/${encodeURIComponent(validated)}`, { auth: 'optional', etag: previous?.etag }, parsePluginDetail, ); if (result.notModified) { if (!previous) fail('marketplace_response_invalid', '304 received without a detail snapshot'); const fresh = { ...previous, stale: false, fetchedAt: this.now() }; this.detailCache.set(validated, fresh); return clone(fresh); } const parsed = result.value!; const metadata = parseCacheMetadata(result.headers, 1); const detail: PluginDetail = { ...parsed, ...metadata, stale: false, fetchedAt: this.now() }; this.detailCache.set(validated, detail); return clone(detail); } catch (error) { if (previous && canServeStale(error)) { const stale = { ...previous, stale: true, fetchedAt: previous.fetchedAt }; this.detailCache.set(validated, stale); return clone(stale); } throw normalizeError(error); } } async readLibrary(): Promise { const binding = this.requireBinding(); const intent = this.accountCache.beginLibraryIntent(binding); return this.readLibraryForIntent(binding, intent); } private async readLibraryForIntent( binding: AccountBinding, intent: number, ): Promise { const previous = this.accountCache.getLibrary(binding); try { const result = await this.requestJson('/api/plugin-marketplace/v1/library', { auth: 'required' }, parseLibrary); const parsed = result.value!; const snapshot: MarketplaceLibrarySnapshot = { ...parsed, stale: false, fetchedAt: this.now(), }; this.assertBinding(binding); if (!this.accountCache.commitLibrary(binding, intent, snapshot)) { return clone(this.accountCache.getLibrary(binding) ?? snapshot); } return clone(snapshot); } catch (error) { if (!this.bindingMatches(binding)) { fail('marketplace_account_changed', 'Marketplace account changed while the request was active'); } if (previous && canServeStale(error)) { const stale = this.accountCache.markLibraryStale(binding, intent); if (stale) return clone(stale); } throw normalizeError(error); } } async acquire(pluginId: string): Promise { return this.mutateLibrary('PUT', pluginId); } async remove(pluginId: string): Promise { return this.mutateLibrary('DELETE', pluginId); } async resolve(input: ResolveRequest): Promise { const binding = this.requireBinding(); const prepared = prepareResolveInput(input); const previous = this.accountCache.getResolve(binding, prepared.cacheKey); try { const result = await this.requestJson( '/api/plugin-marketplace/v1/releases/resolve', { auth: 'required', method: 'POST', body: prepared.payload }, parseResolve, ); const parsed = result.value!; if (parsed.resolveRequestId !== prepared.requestId || parsed.resolveRequestDigest !== prepared.digest) { fail('marketplace_response_invalid', 'resolve response identity does not match request'); } const metadata = parseCacheMetadata(result.headers, parsed.catalogGeneration); if (metadata.generation !== parsed.catalogGeneration) fail('marketplace_response_invalid', 'resolve generation header does not match body'); const snapshot: MarketplaceResolveSnapshot = { ...parsed, etag: metadata.etag, stale: false, }; this.assertBinding(binding); this.accountCache.setResolve(binding, prepared.cacheKey, snapshot); return clone(snapshot); } catch (error) { if (!this.bindingMatches(binding)) { fail('marketplace_account_changed', 'Marketplace account changed while the request was active'); } if (previous && canServeStale(error)) { const stale = this.accountCache.markResolveStale(binding, prepared.cacheKey); if (stale) return clone(stale); } throw normalizeError(error); } } async issueDownload(input: DownloadRequest): Promise { const releaseId = releaseIdValue(input.releaseId, 'releaseId'); const admissionId = releaseIdValue(input.releaseAdmissionId, 'releaseAdmissionId'); const result = await this.requestJson( `/api/plugin-marketplace/v1/releases/${encodeURIComponent(releaseId)}/download`, { auth: 'required', method: 'POST', body: { release_admission_id: admissionId } }, parseDownloadGrant, ); const grant = result.value!; if (grant.releaseId !== releaseId || grant.releaseAdmissionId !== admissionId) { fail('marketplace_response_invalid', 'download grant identity does not match request'); } relativeContentUrl(grant.contentUrl, this.apiBaseUrl, releaseId, admissionId); return clone(grant); } async downloadContent(grant: DownloadGrant): Promise { const releaseId = releaseIdValue(grant.releaseId, 'releaseId'); const admissionId = releaseIdValue(grant.releaseAdmissionId, 'releaseAdmissionId'); const localUrl = relativeContentUrl(grant.contentUrl, this.apiBaseUrl, releaseId, admissionId); const binding = this.requireBinding(); let token = await this.getAccessTokenImpl({ forceRefresh: false }); if (!token) fail('marketplace_auth_required', 'Marketplace session is unavailable', 401); let refreshed = false; let url = localUrl.toString(); let sendAuthorization = true; for (let redirect = 0; redirect <= 3; redirect += 1) { const { response, bytes } = await fetchResponseWithBodyDeadline( this.fetchImpl as typeof fetch, url, { method: 'GET', headers: { Accept: 'application/zip', ...(sendAuthorization ? { Authorization: `Bearer ${token}` } : {}), }, redirect: 'manual', }, this.requestTimeoutMs, Math.min(this.maxArtifactBytes, grant.sizeBytes), ); if (response.status === 401 && sendAuthorization && !refreshed) { await response.body?.cancel().catch(() => undefined); refreshed = true; token = await this.getAccessTokenImpl({ forceRefresh: true }); if (!token) fail('marketplace_auth_required', 'Marketplace session refresh failed', 401); this.assertBinding(binding); continue; } if (response.status >= 300 && response.status < 400) { const location = response.headers.get('location'); await response.body?.cancel().catch(() => undefined); if (!location || location.length > 4096 || redirect === 3) fail('marketplace_download_invalid', 'Marketplace download redirect is invalid'); let redirected: URL; try { redirected = new URL(location, url); } catch { fail('marketplace_download_invalid', 'Marketplace download redirect is invalid'); } if (redirected.protocol !== 'https:' || redirected.username || redirected.password || redirected.hash) { fail('marketplace_download_invalid', 'Marketplace download redirect is not trusted'); } this.assertBinding(binding); url = redirected.toString(); sendAuthorization = false; continue; } if (!response.ok) { failForResponse(response, bytes, 'Marketplace download failed'); } if (!bytes) fail('marketplace_download_invalid', 'Marketplace response body is unavailable'); if (bytes.byteLength !== grant.sizeBytes) fail('marketplace_download_invalid', 'Marketplace artifact size does not match its grant'); this.assertBinding(binding); return bytes; } fail('marketplace_download_invalid', 'Marketplace download redirect loop'); } private async mutateLibrary(method: 'PUT' | 'DELETE', pluginId: string): Promise { const validated = idValue(pluginId, 'pluginId'); const binding = this.requireBinding(); const intent = this.accountCache.beginLibraryIntent(binding); const previous = this.accountCache.getLibrary(binding); const result = await this.requestJson( `/api/plugin-marketplace/v1/library/${encodeURIComponent(validated)}`, { auth: 'required', method }, parseLibraryEntry, ); const entry = result.value!; this.assertBinding(binding); try { return await this.readLibraryForIntent(binding, intent); } catch (error) { if (!this.bindingMatches(binding)) { fail('marketplace_account_changed', 'Marketplace account changed while the request was active'); } if (!canServeStale(error)) throw normalizeError(error); if (previous) { const items = [entry, ...previous.items.filter((item) => item.pluginId !== entry.pluginId)]; const snapshot: MarketplaceLibrarySnapshot = { items, total: Math.max(previous.total, items.length), stale: true, fetchedAt: previous.fetchedAt, }; if (this.accountCache.commitLibrary(binding, intent, snapshot)) return clone(snapshot); return clone(this.accountCache.getLibrary(binding) ?? snapshot); } throw normalizeError(error); } } private requireBinding(): AccountBinding { const binding = this.getCurrentAccountBinding(); if (!binding) fail('marketplace_auth_required', 'Marketplace session is unavailable', 401); return binding; } private assertBinding(binding: AccountBinding): void { if (!this.bindingMatches(binding)) { fail('marketplace_account_changed', 'Marketplace account changed while the request was active'); } } private bindingMatches(binding: AccountBinding): boolean { const current = this.getCurrentAccountBinding(); return current !== null && current.accountKey === binding.accountKey && current.epoch === binding.epoch; } private async requestJson( route: string, options: { readonly auth: 'required' | 'optional'; readonly method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; readonly body?: Record; readonly etag?: string; }, parser: (value: unknown) => T, ): Promise> { const method = options.method ?? 'GET'; const binding = options.auth === 'required' ? this.requireBinding() : this.getCurrentAccountBinding(); let token = await this.getAccessTokenImpl({ forceRefresh: false }); if (options.auth === 'required' && !token) fail('marketplace_auth_required', 'Marketplace session is unavailable', 401); const body = options.body === undefined ? undefined : JSON.stringify(options.body); if (body !== undefined && Buffer.byteLength(body, 'utf8') > this.maxRequestBytes) { fail('marketplace_request_invalid', 'Marketplace request body exceeds its bound'); } let refreshed = false; const url = `${this.apiBaseUrl}${route}`; for (;;) { const headers: Record = { Accept: 'application/json', ...(body === undefined ? {} : { 'Content-Type': 'application/json' }), ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(options.etag ? { 'If-None-Match': options.etag } : {}), }; const { response, bytes } = await fetchResponseWithBodyDeadline( this.fetchImpl as typeof fetch, url, { method, headers, body, redirect: 'manual' }, this.requestTimeoutMs, this.maxResponseBytes, ); if (response.status === 401 && !refreshed && token) { await response.body?.cancel().catch(() => undefined); refreshed = true; token = await this.getAccessTokenImpl({ forceRefresh: true }); if (!token) fail('marketplace_auth_required', 'Marketplace session refresh failed', 401); if (binding) this.assertBinding(binding); continue; } if (response.status === 304) { if (binding) this.assertBinding(binding); return { status: response.status, headers: response.headers, value: null, notModified: true }; } if (!response.ok) { failForResponse(response, bytes, 'Marketplace request failed'); } if (!bytes) fail('marketplace_response_invalid', 'Marketplace response body is unavailable'); const value = parser(parseJsonBytes(bytes)); if (binding) this.assertBinding(binding); return { status: response.status, headers: response.headers, value, notModified: false }; } } } function positiveOption(value: number | undefined, fallback: number, field: string): number { const resolved = value ?? fallback; if (!Number.isSafeInteger(resolved) || resolved < 1) throw new RangeError(`${field} must be a positive safe integer`); return resolved; } function normalizeError(error: unknown): MarketplaceClientError { if (error instanceof MarketplaceClientError) return error; return new MarketplaceClientError('marketplace_request_failed', 0, 'Marketplace request failed'); } function canServeStale(error: unknown): boolean { if (!(error instanceof MarketplaceClientError)) return true; if (error.code !== 'marketplace_request_failed') return false; return error.status === 0 || error.status >= 500; } export function createMarketplaceClient(options: MarketplaceClientOptions = {}): MarketplaceClient { return new MarketplaceClientImpl(options); } export const parseMarketplaceCatalogPage = parseCatalogPage; export const parseMarketplacePluginDetail = parsePluginDetail; export const parseMarketplaceLibrary = parseLibrary; export const parseMarketplaceResolve = parseResolve; export const parseMarketplaceDownloadGrant = parseDownloadGrant;