import type { PluginGame, PluginGameSourceDraft, PluginGameSourcePatch, PluginHost, PluginImportGame, PluginInvocation, PluginTrackingEvent, XLibraryPlugin, } from '@xlibrary/plugin-sdk'; const IMPORT_CHUNK_BYTES = 512 * 1024; function isRecord(value: unknown): value is Record { return !!value && typeof value === 'object' && !Array.isArray(value); } function encodeBase64Utf8(value: string): string { const bytes = new TextEncoder().encode(value); let binary = ''; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary); } function decodeBase64(value: string): Uint8Array { const binary = atob(value); return Uint8Array.from(binary, (character) => character.charCodeAt(0)); } function getSourceId(input: {externalId?: string; url?: string}): string { if (input.externalId?.trim()) return input.externalId.trim(); const url = input.url ? new URL(input.url) : undefined; const slug = url?.pathname.split('/').filter(Boolean).at(-1); if (!slug) throw new Error('A source external id or URL is required'); return decodeURIComponent(slug); } function createSourceDraft(input: {externalId?: string; url?: string; name?: string; description?: string}): PluginGameSourceDraft { const externalId = getSourceId(input); return { externalId, canonicalUrl: input.url || `https://catalog.example/games/${encodeURIComponent(externalId)}`, name: input.name || `Reference Game (${externalId})`, ...(input.description ? {description: input.description} : {}), tags: [], screenshotUrls: [], isPartial: false, missingFields: [], metadata: {}, }; } async function readImportGames(host: PluginHost, input: Record): Promise { const inputId = typeof input.id === 'string' ? input.id : ''; if (!inputId) throw new Error('Import input id is required'); const chunks: Uint8Array[] = []; let offset = 0; while (true) { const chunk = await host.importInput.readChunk(inputId, offset, IMPORT_CHUNK_BYTES); const bytes = decodeBase64(chunk.data); chunks.push(bytes); offset += bytes.byteLength; if (chunk.eof) break; if (bytes.byteLength === 0) throw new Error('Import input returned an empty non-terminal chunk'); } const totalLength = chunks.reduce((total, chunk) => total + chunk.byteLength, 0); const data = new Uint8Array(totalLength); let cursor = 0; for (const chunk of chunks) { data.set(chunk, cursor); cursor += chunk.byteLength; } const parsed: unknown = JSON.parse(new TextDecoder().decode(data)); const rawGames = Array.isArray(parsed) ? parsed : isRecord(parsed) && Array.isArray(parsed.games) ? parsed.games : []; return rawGames.flatMap((value): PluginImportGame[] => { if (!isRecord(value) || typeof value.name !== 'string' || !value.name.trim()) return []; return [{ name: value.name.trim(), ...(typeof value.description === 'string' ? {description: value.description} : {}), ...(typeof value.version === 'string' ? {version: value.version} : {}), ...(Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === 'string') ? {tags: value.tags} : {}), }]; }); } function filterGames(games: PluginGame[], facetId: string, selection: unknown): string[] { return games.filter((game) => { if (facetId === 'has-reference-tag') { return selection === true ? game.tags.includes('reference') : !game.tags.includes('reference'); } if (facetId === 'reference-status') { return typeof selection === 'string' ? game.status === selection : true; } return true; }).map((game) => game.id); } function referencePanel(title: string, description: string) { return { title, description, nodes: [ {type: 'notice' as const, tone: 'info' as const, text: 'This panel is rendered from a validated data-only UI contract.'}, {type: 'action' as const, id: 'check-remote', label: 'Check remote endpoint'}, {type: 'action' as const, id: 'connect-account', label: 'Connect account'}, ], }; } const plugin: XLibraryPlugin = { async initialize(context, host) { const settings = await host.settings.get(); const games = await host.games.list(); const sessions = await host.sessions.list(); const firstGameFields = games[0] ? await host.gameFields.get(games[0].id) : {}; await host.cache.set('reference:last-start', {version: context.plugin.version}, {ttlSeconds: 300}); await host.diagnostics.log({ level: 'info', event: 'reference.initialized', message: 'Reference plugin initialized', context: { configured: typeof settings.endpoint === 'string', games: games.length, sessions: sessions.length, fields: Object.keys(firstGameFields).length, }, }); }, async deactivate(_context, host) { await host.diagnostics.log({ level: 'info', event: 'reference.deactivated', message: 'Reference plugin deactivated', }); }, async invoke(invocation: PluginInvocation, host: PluginHost) { if (invocation.method === 'game-sources.search') { return { candidates: [{ identity: { externalId: 'reference-game', canonicalUrl: 'https://catalog.example/games/reference-game', }, title: `Reference result for ${invocation.payload.query}`, summary: 'A deterministic provider candidate.', score: 1, }], }; } if (invocation.method === 'game-sources.resolve') { return createSourceDraft(invocation.payload); } if (invocation.method === 'game-sources.refresh') { const patch: PluginGameSourcePatch = { ...createSourceDraft({ externalId: invocation.payload.externalId, url: invocation.payload.url, name: invocation.payload.game.name, }), version: invocation.payload.game.version || '1.0.0', changedFields: ['version'], }; return patch; } if (invocation.method === 'game-sources.parse-page') { return createSourceDraft({ externalId: invocation.payload.externalId, url: invocation.payload.page.url, name: invocation.payload.page.title, description: invocation.payload.page.text.slice(0, 1_000), }); } if (invocation.method === 'imports.parse') { const games = await readImportGames(host, invocation.payload.input); return {games, warnings: []}; } if (invocation.method === 'exports.serialize') { const data = JSON.stringify({version: 1, games: invocation.payload.games ?? []}, null, 2); await host.exportOutput.write(encodeBase64Utf8(data)); return {completed: true}; } if (invocation.method === 'filters.evaluate') { const matches: Record = {}; for (const facet of invocation.payload.facets) { matches[facet.id] = filterGames(invocation.payload.games, facet.id, facet.selection); } return {matches}; } if (invocation.method === 'backup.capture') { return { dataVersion: invocation.payload.dataVersion, data: {marker: await host.storage.get('reference:backup-marker')}, }; } if (invocation.method === 'backup.restore') { if (!invocation.payload.dryRun) { await host.storage.set('reference:backup-marker', invocation.payload.data.marker); } return {restored: true}; } if (invocation.method === 'ui.render') { return referencePanel('Reference contribution', `Slot: ${invocation.payload.slot}`); } if (invocation.method === 'ui.action') { if (invocation.payload.actionId === 'connect-account') { await host.auth.begin('reference-account'); await host.notifications.show({ title: 'Reference account', message: 'Account connection flow started.', level: 'info', }); } if (invocation.payload.actionId === 'check-remote') { const settings = await host.settings.get(); const endpoint = typeof settings.endpoint === 'string' ? settings.endpoint : 'https://example.com'; const account = await host.auth.getStatus('reference-account'); const response = account.connected ? await host.auth.request('reference-account', {url: endpoint}) : await host.network.request({url: endpoint}); await host.diagnostics.log({ level: 'info', event: 'reference.remote-check', message: 'Remote endpoint checked', context: {status: response.status, authenticated: account.connected}, }); } return {panel: referencePanel('Action completed', `Action: ${invocation.payload.actionId}`)}; } if (invocation.method === 'tracking.session-event') { const event: PluginTrackingEvent = invocation.payload.event; return event.type === 'ended' ? {fieldValues: {'reference-score': Math.min(100, Math.round((event.duration ?? 0) / 60))}} : {fieldValues: {'reference-note': 'Session started'}}; } if (invocation.method === 'game-actions.execute') { return { message: invocation.payload.dryRun ? 'Dry run: game would be marked.' : 'Game marked by the reference action.', gamePatch: {addTags: ['reference']}, fieldValues: {'reference-score': 100}, }; } if (invocation.method === 'jobs.run') { await host.diagnostics.log({ level: 'debug', event: 'reference.job', message: 'Reference job executed', context: {jobId: invocation.payload.jobId, trigger: invocation.payload.trigger}, }); return undefined; } if (invocation.method === 'settings.migrate') { return {values: {...invocation.payload.values, endpoint: invocation.payload.values.endpoint || 'https://example.com'}}; } if (invocation.method === 'storage.migrate') { return {values: {...invocation.payload.values, migratedAtVersion: invocation.payload.fromDataVersion + 1}}; } if (invocation.method === 'lifecycle.deactivate') { return undefined; } throw new Error('Unsupported plugin method'); }, }; export default plugin;