<div align="center">
  <img src="https://raw.githubusercontent.com/Gryt-chat/client/main/public/logo.svg" width="80" alt="Gryt logo" />
  <h1>@gryt/core</h1>
  <p>The application logic from <a href="https://github.com/Gryt-chat/gryt">Gryt</a> that the desktop and the phone both run.<br />One implementation of the things they were each doing separately.</p>
</div>

<br />

```sh
npm install @gryt/core
```

The desktop app and the mobile app carried a copy each of the same decisions.
Some were real copies, some were the same idea written twice, and nothing told
the two apart until somebody diffed them by hand. `report.ts` had the same seven
exports on both sides and a different `MESSAGE_MAX` on each, 8000 against 4000,
with nothing recording why.

They run this, not two ports of it. Both apps should pin the same version: a
shared package on two versions is two implementations again.

## What goes in here

Two questions, and a module needs both answers to be yes:

- Would it compile with no DOM and no React Native?
- Would both apps otherwise need a copy?

`tsconfig.json` enforces the first by leaving `DOM` out of `lib`, so `document`,
`window` and `localStorage` fail to typecheck rather than working in one app and
breaking in the other. The second is a judgement call, and the surface guard is
what stops it drifting: `scripts/check-public-surface.mjs` fails when an export
goes missing **and** when one appears that nobody listed.

## What it is

- **`reports`** — the shape [`Gryt-chat/reports`](https://github.com/Gryt-chat/reports)
  takes, and how an app fills it in. `Diagnostics` is the union of what each
  platform can find out, so a renderer sends its Chrome build, a phone sends
  whether it's a simulator, and a field neither fills can be seen to be dead.
- **`links`** — which site a URL belongs to, what colour to draw its card, what
  line to read out of its path, and which of four shapes a preview earns. 73
  sites, seven of which also read a detail from the path.
- **`profile`** — the pool of placeholder names a person gets before they pick
  one. Both apps handed these out and both files said to keep the other in step
  by hand.
- **`permissions`** — the channel scope matrix: rules in, grid out, and back.
  The two apps keyed their cell map differently, a NUL on the desktop and a
  space on the phone, which is a key one of them could collide on.
- **`mls`** — the DM half of MLS stage 1. It keeps a device's KeyPackages
  topped up, opens a DM group, joins from a Welcome, sends, and reads the group
  log in order. For linking a device, `addOwnDevice` puts a new device of yours
  in every DM straight away and says where in each group's log it went in.
  `groupPositions` says where each log stood when the history snapshot was
  taken. Each app hands it the socket, the storage and the pins through
  the interfaces in `src/mls/interfaces.ts`. `mlsPinsFromPeerPins` builds the
  pins from the peer pins `@gryt/crypto` already keeps.
  `encodeMlsDmContent` and `decodeMlsDmContent` write and read what goes inside
  each message, so both apps use the same JSON: a message, an edit, a delete or
  a reaction. `readMlsDmContent` also says when something came from a newer app,
  so it can be skipped quietly rather than counted as broken. `applyMlsReaction` puts a reaction on a message in the archive,
  since the server never sees one. It depends on `@gryt/crypto`, pinned
  exactly the way the apps pin it.
- **`pairing`** — linking a new device by QR code or short code, as
  `docs/pairing-design.md` in `@gryt/crypto` lays it out. There's one state
  machine per side. `createNewDevicePairing` shows the QR and code, checks the
  emoji, opens the envelope and, for an account, signs in with the device grant
  and checks the ID token is for the right person. It writes nothing until all
  of that passes. `createApproverPairing` claims the session, shows who's
  asking, seals the envelope, approves the sign-in through the Keycloak
  extension, and adds the new device to every DM. If the extension isn't there,
  it hands back Keycloak's own device page to open instead. The app passes in
  `fetch`, a clock, storage and the OIDC calls through
  `src/pairing/interfaces.ts`. When the app passes its archive, history goes
  across too. On Approve, A notes where each group's log stands, reads the
  archive newest day first through `HistoryArchive`, and seals and uploads it
  in chunks. The envelope carries the history key and the first chunks, and
  later sealed messages list the rest. After the adds, A sends the tail: what
  it decrypted between the snapshot and each add, plus old-epoch messages that
  land after it, which the app reports with `noteMessage`. N fetches the chunks
  newest first, checks each against its manifest entry, and hands each message
  to `HistorySink` once. Both sides report progress through
  `subscribeHistory`. It uses
  `@noble/hashes` for the PKCE challenge, which `@gryt/crypto` already pulls in.

## What deliberately isn't

Anything needing a platform. Fetching is the clearest case: the desktop and the
phone reach a server differently, so this decides what a link preview *means*
and each app goes and gets it.

Artwork is the other one. A brand logo is a React component on the desktop and
an SVG path or a favicon on the phone, so the package owns the hostnames, the
colours and the path rules, and each app maps a provider `id` to its own icon.

[`@gryt/voice`](https://github.com/Gryt-chat/voice) splits web and native behind
two entry points because a media engine can't avoid the platform. This package
has no such entry, on purpose. If one becomes necessary, that's a decision to
make out loud rather than by adding `DOM` to the tsconfig.

## Checks

```bash
npm test          # node --test, no runner to install
npm run typecheck
npm run build     # tsc to dist/, then rewrite specifiers for ESM
npm run check-surface
```

`prepublishOnly` runs the build, the tests and the surface guard, so a release
that would have shipped a missing export fails before it leaves.

## Issues

Please report bugs and request features in the
[main Gryt repository](https://github.com/Gryt-chat/gryt/issues).

## Sponsors

What sponsoring pays for, the tiers, and everyone who has sponsored:
[gryt.chat/sponsors](https://gryt.chat/sponsors). To sponsor:
[GitHub Sponsors](https://github.com/sponsors/Gryt-chat).

The list itself lives in the [Gryt README](https://github.com/Gryt-chat/gryt#sponsors),
in one place rather than ten, so it cannot fall out of step across repositories.

## License

[AGPL-3.0](https://github.com/Gryt-chat/gryt/blob/main/LICENSE) — Part of [Gryt](https://github.com/Gryt-chat/gryt)

[`@gryt/ui`](https://github.com/Gryt-chat/ui) is the exception in this org, and
deliberately so: it's generic components with nothing of Gryt in them, and
copyleft there would rule out most of the people who might use it.

This isn't that. It's the decisions the Gryt apps make, which is the product
rather than scaffolding around it. It's still yours to embed, self-host and
modify. The licence only bites for running a modified version as a closed
service.
