Spreadsheet-UX op een echte relationele engine

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

3.8 kB Ruw bestand
/**
 * Whose licence this install runs under.
 *
 * `WELLFORMED_LICENSE` holds a licence line: a short statement, signed by the
 * copyright holder, naming the party the licence was granted to. The software
 * reads it, checks the signature against the keys it was built with, and says
 * on start whose it is.
 *
 * What that setting is *not* is a lock. Wellformed starts either way and no
 * data is ever out of reach because of it. What a valid licence changes is
 * that the hosted plans stop applying — which is the honest consequence, since
 * a plan is a thing you buy from Bram and a licensed install has already
 * settled that on paper.
 *
 * The line asserts something attributable, and that is the whole of its force:
 * an installation carrying it is claiming, in a form nobody else can forge, to
 * be running under a named party's licence. Copying an install copies that
 * name along with it.
 */
import { LICENCE_PRODUCT, LICENCE_PUBLIC_KEYS } from './key.js';
import { describeLicence, verifyLicence, LicenceError, type Licence, type LicenceFault } from './verify.js';

export type LicenceState =
  /** Signed, checked, and for this software. */
  | { status: 'licensed'; licence: Licence; description: string }
  /** Nothing set. The ordinary state of the hosted service and of a trial. */
  | { status: 'none' }
  /**
   * The setting still holds one of the mode words this used to take.
   *
   * Worth its own state rather than a parse failure: an install upgrading from
   * a version where `selfhosted` meant something deserves to be told what
   * changed, not told its licence is malformed.
   */
  | { status: 'legacy'; word: string }
  /** Something is there, and it is not a licence this software accepts. */
  | { status: 'rejected'; fault: LicenceFault; message: string };

const LEGACY_WORDS = new Set(['hosted', 'selfhosted', 'self-hosted']);

export function readLicence(value: string | undefined = process.env.WELLFORMED_LICENSE): LicenceState {
  const raw = value?.trim() ?? '';
  if (raw === '') return { status: 'none' };
  if (LEGACY_WORDS.has(raw.toLowerCase())) return { status: 'legacy', word: raw };

  try {
    const licence = verifyLicence(raw, LICENCE_PUBLIC_KEYS, { product: LICENCE_PRODUCT });
    return { status: 'licensed', licence, description: describeLicence(licence) };
  } catch (error) {
    if (error instanceof LicenceError) return { status: 'rejected', fault: error.fault, message: error.message };
    throw error;
  }
}

/** The name to show an operator, or null where there is no licence to name. */
export function licensedTo(state: LicenceState): string | null {
  return state.status === 'licensed' ? state.description : null;
}

/**
 * What to print on start.
 *
 * A licensed install gets one line, because that is a fact and not a warning.
 * Everything else gets said plainly enough that an operator who set the value
 * and got it wrong finds out now rather than from a support thread later.
 */
export function licenceNotice(state: LicenceState): string[] {
  switch (state.status) {
    case 'licensed':
      return [`[wellformed] licensed to ${state.description}`];
    case 'none':
      return ['[wellformed] no licence set — the hosted plans apply on this install'];
    case 'legacy':
      return [
        `[wellformed] WELLFORMED_LICENSE=${state.word} is no longer a setting this understands.`,
        '  It used to be a mode word. It now holds the licence itself: one signed line,',
        '  naming the party the licence was granted to. Until one is set, the hosted',
        '  plans apply. Ask the copyright holder for the licence line.',
      ];
    case 'rejected':
      return [
        `[wellformed] the licence in WELLFORMED_LICENSE was not accepted: ${state.message}`,
        '  The hosted plans apply until it is. If the line was pasted, check that all of',
        '  it is on one line, unbroken and unquoted.',
      ];
  }
}