From 0617663b249f9ca488e5de652108b17d67fbaf45 Mon Sep 17 00:00:00 2001 From: Vika Date: Sat, 29 Jul 2023 21:59:56 +0300 Subject: Moved the entire Kittybox tree into the root --- companion-lite/index.html | 127 ++++++++++++++++++++++++++ companion-lite/src/base64.ts | 89 +++++++++++++++++++ companion-lite/src/indieauth.ts | 113 +++++++++++++++++++++++ companion-lite/src/main.ts | 178 +++++++++++++++++++++++++++++++++++++ companion-lite/src/micropub_api.ts | 125 ++++++++++++++++++++++++++ companion-lite/style.css | 47 ++++++++++ companion-lite/tsconfig.json | 104 ++++++++++++++++++++++ 7 files changed, 783 insertions(+) create mode 100644 companion-lite/index.html create mode 100644 companion-lite/src/base64.ts create mode 100644 companion-lite/src/indieauth.ts create mode 100644 companion-lite/src/main.ts create mode 100644 companion-lite/src/micropub_api.ts create mode 100644 companion-lite/style.css create mode 100644 companion-lite/tsconfig.json (limited to 'companion-lite') diff --git a/companion-lite/index.html b/companion-lite/index.html new file mode 100644 index 0000000..fc99c60 --- /dev/null +++ b/companion-lite/index.html @@ -0,0 +1,127 @@ + + + + + Kittybox-Micropub debug client + + + + + + + + + + + + + +
+ JavaScript licensing information for this software + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
main.jsAGPL-3.0main.ts (Kittybox source code)
micropub_api.jsAGPL-3.0micropub_api.ts (Kittybox source code)
indieauth.jsAGPL-3.0indieauth.ts (Kittybox source code)
base64.jsCC0 (Public Domain)base64.ts, adapted from MDN page on Base64
ESM.sh entrypoint for microformats-parserMIT/Expatesm.sh source code +
microformats-parserMIT/ExpatGitHub repository +
parse5 + MIT/Expat +
+ BSD-2-Clause +
+ GitHub repository for parse5 +
+ GitHub repository for entitites, a parse5 dependency +
+
+ + diff --git a/companion-lite/src/base64.ts b/companion-lite/src/base64.ts new file mode 100644 index 0000000..2429894 --- /dev/null +++ b/companion-lite/src/base64.ts @@ -0,0 +1,89 @@ +// Array of bytes to Base64 string decoding +function b64ToUint6(nChr: number) { + return nChr > 64 && nChr < 91 + ? nChr - 65 + : nChr > 96 && nChr < 123 + ? nChr - 71 + : nChr > 47 && nChr < 58 + ? nChr + 4 + : nChr === 43 + ? 62 + : nChr === 47 + ? 63 + : 0; +} + +export function decode(sBase64: string, nBlocksSize?: number) { + const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, ""); + const nInLen = sB64Enc.length; + const nOutLen = nBlocksSize + ? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize + : (nInLen * 3 + 1) >> 2; + const taBytes = new Uint8Array(nOutLen); + + let nMod3; + let nMod4; + let nUint24 = 0; + let nOutIdx = 0; + for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4)); + if (nMod4 === 3 || nInLen - nInIdx === 1) { + nMod3 = 0; + while (nMod3 < 3 && nOutIdx < nOutLen) { + taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255; + nMod3++; + nOutIdx++; + } + nUint24 = 0; + } + } + + return taBytes; +} + +/* Base64 string to array encoding */ +function uint6ToB64(nUint6: number) { + return nUint6 < 26 + ? nUint6 + 65 + : nUint6 < 52 + ? nUint6 + 71 + : nUint6 < 62 + ? nUint6 - 4 + : nUint6 === 62 + ? 43 + : nUint6 === 63 + ? 47 + : 65; +} + +export function encode(aBytes: Uint8Array) { + let nMod3 = 2; + let sB64Enc = ""; + + const nLen = aBytes.length; + let nUint24 = 0; + for (let nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) { + sB64Enc += "\r\n"; + } + + nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCodePoint( + uint6ToB64((nUint24 >>> 18) & 63), + uint6ToB64((nUint24 >>> 12) & 63), + uint6ToB64((nUint24 >>> 6) & 63), + uint6ToB64(nUint24 & 63) + ); + nUint24 = 0; + } + } + return ( + sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + + (nMod3 === 2 ? "" : nMod3 === 1 ? "=" : "==") + ); +} + +export default { encode, decode } diff --git a/companion-lite/src/indieauth.ts b/companion-lite/src/indieauth.ts new file mode 100644 index 0000000..40facab --- /dev/null +++ b/companion-lite/src/indieauth.ts @@ -0,0 +1,113 @@ +// @ts-ignore +import { mf2 } from "https://esm.sh/microformats-parser@1.4.1?pin=v96" +import { MF2 } from "./micropub_api.js" +import base64 from "./base64.js" + /* + const { mf2 }: { + mf2: (html: string, options: { + baseUrl: string, + experimental?: { lang?: boolean, textContent?: boolean } + }) => { + items: MF2[], + rels: {[key: string]: string[]}, + "rel-urls": {[key: string]: { rels: string[], text?: string }} + } + } = + // @ts-ignore + await import("https://esm.sh/microformats-parser@1.4.1?pin=v96"); + */ + +interface IndieauthMetadata { + authorization_endpoint: string, + token_endpoint: string, + issuer: string, + introspection_endpoint?: string, + introspection_endpoint_auth_methods_supported?: ("Bearer")[], + revocation_endpoint?: string, + revocation_endpoint_auth_methods_supported?: ["none"], + scopes_supported?: string[], + response_types_supported: ["code"], + grant_types_supported: ("authorization_code" | "refresh_token")[] + code_challenge_methods_supported: ("S256")[] + authorization_response_iss_parameter_supported: true, + userinfo_endpoint?: string +} + +interface MF2ParsedData { + items: MF2[], + rels: {[key: string]: string[]}, + "rel-urls": {[key: string]: { rels: string[], text?: string }} +} + +export interface IndiewebEndpoints { + authorization_endpoint: URL, + token_endpoint: URL, + userinfo_endpoint: URL | null, + revocation_endpoint: URL | null, + micropub: URL, + +} + +export function create_verifier() { + const array = new Uint8Array(64) + crypto.getRandomValues(array) + + return array.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '') +} + +export async function create_challenge(verifier: string): Promise { + return await crypto.subtle.digest('SHA-256', Uint8Array.from(verifier, c => c.charCodeAt(0))) + .then((buf) => base64.encode(new Uint8Array(buf))) + .then(s => { + return s + .replaceAll("+", "-") + .replaceAll("/", "_") + .replaceAll(/=$/g, "") + }) +} + +export async function discover_endpoints(me: URL): Promise { + const response = await fetch(me); + const data: MF2ParsedData = mf2(await response.text(), { baseUrl: me.toString() }); + let endpoints: Partial = {}; + if ("micropub" in data.rels) { + endpoints.micropub = new URL(data.rels.micropub[0]) + } else { + return null + } + if ("indieauth_metadata" in data.rels) { + const metadata_response = await fetch(data.rels.indieauth_metadata[0], { + headers: { + "Accept": "application/json" + } + }); + + const metadata = await metadata_response.json() as IndieauthMetadata; + endpoints.authorization_endpoint = new URL(metadata.authorization_endpoint) + endpoints.token_endpoint = new URL(metadata.token_endpoint) + if (metadata.userinfo_endpoint != null) { + endpoints.userinfo_endpoint = new URL(metadata.userinfo_endpoint) + } else { + endpoints.userinfo_endpoint = null + } + if (metadata.revocation_endpoint != null) { + endpoints.revocation_endpoint = new URL(metadata.revocation_endpoint) + } else { + endpoints.revocation_endpoint = null + } + + return endpoints as IndiewebEndpoints + } else if ( + "authorization_endpoint" in data.rels + && "token_endpoint" in data.rels + ) { + endpoints.authorization_endpoint = new URL(data.rels.authorization_endpoint[0]) + endpoints.token_endpoint = new URL(data.rels.token_endpoint[0]) + endpoints.userinfo_endpoint = null + endpoints.revocation_endpoint = null + + return endpoints as IndiewebEndpoints + } else { + return null + } +} diff --git a/companion-lite/src/main.ts b/companion-lite/src/main.ts new file mode 100644 index 0000000..f45cb95 --- /dev/null +++ b/companion-lite/src/main.ts @@ -0,0 +1,178 @@ +import { Micropub, MicropubChannel, MF2 } from "./micropub_api.js"; + +const channel_select_radio = document.getElementById("select_channels") as HTMLInputElement; +channel_select_radio.onclick = async () => { + function populate_channel_list(channels: MicropubChannel[]) { + (document.getElementById("channels") as HTMLElement).style.display = "block"; + const channel_list = document.getElementById("channels_target") as HTMLElement; + channel_list.innerHTML = ""; + channels.forEach((channel) => { + const template = (document.getElementById("channel_selector") as HTMLTemplateElement).content.cloneNode(true) as HTMLElement; + const input = template.querySelector("input") as HTMLInputElement; + const label = template.querySelector("label") as HTMLLabelElement; + input.id = `channel_selector_option_${channel.uid}` + input.value = channel.uid + label.htmlFor = input.id + label.innerHTML = `${channel.name}` + + channel_list.appendChild(template) + }) + } + + if (micropub == null) { + throw new Error("need to authenticate first"); + } + const config = await micropub.config(); + if (config.channels !== undefined) { + populate_channel_list(config.channels) + } +} + +const no_channel_radio = document.getElementById("no_channel") as HTMLInputElement; +no_channel_radio.onclick = () => { + (document.getElementById("channels") as HTMLElement).style.display = "none"; + const channel_list = document.getElementById("channels_target") as HTMLElement + channel_list.innerHTML = ""; +} + +const main_form = document.getElementById("micropub") as HTMLFormElement; +main_form.onsubmit = async (event) => { + function construct_body(form: HTMLFormElement): MF2 { + let content = (form.elements.namedItem("content") as HTMLInputElement).value; + let name: string | undefined = (form.elements.namedItem("name") as HTMLInputElement).value || undefined; + let category: string[] = (form.elements.namedItem("category") as HTMLInputElement).value + .split(",") + .map(val => val.trim()); + + let channel: string[] | undefined = undefined; + let channel_select = (form.elements.namedItem("channel_select") as HTMLInputElement).value; + if (channel_select) { + let channel_selector = form.elements.namedItem("channel"); + if (channel_selector instanceof RadioNodeList) { + channel = (Array.from(channel_selector) as HTMLInputElement[]) + .map(i => i.checked ? i.value : false) + .filter(i => i) as string[]; + } else if (channel_selector instanceof HTMLInputElement) { + channel = [channel_selector.value] + } + } + return { + type: ["h-entry"], + properties: { + content: [content], + name: name ? [name] : undefined, + category: category.length ? category : undefined, + channel: channel ? channel : undefined + } + } + } + + event.preventDefault() + const mf2 = construct_body(main_form); + console.log(JSON.stringify(mf2)); + if (micropub == null) { + throw new Error("need to authenticate first"); + } + try { + const location = await micropub.submit(mf2); + main_form.clear() + + window.open(location, "_blank") + } catch (e) { + console.error(e) + alert(`Error: ${e}`) + return + } + +} + +const indieauth_form = document.getElementById("indieauth") as HTMLFormElement; +indieauth_form.onsubmit = async (event) => { + event.preventDefault() + const form = event.target as HTMLFormElement; + const me = (form.elements.namedItem("me") as HTMLInputElement).value; + if (me != null) { + const { discover_endpoints, create_verifier, create_challenge } = await import("./indieauth.js"); + + const endpoints = await discover_endpoints(new URL(me)); + + if (endpoints != null) { + localStorage.setItem("micropub_endpoint", endpoints.micropub.toString()) + localStorage.setItem("token_endpoint", endpoints.token_endpoint.toString()) + if (endpoints.revocation_endpoint != null) { + localStorage.setItem("revocation_endpoint", endpoints.revocation_endpoint.toString()) + } + } else { + alert("Your website doesn't support Micropub.") + return + } + (document.getElementById("unauthorized") as HTMLElement).style.display = "none"; + (document.getElementById("authorizing") as HTMLElement).style.display = "block"; + const url = endpoints.authorization_endpoint; + let params = new URLSearchParams(); + for (const [key, val] of url.searchParams) { + params.append(key, val) + } + params.set("client_id", window.location.href) + params.set("redirect_uri", window.location.href) + params.set("response_type", "code") + params.set("scope", "profile create media") + params.set("state", "awoo") + const code_verifier = create_verifier() + localStorage.setItem("code_verifier", code_verifier) + params.set("code_challenge", await create_challenge(code_verifier)) + params.set("code_challenge_method", "S256") + + url.search = "?" + params.toString() + + console.log(url) + + window.location.href = url.toString() + } +} + +if (window.location.search != "") { + (document.getElementById("authorizing") as HTMLElement).style.display = "block"; + const params = new URLSearchParams(window.location.search) + if (params.has("code") && params.has("state")) { + const token_endpoint = new URL(localStorage.getItem("token_endpoint")!) + const state = params.get("state") + // XXX check state + + const client_id = new URL(window.location.href); + client_id.search = ""; + const form = new URLSearchParams(); + form.set("grant_type", "authorization_code") + form.set("code", params.get("code")!) + form.set("client_id", client_id.toString()) + form.set("redirect_uri", client_id.toString()) + form.set("code_verifier", localStorage.getItem("code_verifier")!) + + const response = await fetch(token_endpoint, { + method: "POST", + headers: { + "Accept": "application/json", + "Content-Type": "application/x-www-form-urlencoded" + }, + body: form.toString() + }); + + const grant = await response.json(); + + if ("access_token" in grant) { + localStorage.setItem("access_token", grant.access_token); + (document.getElementById("authorizing") as HTMLElement).style.display = "none"; + } + } +} + +let micropub: Micropub | null = null; +const token = localStorage.getItem("access_token") +const endpoint = localStorage.getItem("micropub_endpoint") +if (token == null || endpoint == null) { + (document.getElementById("unauthorized") as HTMLElement).style.display = "block"; +} else { + (document.getElementById("authorized") as HTMLElement).style.display = "block"; + + micropub = new Micropub({ endpoint: new URL(endpoint), token }); +} diff --git a/companion-lite/src/micropub_api.ts b/companion-lite/src/micropub_api.ts new file mode 100644 index 0000000..fa1c431 --- /dev/null +++ b/companion-lite/src/micropub_api.ts @@ -0,0 +1,125 @@ +export interface MicropubChannel { + readonly uid: string, + readonly name: string +} + +export interface MF2 { + type: string[], + properties: { [key:string]: (string | MF2 | {[key:string]: string})[] | undefined } +} + +export interface MicropubConfig { + readonly channels?: MicropubChannel[], + readonly "media-endpoint"?: string +} + +export interface MicropubErrorMessage { + readonly error: string, + readonly error_description: string | undefined +} + +export class MicropubError extends Error { + readonly status: number | null + readonly response: MicropubErrorMessage | null + + constructor(status: number | null, response: MicropubErrorMessage | null, cause: Error | null = null) { + // Needs to pass both `message` and `options` to install the "cause" property. + if (status == null) { + super("Micropub endpoint didn't respond properly", { cause }); + } else if (response == null) { + super(`Micropub endpoint returned HTTP ${status}`, { cause }); + } else { + super( + `Micropub endpoint returned ${response.error}: ${response.error_description ?? "(no description was provided)"}`, + { cause } + ) + } + + this.status = status; + this.response = response; + } +} + +export class Micropub { + readonly token: string + readonly micropub_endpoint: URL + private config_response: MicropubConfig | null + + constructor({ endpoint, token }: { endpoint: URL, token: string }) { + this.micropub_endpoint = endpoint; + this.token = token; + this.config_response = null; + } + + async config(): Promise { + if (this.config_response != null) { + return this.config_response + } + let url = this.micropub_endpoint; + let params = new URLSearchParams(); + for (const [key, val] of url.searchParams) { + params.append(key, val) + } + params.set("q", "config") + + url.search = "?" + params.toString(); + + const response = await fetch(url, { + headers: { + "Authorization": `Bearer ${this.token}` + } + }); + if (response.ok) { + const config = await response.json() as MicropubConfig; + this.config_response = config + + return config + } else { + throw new MicropubError(response.status, await response.json() as MicropubErrorMessage); + } + } + + async submit(mf2: MF2): Promise { + const response = await fetch(this.micropub_endpoint, { + method: "POST", + headers: { + "Authorization": `Bearer ${this.token}`, + "Content-Type": "application/json" + }, + body: JSON.stringify(mf2) + }) + + if (response.status != 201 && response.status != 202) { + let err = await response.json() as MicropubErrorMessage; + + throw new MicropubError(response.status, err) + } else { + return new URL(response.headers.get("Location") as string) + } + } + + async upload(file: File): Promise { + const config = await this.config(); + const media = config["media-endpoint"]; + if (media == null) { + throw new Error("Micropub endpoint doesn't support file uploads") + } + + const form = new FormData(); + form.set("file", file); + + const response = await fetch(media, { + method: "POST", + headers: { + "Authorization": `Bearer ${this.token}`, + }, + body: form + }) + + if (response.ok) { + return new URL(response.headers.get("Location") as string) + } else { + throw new MicropubError(response.status, await response.json()); + } + } +} diff --git a/companion-lite/style.css b/companion-lite/style.css new file mode 100644 index 0000000..09ed398 --- /dev/null +++ b/companion-lite/style.css @@ -0,0 +1,47 @@ +* { + box-sizing: border-box; +} + +:root { + font-family: sans-serif; +} + +body { + margin: 0; +} + +body > main { + margin: auto; + max-width: 1024px; +} + +h1.header { + margin-top: 0.75em; + text-align: center; +} + +fieldset + fieldset, +fieldset + input, +section + section, +section + fieldset +{ + margin-top: 0.75em; +} + +input[type="submit"] { + margin-left: auto; + display: block; +} + +form > fieldset > section > label { + width: 100%; + display: block; +} + +form > fieldset > section > input, form > fieldset > section > textarea { + width: 100%; +} + +textarea { + min-height: 10em; +} diff --git a/companion-lite/tsconfig.json b/companion-lite/tsconfig.json new file mode 100644 index 0000000..18b94c7 --- /dev/null +++ b/companion-lite/tsconfig.json @@ -0,0 +1,104 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "es2022", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["src/**/*"] +} -- cgit 1.4.1