@gamerdexapp/gamerdex-sync 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +16 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/layout.ts","../src/meta.ts","../src/merge/equal.ts","../src/merge/cloud.ts","../src/merge/merge.ts","../src/merge/resolve.ts","../src/provider/base64.ts","../src/provider/detail.ts","../src/provider/types.ts","../src/provider/github.ts","../src/provider/memory.ts","../src/sync/sync.ts","../src/auth/app.ts","../src/auth/tokens.ts","../src/auth/source.ts","../src/auth/urls.ts","../src/provider/repositories.ts","../src/provider/drive.ts","../src/auth/google.ts","../src/provider/dropbox.ts","../src/provider/onedrive.ts","../src/auth/dropbox.ts","../src/auth/microsoft.ts"],"sourcesContent":["export * from './layout';\nexport * from './meta';\nexport * from './merge/equal';\nexport * from './merge/merge';\nexport * from './merge/resolve';\nexport * from './merge/types';\nexport * from './provider/base64';\nexport * from './provider/github';\nexport * from './provider/memory';\nexport * from './provider/types';\nexport * from './sync/sync';\nexport * from './merge/cloud';\nexport * from './auth/app';\nexport * from './auth/source';\nexport * from './auth/tokens';\nexport * from './auth/urls';\nexport * from './provider/repositories';\nexport * from './provider/drive';\nexport * from './auth/google';\nexport * from './provider/dropbox';\nexport * from './provider/onedrive';\nexport * from './auth/dropbox';\nexport * from './auth/microsoft';\n","export const ROOT_FOLDER = 'Gamerdex';\nexport const DATA_FILE = 'data.json';\nexport const META_FILE = 'meta.json';\n\nconst APP_ID = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\n/** An app id names a folder, so it is lowercase words joined by single dashes. */\nexport const isAppId = (value: string): boolean => APP_ID.test(value);\n\nconst folderOf = (appId: string): string => {\n if (!isAppId(appId)) throw new RangeError(`Invalid app id: ${appId}`);\n return `${ROOT_FOLDER}/${appId}`;\n};\n\n/** The path of the saved data of an app, such as `Gamerdex/whiteout-survival/data.json`. */\nexport const dataPath = (appId: string): string => `${folderOf(appId)}/${DATA_FILE}`;\n\n/** The path of the bookkeeping file of an app, next to its saved data. */\nexport const metaPath = (appId: string): string => `${folderOf(appId)}/${META_FILE}`;\n","export const META_FORMAT = 1;\n\nexport interface DeviceRecord {\n name: string;\n /** ISO time of the last sync of the device. */\n lastSeenAt: string;\n}\n\n/**\n * The bookkeeping file beside the saved data. It holds no game data and no token. A revision only\n * counts up, so a device with a wrong clock cannot make old data look new.\n */\nexport interface SyncMeta {\n format: typeof META_FORMAT;\n appId: string;\n /** One counter for each chief id. A chief that was removed keeps its counter. */\n revisions: Record<string, number>;\n settingsRevision: number;\n /** Chief ids removed on purpose, with the revision of the removal. */\n removed: Record<string, number>;\n devices: Record<string, DeviceRecord>;\n lastSyncAt: string | null;\n}\n\nexport type ParsedMeta = { ok: true; meta: SyncMeta } | { ok: false; error: string };\n\nexport const emptyMeta = (appId: string): SyncMeta => ({\n format: META_FORMAT,\n appId,\n revisions: {},\n settingsRevision: 0,\n removed: {},\n devices: {},\n lastSyncAt: null,\n});\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isCounter = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value >= 0;\n\nconst isCounterMap = (value: unknown): value is Record<string, number> =>\n isRecord(value) && Object.values(value).every(isCounter);\n\nconst isDevice = (value: unknown): value is DeviceRecord =>\n isRecord(value) && typeof value.name === 'string' && typeof value.lastSeenAt === 'string';\n\n/** Reads the meta file and checks the whole shape. Nothing is returned for a file that fails. */\nexport const parseMeta = (text: string, appId: string): ParsedMeta => {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return { ok: false, error: 'The meta file is not valid JSON.' };\n }\n if (!isRecord(value)) return { ok: false, error: 'The meta file is not an object.' };\n if (value.format !== META_FORMAT) return { ok: false, error: 'The meta format is not known.' };\n if (value.appId !== appId) return { ok: false, error: 'The meta file belongs to another app.' };\n if (!isCounterMap(value.revisions)) return { ok: false, error: 'The revisions are not valid.' };\n if (!isCounter(value.settingsRevision)) {\n return { ok: false, error: 'The settings revision is not valid.' };\n }\n if (!isCounterMap(value.removed)) return { ok: false, error: 'The removed list is not valid.' };\n if (!isRecord(value.devices) || !Object.values(value.devices).every(isDevice)) {\n return { ok: false, error: 'The devices are not valid.' };\n }\n if (value.lastSyncAt !== null && typeof value.lastSyncAt !== 'string') {\n return { ok: false, error: 'The last sync time is not valid.' };\n }\n return { ok: true, meta: value as unknown as SyncMeta };\n};\n\n/** Counts one change of a chief. A chief that returns after a removal leaves the removed list. */\nexport const withChiefRevision = (meta: SyncMeta, chiefId: string): SyncMeta => {\n return {\n ...meta,\n revisions: { ...meta.revisions, [chiefId]: (meta.revisions[chiefId] ?? 0) + 1 },\n removed: Object.fromEntries(Object.entries(meta.removed).filter(([id]) => id !== chiefId)),\n };\n};\n\n/** Counts the removal of a chief. The counter keeps rising so an old copy cannot return it. */\nexport const withChiefRemoved = (meta: SyncMeta, chiefId: string): SyncMeta => {\n const revision = (meta.revisions[chiefId] ?? 0) + 1;\n return {\n ...meta,\n revisions: { ...meta.revisions, [chiefId]: revision },\n removed: { ...meta.removed, [chiefId]: revision },\n };\n};\n\nexport const withSettingsRevision = (meta: SyncMeta): SyncMeta => ({\n ...meta,\n settingsRevision: meta.settingsRevision + 1,\n});\n\nexport const withDevice = (meta: SyncMeta, deviceId: string, device: DeviceRecord): SyncMeta => ({\n ...meta,\n devices: { ...meta.devices, [deviceId]: device },\n lastSyncAt: device.lastSeenAt,\n});\n","const isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** Deep equality of two JSON values. The order of the keys of an object does not matter. */\nexport const equal = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n if (Array.isArray(a)) {\n return Array.isArray(b) && a.length === b.length && a.every((item, i) => equal(item, b[i]));\n }\n if (isObject(a) && isObject(b)) {\n const keys = Object.keys(a);\n return (\n keys.length === Object.keys(b).length &&\n keys.every((key) => Object.hasOwn(b, key) && equal(a[key], b[key]))\n );\n }\n return false;\n};\n\nexport { isObject };\n","import { isObject } from './equal';\nimport { Schema } from './types';\n\nconst own = (value: Record<string, unknown>, key: string): unknown =>\n Object.hasOwn(value, key) ? value[key] : undefined;\n\nconst childSchema = (schema: Extract<Schema, { type: 'object' }>, key: string): Schema =>\n (schema.fields && Object.hasOwn(schema.fields, key) ? schema.fields[key] : undefined) ??\n schema.each ?? { type: 'record' };\n\nconst holdsDeviceValue = (schema: Schema): boolean => {\n if (schema.type === 'local') return true;\n if (schema.type === 'keyed') return holdsDeviceValue(schema.item);\n if (schema.type !== 'object') return false;\n return (\n (schema.each !== undefined && holdsDeviceValue(schema.each)) ||\n Object.values(schema.fields ?? {}).some(holdsDeviceValue)\n );\n};\n\n/**\n * Returns the merged data as it must be stored in the cloud: every part with the `local` strategy\n * keeps the value that the cloud already holds, so a value of one device never changes the cloud.\n */\nexport const restoreCloudValues = (merged: unknown, remote: unknown, schema: Schema): unknown => {\n if (schema.type === 'local') return remote;\n if (!holdsDeviceValue(schema)) return merged;\n\n if (schema.type === 'object' && isObject(merged) && (isObject(remote) || remote === undefined)) {\n const cloud = remote ?? {};\n const result: Record<string, unknown> = {};\n for (const key of new Set([...Object.keys(merged), ...Object.keys(cloud)])) {\n const child = childSchema(schema, key);\n const value = restoreCloudValues(own(merged, key), own(cloud, key), child);\n if (value !== undefined) result[key] = value;\n }\n return result;\n }\n\n if (schema.type === 'keyed' && Array.isArray(merged) && Array.isArray(remote)) {\n const remoteById = new Map(\n remote.filter(isObject).map((item) => [item[schema.key], item] as const),\n );\n return merged.map((item) =>\n isObject(item)\n ? restoreCloudValues(item, remoteById.get(item[schema.key]), schema.item)\n : item,\n );\n }\n return merged;\n};\n","import { restoreCloudValues } from './cloud';\nimport { equal, isObject } from './equal';\nimport { Conflict, MergeInput, MergeResult, Path, Schema, Side } from './types';\n\ninterface Context {\n prefer: Side;\n conflicts: Conflict[];\n}\n\n/** Reads an own key only, so an id such as `constructor` never reads from the prototype. */\nconst own = <T>(map: Record<string, T> | undefined, key: string): T | undefined =>\n map !== undefined && Object.hasOwn(map, key) ? map[key] : undefined;\n\nconst record = (\n base: unknown,\n local: unknown,\n remote: unknown,\n path: Path,\n context: Context,\n): unknown => {\n context.conflicts.push({\n path,\n kind: 'both-changed',\n base,\n local,\n remote,\n taken: context.prefer,\n });\n return context.prefer === 'local' ? local : remote;\n};\n\nconst joinLists = (base: unknown[], local: unknown[], remote: unknown[]): unknown[] => {\n const has = (list: unknown[], value: unknown) => list.some((item) => equal(item, value));\n const kept = (value: unknown) => {\n const inBase = has(base, value);\n const inLocal = has(local, value);\n const inRemote = has(remote, value);\n return inBase ? inLocal && inRemote : inLocal || inRemote;\n };\n const result: unknown[] = [];\n for (const value of [...local, ...remote]) {\n if (kept(value) && !has(result, value)) result.push(value);\n }\n return result;\n};\n\nconst entriesOf = (list: unknown[], key: string): Map<string, unknown> | null => {\n const entries = new Map<string, unknown>();\n for (const item of list) {\n if (!isObject(item) || typeof item[key] !== 'string') return null;\n entries.set(item[key], item);\n }\n return entries;\n};\n\nconst mergeEntries = (\n ids: string[],\n pick: (id: string) => [unknown, unknown, unknown],\n schemaOf: (id: string) => Schema,\n path: Path,\n context: Context,\n deleteWins: boolean,\n): Map<string, unknown> => {\n const merged = new Map<string, unknown>();\n for (const id of ids) {\n const [base, local, remote] = pick(id);\n const value = mergeNode(base, local, remote, schemaOf(id), [...path, id], context, deleteWins);\n if (value !== undefined) merged.set(id, value);\n }\n return merged;\n};\n\nconst orderedIds = (local: Iterable<string>, remote: Iterable<string>, base: Iterable<string>) => [\n ...new Set([...local, ...remote, ...base]),\n];\n\nconst mergeNode = (\n base: unknown,\n local: unknown,\n remote: unknown,\n schema: Schema,\n path: Path,\n context: Context,\n deleteWins: boolean,\n): unknown => {\n if (schema.type === 'local') return local;\n if (equal(local, remote)) return local;\n if (equal(local, base)) return remote;\n if (equal(remote, base)) return local;\n\n if (local === undefined || remote === undefined) {\n if (deleteWins) return undefined;\n const taken: Side = local === undefined ? 'remote' : 'local';\n context.conflicts.push({ path, kind: 'delete-edit', base, local, remote, taken });\n return taken === 'local' ? local : remote;\n }\n\n switch (schema.type) {\n case 'max': {\n const l = schema.order(local);\n const r = schema.order(remote);\n if (l === r) return record(base, local, remote, path, context);\n return l > r ? local : remote;\n }\n case 'join':\n if (Array.isArray(local) && Array.isArray(remote)) {\n return joinLists(Array.isArray(base) ? base : [], local, remote);\n }\n return record(base, local, remote, path, context);\n case 'object': {\n if (!isObject(local) || !isObject(remote)) return record(base, local, remote, path, context);\n const baseObject = isObject(base) ? base : {};\n const ids = orderedIds(Object.keys(local), Object.keys(remote), Object.keys(baseObject));\n const merged = mergeEntries(\n ids,\n (id) => [own(baseObject, id), own(local, id), own(remote, id)],\n (id) => own(schema.fields, id) ?? schema.each ?? { type: 'record' },\n path,\n context,\n deleteWins || schema.deleteWins === true,\n );\n return Object.fromEntries(merged);\n }\n case 'keyed': {\n const localEntries = Array.isArray(local) ? entriesOf(local, schema.key) : null;\n const remoteEntries = Array.isArray(remote) ? entriesOf(remote, schema.key) : null;\n const baseEntries = (Array.isArray(base) ? entriesOf(base, schema.key) : null) ?? new Map();\n if (!localEntries || !remoteEntries) return record(base, local, remote, path, context);\n const ids = orderedIds(localEntries.keys(), remoteEntries.keys(), baseEntries.keys());\n const merged = mergeEntries(\n ids,\n (id) => [baseEntries.get(id), localEntries.get(id), remoteEntries.get(id)],\n () => schema.item,\n path,\n context,\n deleteWins || schema.deleteWins === true,\n );\n return [...merged.values()];\n }\n case 'record':\n return record(base, local, remote, path, context);\n }\n};\n\n/**\n * Merges the data of this device and the data of the cloud against the data of the last sync.\n * A part that only one side changed takes that change. A part that both sides changed merges by its\n * schema, and a part that cannot merge is reported in `conflicts` with the preferred side already\n * in `merged`.\n */\nexport const merge = ({\n base,\n local,\n remote,\n schema,\n prefer = 'remote',\n}: MergeInput): MergeResult => {\n const context: Context = { prefer, conflicts: [] };\n const merged = mergeNode(base, local, remote, schema, [], context, false);\n return {\n merged,\n cloud: restoreCloudValues(merged, remote, schema),\n conflicts: context.conflicts,\n };\n};\n","import { isObject } from './equal';\nimport { Conflict, Schema, Side } from './types';\n\nconst own = (value: Record<string, unknown>, key: string): unknown =>\n Object.hasOwn(value, key) ? value[key] : undefined;\n\nconst childSchema = (schema: Schema, step: string): Schema => {\n if (schema.type === 'object') {\n const named =\n schema.fields && Object.hasOwn(schema.fields, step) ? schema.fields[step] : undefined;\n return named ?? schema.each ?? { type: 'record' };\n }\n return { type: 'record' };\n};\n\nconst setAt = (data: unknown, schema: Schema, path: string[], value: unknown): unknown => {\n if (path.length === 0) return value;\n const [step, ...rest] = path;\n if (schema.type === 'keyed') {\n const list = Array.isArray(data) ? data : [];\n const index = list.findIndex((item) => isObject(item) && item[schema.key] === step);\n const next = setAt(index >= 0 ? list[index] : undefined, schema.item, rest, value);\n if (index < 0) return next === undefined ? list : [...list, next];\n if (next === undefined) return list.filter((_, i) => i !== index);\n return list.map((item, i) => (i === index ? next : item));\n }\n const object = isObject(data) ? data : {};\n const next = setAt(own(object, step), childSchema(schema, step), rest, value);\n if (next === undefined) {\n return Object.fromEntries(Object.entries(object).filter(([key]) => key !== step));\n }\n return { ...object, [step]: next };\n};\n\n/**\n * Puts the value of one side of a conflict into the merged data. The player uses this to pick the\n * side that the merge did not take. A side that holds no value removes the entry.\n */\nexport const resolveConflict = (\n merged: unknown,\n schema: Schema,\n conflict: Conflict,\n side: Side,\n): unknown => setAt(merged, schema, conflict.path, conflict[side]);\n","const CHUNK = 0x8000;\n\n/** Encodes text as base64 from its UTF-8 bytes. It works in the browser and in Node. */\nexport const encodeBase64 = (text: string): string => {\n const bytes = new TextEncoder().encode(text);\n let binary = '';\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));\n }\n return btoa(binary);\n};\n\n/** Decodes base64 to text. Line breaks in the base64 text are ignored. */\nexport const decodeBase64 = (base64: string): string => {\n const binary = atob(base64.replace(/\\s/g, ''));\n return new TextDecoder().decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));\n};\n","const MAX_DETAIL_LENGTH = 300;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Reads the message that a provider sends with a failed request, for the logs and the player.\n * GitHub sends `{ message }`, Google and Microsoft send `{ error: { message } }` and Dropbox sends\n * `{ error_summary }`.\n */\nexport const detailOf = async (response: Response): Promise<string | undefined> => {\n const body: unknown = await response.json().catch(() => null);\n if (!isRecord(body)) return undefined;\n const nested = isRecord(body.error) ? body.error.message : undefined;\n const message = nested ?? body.message ?? body.error_summary;\n return typeof message === 'string' ? message.slice(0, MAX_DETAIL_LENGTH) : undefined;\n};\n","/** A file in the cloud. The version changes with every write and settles which write is newer. */\nexport interface RemoteFile {\n content: string;\n version: string;\n}\n\n/**\n * Where the files of an app are kept. A provider reads, writes and removes whole files by path, and\n * refuses a write that was made from an old version of the file. That refusal is what keeps two\n * devices from overwriting each other.\n */\nexport interface SyncProvider {\n /** Returns null when the file does not exist. */\n read(path: string): Promise<RemoteFile | null>;\n /**\n * Writes the file. The expected version is the version that was read, or null to create a file\n * that must not exist yet. Throws `VersionConflictError` when the file changed since.\n */\n write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }>;\n /** Removes the file. Throws `VersionConflictError` when the file changed since. */\n remove(path: string, expectedVersion: string): Promise<void>;\n}\n\n/** The file changed in the cloud after it was read. Read it again and merge. */\nexport class VersionConflictError extends Error {\n constructor(public readonly path: string) {\n super(`The file ${path} changed in the cloud.`);\n this.name = 'VersionConflictError';\n }\n}\n\nexport type ProviderErrorKind =\n /** The token is missing, expired, revoked or has no permission. The player signs in again. */\n | 'auth'\n /** The provider limits the calls for now. Try again after `retryAfterSeconds`. */\n | 'rate-limit'\n /** The repository or folder does not exist, or the token cannot see it. */\n | 'not-found'\n /** The device is offline or the provider cannot be reached. */\n | 'network'\n /** The provider failed. A later try may work. */\n | 'server'\n /** The provider answered with something this package does not understand. */\n | 'invalid';\n\nexport class ProviderError extends Error {\n constructor(\n public readonly kind: ProviderErrorKind,\n message: string,\n public readonly status?: number,\n public readonly retryAfterSeconds?: number,\n /** The message that the provider sent with the failed request, when it sent one. */\n public readonly detail?: string,\n ) {\n super(message);\n this.name = 'ProviderError';\n }\n}\n","import { decodeBase64, encodeBase64 } from './base64';\nimport { detailOf } from './detail';\nimport { ProviderError, RemoteFile, SyncProvider, VersionConflictError } from './types';\n\nconst API = 'https://api.github.com';\nconst API_VERSION = '2022-11-28';\n\nexport interface GitHubOptions {\n /** The owner and the name of the repository, such as `chiefpansancolt/gamerdex-data`. */\n repository: string;\n /** Returns the token. It may refresh the token first, so it is called for every request. */\n getToken: () => Promise<string>;\n /** The branch to use. Without it GitHub uses the default branch of the repository. */\n branch?: string;\n /** The message of the commit that a write or a removal makes. */\n commitMessage?: (path: string) => string;\n fetch?: typeof fetch;\n}\n\nexport interface RepositoryCheck {\n private: boolean;\n canWrite: boolean;\n defaultBranch: string;\n}\n\ninterface ContentsResponse {\n sha?: unknown;\n content?: unknown;\n encoding?: unknown;\n}\n\nconst encodePath = (path: string): string => path.split('/').map(encodeURIComponent).join('/');\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst retryAfterOf = (response: Response): number | undefined => {\n const header = response.headers.get('retry-after');\n if (header !== null && Number.isFinite(Number(header))) return Number(header);\n const reset = response.headers.get('x-ratelimit-reset');\n if (reset !== null && Number.isFinite(Number(reset))) {\n return Math.max(0, Number(reset) - Math.floor(Date.now() / 1000));\n }\n return undefined;\n};\n\n/** Keeps the files of an app in a private GitHub repository, through the contents API. */\nexport class GitHubProvider implements SyncProvider {\n private readonly send: typeof fetch;\n\n constructor(private readonly options: GitHubOptions) {\n this.send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n }\n\n async read(path: string): Promise<RemoteFile | null> {\n const response = await this.request('GET', this.contentsUrl(path));\n if (response.status === 404) return null;\n await this.assertOk(response);\n const body = (await response.json()) as ContentsResponse;\n if (typeof body.sha !== 'string') throw this.invalid('The file has no version.');\n if (body.encoding === 'base64' && typeof body.content === 'string') {\n return { content: decodeBase64(body.content), version: body.sha };\n }\n if (body.encoding === 'none') {\n const raw = await this.request('GET', this.contentsUrl(path), undefined, {\n Accept: 'application/vnd.github.raw+json',\n });\n await this.assertOk(raw);\n return { content: await raw.text(), version: body.sha };\n }\n throw this.invalid('The file content is not in a known encoding.');\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n const body: Record<string, unknown> = {\n message: this.message(path),\n content: encodeBase64(content),\n };\n if (expectedVersion !== null) body.sha = expectedVersion;\n if (this.options.branch) body.branch = this.options.branch;\n const response = await this.request('PUT', this.contentsUrl(path), body);\n if (response.status === 409 || response.status === 422) throw new VersionConflictError(path);\n await this.assertOk(response);\n const result = (await response.json()) as { content?: { sha?: unknown } };\n const version = result.content?.sha;\n if (typeof version !== 'string') throw this.invalid('The write returned no version.');\n return { version };\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const body: Record<string, unknown> = { message: this.message(path), sha: expectedVersion };\n if (this.options.branch) body.branch = this.options.branch;\n const response = await this.request('DELETE', this.contentsUrl(path), body);\n if (response.status === 404) return;\n if (response.status === 409 || response.status === 422) throw new VersionConflictError(path);\n await this.assertOk(response);\n }\n\n /**\n * Checks that the repository can be used: it exists, the token can write to it, and it is\n * private. The saved data of a player must never sit in a public repository.\n */\n async checkRepository(): Promise<RepositoryCheck> {\n const response = await this.request('GET', `${API}/repos/${this.options.repository}`);\n await this.assertOk(response);\n const body: unknown = await response.json();\n if (!isRecord(body) || typeof body.private !== 'boolean') {\n throw this.invalid('The repository answer is not understood.');\n }\n const permissions = isRecord(body.permissions) ? body.permissions : {};\n return {\n private: body.private,\n canWrite: permissions.push === true,\n defaultBranch: typeof body.default_branch === 'string' ? body.default_branch : 'main',\n };\n }\n\n private contentsUrl(path: string): string {\n const base = `${API}/repos/${this.options.repository}/contents/${encodePath(path)}`;\n return this.options.branch ? `${base}?ref=${encodeURIComponent(this.options.branch)}` : base;\n }\n\n private message(path: string): string {\n return this.options.commitMessage?.(path) ?? `Gamerdex sync: ${path}`;\n }\n\n private async request(\n method: string,\n url: string,\n body?: Record<string, unknown>,\n headers: Record<string, string> = {},\n ): Promise<Response> {\n const token = await this.options.getToken();\n try {\n return await this.send(url, {\n method,\n headers: {\n Accept: 'application/vnd.github+json',\n Authorization: `Bearer ${token}`,\n 'X-GitHub-Api-Version': API_VERSION,\n ...(body ? { 'Content-Type': 'application/json' } : {}),\n ...headers,\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n } catch {\n throw new ProviderError('network', 'GitHub could not be reached.');\n }\n }\n\n private async assertOk(response: Response): Promise<void> {\n if (response.ok) return;\n const { status } = response;\n const detail = await detailOf(response);\n if (status === 401) {\n throw new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n if (status === 403 || status === 429) {\n const limited = status === 429 || response.headers.get('x-ratelimit-remaining') === '0';\n if (limited) {\n throw new ProviderError(\n 'rate-limit',\n 'GitHub limits the calls.',\n status,\n retryAfterOf(response),\n detail,\n );\n }\n throw new ProviderError(\n 'auth',\n 'The token has no permission for the repository.',\n status,\n undefined,\n detail,\n );\n }\n if (status === 404) {\n throw new ProviderError(\n 'not-found',\n 'The repository was not found.',\n status,\n undefined,\n detail,\n );\n }\n if (status >= 500) {\n throw new ProviderError('server', 'GitHub failed.', status, undefined, detail);\n }\n throw this.invalid(`GitHub answered ${status}.`, status, detail);\n }\n\n private invalid(message: string, status?: number, detail?: string): ProviderError {\n return new ProviderError('invalid', message, status, undefined, detail);\n }\n}\n","import { RemoteFile, SyncProvider, VersionConflictError } from './types';\n\n/**\n * A provider that keeps the files in memory. It follows the same version rules as a real provider,\n * so the sync flow of an app can be tested without a network.\n */\nexport class MemoryProvider implements SyncProvider {\n private files = new Map<string, { content: string; counter: number }>();\n private counter = 0;\n\n async read(path: string): Promise<RemoteFile | null> {\n const file = this.files.get(path);\n return file ? { content: file.content, version: String(file.counter) } : null;\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n const current = this.files.get(path);\n const currentVersion = current ? String(current.counter) : null;\n if (currentVersion !== expectedVersion) throw new VersionConflictError(path);\n this.counter += 1;\n this.files.set(path, { content, counter: this.counter });\n return { version: String(this.counter) };\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const current = this.files.get(path);\n if (current && String(current.counter) !== expectedVersion) {\n throw new VersionConflictError(path);\n }\n this.files.delete(path);\n }\n\n /** The paths of the files that exist, in the order they were first written. */\n paths(): string[] {\n return [...this.files.keys()];\n }\n}\n","import { dataPath, metaPath } from '../layout';\nimport { restoreCloudValues } from '../merge/cloud';\nimport { equal } from '../merge/equal';\nimport { merge } from '../merge/merge';\nimport { Conflict, Schema } from '../merge/types';\nimport { emptyMeta, parseMeta, SyncMeta, withDevice } from '../meta';\nimport { SyncProvider, VersionConflictError } from '../provider/types';\n\nconst MAX_ATTEMPTS = 4;\n\n/**\n * What this device remembers about the last sync. The app keeps it in the browser, next to the\n * saved data but outside it, and passes it back on the next sync.\n */\nexport interface SyncState {\n /** The cloud data as this device last saw it. */\n base: unknown;\n dataVersion: string | null;\n metaVersion: string | null;\n}\n\n/** What to do when this device never synced and both the device and the cloud hold data. */\nexport type FirstSyncChoice = 'merge' | 'keep-local' | 'keep-cloud';\n\nexport interface SyncOptions {\n appId: string;\n deviceId: string;\n deviceName: string;\n provider: SyncProvider;\n schema: Schema;\n /** The data of this device. */\n local: unknown;\n /** The state kept after the last sync, or null when this device never synced. */\n state: SyncState | null;\n firstSync?: FirstSyncChoice;\n /** Returns true when the data is whole and safe to store. Called before any write. */\n validate?: (data: unknown) => boolean;\n /** Lets the app change the revisions of the meta file for this sync. */\n updateMeta?: (meta: SyncMeta) => SyncMeta;\n /** The current time as an ISO text. */\n now: () => string;\n}\n\nexport type SyncResult =\n | {\n status: 'synced';\n /** The data to store on this device. */\n data: unknown;\n /** The state to keep for the next sync. */\n state: SyncState;\n conflicts: Conflict[];\n /** True when the cloud changed during this sync. */\n uploaded: boolean;\n }\n | {\n /** The device and the cloud both hold data and the app has not said what to do. */\n status: 'needs-choice';\n local: unknown;\n remote: unknown;\n };\n\n/** The data in the cloud is not whole, so nothing was written. */\nexport class RemoteDataError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RemoteDataError';\n }\n}\n\n/** Other devices kept writing during every try, so the sync stopped. Try again later. */\nexport class SyncBusyError extends Error {\n constructor() {\n super('The cloud kept changing during the sync.');\n this.name = 'SyncBusyError';\n }\n}\n\nconst parseData = (text: string, validate: SyncOptions['validate']): unknown => {\n let data: unknown;\n try {\n data = JSON.parse(text);\n } catch {\n throw new RemoteDataError('The synced data is not valid JSON.');\n }\n if (validate && !validate(data)) throw new RemoteDataError('The synced data is not valid.');\n return data;\n};\n\nconst readMeta = (text: string | undefined, appId: string): SyncMeta => {\n if (text === undefined) return emptyMeta(appId);\n const parsed = parseMeta(text, appId);\n if (!parsed.ok) throw new RemoteDataError(parsed.error);\n return parsed.meta;\n};\n\n/**\n * Records this device and the time of the sync in the meta file. A sync that changed nothing writes\n * no file when this device is already recorded, so the repository gets no empty commits.\n */\nconst writeMeta = async (options: SyncOptions, uploaded: boolean): Promise<string> => {\n const { provider, appId, deviceId, deviceName, updateMeta, now } = options;\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n const current = await provider.read(metaPath(appId));\n const meta = readMeta(current?.content, appId);\n if (current && !uploaded && !updateMeta && Object.hasOwn(meta.devices, deviceId)) {\n return current.version;\n }\n const time = now();\n const updated = withDevice(updateMeta ? updateMeta(meta) : meta, deviceId, {\n name: deviceName,\n lastSeenAt: time,\n });\n try {\n const written = await provider.write(\n metaPath(appId),\n JSON.stringify({ ...updated, lastSyncAt: time }, null, 2),\n current?.version ?? null,\n );\n return written.version;\n } catch (error) {\n if (!(error instanceof VersionConflictError)) throw error;\n }\n }\n throw new SyncBusyError();\n};\n\n/**\n * Brings this device and the cloud to the same data. It reads the cloud, merges the three copies,\n * writes the cloud copy when it changed, and returns the data to store on this device with the\n * state to keep. It never changes the device by itself, and it does not write when the cloud data\n * or the merged data fail `validate`.\n *\n * Throws `ProviderError` when the provider fails, `RemoteDataError` when the cloud data is not\n * whole, and `SyncBusyError` when other devices keep writing.\n */\nexport const sync = async (options: SyncOptions): Promise<SyncResult> => {\n const { appId, provider, schema, local, state, firstSync, validate } = options;\n const path = dataPath(appId);\n\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n const file = await provider.read(path);\n\n if (file === null) {\n if (validate && !validate(local))\n throw new RemoteDataError('The data of this device is not valid.');\n const cloud = restoreCloudValues(local, undefined, schema);\n const created = await tryWrite(provider, path, cloud, null);\n if (created === null) continue;\n const metaVersion = await writeMeta(options, true);\n return done(local, cloud, created, metaVersion, [], true);\n }\n\n const remote = parseData(file.content, validate);\n const differs = state === null && !equal(restoreCloudValues(local, remote, schema), remote);\n const choice = differs ? firstSync : undefined;\n if (differs && choice === undefined) return { status: 'needs-choice', local, remote };\n\n const base = state === null ? undefined : state.base;\n const merged = mergeFor(choice, { base, local, remote, schema });\n if (validate && !validate(merged.merged)) {\n throw new RemoteDataError('The merged data is not valid.');\n }\n\n if (equal(merged.cloud, remote)) {\n const metaVersion = await writeMeta(options, false);\n return done(merged.merged, merged.cloud, file.version, metaVersion, merged.conflicts, false);\n }\n\n const version = await tryWrite(provider, path, merged.cloud, file.version);\n if (version === null) continue;\n const metaVersion = await writeMeta(options, true);\n return done(merged.merged, merged.cloud, version, metaVersion, merged.conflicts, true);\n }\n throw new SyncBusyError();\n};\n\nconst mergeFor = (\n choice: FirstSyncChoice | undefined,\n input: { base: unknown; local: unknown; remote: unknown; schema: Schema },\n) => {\n if (choice === 'keep-local') {\n const cloud = restoreCloudValues(input.local, input.remote, input.schema);\n return { merged: input.local, cloud, conflicts: [] };\n }\n if (choice === 'keep-cloud') {\n const merged = restoreCloudValues(input.remote, input.local, input.schema);\n return { merged, cloud: input.remote, conflicts: [] };\n }\n return merge(input);\n};\n\nconst tryWrite = async (\n provider: SyncProvider,\n path: string,\n data: unknown,\n version: string | null,\n): Promise<string | null> => {\n try {\n return (await provider.write(path, JSON.stringify(data, null, 2), version)).version;\n } catch (error) {\n if (error instanceof VersionConflictError) return null;\n throw error;\n }\n};\n\nconst done = (\n data: unknown,\n cloud: unknown,\n dataVersion: string,\n metaVersion: string,\n conflicts: Conflict[],\n uploaded: boolean,\n): SyncResult => ({\n status: 'synced',\n data,\n state: { base: cloud, dataVersion, metaVersion },\n conflicts,\n uploaded,\n});\n","/** The public values of the Gamerdex Sync GitHub App. The client secret is never kept here. */\nexport const GITHUB_APP = {\n slug: 'gamerdex-sync',\n clientId: 'Iv23liwzjj3x8O2K5ALA',\n} as const;\n","/** The tokens of a signed in player. Times are milliseconds since 1970. */\nexport interface StoredTokens {\n accessToken: string;\n accessExpiresAt: number;\n refreshToken: string;\n refreshExpiresAt: number;\n}\n\n/** The answer of the token function, with lifetimes in seconds. */\nexport interface TokenResponse {\n accessToken: string;\n expiresIn: number;\n refreshToken: string;\n refreshExpiresIn: number;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isSeconds = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0;\n\nexport const parseTokenResponse = (body: unknown): TokenResponse | null => {\n if (!isRecord(body)) return null;\n const { accessToken, expiresIn, refreshToken, refreshExpiresIn } = body;\n if (typeof accessToken !== 'string' || accessToken === '') return null;\n if (typeof refreshToken !== 'string' || refreshToken === '') return null;\n if (!isSeconds(expiresIn) || !isSeconds(refreshExpiresIn)) return null;\n return { accessToken, expiresIn, refreshToken, refreshExpiresIn };\n};\n\nexport const toStoredTokens = (response: TokenResponse, nowMs: number): StoredTokens => ({\n accessToken: response.accessToken,\n accessExpiresAt: nowMs + response.expiresIn * 1000,\n refreshToken: response.refreshToken,\n refreshExpiresAt: nowMs + response.refreshExpiresIn * 1000,\n});\n","import { ProviderError } from '../provider/types';\nimport { parseTokenResponse, StoredTokens, toStoredTokens } from './tokens';\n\nconst RENEW_BEFORE_MS = 60_000;\n\nexport interface TokenSourceOptions {\n /** The address of the token function, without a trailing slash. */\n endpoint: string;\n /** Reads the tokens that this device saved, or null when the player is signed out. */\n load: () => StoredTokens | null;\n /** Saves the tokens, or removes them when null. */\n save: (tokens: StoredTokens | null) => void;\n now: () => number;\n fetch?: typeof fetch;\n}\n\n/**\n * Keeps a player signed in. `getToken` returns a token that is valid for at least a minute and\n * renews it first when needed. It is meant to be the `getToken` of `GitHubProvider`.\n */\nexport const createTokenSource = (options: TokenSourceOptions) => {\n const send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n let renewing: Promise<StoredTokens> | null = null;\n\n const post = async (path: string, body: Record<string, string>): Promise<StoredTokens> => {\n let response: Response;\n try {\n response = await send(`${options.endpoint}/${path}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n } catch {\n throw new ProviderError('network', 'The sign in service could not be reached.');\n }\n if (response.status >= 500) throw new ProviderError('server', 'The sign in service failed.');\n const parsed = response.ok ? parseTokenResponse(await response.json().catch(() => null)) : null;\n if (parsed === null)\n throw new ProviderError('auth', 'The sign in was refused.', response.status);\n const tokens = toStoredTokens(parsed, options.now());\n options.save(tokens);\n return tokens;\n };\n\n const renew = (tokens: StoredTokens): Promise<StoredTokens> => {\n if (tokens.refreshExpiresAt <= options.now()) {\n options.save(null);\n throw new ProviderError('auth', 'The sign in expired.');\n }\n renewing ??= post('refresh', { refreshToken: tokens.refreshToken })\n .catch((error: unknown) => {\n if (error instanceof ProviderError && error.kind === 'auth') options.save(null);\n throw error;\n })\n .finally(() => {\n renewing = null;\n });\n return renewing;\n };\n\n return {\n /** Finishes a sign in with the code from the callback page. */\n signIn: (code: string, redirectUri: string): Promise<StoredTokens> =>\n post('token', { code, redirectUri }),\n signOut: (): void => options.save(null),\n isSignedIn: (): boolean => options.load() !== null,\n getToken: async (): Promise<string> => {\n const tokens = options.load();\n if (tokens === null) throw new ProviderError('auth', 'The player is not signed in.');\n if (tokens.accessExpiresAt - RENEW_BEFORE_MS > options.now()) return tokens.accessToken;\n return (await renew(tokens)).accessToken;\n },\n };\n};\n","import { GITHUB_APP } from './app';\n\n/** A random value that ties a sign-in answer to the sign-in that this device started. */\nexport const newState = (): string => crypto.randomUUID();\n\nexport const authorizeUrl = (redirectUri: string, state: string): string => {\n const query = new URLSearchParams({\n client_id: GITHUB_APP.clientId,\n redirect_uri: redirectUri,\n state,\n });\n return `https://github.com/login/oauth/authorize?${query}`;\n};\n\n/** The page where the player picks the repository that the app may use. */\nexport const installUrl = (state: string): string =>\n `https://github.com/apps/${GITHUB_APP.slug}/installations/new?${new URLSearchParams({ state })}`;\n\nexport type CallbackResult =\n { ok: true; code: string } | { ok: false; reason: 'denied' | 'state' | 'missing' };\n\n/**\n * Reads the query of the sign-in callback page. The state must equal the state that was saved when\n * the sign-in started, or the answer is refused.\n */\nexport const readCallback = (search: string, expectedState: string | null): CallbackResult => {\n const query = new URLSearchParams(search);\n if (query.get('error') !== null) return { ok: false, reason: 'denied' };\n const code = query.get('code');\n if (code === null || code === '') return { ok: false, reason: 'missing' };\n if (expectedState === null || query.get('state') !== expectedState) {\n return { ok: false, reason: 'state' };\n }\n return { ok: true, code };\n};\n","import { detailOf } from './detail';\nimport { ProviderError } from './types';\n\nexport interface InstalledRepository {\n /** The owner and the name, such as `chiefpansancolt/gamerdex-data`. */\n fullName: string;\n private: boolean;\n}\n\nconst API = 'https://api.github.com';\nconst PAGE_SIZE = 100;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst getJson = async (\n send: typeof fetch,\n token: string,\n url: string,\n): Promise<Record<string, unknown>> => {\n let response: Response;\n try {\n response = await send(url, {\n headers: {\n Accept: 'application/vnd.github+json',\n Authorization: `Bearer ${token}`,\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n } catch {\n throw new ProviderError('network', 'GitHub could not be reached.');\n }\n if (!response.ok) {\n const { status } = response;\n const detail = await detailOf(response);\n if (status === 401 || status === 403) {\n throw new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n if (status >= 500)\n throw new ProviderError('server', 'GitHub failed.', status, undefined, detail);\n throw new ProviderError(\n 'invalid',\n 'GitHub answer is not understood.',\n status,\n undefined,\n detail,\n );\n }\n const body: unknown = await response.json().catch(() => null);\n if (!isRecord(body)) {\n throw new ProviderError('invalid', 'GitHub answer is not understood.', response.status);\n }\n return body;\n};\n\n/**\n * Lists the repositories that the player gave to the GitHub App, for the first 100 installations\n * and the first 100 repositories of each. The player picks the repository for the synced data.\n */\nexport const listInstalledRepositories = async (\n token: string,\n send: typeof fetch = (...args) => globalThis.fetch(...args),\n): Promise<InstalledRepository[]> => {\n const installations = await getJson(\n send,\n token,\n `${API}/user/installations?per_page=${PAGE_SIZE}`,\n );\n const ids = (Array.isArray(installations.installations) ? installations.installations : [])\n .filter(isRecord)\n .map((installation) => installation.id)\n .filter((id): id is number => typeof id === 'number');\n\n const repositories: InstalledRepository[] = [];\n for (const id of ids) {\n const page = await getJson(\n send,\n token,\n `${API}/user/installations/${id}/repositories?per_page=${PAGE_SIZE}`,\n );\n for (const item of Array.isArray(page.repositories) ? page.repositories : []) {\n if (\n isRecord(item) &&\n typeof item.full_name === 'string' &&\n typeof item.private === 'boolean'\n ) {\n repositories.push({ fullName: item.full_name, private: item.private });\n }\n }\n }\n return repositories;\n};\n","import { detailOf } from './detail';\nimport { ProviderError, RemoteFile, SyncProvider, VersionConflictError } from './types';\n\nconst API = 'https://www.googleapis.com/drive/v3';\nconst UPLOAD = 'https://www.googleapis.com/upload/drive/v3';\nconst FOLDER_TYPE = 'application/vnd.google-apps.folder';\nconst RATE_LIMIT_REASONS = ['rateLimitExceeded', 'userRateLimitExceeded', 'dailyLimitExceeded'];\n\nexport interface GoogleDriveOptions {\n /** Returns the token. It may refresh the token first, so it is called for every request. */\n getToken: () => Promise<string>;\n fetch?: typeof fetch;\n}\n\ninterface DriveItem {\n id: string;\n createdTime: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst quoted = (value: string): string => `'${value.replace(/[\\\\']/g, '\\\\$&')}'`;\n\nconst oldestFirst = (a: DriveItem, b: DriveItem): number =>\n a.createdTime.localeCompare(b.createdTime) || a.id.localeCompare(b.id);\n\n/** The search can lag behind a create, so the item that was just created is always part of it. */\nconst withOwn = (found: DriveItem[], own: DriveItem): DriveItem[] =>\n (found.some((item) => item.id === own.id) ? found : [...found, own]).sort(oldestFirst);\n\nconst reasonOf = (body: unknown): string | undefined => {\n if (!isRecord(body) || !isRecord(body.error) || !Array.isArray(body.error.errors))\n return undefined;\n const first: unknown = body.error.errors[0];\n return isRecord(first) && typeof first.reason === 'string' ? first.reason : undefined;\n};\n\n/**\n * Keeps the files of an app in the Google Drive of the player, in a visible `Gamerdex` folder. It\n * needs only the `drive.file` scope, which sees the files that this app created.\n *\n * Drive cannot refuse a write made from an old version. Writes are checked in two steps instead:\n * the head revision must equal the expected version before the upload, and the revision list must\n * show the new revision directly on top of the expected one after it. When another device wrote in\n * between, its content is written back as the head and `VersionConflictError` is thrown, so the\n * next sync merges both sides.\n */\nexport class GoogleDriveProvider implements SyncProvider {\n private readonly send: typeof fetch;\n private readonly folders = new Map<string, string>();\n\n constructor(private readonly options: GoogleDriveOptions) {\n this.send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n }\n\n async read(path: string): Promise<RemoteFile | null> {\n const file = await this.findFile(path);\n if (!file) return null;\n const head = await this.headOf(file.id);\n if (head === null) return null;\n return { content: await this.revisionContent(file.id, head), version: head };\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n if (expectedVersion === null) return this.create(path, content);\n\n const file = await this.findFile(path);\n if (!file || (await this.headOf(file.id)) !== expectedVersion) {\n throw new VersionConflictError(path);\n }\n const version = await this.upload(file.id, content);\n if (version === expectedVersion) return { version };\n\n const previous = await this.revisionBefore(file.id, version);\n if (previous === expectedVersion) return { version };\n\n await this.upload(file.id, await this.revisionContent(file.id, previous));\n throw new VersionConflictError(path);\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const file = await this.findFile(path);\n if (!file) return;\n if ((await this.headOf(file.id)) !== expectedVersion) throw new VersionConflictError(path);\n await this.deleteItem(file.id);\n }\n\n private async create(path: string, content: string): Promise<{ version: string }> {\n const { parentId, name } = await this.locate(path, true);\n if ((await this.children(parentId, name)).length > 0) throw new VersionConflictError(path);\n\n const created = await this.createFile(parentId, name, content);\n const [oldest] = withOwn(await this.children(parentId, name), created);\n if (oldest.id !== created.id) {\n await this.deleteItem(created.id);\n throw new VersionConflictError(path);\n }\n return { version: created.version };\n }\n\n private async findFile(path: string): Promise<DriveItem | null> {\n const located = await this.locate(path, false);\n if (!located) return null;\n return (await this.children(located.parentId, located.name))[0] ?? null;\n }\n\n private async locate(path: string, create: true): Promise<{ parentId: string; name: string }>;\n private async locate(\n path: string,\n create: false,\n ): Promise<{ parentId: string; name: string } | null>;\n private async locate(path: string, create: boolean) {\n const parts = path.split('/');\n const name = parts.pop() as string;\n let parentId = 'root';\n let walked = '';\n for (const folder of parts) {\n walked += `/${folder}`;\n const known = this.folders.get(walked);\n if (known) {\n parentId = known;\n continue;\n }\n const id = await this.folderId(parentId, folder, create);\n if (id === null) return null;\n this.folders.set(walked, id);\n parentId = id;\n }\n return { parentId, name };\n }\n\n private async folderId(parentId: string, name: string, create: boolean): Promise<string | null> {\n const found = await this.children(parentId, name, true);\n if (found.length > 0) return found[0].id;\n if (!create) return null;\n\n const response = await this.request('POST', `${API}/files?fields=id,createdTime`, {\n body: JSON.stringify({ name, mimeType: FOLDER_TYPE, parents: [parentId] }),\n headers: { 'Content-Type': 'application/json' },\n });\n const created = this.itemOf(await response.json());\n const [oldest] = withOwn(await this.children(parentId, name, true), created);\n if (oldest.id !== created.id) {\n await this.deleteItem(created.id);\n return oldest.id;\n }\n return created.id;\n }\n\n private async children(parentId: string, name: string, folder = false): Promise<DriveItem[]> {\n const query = [\n `name = ${quoted(name)}`,\n `${quoted(parentId)} in parents`,\n 'trashed = false',\n `mimeType ${folder ? '=' : '!='} '${FOLDER_TYPE}'`,\n ].join(' and ');\n const url = `${API}/files?${new URLSearchParams({\n q: query,\n fields: 'files(id,createdTime)',\n pageSize: '100',\n })}`;\n const body: unknown = await (await this.request('GET', url)).json();\n const files = isRecord(body) && Array.isArray(body.files) ? body.files : [];\n return files\n .filter(\n (item): item is DriveItem =>\n isRecord(item) && typeof item.id === 'string' && typeof item.createdTime === 'string',\n )\n .sort(oldestFirst);\n }\n\n private async headOf(id: string): Promise<string | null> {\n const response = await this.request(\n 'GET',\n `${API}/files/${encodeURIComponent(id)}?fields=headRevisionId,trashed`,\n { allowMissing: true },\n );\n if (response.status === 404) return null;\n const body: unknown = await response.json();\n if (isRecord(body) && body.trashed === true) return null;\n return this.stringField(body, 'headRevisionId');\n }\n\n private async revisionContent(id: string, revision: string): Promise<string> {\n const url = `${API}/files/${encodeURIComponent(id)}/revisions/${encodeURIComponent(revision)}?alt=media`;\n return (await this.request('GET', url)).text();\n }\n\n private async revisionBefore(id: string, revision: string): Promise<string> {\n const url = `${API}/files/${encodeURIComponent(id)}/revisions?${new URLSearchParams({\n fields: 'revisions(id)',\n pageSize: '1000',\n })}`;\n const body: unknown = await (await this.request('GET', url)).json();\n const revisions = (isRecord(body) && Array.isArray(body.revisions) ? body.revisions : [])\n .filter(isRecord)\n .map((item) => item.id);\n const index = revisions.indexOf(revision);\n const previous = revisions[index - 1];\n if (index < 1 || typeof previous !== 'string') {\n throw new ProviderError('server', 'The new revision is not in the revision list.');\n }\n return previous;\n }\n\n private async upload(id: string, content: string): Promise<string> {\n const url = `${UPLOAD}/files/${encodeURIComponent(id)}?uploadType=media&fields=headRevisionId`;\n const response = await this.request('PATCH', url, {\n body: content,\n headers: { 'Content-Type': 'application/json' },\n });\n return this.stringField(await response.json(), 'headRevisionId');\n }\n\n private async createFile(\n parentId: string,\n name: string,\n content: string,\n ): Promise<DriveItem & { version: string }> {\n const boundary = `gamerdex-${crypto.randomUUID()}`;\n const metadata = JSON.stringify({ name, parents: [parentId], mimeType: 'application/json' });\n const body =\n `--${boundary}\\r\\nContent-Type: application/json; charset=UTF-8\\r\\n\\r\\n${metadata}\\r\\n` +\n `--${boundary}\\r\\nContent-Type: application/json\\r\\n\\r\\n${content}\\r\\n--${boundary}--`;\n const response = await this.request(\n 'POST',\n `${UPLOAD}/files?uploadType=multipart&fields=id,headRevisionId,createdTime`,\n { body, headers: { 'Content-Type': `multipart/related; boundary=${boundary}` } },\n );\n const answer: unknown = await response.json();\n return { ...this.itemOf(answer), version: this.stringField(answer, 'headRevisionId') };\n }\n\n private async deleteItem(id: string): Promise<void> {\n await this.request('DELETE', `${API}/files/${encodeURIComponent(id)}`, { allowMissing: true });\n }\n\n private itemOf(body: unknown): DriveItem {\n return { id: this.stringField(body, 'id'), createdTime: this.stringField(body, 'createdTime') };\n }\n\n private stringField(body: unknown, field: string): string {\n const value = isRecord(body) ? body[field] : undefined;\n if (typeof value !== 'string') {\n throw new ProviderError('invalid', `Google Drive answered without ${field}.`);\n }\n return value;\n }\n\n private async request(\n method: string,\n url: string,\n options: { body?: string; headers?: Record<string, string>; allowMissing?: boolean } = {},\n ): Promise<Response> {\n const token = await this.options.getToken();\n let response: Response;\n try {\n response = await this.send(url, {\n method,\n headers: { Authorization: `Bearer ${token}`, ...(options.headers ?? {}) },\n body: options.body,\n });\n } catch {\n throw new ProviderError('network', 'Google Drive could not be reached.');\n }\n if (response.ok || (options.allowMissing && response.status === 404)) return response;\n throw await this.failure(response);\n }\n\n private async failure(response: Response): Promise<ProviderError> {\n const { status } = response;\n const body: unknown = await response\n .clone()\n .json()\n .catch(() => null);\n const detail = await detailOf(response);\n if (status === 401) {\n return new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n const reason = reasonOf(body);\n if (status === 429 || (status === 403 && reason && RATE_LIMIT_REASONS.includes(reason))) {\n const retryAfter = Number(response.headers.get('retry-after'));\n return new ProviderError(\n 'rate-limit',\n 'Google Drive limits the calls.',\n status,\n Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : undefined,\n detail,\n );\n }\n if (status === 403) {\n return new ProviderError(\n 'auth',\n 'The token has no permission for Drive.',\n status,\n undefined,\n detail,\n );\n }\n if (status === 404) {\n return new ProviderError(\n 'not-found',\n 'The file or folder was not found.',\n status,\n undefined,\n detail,\n );\n }\n if (status >= 500) {\n return new ProviderError('server', 'Google Drive failed.', status, undefined, detail);\n }\n return new ProviderError(\n 'invalid',\n `Google Drive answered ${status}.`,\n status,\n undefined,\n detail,\n );\n }\n}\n","/** The only Drive scope that sync asks for. It sees the files that the app created itself. */\nexport const GOOGLE_DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive.file';\n\n/**\n * The address of the Google sign in page. `access_type=offline` and `prompt=consent` make Google\n * send a refresh token, which it otherwise sends only the first time the player agrees.\n */\nexport const googleAuthorizeUrl = (\n clientId: string,\n redirectUri: string,\n state: string,\n): string => {\n const query = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n scope: GOOGLE_DRIVE_SCOPE,\n access_type: 'offline',\n prompt: 'consent',\n state,\n });\n return `https://accounts.google.com/o/oauth2/v2/auth?${query}`;\n};\n","import { detailOf } from './detail';\nimport { ProviderError, RemoteFile, SyncProvider, VersionConflictError } from './types';\n\nconst API = 'https://api.dropboxapi.com/2';\nconst CONTENT = 'https://content.dropboxapi.com/2';\n\nexport interface DropboxOptions {\n /** Returns the token. It may refresh the token first, so it is called for every request. */\n getToken: () => Promise<string>;\n fetch?: typeof fetch;\n}\n\ninterface Failure {\n status: number;\n tags: Set<string>;\n detail: string | undefined;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** A header value must be ASCII, so every other character of the JSON is written as an escape. */\nconst headerJson = (value: unknown): string =>\n JSON.stringify(value).replace(\n /[\\u007f-]/g,\n (char) => `\\\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`,\n );\n\n/** Collects the `.tag` values of an error. Dropbox names each failure by a chain of tags. */\nconst tagsOf = (value: unknown, found = new Set<string>()): Set<string> => {\n if (!isRecord(value)) return found;\n for (const [key, entry] of Object.entries(value)) {\n if (key === '.tag' && typeof entry === 'string') found.add(entry);\n else tagsOf(entry, found);\n }\n return found;\n};\n\nconst retryAfterOf = (response: Response, body: unknown): number | undefined => {\n const header = response.headers.get('retry-after');\n if (header !== null && Number.isFinite(Number(header))) return Number(header);\n const seconds = isRecord(body) && isRecord(body.error) ? body.error.retry_after : undefined;\n return typeof seconds === 'number' ? seconds : undefined;\n};\n\n/**\n * Keeps the files of an app in the Dropbox of the player. The app should have the App folder\n * access type with the `files.content.read` and `files.content.write` scopes, so it sees only its\n * own folder under `Apps`.\n *\n * Dropbox writes with an expected revision. An upload in `update` mode with `strict_conflict`\n * fails when the file changed since, and an upload in `add` mode fails when the file exists.\n */\nexport class DropboxProvider implements SyncProvider {\n private readonly send: typeof fetch;\n\n constructor(private readonly options: DropboxOptions) {\n this.send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n }\n\n async read(path: string): Promise<RemoteFile | null> {\n const response = await this.request(`${CONTENT}/files/download`, {\n 'Dropbox-API-Arg': headerJson({ path: `/${path}` }),\n });\n if (!response.ok) {\n const failure = await this.failure(response);\n if (failure.tags.has('not_found')) return null;\n throw this.unexpected(failure);\n }\n const result: unknown = JSON.parse(response.headers.get('dropbox-api-result') ?? 'null');\n if (!isRecord(result) || typeof result.rev !== 'string') {\n throw this.invalid('The file has no version.');\n }\n return { content: await response.text(), version: result.rev };\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n const mode =\n expectedVersion === null ? { '.tag': 'add' } : { '.tag': 'update', update: expectedVersion };\n const response = await this.request(\n `${CONTENT}/files/upload`,\n {\n 'Content-Type': 'application/octet-stream',\n 'Dropbox-API-Arg': headerJson({\n path: `/${path}`,\n mode,\n autorename: false,\n mute: true,\n strict_conflict: true,\n }),\n },\n content,\n );\n if (!response.ok) {\n const failure = await this.failure(response);\n if (failure.tags.has('conflict')) throw new VersionConflictError(path);\n throw this.unexpected(failure);\n }\n const result: unknown = await response.json().catch(() => null);\n if (!isRecord(result) || typeof result.rev !== 'string') {\n throw this.invalid('The write returned no version.');\n }\n return { version: result.rev };\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const response = await this.request(\n `${API}/files/delete_v2`,\n { 'Content-Type': 'application/json' },\n JSON.stringify({ path: `/${path}`, parent_rev: expectedVersion }),\n );\n if (response.ok) return;\n const failure = await this.failure(response);\n if (failure.tags.has('conflict')) throw new VersionConflictError(path);\n if (failure.tags.has('not_found')) {\n if ((await this.read(path)) !== null) throw new VersionConflictError(path);\n return;\n }\n throw this.unexpected(failure);\n }\n\n private async request(\n url: string,\n headers: Record<string, string>,\n body?: string,\n ): Promise<Response> {\n const token = await this.options.getToken();\n try {\n return await this.send(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${token}`, ...headers },\n body,\n });\n } catch {\n throw new ProviderError('network', 'Dropbox could not be reached.');\n }\n }\n\n /**\n * Reads a failed answer. Dropbox reports a failure that belongs to the call, such as a missing\n * file or a conflict, with status 409. The caller decides what that means. Every other status\n * is a failure of the request and is thrown here.\n */\n private async failure(response: Response): Promise<Failure> {\n const { status } = response;\n const body: unknown = await response\n .clone()\n .json()\n .catch(() => null);\n const detail = await detailOf(response);\n if (status === 409) return { status, tags: tagsOf(isRecord(body) ? body.error : null), detail };\n if (status === 401) {\n throw new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n if (status === 403) {\n throw new ProviderError(\n 'auth',\n 'The token has no permission for Dropbox.',\n status,\n undefined,\n detail,\n );\n }\n if (status === 429) {\n throw new ProviderError(\n 'rate-limit',\n 'Dropbox limits the calls.',\n status,\n retryAfterOf(response, body),\n detail,\n );\n }\n if (status >= 500) {\n throw new ProviderError('server', 'Dropbox failed.', status, undefined, detail);\n }\n throw this.invalid(`Dropbox answered ${status}.`, status, detail);\n }\n\n private unexpected({ status, tags, detail }: Failure): ProviderError {\n if (tags.has('too_many_write_operations')) {\n return new ProviderError(\n 'rate-limit',\n 'Dropbox limits the writes.',\n status,\n undefined,\n detail,\n );\n }\n if (tags.has('no_write_permission')) {\n return new ProviderError(\n 'auth',\n 'The token has no permission for Dropbox.',\n status,\n undefined,\n detail,\n );\n }\n if (tags.has('insufficient_space')) {\n return this.invalid('Dropbox has no space left.', status, detail);\n }\n return this.invalid(`Dropbox answered ${status}.`, status, detail);\n }\n\n private invalid(message: string, status?: number, detail?: string): ProviderError {\n return new ProviderError('invalid', message, status, undefined, detail);\n }\n}\n","import { detailOf } from './detail';\nimport { ProviderError, RemoteFile, SyncProvider, VersionConflictError } from './types';\n\nconst APP_FOLDER = 'https://graph.microsoft.com/v1.0/me/drive/special/approot';\n\nexport interface OneDriveOptions {\n /** Returns the token. It may refresh the token first, so it is called for every request. */\n getToken: () => Promise<string>;\n fetch?: typeof fetch;\n}\n\ninterface ItemInfo {\n eTag: string;\n downloadUrl: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst encodePath = (path: string): string => path.split('/').map(encodeURIComponent).join('/');\n\nconst retryAfterOf = (response: Response): number | undefined => {\n const header = response.headers.get('retry-after');\n return header !== null && Number.isFinite(Number(header)) ? Number(header) : undefined;\n};\n\n/**\n * Keeps the files of an app in the OneDrive of the player, in the app folder under `Apps`. It\n * needs only the `Files.ReadWrite.AppFolder` scope, so it sees no other file. The app folder\n * exists for personal Microsoft accounts.\n *\n * A write sends the `eTag` that was read in an `If-Match` header, and a new file is created with\n * `conflictBehavior=fail`. OneDrive answers 412 and 409 when the file changed or exists.\n */\nexport class OneDriveProvider implements SyncProvider {\n private readonly send: typeof fetch;\n\n constructor(private readonly options: OneDriveOptions) {\n this.send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n }\n\n async read(path: string): Promise<RemoteFile | null> {\n const info = await this.info(path);\n if (info === null) return null;\n const content = await this.fetchContent(info.downloadUrl);\n return content === null ? null : { content, version: info.eTag };\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n const url =\n expectedVersion === null\n ? `${this.itemUrl(path)}:/content?@microsoft.graph.conflictBehavior=fail`\n : `${this.itemUrl(path)}:/content`;\n const response = await this.request(\n 'PUT',\n url,\n { 'Content-Type': 'text/plain', ...this.ifMatch(expectedVersion) },\n content,\n );\n const expectedFileIsGone = expectedVersion !== null && response.status === 404;\n if (response.status === 412 || response.status === 409 || expectedFileIsGone) {\n throw new VersionConflictError(path);\n }\n await this.assertOk(response);\n if (expectedVersion !== null && response.status === 201) throw new VersionConflictError(path);\n const result: unknown = await response.json().catch(() => null);\n if (!isRecord(result) || typeof result.eTag !== 'string') {\n throw this.invalid('The write returned no version.');\n }\n return { version: result.eTag };\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const response = await this.request(\n 'DELETE',\n this.itemUrl(path),\n this.ifMatch(expectedVersion),\n );\n if (response.status === 404) return;\n if (response.status === 412) throw new VersionConflictError(path);\n await this.assertOk(response);\n }\n\n private itemUrl(path: string): string {\n return `${APP_FOLDER}:/${encodePath(path)}`;\n }\n\n private ifMatch(version: string | null): Record<string, string> {\n return version === null ? {} : { 'If-Match': version };\n }\n\n private async info(path: string): Promise<ItemInfo | null> {\n const response = await this.request('GET', this.itemUrl(path));\n if (response.status === 404) return null;\n await this.assertOk(response);\n const body: unknown = await response.json().catch(() => null);\n if (!isRecord(body) || typeof body.eTag !== 'string') {\n throw this.invalid('The file has no version.');\n }\n const url = body['@microsoft.graph.downloadUrl'];\n if (typeof url !== 'string') throw this.invalid('The file has no download address.');\n return { eTag: body.eTag, downloadUrl: url };\n }\n\n /** The download address is signed, so it must not carry the token. */\n private async fetchContent(url: string): Promise<string | null> {\n let response: Response;\n try {\n response = await this.send(url);\n } catch {\n throw new ProviderError('network', 'OneDrive could not be reached.');\n }\n if (response.status === 404) return null;\n await this.assertOk(response);\n return response.text();\n }\n\n private async request(\n method: string,\n url: string,\n headers: Record<string, string> = {},\n body?: string,\n ): Promise<Response> {\n const token = await this.options.getToken();\n try {\n return await this.send(url, {\n method,\n headers: { Authorization: `Bearer ${token}`, ...headers },\n body,\n });\n } catch {\n throw new ProviderError('network', 'OneDrive could not be reached.');\n }\n }\n\n private async assertOk(response: Response): Promise<void> {\n if (response.ok) return;\n const { status } = response;\n const detail = await detailOf(response);\n if (status === 401) {\n throw new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n if (status === 403) {\n throw new ProviderError(\n 'auth',\n 'The token has no permission for OneDrive.',\n status,\n undefined,\n detail,\n );\n }\n if (status === 429 || (status === 503 && retryAfterOf(response) !== undefined)) {\n throw new ProviderError(\n 'rate-limit',\n 'OneDrive limits the calls.',\n status,\n retryAfterOf(response),\n detail,\n );\n }\n if (status === 404) {\n throw new ProviderError(\n 'not-found',\n 'The app folder was not found.',\n status,\n undefined,\n detail,\n );\n }\n if (status >= 500) {\n throw new ProviderError('server', 'OneDrive failed.', status, undefined, detail);\n }\n throw this.invalid(`OneDrive answered ${status}.`, status, detail);\n }\n\n private invalid(message: string, status?: number, detail?: string): ProviderError {\n return new ProviderError('invalid', message, status, undefined, detail);\n }\n}\n","/** The scopes that sync asks for. Together with the App folder access type, they reach one folder. */\nexport const DROPBOX_SCOPES = ['files.content.read', 'files.content.write'] as const;\n\n/**\n * The address of the Dropbox sign in page. `token_access_type=offline` makes Dropbox send a\n * refresh token, which does not expire until the player removes the access.\n */\nexport const dropboxAuthorizeUrl = (\n clientId: string,\n redirectUri: string,\n state: string,\n): string => {\n const query = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n token_access_type: 'offline',\n scope: DROPBOX_SCOPES.join(' '),\n state,\n });\n return `https://www.dropbox.com/oauth2/authorize?${query}`;\n};\n","/** The file scope that sync asks for. It reaches only the app folder of the player. */\nexport const ONEDRIVE_FILE_SCOPE = 'Files.ReadWrite.AppFolder';\n\n/** The file scope, and `offline_access`, which makes Microsoft send a refresh token. */\nexport const ONEDRIVE_SCOPES = [ONEDRIVE_FILE_SCOPE, 'offline_access'] as const;\n\n/** The address of the Microsoft sign in page for personal accounts. */\nexport const microsoftAuthorizeUrl = (\n clientId: string,\n redirectUri: string,\n state: string,\n): string => {\n const query = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n response_mode: 'query',\n scope: ONEDRIVE_SCOPES.join(' '),\n state,\n });\n return `https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?${query}`;\n};\n"],"mappings":"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","names":["own","own","childSchema","next","isRecord","isRecord","metaVersion","isRecord","API","isRecord","API","isRecord","own","API","isRecord","retryAfterOf","isRecord","encodePath","retryAfterOf"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/layout.ts","../src/meta.ts","../src/merge/equal.ts","../src/merge/cloud.ts","../src/merge/merge.ts","../src/merge/resolve.ts","../src/provider/base64.ts","../src/provider/detail.ts","../src/provider/types.ts","../src/provider/github.ts","../src/provider/memory.ts","../src/sync/sync.ts","../src/auth/app.ts","../src/auth/tokens.ts","../src/auth/source.ts","../src/auth/urls.ts","../src/provider/repositories.ts","../src/provider/drive.ts","../src/auth/google.ts","../src/provider/dropbox.ts","../src/provider/onedrive.ts","../src/auth/dropbox.ts","../src/auth/microsoft.ts"],"sourcesContent":["export * from './layout';\nexport * from './meta';\nexport * from './merge/equal';\nexport * from './merge/merge';\nexport * from './merge/resolve';\nexport * from './merge/types';\nexport * from './provider/base64';\nexport * from './provider/github';\nexport * from './provider/memory';\nexport * from './provider/types';\nexport * from './sync/sync';\nexport * from './merge/cloud';\nexport * from './auth/app';\nexport * from './auth/source';\nexport * from './auth/tokens';\nexport * from './auth/urls';\nexport * from './provider/repositories';\nexport * from './provider/drive';\nexport * from './auth/google';\nexport * from './provider/dropbox';\nexport * from './provider/onedrive';\nexport * from './auth/dropbox';\nexport * from './auth/microsoft';\n","export const ROOT_FOLDER = 'Gamerdex';\nexport const DATA_FILE = 'data.json';\nexport const META_FILE = 'meta.json';\n\nconst APP_ID = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\n/** An app id names a folder, so it is lowercase words joined by single dashes. */\nexport const isAppId = (value: string): boolean => APP_ID.test(value);\n\nconst folderOf = (appId: string): string => {\n if (!isAppId(appId)) throw new RangeError(`Invalid app id: ${appId}`);\n return `${ROOT_FOLDER}/${appId}`;\n};\n\n/** The path of the saved data of an app, such as `Gamerdex/whiteout-survival/data.json`. */\nexport const dataPath = (appId: string): string => `${folderOf(appId)}/${DATA_FILE}`;\n\n/** The path of the bookkeeping file of an app, next to its saved data. */\nexport const metaPath = (appId: string): string => `${folderOf(appId)}/${META_FILE}`;\n","export const META_FORMAT = 1;\n\nexport interface DeviceRecord {\n name: string;\n /** ISO time of the last sync of the device. */\n lastSeenAt: string;\n}\n\n/**\n * The bookkeeping file beside the saved data. It holds no game data and no token. A revision only\n * counts up, so a device with a wrong clock cannot make old data look new.\n */\nexport interface SyncMeta {\n format: typeof META_FORMAT;\n appId: string;\n /** One counter for each chief id. A chief that was removed keeps its counter. */\n revisions: Record<string, number>;\n settingsRevision: number;\n /** Chief ids removed on purpose, with the revision of the removal. */\n removed: Record<string, number>;\n devices: Record<string, DeviceRecord>;\n lastSyncAt: string | null;\n}\n\nexport type ParsedMeta = { ok: true; meta: SyncMeta } | { ok: false; error: string };\n\nexport const emptyMeta = (appId: string): SyncMeta => ({\n format: META_FORMAT,\n appId,\n revisions: {},\n settingsRevision: 0,\n removed: {},\n devices: {},\n lastSyncAt: null,\n});\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isCounter = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value >= 0;\n\nconst isCounterMap = (value: unknown): value is Record<string, number> =>\n isRecord(value) && Object.values(value).every(isCounter);\n\nconst isDevice = (value: unknown): value is DeviceRecord =>\n isRecord(value) && typeof value.name === 'string' && typeof value.lastSeenAt === 'string';\n\n/** Reads the meta file and checks the whole shape. Nothing is returned for a file that fails. */\nexport const parseMeta = (text: string, appId: string): ParsedMeta => {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return { ok: false, error: 'The meta file is not valid JSON.' };\n }\n if (!isRecord(value)) return { ok: false, error: 'The meta file is not an object.' };\n if (value.format !== META_FORMAT) return { ok: false, error: 'The meta format is not known.' };\n if (value.appId !== appId) return { ok: false, error: 'The meta file belongs to another app.' };\n if (!isCounterMap(value.revisions)) return { ok: false, error: 'The revisions are not valid.' };\n if (!isCounter(value.settingsRevision)) {\n return { ok: false, error: 'The settings revision is not valid.' };\n }\n if (!isCounterMap(value.removed)) return { ok: false, error: 'The removed list is not valid.' };\n if (!isRecord(value.devices) || !Object.values(value.devices).every(isDevice)) {\n return { ok: false, error: 'The devices are not valid.' };\n }\n if (value.lastSyncAt !== null && typeof value.lastSyncAt !== 'string') {\n return { ok: false, error: 'The last sync time is not valid.' };\n }\n return { ok: true, meta: value as unknown as SyncMeta };\n};\n\n/** Counts one change of a chief. A chief that returns after a removal leaves the removed list. */\nexport const withChiefRevision = (meta: SyncMeta, chiefId: string): SyncMeta => {\n return {\n ...meta,\n revisions: { ...meta.revisions, [chiefId]: (meta.revisions[chiefId] ?? 0) + 1 },\n removed: Object.fromEntries(Object.entries(meta.removed).filter(([id]) => id !== chiefId)),\n };\n};\n\n/** Counts the removal of a chief. The counter keeps rising so an old copy cannot return it. */\nexport const withChiefRemoved = (meta: SyncMeta, chiefId: string): SyncMeta => {\n const revision = (meta.revisions[chiefId] ?? 0) + 1;\n return {\n ...meta,\n revisions: { ...meta.revisions, [chiefId]: revision },\n removed: { ...meta.removed, [chiefId]: revision },\n };\n};\n\nexport const withSettingsRevision = (meta: SyncMeta): SyncMeta => ({\n ...meta,\n settingsRevision: meta.settingsRevision + 1,\n});\n\nexport const withDevice = (meta: SyncMeta, deviceId: string, device: DeviceRecord): SyncMeta => ({\n ...meta,\n devices: { ...meta.devices, [deviceId]: device },\n lastSyncAt: device.lastSeenAt,\n});\n","const isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** Deep equality of two JSON values. The order of the keys of an object does not matter. */\nexport const equal = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n if (Array.isArray(a)) {\n return Array.isArray(b) && a.length === b.length && a.every((item, i) => equal(item, b[i]));\n }\n if (isObject(a) && isObject(b)) {\n const keys = Object.keys(a);\n return (\n keys.length === Object.keys(b).length &&\n keys.every((key) => Object.hasOwn(b, key) && equal(a[key], b[key]))\n );\n }\n return false;\n};\n\nexport { isObject };\n","import { isObject } from './equal';\nimport { Schema } from './types';\n\nconst own = (value: Record<string, unknown>, key: string): unknown =>\n Object.hasOwn(value, key) ? value[key] : undefined;\n\nconst childSchema = (schema: Extract<Schema, { type: 'object' }>, key: string): Schema =>\n (schema.fields && Object.hasOwn(schema.fields, key) ? schema.fields[key] : undefined) ??\n schema.each ?? { type: 'record' };\n\nconst holdsDeviceValue = (schema: Schema): boolean => {\n if (schema.type === 'local') return true;\n if (schema.type === 'keyed') return holdsDeviceValue(schema.item);\n if (schema.type !== 'object') return false;\n return (\n (schema.each !== undefined && holdsDeviceValue(schema.each)) ||\n Object.values(schema.fields ?? {}).some(holdsDeviceValue)\n );\n};\n\n/**\n * Returns the merged data as it must be stored in the cloud: every part with the `local` strategy\n * keeps the value that the cloud already holds, so a value of one device never changes the cloud.\n */\nexport const restoreCloudValues = (merged: unknown, remote: unknown, schema: Schema): unknown => {\n if (schema.type === 'local') return remote;\n if (!holdsDeviceValue(schema)) return merged;\n\n if (schema.type === 'object' && isObject(merged) && (isObject(remote) || remote === undefined)) {\n const cloud = remote ?? {};\n const result: Record<string, unknown> = {};\n for (const key of new Set([...Object.keys(merged), ...Object.keys(cloud)])) {\n const child = childSchema(schema, key);\n const value = restoreCloudValues(own(merged, key), own(cloud, key), child);\n if (value !== undefined) result[key] = value;\n }\n return result;\n }\n\n if (schema.type === 'keyed' && Array.isArray(merged) && Array.isArray(remote)) {\n const remoteById = new Map(\n remote.filter(isObject).map((item) => [item[schema.key], item] as const),\n );\n return merged.map((item) =>\n isObject(item)\n ? restoreCloudValues(item, remoteById.get(item[schema.key]), schema.item)\n : item,\n );\n }\n return merged;\n};\n","import { restoreCloudValues } from './cloud';\nimport { equal, isObject } from './equal';\nimport { Conflict, MergeInput, MergeResult, Path, Schema, Side } from './types';\n\ninterface Context {\n prefer: Side;\n conflicts: Conflict[];\n}\n\n/** Reads an own key only, so an id such as `constructor` never reads from the prototype. */\nconst own = <T>(map: Record<string, T> | undefined, key: string): T | undefined =>\n map !== undefined && Object.hasOwn(map, key) ? map[key] : undefined;\n\nconst record = (\n base: unknown,\n local: unknown,\n remote: unknown,\n path: Path,\n context: Context,\n): unknown => {\n context.conflicts.push({\n path,\n kind: 'both-changed',\n base,\n local,\n remote,\n taken: context.prefer,\n });\n return context.prefer === 'local' ? local : remote;\n};\n\nconst joinLists = (base: unknown[], local: unknown[], remote: unknown[]): unknown[] => {\n const has = (list: unknown[], value: unknown) => list.some((item) => equal(item, value));\n const kept = (value: unknown) => {\n const inBase = has(base, value);\n const inLocal = has(local, value);\n const inRemote = has(remote, value);\n return inBase ? inLocal && inRemote : inLocal || inRemote;\n };\n const result: unknown[] = [];\n for (const value of [...local, ...remote]) {\n if (kept(value) && !has(result, value)) result.push(value);\n }\n return result;\n};\n\nconst entriesOf = (list: unknown[], key: string): Map<string, unknown> | null => {\n const entries = new Map<string, unknown>();\n for (const item of list) {\n if (!isObject(item) || typeof item[key] !== 'string') return null;\n entries.set(item[key], item);\n }\n return entries;\n};\n\nconst mergeEntries = (\n ids: string[],\n pick: (id: string) => [unknown, unknown, unknown],\n schemaOf: (id: string) => Schema,\n path: Path,\n context: Context,\n deleteWins: boolean,\n): Map<string, unknown> => {\n const merged = new Map<string, unknown>();\n for (const id of ids) {\n const [base, local, remote] = pick(id);\n const value = mergeNode(base, local, remote, schemaOf(id), [...path, id], context, deleteWins);\n if (value !== undefined) merged.set(id, value);\n }\n return merged;\n};\n\nconst orderedIds = (local: Iterable<string>, remote: Iterable<string>, base: Iterable<string>) => [\n ...new Set([...local, ...remote, ...base]),\n];\n\nconst mergeNode = (\n base: unknown,\n local: unknown,\n remote: unknown,\n schema: Schema,\n path: Path,\n context: Context,\n deleteWins: boolean,\n): unknown => {\n if (schema.type === 'local') return local;\n if (equal(local, remote)) return local;\n if (equal(local, base)) return remote;\n if (equal(remote, base)) return local;\n\n if (local === undefined || remote === undefined) {\n if (deleteWins) return undefined;\n const taken: Side = local === undefined ? 'remote' : 'local';\n context.conflicts.push({ path, kind: 'delete-edit', base, local, remote, taken });\n return taken === 'local' ? local : remote;\n }\n\n switch (schema.type) {\n case 'max': {\n const l = schema.order(local);\n const r = schema.order(remote);\n if (l === r) return record(base, local, remote, path, context);\n return l > r ? local : remote;\n }\n case 'join':\n if (Array.isArray(local) && Array.isArray(remote)) {\n return joinLists(Array.isArray(base) ? base : [], local, remote);\n }\n return record(base, local, remote, path, context);\n case 'object': {\n if (!isObject(local) || !isObject(remote)) return record(base, local, remote, path, context);\n const baseObject = isObject(base) ? base : {};\n const ids = orderedIds(Object.keys(local), Object.keys(remote), Object.keys(baseObject));\n const merged = mergeEntries(\n ids,\n (id) => [own(baseObject, id), own(local, id), own(remote, id)],\n (id) => own(schema.fields, id) ?? schema.each ?? { type: 'record' },\n path,\n context,\n deleteWins || schema.deleteWins === true,\n );\n return Object.fromEntries(merged);\n }\n case 'keyed': {\n const localEntries = Array.isArray(local) ? entriesOf(local, schema.key) : null;\n const remoteEntries = Array.isArray(remote) ? entriesOf(remote, schema.key) : null;\n const baseEntries = (Array.isArray(base) ? entriesOf(base, schema.key) : null) ?? new Map();\n if (!localEntries || !remoteEntries) return record(base, local, remote, path, context);\n const ids = orderedIds(localEntries.keys(), remoteEntries.keys(), baseEntries.keys());\n const merged = mergeEntries(\n ids,\n (id) => [baseEntries.get(id), localEntries.get(id), remoteEntries.get(id)],\n () => schema.item,\n path,\n context,\n deleteWins || schema.deleteWins === true,\n );\n return [...merged.values()];\n }\n case 'record':\n return record(base, local, remote, path, context);\n }\n};\n\n/**\n * Merges the data of this device and the data of the cloud against the data of the last sync.\n * A part that only one side changed takes that change. A part that both sides changed merges by its\n * schema, and a part that cannot merge is reported in `conflicts` with the preferred side already\n * in `merged`.\n */\nexport const merge = ({\n base,\n local,\n remote,\n schema,\n prefer = 'remote',\n}: MergeInput): MergeResult => {\n const context: Context = { prefer, conflicts: [] };\n const merged = mergeNode(base, local, remote, schema, [], context, false);\n return {\n merged,\n cloud: restoreCloudValues(merged, remote, schema),\n conflicts: context.conflicts,\n };\n};\n","import { isObject } from './equal';\nimport { Conflict, Schema, Side } from './types';\n\nconst own = (value: Record<string, unknown>, key: string): unknown =>\n Object.hasOwn(value, key) ? value[key] : undefined;\n\nconst childSchema = (schema: Schema, step: string): Schema => {\n if (schema.type === 'object') {\n const named =\n schema.fields && Object.hasOwn(schema.fields, step) ? schema.fields[step] : undefined;\n return named ?? schema.each ?? { type: 'record' };\n }\n return { type: 'record' };\n};\n\nconst setAt = (data: unknown, schema: Schema, path: string[], value: unknown): unknown => {\n if (path.length === 0) return value;\n const [step, ...rest] = path;\n if (schema.type === 'keyed') {\n const list = Array.isArray(data) ? data : [];\n const index = list.findIndex((item) => isObject(item) && item[schema.key] === step);\n const next = setAt(index >= 0 ? list[index] : undefined, schema.item, rest, value);\n if (index < 0) return next === undefined ? list : [...list, next];\n if (next === undefined) return list.filter((_, i) => i !== index);\n return list.map((item, i) => (i === index ? next : item));\n }\n const object = isObject(data) ? data : {};\n const next = setAt(own(object, step), childSchema(schema, step), rest, value);\n if (next === undefined) {\n return Object.fromEntries(Object.entries(object).filter(([key]) => key !== step));\n }\n return { ...object, [step]: next };\n};\n\n/**\n * Puts the value of one side of a conflict into the merged data. The player uses this to pick the\n * side that the merge did not take. A side that holds no value removes the entry.\n */\nexport const resolveConflict = (\n merged: unknown,\n schema: Schema,\n conflict: Conflict,\n side: Side,\n): unknown => setAt(merged, schema, conflict.path, conflict[side]);\n","const CHUNK = 0x8000;\n\n/** Encodes text as base64 from its UTF-8 bytes. It works in the browser and in Node. */\nexport const encodeBase64 = (text: string): string => {\n const bytes = new TextEncoder().encode(text);\n let binary = '';\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));\n }\n return btoa(binary);\n};\n\n/** Decodes base64 to text. Line breaks in the base64 text are ignored. */\nexport const decodeBase64 = (base64: string): string => {\n const binary = atob(base64.replace(/\\s/g, ''));\n return new TextDecoder().decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));\n};\n","const MAX_DETAIL_LENGTH = 300;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Reads the message that a provider sends with a failed request, for the logs and the player.\n * GitHub sends `{ message }`, Google and Microsoft send `{ error: { message } }` and Dropbox sends\n * `{ error_summary }`.\n */\nexport const detailOf = async (response: Response): Promise<string | undefined> => {\n const body: unknown = await response.json().catch(() => null);\n if (!isRecord(body)) return undefined;\n const nested = isRecord(body.error) ? body.error.message : undefined;\n const message = nested ?? body.message ?? body.error_summary;\n return typeof message === 'string' ? message.slice(0, MAX_DETAIL_LENGTH) : undefined;\n};\n","/** A file in the cloud. The version changes with every write and settles which write is newer. */\nexport interface RemoteFile {\n content: string;\n version: string;\n}\n\n/**\n * Where the files of an app are kept. A provider reads, writes and removes whole files by path, and\n * refuses a write that was made from an old version of the file. That refusal is what keeps two\n * devices from overwriting each other.\n */\nexport interface SyncProvider {\n /** Returns null when the file does not exist. */\n read(path: string): Promise<RemoteFile | null>;\n /**\n * Writes the file. The expected version is the version that was read, or null to create a file\n * that must not exist yet. Throws `VersionConflictError` when the file changed since.\n */\n write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }>;\n /** Removes the file. Throws `VersionConflictError` when the file changed since. */\n remove(path: string, expectedVersion: string): Promise<void>;\n}\n\n/** The file changed in the cloud after it was read. Read it again and merge. */\nexport class VersionConflictError extends Error {\n constructor(public readonly path: string) {\n super(`The file ${path} changed in the cloud.`);\n this.name = 'VersionConflictError';\n }\n}\n\nexport type ProviderErrorKind =\n /** The token is missing, expired, revoked or has no permission. The player signs in again. */\n | 'auth'\n /** The provider limits the calls for now. Try again after `retryAfterSeconds`. */\n | 'rate-limit'\n /** The repository or folder does not exist, or the token cannot see it. */\n | 'not-found'\n /** The device is offline or the provider cannot be reached. */\n | 'network'\n /** The provider failed. A later try may work. */\n | 'server'\n /** The provider answered with something this package does not understand. */\n | 'invalid';\n\nexport class ProviderError extends Error {\n constructor(\n public readonly kind: ProviderErrorKind,\n message: string,\n public readonly status?: number,\n public readonly retryAfterSeconds?: number,\n /** The message that the provider sent with the failed request, when it sent one. */\n public readonly detail?: string,\n ) {\n super(message);\n this.name = 'ProviderError';\n }\n}\n","import { decodeBase64, encodeBase64 } from './base64';\nimport { detailOf } from './detail';\nimport { ProviderError, RemoteFile, SyncProvider, VersionConflictError } from './types';\n\nconst API = 'https://api.github.com';\nconst API_VERSION = '2022-11-28';\n\nexport interface GitHubOptions {\n /** The owner and the name of the repository, such as `chiefpansancolt/gamerdex-data`. */\n repository: string;\n /** Returns the token. It may refresh the token first, so it is called for every request. */\n getToken: () => Promise<string>;\n /** The branch to use. Without it GitHub uses the default branch of the repository. */\n branch?: string;\n /** The message of the commit that a write or a removal makes. */\n commitMessage?: (path: string) => string;\n fetch?: typeof fetch;\n}\n\nexport interface RepositoryCheck {\n private: boolean;\n canWrite: boolean;\n defaultBranch: string;\n}\n\ninterface ContentsResponse {\n sha?: unknown;\n content?: unknown;\n encoding?: unknown;\n}\n\nconst encodePath = (path: string): string => path.split('/').map(encodeURIComponent).join('/');\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst retryAfterOf = (response: Response): number | undefined => {\n const header = response.headers.get('retry-after');\n if (header !== null && Number.isFinite(Number(header))) return Number(header);\n const reset = response.headers.get('x-ratelimit-reset');\n if (reset !== null && Number.isFinite(Number(reset))) {\n return Math.max(0, Number(reset) - Math.floor(Date.now() / 1000));\n }\n return undefined;\n};\n\n/** Keeps the files of an app in a private GitHub repository, through the contents API. */\nexport class GitHubProvider implements SyncProvider {\n private readonly send: typeof fetch;\n\n constructor(private readonly options: GitHubOptions) {\n this.send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n }\n\n async read(path: string): Promise<RemoteFile | null> {\n const response = await this.request('GET', this.contentsUrl(path));\n if (response.status === 404) return null;\n await this.assertOk(response);\n const body = (await response.json()) as ContentsResponse;\n if (typeof body.sha !== 'string') throw this.invalid('The file has no version.');\n if (body.encoding === 'base64' && typeof body.content === 'string') {\n return { content: decodeBase64(body.content), version: body.sha };\n }\n if (body.encoding === 'none') {\n const raw = await this.request('GET', this.contentsUrl(path), undefined, {\n Accept: 'application/vnd.github.raw+json',\n });\n await this.assertOk(raw);\n return { content: await raw.text(), version: body.sha };\n }\n throw this.invalid('The file content is not in a known encoding.');\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n const body: Record<string, unknown> = {\n message: this.message(path),\n content: encodeBase64(content),\n };\n if (expectedVersion !== null) body.sha = expectedVersion;\n if (this.options.branch) body.branch = this.options.branch;\n const response = await this.request('PUT', this.contentsUrl(path), body);\n if (response.status === 409 || response.status === 422) throw new VersionConflictError(path);\n await this.assertOk(response);\n const result = (await response.json()) as { content?: { sha?: unknown } };\n const version = result.content?.sha;\n if (typeof version !== 'string') throw this.invalid('The write returned no version.');\n return { version };\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const body: Record<string, unknown> = { message: this.message(path), sha: expectedVersion };\n if (this.options.branch) body.branch = this.options.branch;\n const response = await this.request('DELETE', this.contentsUrl(path), body);\n if (response.status === 404) return;\n if (response.status === 409 || response.status === 422) throw new VersionConflictError(path);\n await this.assertOk(response);\n }\n\n /**\n * Checks that the repository can be used: it exists, the token can write to it, and it is\n * private. The saved data of a player must never sit in a public repository.\n */\n async checkRepository(): Promise<RepositoryCheck> {\n const response = await this.request('GET', `${API}/repos/${this.options.repository}`);\n await this.assertOk(response);\n const body: unknown = await response.json();\n if (!isRecord(body) || typeof body.private !== 'boolean') {\n throw this.invalid('The repository answer is not understood.');\n }\n const permissions = isRecord(body.permissions) ? body.permissions : {};\n return {\n private: body.private,\n canWrite: permissions.push === true,\n defaultBranch: typeof body.default_branch === 'string' ? body.default_branch : 'main',\n };\n }\n\n private contentsUrl(path: string): string {\n const base = `${API}/repos/${this.options.repository}/contents/${encodePath(path)}`;\n return this.options.branch ? `${base}?ref=${encodeURIComponent(this.options.branch)}` : base;\n }\n\n private message(path: string): string {\n return this.options.commitMessage?.(path) ?? `Gamerdex sync: ${path}`;\n }\n\n private async request(\n method: string,\n url: string,\n body?: Record<string, unknown>,\n headers: Record<string, string> = {},\n ): Promise<Response> {\n const token = await this.options.getToken();\n try {\n return await this.send(url, {\n method,\n headers: {\n Accept: 'application/vnd.github+json',\n Authorization: `Bearer ${token}`,\n 'X-GitHub-Api-Version': API_VERSION,\n ...(body ? { 'Content-Type': 'application/json' } : {}),\n ...headers,\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n } catch {\n throw new ProviderError('network', 'GitHub could not be reached.');\n }\n }\n\n private async assertOk(response: Response): Promise<void> {\n if (response.ok) return;\n const { status } = response;\n const detail = await detailOf(response);\n if (status === 401) {\n throw new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n if (status === 403 || status === 429) {\n const limited = status === 429 || response.headers.get('x-ratelimit-remaining') === '0';\n if (limited) {\n throw new ProviderError(\n 'rate-limit',\n 'GitHub limits the calls.',\n status,\n retryAfterOf(response),\n detail,\n );\n }\n throw new ProviderError(\n 'auth',\n 'The token has no permission for the repository.',\n status,\n undefined,\n detail,\n );\n }\n if (status === 404) {\n throw new ProviderError(\n 'not-found',\n 'The repository was not found.',\n status,\n undefined,\n detail,\n );\n }\n if (status >= 500) {\n throw new ProviderError('server', 'GitHub failed.', status, undefined, detail);\n }\n throw this.invalid(`GitHub answered ${status}.`, status, detail);\n }\n\n private invalid(message: string, status?: number, detail?: string): ProviderError {\n return new ProviderError('invalid', message, status, undefined, detail);\n }\n}\n","import { RemoteFile, SyncProvider, VersionConflictError } from './types';\n\n/**\n * A provider that keeps the files in memory. It follows the same version rules as a real provider,\n * so the sync flow of an app can be tested without a network.\n */\nexport class MemoryProvider implements SyncProvider {\n private files = new Map<string, { content: string; counter: number }>();\n private counter = 0;\n\n async read(path: string): Promise<RemoteFile | null> {\n const file = this.files.get(path);\n return file ? { content: file.content, version: String(file.counter) } : null;\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n const current = this.files.get(path);\n const currentVersion = current ? String(current.counter) : null;\n if (currentVersion !== expectedVersion) throw new VersionConflictError(path);\n this.counter += 1;\n this.files.set(path, { content, counter: this.counter });\n return { version: String(this.counter) };\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const current = this.files.get(path);\n if (current && String(current.counter) !== expectedVersion) {\n throw new VersionConflictError(path);\n }\n this.files.delete(path);\n }\n\n /** The paths of the files that exist, in the order they were first written. */\n paths(): string[] {\n return [...this.files.keys()];\n }\n}\n","import { dataPath, metaPath } from '../layout';\nimport { restoreCloudValues } from '../merge/cloud';\nimport { equal } from '../merge/equal';\nimport { merge } from '../merge/merge';\nimport { Conflict, Schema } from '../merge/types';\nimport { emptyMeta, parseMeta, SyncMeta, withDevice } from '../meta';\nimport { SyncProvider, VersionConflictError } from '../provider/types';\n\nconst MAX_ATTEMPTS = 4;\n\n/**\n * What this device remembers about the last sync. The app keeps it in the browser, next to the\n * saved data but outside it, and passes it back on the next sync.\n */\nexport interface SyncState {\n /** The cloud data as this device last saw it. */\n base: unknown;\n dataVersion: string | null;\n metaVersion: string | null;\n}\n\n/** What to do when this device never synced and both the device and the cloud hold data. */\nexport type FirstSyncChoice = 'merge' | 'keep-local' | 'keep-cloud';\n\nexport interface SyncOptions {\n appId: string;\n deviceId: string;\n deviceName: string;\n provider: SyncProvider;\n schema: Schema;\n /** The data of this device. */\n local: unknown;\n /** The state kept after the last sync, or null when this device never synced. */\n state: SyncState | null;\n firstSync?: FirstSyncChoice;\n /** Returns true when the data is whole and safe to store. Called before any write. */\n validate?: (data: unknown) => boolean;\n /** Lets the app change the revisions of the meta file for this sync. */\n updateMeta?: (meta: SyncMeta) => SyncMeta;\n /** The current time as an ISO text. */\n now: () => string;\n}\n\nexport type SyncResult =\n | {\n status: 'synced';\n /** The data to store on this device. */\n data: unknown;\n /** The state to keep for the next sync. */\n state: SyncState;\n conflicts: Conflict[];\n /** True when the cloud changed during this sync. */\n uploaded: boolean;\n }\n | {\n /** The device and the cloud both hold data and the app has not said what to do. */\n status: 'needs-choice';\n local: unknown;\n remote: unknown;\n };\n\n/** The data in the cloud is not whole, so nothing was written. */\nexport class RemoteDataError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RemoteDataError';\n }\n}\n\n/** Other devices kept writing during every try, so the sync stopped. Try again later. */\nexport class SyncBusyError extends Error {\n constructor() {\n super('The cloud kept changing during the sync.');\n this.name = 'SyncBusyError';\n }\n}\n\nconst parseData = (text: string, validate: SyncOptions['validate']): unknown => {\n let data: unknown;\n try {\n data = JSON.parse(text);\n } catch {\n throw new RemoteDataError('The synced data is not valid JSON.');\n }\n if (validate && !validate(data)) throw new RemoteDataError('The synced data is not valid.');\n return data;\n};\n\nconst readMeta = (text: string | undefined, appId: string): SyncMeta => {\n if (text === undefined) return emptyMeta(appId);\n const parsed = parseMeta(text, appId);\n if (!parsed.ok) throw new RemoteDataError(parsed.error);\n return parsed.meta;\n};\n\n/**\n * Records this device and the time of the sync in the meta file. A sync that changed nothing writes\n * no file when this device is already recorded, so the repository gets no empty commits.\n */\nconst writeMeta = async (options: SyncOptions, uploaded: boolean): Promise<string> => {\n const { provider, appId, deviceId, deviceName, updateMeta, now } = options;\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n const current = await provider.read(metaPath(appId));\n const meta = readMeta(current?.content, appId);\n if (current && !uploaded && !updateMeta && Object.hasOwn(meta.devices, deviceId)) {\n return current.version;\n }\n const time = now();\n const updated = withDevice(updateMeta ? updateMeta(meta) : meta, deviceId, {\n name: deviceName,\n lastSeenAt: time,\n });\n try {\n const written = await provider.write(\n metaPath(appId),\n JSON.stringify({ ...updated, lastSyncAt: time }, null, 2),\n current?.version ?? null,\n );\n return written.version;\n } catch (error) {\n if (!(error instanceof VersionConflictError)) throw error;\n }\n }\n throw new SyncBusyError();\n};\n\n/**\n * Brings this device and the cloud to the same data. It reads the cloud, merges the three copies,\n * writes the cloud copy when it changed, and returns the data to store on this device with the\n * state to keep. It never changes the device by itself, and it does not write when the cloud data\n * or the merged data fail `validate`.\n *\n * Throws `ProviderError` when the provider fails, `RemoteDataError` when the cloud data is not\n * whole, and `SyncBusyError` when other devices keep writing.\n */\nexport const sync = async (options: SyncOptions): Promise<SyncResult> => {\n const { appId, provider, schema, local, state, firstSync, validate } = options;\n const path = dataPath(appId);\n\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n const file = await provider.read(path);\n\n if (file === null) {\n if (validate && !validate(local))\n throw new RemoteDataError('The data of this device is not valid.');\n const cloud = restoreCloudValues(local, undefined, schema);\n const created = await tryWrite(provider, path, cloud, null);\n if (created === null) continue;\n const metaVersion = await writeMeta(options, true);\n return done(local, cloud, created, metaVersion, [], true);\n }\n\n const remote = parseData(file.content, validate);\n const differs = state === null && !equal(restoreCloudValues(local, remote, schema), remote);\n const choice = differs ? firstSync : undefined;\n if (differs && choice === undefined) return { status: 'needs-choice', local, remote };\n\n const base = state === null ? undefined : state.base;\n const merged = mergeFor(choice, { base, local, remote, schema });\n if (validate && !validate(merged.merged)) {\n throw new RemoteDataError('The merged data is not valid.');\n }\n\n if (equal(merged.cloud, remote)) {\n const metaVersion = await writeMeta(options, false);\n return done(merged.merged, merged.cloud, file.version, metaVersion, merged.conflicts, false);\n }\n\n const version = await tryWrite(provider, path, merged.cloud, file.version);\n if (version === null) continue;\n const metaVersion = await writeMeta(options, true);\n return done(merged.merged, merged.cloud, version, metaVersion, merged.conflicts, true);\n }\n throw new SyncBusyError();\n};\n\nconst mergeFor = (\n choice: FirstSyncChoice | undefined,\n input: { base: unknown; local: unknown; remote: unknown; schema: Schema },\n) => {\n if (choice === 'keep-local') {\n const cloud = restoreCloudValues(input.local, input.remote, input.schema);\n return { merged: input.local, cloud, conflicts: [] };\n }\n if (choice === 'keep-cloud') {\n const merged = restoreCloudValues(input.remote, input.local, input.schema);\n return { merged, cloud: input.remote, conflicts: [] };\n }\n return merge(input);\n};\n\nconst tryWrite = async (\n provider: SyncProvider,\n path: string,\n data: unknown,\n version: string | null,\n): Promise<string | null> => {\n try {\n return (await provider.write(path, JSON.stringify(data, null, 2), version)).version;\n } catch (error) {\n if (error instanceof VersionConflictError) return null;\n throw error;\n }\n};\n\nconst done = (\n data: unknown,\n cloud: unknown,\n dataVersion: string,\n metaVersion: string,\n conflicts: Conflict[],\n uploaded: boolean,\n): SyncResult => ({\n status: 'synced',\n data,\n state: { base: cloud, dataVersion, metaVersion },\n conflicts,\n uploaded,\n});\n","/** The public values of the Gamerdex Sync GitHub App. The client secret is never kept here. */\nexport const GITHUB_APP = {\n slug: 'gamerdex-sync',\n clientId: 'Iv23liwzjj3x8O2K5ALA',\n} as const;\n","/** The tokens of a signed in player. Times are milliseconds since 1970. */\nexport interface StoredTokens {\n accessToken: string;\n accessExpiresAt: number;\n refreshToken: string;\n refreshExpiresAt: number;\n}\n\n/** The answer of the token function, with lifetimes in seconds. */\nexport interface TokenResponse {\n accessToken: string;\n expiresIn: number;\n refreshToken: string;\n refreshExpiresIn: number;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isSeconds = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0;\n\nexport const parseTokenResponse = (body: unknown): TokenResponse | null => {\n if (!isRecord(body)) return null;\n const { accessToken, expiresIn, refreshToken, refreshExpiresIn } = body;\n if (typeof accessToken !== 'string' || accessToken === '') return null;\n if (typeof refreshToken !== 'string' || refreshToken === '') return null;\n if (!isSeconds(expiresIn) || !isSeconds(refreshExpiresIn)) return null;\n return { accessToken, expiresIn, refreshToken, refreshExpiresIn };\n};\n\nexport const toStoredTokens = (response: TokenResponse, nowMs: number): StoredTokens => ({\n accessToken: response.accessToken,\n accessExpiresAt: nowMs + response.expiresIn * 1000,\n refreshToken: response.refreshToken,\n refreshExpiresAt: nowMs + response.refreshExpiresIn * 1000,\n});\n","import { ProviderError } from '../provider/types';\nimport { parseTokenResponse, StoredTokens, toStoredTokens } from './tokens';\n\nconst RENEW_BEFORE_MS = 60_000;\n\nexport interface TokenSourceOptions {\n /** The address of the token function, without a trailing slash. */\n endpoint: string;\n /** Reads the tokens that this device saved, or null when the player is signed out. */\n load: () => StoredTokens | null;\n /** Saves the tokens, or removes them when null. */\n save: (tokens: StoredTokens | null) => void;\n now: () => number;\n fetch?: typeof fetch;\n}\n\n/**\n * Keeps a player signed in. `getToken` returns a token that is valid for at least a minute and\n * renews it first when needed. It is meant to be the `getToken` of `GitHubProvider`.\n */\nexport const createTokenSource = (options: TokenSourceOptions) => {\n const send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n let renewing: Promise<StoredTokens> | null = null;\n\n const post = async (path: string, body: Record<string, string>): Promise<StoredTokens> => {\n let response: Response;\n try {\n response = await send(`${options.endpoint}/${path}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n } catch {\n throw new ProviderError('network', 'The sign in service could not be reached.');\n }\n if (response.status >= 500) throw new ProviderError('server', 'The sign in service failed.');\n const parsed = response.ok ? parseTokenResponse(await response.json().catch(() => null)) : null;\n if (parsed === null)\n throw new ProviderError('auth', 'The sign in was refused.', response.status);\n const tokens = toStoredTokens(parsed, options.now());\n options.save(tokens);\n return tokens;\n };\n\n const renew = (tokens: StoredTokens): Promise<StoredTokens> => {\n if (tokens.refreshExpiresAt <= options.now()) {\n options.save(null);\n throw new ProviderError('auth', 'The sign in expired.');\n }\n renewing ??= post('refresh', { refreshToken: tokens.refreshToken })\n .catch((error: unknown) => {\n if (error instanceof ProviderError && error.kind === 'auth') options.save(null);\n throw error;\n })\n .finally(() => {\n renewing = null;\n });\n return renewing;\n };\n\n return {\n /** Finishes a sign in with the code from the callback page. */\n signIn: (code: string, redirectUri: string): Promise<StoredTokens> =>\n post('token', { code, redirectUri }),\n signOut: (): void => options.save(null),\n isSignedIn: (): boolean => options.load() !== null,\n getToken: async (): Promise<string> => {\n const tokens = options.load();\n if (tokens === null) throw new ProviderError('auth', 'The player is not signed in.');\n if (tokens.accessExpiresAt - RENEW_BEFORE_MS > options.now()) return tokens.accessToken;\n return (await renew(tokens)).accessToken;\n },\n };\n};\n","import { GITHUB_APP } from './app';\n\n/** A random value that ties a sign-in answer to the sign-in that this device started. */\nexport const newState = (): string => crypto.randomUUID();\n\nexport const authorizeUrl = (redirectUri: string, state: string): string => {\n const query = new URLSearchParams({\n client_id: GITHUB_APP.clientId,\n redirect_uri: redirectUri,\n state,\n });\n return `https://github.com/login/oauth/authorize?${query}`;\n};\n\n/** The page where the player picks the repository that the app may use. */\nexport const installUrl = (state: string): string =>\n `https://github.com/apps/${GITHUB_APP.slug}/installations/new?${new URLSearchParams({ state })}`;\n\nexport type CallbackResult =\n { ok: true; code: string } | { ok: false; reason: 'denied' | 'state' | 'missing' };\n\n/**\n * Reads the query of the sign-in callback page. The state must equal the state that was saved when\n * the sign-in started, or the answer is refused.\n */\nexport const readCallback = (search: string, expectedState: string | null): CallbackResult => {\n const query = new URLSearchParams(search);\n if (query.get('error') !== null) return { ok: false, reason: 'denied' };\n const code = query.get('code');\n if (code === null || code === '') return { ok: false, reason: 'missing' };\n if (expectedState === null || query.get('state') !== expectedState) {\n return { ok: false, reason: 'state' };\n }\n return { ok: true, code };\n};\n","import { detailOf } from './detail';\nimport { ProviderError } from './types';\n\nexport interface InstalledRepository {\n /** The owner and the name, such as `chiefpansancolt/gamerdex-data`. */\n fullName: string;\n private: boolean;\n}\n\nconst API = 'https://api.github.com';\nconst PAGE_SIZE = 100;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst getJson = async (\n send: typeof fetch,\n token: string,\n url: string,\n): Promise<Record<string, unknown>> => {\n let response: Response;\n try {\n response = await send(url, {\n headers: {\n Accept: 'application/vnd.github+json',\n Authorization: `Bearer ${token}`,\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n } catch {\n throw new ProviderError('network', 'GitHub could not be reached.');\n }\n if (!response.ok) {\n const { status } = response;\n const detail = await detailOf(response);\n if (status === 401 || status === 403) {\n throw new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n if (status >= 500)\n throw new ProviderError('server', 'GitHub failed.', status, undefined, detail);\n throw new ProviderError(\n 'invalid',\n 'GitHub answer is not understood.',\n status,\n undefined,\n detail,\n );\n }\n const body: unknown = await response.json().catch(() => null);\n if (!isRecord(body)) {\n throw new ProviderError('invalid', 'GitHub answer is not understood.', response.status);\n }\n return body;\n};\n\n/**\n * Lists the repositories that the player gave to the GitHub App, for the first 100 installations\n * and the first 100 repositories of each. The player picks the repository for the synced data.\n */\nexport const listInstalledRepositories = async (\n token: string,\n send: typeof fetch = (...args) => globalThis.fetch(...args),\n): Promise<InstalledRepository[]> => {\n const installations = await getJson(\n send,\n token,\n `${API}/user/installations?per_page=${PAGE_SIZE}`,\n );\n const ids = (Array.isArray(installations.installations) ? installations.installations : [])\n .filter(isRecord)\n .map((installation) => installation.id)\n .filter((id): id is number => typeof id === 'number');\n\n const repositories: InstalledRepository[] = [];\n for (const id of ids) {\n const page = await getJson(\n send,\n token,\n `${API}/user/installations/${id}/repositories?per_page=${PAGE_SIZE}`,\n );\n for (const item of Array.isArray(page.repositories) ? page.repositories : []) {\n if (\n isRecord(item) &&\n typeof item.full_name === 'string' &&\n typeof item.private === 'boolean'\n ) {\n repositories.push({ fullName: item.full_name, private: item.private });\n }\n }\n }\n return repositories;\n};\n","import { detailOf } from './detail';\nimport { ProviderError, RemoteFile, SyncProvider, VersionConflictError } from './types';\n\nconst API = 'https://www.googleapis.com/drive/v3';\nconst UPLOAD = 'https://www.googleapis.com/upload/drive/v3';\nconst FOLDER_TYPE = 'application/vnd.google-apps.folder';\nconst RATE_LIMIT_REASONS = ['rateLimitExceeded', 'userRateLimitExceeded', 'dailyLimitExceeded'];\n\nexport interface GoogleDriveOptions {\n /** Returns the token. It may refresh the token first, so it is called for every request. */\n getToken: () => Promise<string>;\n fetch?: typeof fetch;\n}\n\ninterface DriveItem {\n id: string;\n createdTime: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst quoted = (value: string): string => `'${value.replace(/[\\\\']/g, '\\\\$&')}'`;\n\nconst oldestFirst = (a: DriveItem, b: DriveItem): number =>\n a.createdTime.localeCompare(b.createdTime) || a.id.localeCompare(b.id);\n\n/** The search can lag behind a create, so the item that was just created is always part of it. */\nconst withOwn = (found: DriveItem[], own: DriveItem): DriveItem[] =>\n (found.some((item) => item.id === own.id) ? found : [...found, own]).sort(oldestFirst);\n\nconst reasonOf = (body: unknown): string | undefined => {\n if (!isRecord(body) || !isRecord(body.error) || !Array.isArray(body.error.errors))\n return undefined;\n const first: unknown = body.error.errors[0];\n return isRecord(first) && typeof first.reason === 'string' ? first.reason : undefined;\n};\n\n/**\n * Keeps the files of an app in the Google Drive of the player, in a visible `Gamerdex` folder. It\n * needs only the `drive.file` scope, which sees the files that this app created.\n *\n * Drive cannot refuse a write made from an old version. Writes are checked in two steps instead:\n * the head revision must equal the expected version before the upload, and the revision list must\n * show the new revision directly on top of the expected one after it. When another device wrote in\n * between, its content is written back as the head and `VersionConflictError` is thrown, so the\n * next sync merges both sides.\n */\nexport class GoogleDriveProvider implements SyncProvider {\n private readonly send: typeof fetch;\n private readonly folders = new Map<string, string>();\n\n constructor(private readonly options: GoogleDriveOptions) {\n this.send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n }\n\n async read(path: string): Promise<RemoteFile | null> {\n const file = await this.findFile(path);\n if (!file) return null;\n const head = await this.headOf(file.id);\n if (head === null) return null;\n return { content: await this.revisionContent(file.id, head), version: head };\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n if (expectedVersion === null) return this.create(path, content);\n\n const file = await this.findFile(path);\n if (!file || (await this.headOf(file.id)) !== expectedVersion) {\n throw new VersionConflictError(path);\n }\n const version = await this.upload(file.id, content);\n if (version === expectedVersion) return { version };\n\n const previous = await this.revisionBefore(file.id, version);\n if (previous === expectedVersion) return { version };\n\n await this.upload(file.id, await this.revisionContent(file.id, previous));\n throw new VersionConflictError(path);\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const file = await this.findFile(path);\n if (!file) return;\n if ((await this.headOf(file.id)) !== expectedVersion) throw new VersionConflictError(path);\n await this.deleteItem(file.id);\n }\n\n private async create(path: string, content: string): Promise<{ version: string }> {\n const { parentId, name } = await this.locate(path, true);\n if ((await this.children(parentId, name)).length > 0) throw new VersionConflictError(path);\n\n const created = await this.createFile(parentId, name, content);\n const [oldest] = withOwn(await this.children(parentId, name), created);\n if (oldest.id !== created.id) {\n await this.deleteItem(created.id);\n throw new VersionConflictError(path);\n }\n return { version: created.version };\n }\n\n private async findFile(path: string): Promise<DriveItem | null> {\n const located = await this.locate(path, false);\n if (!located) return null;\n return (await this.children(located.parentId, located.name))[0] ?? null;\n }\n\n private async locate(path: string, create: true): Promise<{ parentId: string; name: string }>;\n private async locate(\n path: string,\n create: false,\n ): Promise<{ parentId: string; name: string } | null>;\n private async locate(path: string, create: boolean) {\n const parts = path.split('/');\n const name = parts.pop() as string;\n let parentId = 'root';\n let walked = '';\n for (const folder of parts) {\n walked += `/${folder}`;\n const known = this.folders.get(walked);\n if (known) {\n parentId = known;\n continue;\n }\n const id = await this.folderId(parentId, folder, create);\n if (id === null) return null;\n this.folders.set(walked, id);\n parentId = id;\n }\n return { parentId, name };\n }\n\n private async folderId(parentId: string, name: string, create: boolean): Promise<string | null> {\n const found = await this.children(parentId, name, true);\n if (found.length > 0) return found[0].id;\n if (!create) return null;\n\n const response = await this.request('POST', `${API}/files?fields=id,createdTime`, {\n body: JSON.stringify({ name, mimeType: FOLDER_TYPE, parents: [parentId] }),\n headers: { 'Content-Type': 'application/json' },\n });\n const created = this.itemOf(await response.json());\n const [oldest] = withOwn(await this.children(parentId, name, true), created);\n if (oldest.id !== created.id) {\n await this.deleteItem(created.id);\n return oldest.id;\n }\n return created.id;\n }\n\n private async children(parentId: string, name: string, folder = false): Promise<DriveItem[]> {\n const query = [\n `name = ${quoted(name)}`,\n `${quoted(parentId)} in parents`,\n 'trashed = false',\n `mimeType ${folder ? '=' : '!='} '${FOLDER_TYPE}'`,\n ].join(' and ');\n const url = `${API}/files?${new URLSearchParams({\n q: query,\n fields: 'files(id,createdTime)',\n pageSize: '100',\n })}`;\n const body: unknown = await (await this.request('GET', url)).json();\n const files = isRecord(body) && Array.isArray(body.files) ? body.files : [];\n return files\n .filter(\n (item): item is DriveItem =>\n isRecord(item) && typeof item.id === 'string' && typeof item.createdTime === 'string',\n )\n .sort(oldestFirst);\n }\n\n private async headOf(id: string): Promise<string | null> {\n const response = await this.request(\n 'GET',\n `${API}/files/${encodeURIComponent(id)}?fields=headRevisionId,trashed`,\n { allowMissing: true },\n );\n if (response.status === 404) return null;\n const body: unknown = await response.json();\n if (isRecord(body) && body.trashed === true) return null;\n return this.stringField(body, 'headRevisionId');\n }\n\n private async revisionContent(id: string, revision: string): Promise<string> {\n const url = `${API}/files/${encodeURIComponent(id)}/revisions/${encodeURIComponent(revision)}?alt=media`;\n return (await this.request('GET', url)).text();\n }\n\n private async revisionBefore(id: string, revision: string): Promise<string> {\n const url = `${API}/files/${encodeURIComponent(id)}/revisions?${new URLSearchParams({\n fields: 'revisions(id)',\n pageSize: '1000',\n })}`;\n const body: unknown = await (await this.request('GET', url)).json();\n const revisions = (isRecord(body) && Array.isArray(body.revisions) ? body.revisions : [])\n .filter(isRecord)\n .map((item) => item.id);\n const index = revisions.indexOf(revision);\n const previous = revisions[index - 1];\n if (index < 1 || typeof previous !== 'string') {\n throw new ProviderError('server', 'The new revision is not in the revision list.');\n }\n return previous;\n }\n\n private async upload(id: string, content: string): Promise<string> {\n const url = `${UPLOAD}/files/${encodeURIComponent(id)}?uploadType=media&fields=headRevisionId`;\n const response = await this.request('PATCH', url, {\n body: content,\n headers: { 'Content-Type': 'application/json' },\n });\n return this.stringField(await response.json(), 'headRevisionId');\n }\n\n private async createFile(\n parentId: string,\n name: string,\n content: string,\n ): Promise<DriveItem & { version: string }> {\n const boundary = `gamerdex-${crypto.randomUUID()}`;\n const metadata = JSON.stringify({ name, parents: [parentId], mimeType: 'application/json' });\n const body =\n `--${boundary}\\r\\nContent-Type: application/json; charset=UTF-8\\r\\n\\r\\n${metadata}\\r\\n` +\n `--${boundary}\\r\\nContent-Type: application/json\\r\\n\\r\\n${content}\\r\\n--${boundary}--`;\n const response = await this.request(\n 'POST',\n `${UPLOAD}/files?uploadType=multipart&fields=id,headRevisionId,createdTime`,\n { body, headers: { 'Content-Type': `multipart/related; boundary=${boundary}` } },\n );\n const answer: unknown = await response.json();\n return { ...this.itemOf(answer), version: this.stringField(answer, 'headRevisionId') };\n }\n\n private async deleteItem(id: string): Promise<void> {\n await this.request('DELETE', `${API}/files/${encodeURIComponent(id)}`, { allowMissing: true });\n }\n\n private itemOf(body: unknown): DriveItem {\n return { id: this.stringField(body, 'id'), createdTime: this.stringField(body, 'createdTime') };\n }\n\n private stringField(body: unknown, field: string): string {\n const value = isRecord(body) ? body[field] : undefined;\n if (typeof value !== 'string') {\n throw new ProviderError('invalid', `Google Drive answered without ${field}.`);\n }\n return value;\n }\n\n private async request(\n method: string,\n url: string,\n options: { body?: string; headers?: Record<string, string>; allowMissing?: boolean } = {},\n ): Promise<Response> {\n const token = await this.options.getToken();\n let response: Response;\n try {\n response = await this.send(url, {\n method,\n headers: { Authorization: `Bearer ${token}`, ...(options.headers ?? {}) },\n body: options.body,\n });\n } catch {\n throw new ProviderError('network', 'Google Drive could not be reached.');\n }\n if (response.ok || (options.allowMissing && response.status === 404)) return response;\n throw await this.failure(response);\n }\n\n private async failure(response: Response): Promise<ProviderError> {\n const { status } = response;\n const body: unknown = await response\n .clone()\n .json()\n .catch(() => null);\n const detail = await detailOf(response);\n if (status === 401) {\n return new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n const reason = reasonOf(body);\n if (status === 429 || (status === 403 && reason && RATE_LIMIT_REASONS.includes(reason))) {\n const retryAfter = Number(response.headers.get('retry-after'));\n return new ProviderError(\n 'rate-limit',\n 'Google Drive limits the calls.',\n status,\n Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : undefined,\n detail,\n );\n }\n if (status === 403) {\n return new ProviderError(\n 'auth',\n 'The token has no permission for Drive.',\n status,\n undefined,\n detail,\n );\n }\n if (status === 404) {\n return new ProviderError(\n 'not-found',\n 'The file or folder was not found.',\n status,\n undefined,\n detail,\n );\n }\n if (status >= 500) {\n return new ProviderError('server', 'Google Drive failed.', status, undefined, detail);\n }\n return new ProviderError(\n 'invalid',\n `Google Drive answered ${status}.`,\n status,\n undefined,\n detail,\n );\n }\n}\n","/** The only Drive scope that sync asks for. It sees the files that the app created itself. */\nexport const GOOGLE_DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive.file';\n\n/**\n * The address of the Google sign in page. `access_type=offline` and `prompt=consent` make Google\n * send a refresh token, which it otherwise sends only the first time the player agrees.\n */\nexport const googleAuthorizeUrl = (\n clientId: string,\n redirectUri: string,\n state: string,\n): string => {\n const query = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n scope: GOOGLE_DRIVE_SCOPE,\n access_type: 'offline',\n prompt: 'consent',\n state,\n });\n return `https://accounts.google.com/o/oauth2/v2/auth?${query}`;\n};\n","import { detailOf } from './detail';\nimport { ProviderError, RemoteFile, SyncProvider, VersionConflictError } from './types';\n\nconst API = 'https://api.dropboxapi.com/2';\nconst CONTENT = 'https://content.dropboxapi.com/2';\n\nexport interface DropboxOptions {\n /** Returns the token. It may refresh the token first, so it is called for every request. */\n getToken: () => Promise<string>;\n fetch?: typeof fetch;\n}\n\ninterface Failure {\n status: number;\n tags: Set<string>;\n detail: string | undefined;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** A header value must be ASCII, so every other character of the JSON is written as an escape. */\nconst headerJson = (value: unknown): string =>\n JSON.stringify(value).replace(\n /[\\u007f-]/g,\n (char) => `\\\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`,\n );\n\n/** Collects the `.tag` values of an error. Dropbox names each failure by a chain of tags. */\nconst tagsOf = (value: unknown, found = new Set<string>()): Set<string> => {\n if (!isRecord(value)) return found;\n for (const [key, entry] of Object.entries(value)) {\n if (key === '.tag' && typeof entry === 'string') found.add(entry);\n else tagsOf(entry, found);\n }\n return found;\n};\n\nconst retryAfterOf = (response: Response, body: unknown): number | undefined => {\n const header = response.headers.get('retry-after');\n if (header !== null && Number.isFinite(Number(header))) return Number(header);\n const seconds = isRecord(body) && isRecord(body.error) ? body.error.retry_after : undefined;\n return typeof seconds === 'number' ? seconds : undefined;\n};\n\n/**\n * Keeps the files of an app in the Dropbox of the player. The app should have the App folder\n * access type with the `files.content.read` and `files.content.write` scopes, so it sees only its\n * own folder under `Apps`.\n *\n * Dropbox writes with an expected revision. An upload in `update` mode with `strict_conflict`\n * fails when the file changed since, and an upload in `add` mode fails when the file exists.\n */\nexport class DropboxProvider implements SyncProvider {\n private readonly send: typeof fetch;\n\n constructor(private readonly options: DropboxOptions) {\n this.send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n }\n\n async read(path: string): Promise<RemoteFile | null> {\n const response = await this.request(`${CONTENT}/files/download`, {\n 'Dropbox-API-Arg': headerJson({ path: `/${path}` }),\n });\n if (!response.ok) {\n const failure = await this.failure(response);\n if (failure.tags.has('not_found')) return null;\n throw this.unexpected(failure);\n }\n const result: unknown = JSON.parse(response.headers.get('dropbox-api-result') ?? 'null');\n if (!isRecord(result) || typeof result.rev !== 'string') {\n throw this.invalid('The file has no version.');\n }\n return { content: await response.text(), version: result.rev };\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n const mode =\n expectedVersion === null ? { '.tag': 'add' } : { '.tag': 'update', update: expectedVersion };\n const response = await this.request(\n `${CONTENT}/files/upload`,\n {\n 'Content-Type': 'application/octet-stream',\n 'Dropbox-API-Arg': headerJson({\n path: `/${path}`,\n mode,\n autorename: false,\n mute: true,\n strict_conflict: true,\n }),\n },\n content,\n );\n if (!response.ok) {\n const failure = await this.failure(response);\n if (failure.tags.has('conflict')) throw new VersionConflictError(path);\n throw this.unexpected(failure);\n }\n const result: unknown = await response.json().catch(() => null);\n if (!isRecord(result) || typeof result.rev !== 'string') {\n throw this.invalid('The write returned no version.');\n }\n return { version: result.rev };\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const response = await this.request(\n `${API}/files/delete_v2`,\n { 'Content-Type': 'application/json' },\n JSON.stringify({ path: `/${path}`, parent_rev: expectedVersion }),\n );\n if (response.ok) return;\n const failure = await this.failure(response);\n if (failure.tags.has('conflict')) throw new VersionConflictError(path);\n if (failure.tags.has('not_found')) {\n if ((await this.read(path)) !== null) throw new VersionConflictError(path);\n return;\n }\n throw this.unexpected(failure);\n }\n\n private async request(\n url: string,\n headers: Record<string, string>,\n body?: string,\n ): Promise<Response> {\n const token = await this.options.getToken();\n try {\n return await this.send(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${token}`, ...headers },\n body,\n });\n } catch {\n throw new ProviderError('network', 'Dropbox could not be reached.');\n }\n }\n\n /**\n * Reads a failed answer. Dropbox reports a failure that belongs to the call, such as a missing\n * file or a conflict, with status 409. The caller decides what that means. Every other status\n * is a failure of the request and is thrown here.\n */\n private async failure(response: Response): Promise<Failure> {\n const { status } = response;\n const body: unknown = await response\n .clone()\n .json()\n .catch(() => null);\n const detail = await detailOf(response);\n if (status === 409) return { status, tags: tagsOf(isRecord(body) ? body.error : null), detail };\n if (status === 401) {\n throw new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n if (status === 403) {\n throw new ProviderError(\n 'auth',\n 'The token has no permission for Dropbox.',\n status,\n undefined,\n detail,\n );\n }\n if (status === 429) {\n throw new ProviderError(\n 'rate-limit',\n 'Dropbox limits the calls.',\n status,\n retryAfterOf(response, body),\n detail,\n );\n }\n if (status >= 500) {\n throw new ProviderError('server', 'Dropbox failed.', status, undefined, detail);\n }\n throw this.invalid(`Dropbox answered ${status}.`, status, detail);\n }\n\n private unexpected({ status, tags, detail }: Failure): ProviderError {\n if (tags.has('too_many_write_operations')) {\n return new ProviderError(\n 'rate-limit',\n 'Dropbox limits the writes.',\n status,\n undefined,\n detail,\n );\n }\n if (tags.has('no_write_permission')) {\n return new ProviderError(\n 'auth',\n 'The token has no permission for Dropbox.',\n status,\n undefined,\n detail,\n );\n }\n if (tags.has('insufficient_space')) {\n return this.invalid('Dropbox has no space left.', status, detail);\n }\n return this.invalid(`Dropbox answered ${status}.`, status, detail);\n }\n\n private invalid(message: string, status?: number, detail?: string): ProviderError {\n return new ProviderError('invalid', message, status, undefined, detail);\n }\n}\n","import { detailOf } from './detail';\nimport { ProviderError, RemoteFile, SyncProvider, VersionConflictError } from './types';\n\nconst APP_FOLDER = 'https://graph.microsoft.com/v1.0/me/drive/special/approot';\n\nexport interface OneDriveOptions {\n /** Returns the token. It may refresh the token first, so it is called for every request. */\n getToken: () => Promise<string>;\n fetch?: typeof fetch;\n}\n\ninterface ItemInfo {\n eTag: string;\n downloadUrl: string;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst encodePath = (path: string): string => path.split('/').map(encodeURIComponent).join('/');\n\nconst retryAfterOf = (response: Response): number | undefined => {\n const header = response.headers.get('retry-after');\n return header !== null && Number.isFinite(Number(header)) ? Number(header) : undefined;\n};\n\n/**\n * Keeps the files of an app in the OneDrive of the player, in the app folder under `Apps`. It\n * needs only the `Files.ReadWrite.AppFolder` scope, so it sees no other file. The app folder\n * exists for personal Microsoft accounts.\n *\n * A write sends the `eTag` that was read in an `If-Match` header, and a new file is created with\n * `conflictBehavior=fail`. OneDrive answers 412 and 409 when the file changed or exists.\n */\nexport class OneDriveProvider implements SyncProvider {\n private readonly send: typeof fetch;\n\n constructor(private readonly options: OneDriveOptions) {\n this.send = options.fetch ?? ((...args) => globalThis.fetch(...args));\n }\n\n async read(path: string): Promise<RemoteFile | null> {\n const info = await this.info(path);\n if (info === null) return null;\n const content = await this.fetchContent(info.downloadUrl);\n return content === null ? null : { content, version: info.eTag };\n }\n\n async write(\n path: string,\n content: string,\n expectedVersion: string | null,\n ): Promise<{ version: string }> {\n const url =\n expectedVersion === null\n ? `${this.itemUrl(path)}:/content?@microsoft.graph.conflictBehavior=fail`\n : `${this.itemUrl(path)}:/content`;\n const response = await this.request(\n 'PUT',\n url,\n { 'Content-Type': 'text/plain', ...this.ifMatch(expectedVersion) },\n content,\n );\n const expectedFileIsGone = expectedVersion !== null && response.status === 404;\n if (response.status === 412 || response.status === 409 || expectedFileIsGone) {\n throw new VersionConflictError(path);\n }\n await this.assertOk(response);\n const result: unknown = await response.json().catch(() => null);\n if (expectedVersion !== null && response.status === 201) {\n await this.undoCreate(path, result);\n throw new VersionConflictError(path);\n }\n if (!isRecord(result) || typeof result.eTag !== 'string') {\n throw this.invalid('The write returned no version.');\n }\n return { version: result.eTag };\n }\n\n async remove(path: string, expectedVersion: string): Promise<void> {\n const response = await this.request(\n 'DELETE',\n this.itemUrl(path),\n this.ifMatch(expectedVersion),\n );\n if (response.status === 404) return;\n if (response.status === 412) throw new VersionConflictError(path);\n await this.assertOk(response);\n }\n\n /**\n * OneDrive creates the file when a write with `If-Match` finds none, which would undo the\n * removal that another device made. The file that was just created is removed again.\n */\n private async undoCreate(path: string, created: unknown): Promise<void> {\n if (!isRecord(created) || typeof created.eTag !== 'string') return;\n try {\n await this.remove(path, created.eTag);\n } catch (error) {\n if (!(error instanceof VersionConflictError)) throw error;\n }\n }\n\n private itemUrl(path: string): string {\n return `${APP_FOLDER}:/${encodePath(path)}`;\n }\n\n private ifMatch(version: string | null): Record<string, string> {\n return version === null ? {} : { 'If-Match': version };\n }\n\n private async info(path: string): Promise<ItemInfo | null> {\n const response = await this.request('GET', this.itemUrl(path));\n if (response.status === 404) return null;\n await this.assertOk(response);\n const body: unknown = await response.json().catch(() => null);\n if (!isRecord(body) || typeof body.eTag !== 'string') {\n throw this.invalid('The file has no version.');\n }\n const url = body['@microsoft.graph.downloadUrl'];\n if (typeof url !== 'string') throw this.invalid('The file has no download address.');\n return { eTag: body.eTag, downloadUrl: url };\n }\n\n /** The download address is signed, so it must not carry the token. */\n private async fetchContent(url: string): Promise<string | null> {\n let response: Response;\n try {\n response = await this.send(url);\n } catch {\n throw new ProviderError('network', 'OneDrive could not be reached.');\n }\n if (response.status === 404) return null;\n await this.assertOk(response);\n return response.text();\n }\n\n private async request(\n method: string,\n url: string,\n headers: Record<string, string> = {},\n body?: string,\n ): Promise<Response> {\n const token = await this.options.getToken();\n try {\n return await this.send(url, {\n method,\n headers: { Authorization: `Bearer ${token}`, ...headers },\n body,\n });\n } catch {\n throw new ProviderError('network', 'OneDrive could not be reached.');\n }\n }\n\n private async assertOk(response: Response): Promise<void> {\n if (response.ok) return;\n const { status } = response;\n const detail = await detailOf(response);\n if (status === 401) {\n throw new ProviderError('auth', 'The token is not valid.', status, undefined, detail);\n }\n if (status === 403) {\n throw new ProviderError(\n 'auth',\n 'The token has no permission for OneDrive.',\n status,\n undefined,\n detail,\n );\n }\n if (status === 429 || (status === 503 && retryAfterOf(response) !== undefined)) {\n throw new ProviderError(\n 'rate-limit',\n 'OneDrive limits the calls.',\n status,\n retryAfterOf(response),\n detail,\n );\n }\n if (status === 404) {\n throw new ProviderError(\n 'not-found',\n 'The app folder was not found.',\n status,\n undefined,\n detail,\n );\n }\n if (status >= 500) {\n throw new ProviderError('server', 'OneDrive failed.', status, undefined, detail);\n }\n throw this.invalid(`OneDrive answered ${status}.`, status, detail);\n }\n\n private invalid(message: string, status?: number, detail?: string): ProviderError {\n return new ProviderError('invalid', message, status, undefined, detail);\n }\n}\n","/** The scopes that sync asks for. Together with the App folder access type, they reach one folder. */\nexport const DROPBOX_SCOPES = ['files.content.read', 'files.content.write'] as const;\n\n/**\n * The address of the Dropbox sign in page. `token_access_type=offline` makes Dropbox send a\n * refresh token, which does not expire until the player removes the access.\n */\nexport const dropboxAuthorizeUrl = (\n clientId: string,\n redirectUri: string,\n state: string,\n): string => {\n const query = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n token_access_type: 'offline',\n scope: DROPBOX_SCOPES.join(' '),\n state,\n });\n return `https://www.dropbox.com/oauth2/authorize?${query}`;\n};\n","/** The file scope that sync asks for. It reaches only the app folder of the player. */\nexport const ONEDRIVE_FILE_SCOPE = 'Files.ReadWrite.AppFolder';\n\n/** The file scope, and `offline_access`, which makes Microsoft send a refresh token. */\nexport const ONEDRIVE_SCOPES = [ONEDRIVE_FILE_SCOPE, 'offline_access'] as const;\n\n/** The address of the Microsoft sign in page for personal accounts. */\nexport const microsoftAuthorizeUrl = (\n clientId: string,\n redirectUri: string,\n state: string,\n): string => {\n const query = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n response_mode: 'query',\n scope: ONEDRIVE_SCOPES.join(' '),\n state,\n });\n return `https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?${query}`;\n};\n"],"mappings":"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","names":["own","own","childSchema","next","isRecord","isRecord","metaVersion","isRecord","API","isRecord","API","isRecord","own","API","isRecord","retryAfterOf","isRecord","encodePath","retryAfterOf"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1239,8 +1239,11 @@ var OneDriveProvider = class {
|
|
|
1239
1239
|
throw new VersionConflictError(path);
|
|
1240
1240
|
}
|
|
1241
1241
|
await this.assertOk(response);
|
|
1242
|
-
if (expectedVersion !== null && response.status === 201) throw new VersionConflictError(path);
|
|
1243
1242
|
const result = await response.json().catch(() => null);
|
|
1243
|
+
if (expectedVersion !== null && response.status === 201) {
|
|
1244
|
+
await this.undoCreate(path, result);
|
|
1245
|
+
throw new VersionConflictError(path);
|
|
1246
|
+
}
|
|
1244
1247
|
if (!isRecord8(result) || typeof result.eTag !== "string") {
|
|
1245
1248
|
throw this.invalid("The write returned no version.");
|
|
1246
1249
|
}
|
|
@@ -1256,6 +1259,18 @@ var OneDriveProvider = class {
|
|
|
1256
1259
|
if (response.status === 412) throw new VersionConflictError(path);
|
|
1257
1260
|
await this.assertOk(response);
|
|
1258
1261
|
}
|
|
1262
|
+
/**
|
|
1263
|
+
* OneDrive creates the file when a write with `If-Match` finds none, which would undo the
|
|
1264
|
+
* removal that another device made. The file that was just created is removed again.
|
|
1265
|
+
*/
|
|
1266
|
+
async undoCreate(path, created) {
|
|
1267
|
+
if (!isRecord8(created) || typeof created.eTag !== "string") return;
|
|
1268
|
+
try {
|
|
1269
|
+
await this.remove(path, created.eTag);
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
if (!(error instanceof VersionConflictError)) throw error;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1259
1274
|
itemUrl(path) {
|
|
1260
1275
|
return `${APP_FOLDER}:/${encodePath2(path)}`;
|
|
1261
1276
|
}
|