@linkforty/core 1.41.2 → 1.42.0
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/README.md +81 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +33 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +111 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/config.test.d.ts +2 -0
- package/dist/lib/config.test.d.ts.map +1 -0
- package/dist/lib/config.test.js +86 -0
- package/dist/lib/config.test.js.map +1 -0
- package/dist/lib/database.d.ts +15 -0
- package/dist/lib/database.d.ts.map +1 -1
- package/dist/lib/database.js +36 -2
- package/dist/lib/database.js.map +1 -1
- package/dist/lib/database.test.js +73 -0
- package/dist/lib/database.test.js.map +1 -1
- package/dist/lib/not-found-page.d.ts +106 -0
- package/dist/lib/not-found-page.d.ts.map +1 -0
- package/dist/lib/not-found-page.js +212 -0
- package/dist/lib/not-found-page.js.map +1 -0
- package/dist/lib/not-found-page.test.d.ts +2 -0
- package/dist/lib/not-found-page.test.d.ts.map +1 -0
- package/dist/lib/not-found-page.test.js +151 -0
- package/dist/lib/not-found-page.test.js.map +1 -0
- package/dist/lib/schema-guard.d.ts +46 -0
- package/dist/lib/schema-guard.d.ts.map +1 -0
- package/dist/lib/schema-guard.js +135 -0
- package/dist/lib/schema-guard.js.map +1 -0
- package/dist/lib/schema-guard.test.d.ts +2 -0
- package/dist/lib/schema-guard.test.d.ts.map +1 -0
- package/dist/lib/schema-guard.test.js +83 -0
- package/dist/lib/schema-guard.test.js.map +1 -0
- package/dist/routes/redirect.d.ts +21 -0
- package/dist/routes/redirect.d.ts.map +1 -1
- package/dist/routes/redirect.js +72 -7
- package/dist/routes/redirect.js.map +1 -1
- package/dist/routes/redirect.no-web-destination.test.js +2 -2
- package/dist/routes/redirect.no-web-destination.test.js.map +1 -1
- package/dist/routes/redirect.not-found.test.d.ts +2 -0
- package/dist/routes/redirect.not-found.test.d.ts.map +1 -0
- package/dist/routes/redirect.not-found.test.js +280 -0
- package/dist/routes/redirect.not-found.test.js.map +1 -0
- package/linkforty.config.example.json +11 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -245,6 +245,33 @@ GET /:templateSlug/:shortCode
|
|
|
245
245
|
|
|
246
246
|
Automatically redirects users to the appropriate URL based on device type (iOS/Android/web), evaluates targeting rules, and tracks the click asynchronously.
|
|
247
247
|
|
|
248
|
+
**Not-found page:** When a link can't be served, a browser gets a small HTML page ("Link not found") with status 404, `noindex`, `Cache-Control: no-store` and a content-security policy that allows no script. That covers an unknown code, an expired or switched-off link, a link on another organization's host, a visitor outside the link's targeting rules, and a link with no destination for the visitor's device. All of these render the same page, so an expired link can't be told apart from a code that never existed. Clients that don't send `Accept: text/html` (API callers, scripts) keep the JSON body: `{ "error": "Link not found" }`, or `{ "error": "No destination URL configured for this link" }` for a link with no destination. A link withdrawn for abuse keeps its own 410 notice.
|
|
249
|
+
|
|
250
|
+
You can change the page or send browsers somewhere else instead, without writing code. The first of these that is set wins:
|
|
251
|
+
|
|
252
|
+
1. **The organization's settings**, `organizations.settings.notFound`, on a host that belongs to the organization. On a shared host, only a live link that has nothing for this visitor (a targeting miss, no destination) uses its organization's settings. An unknown, expired or switched-off code there uses the instance's.
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"notFound": {
|
|
257
|
+
"redirectUrl": "https://ridealert.example", // send browsers here (302); the rest is ignored
|
|
258
|
+
"title": "This ride link has expired",
|
|
259
|
+
"message": "Open the app to see current rides.",
|
|
260
|
+
"logoUrl": "https://cdn.example/logo.png",
|
|
261
|
+
"accentColor": "#0f766e", // the button
|
|
262
|
+
"backgroundColor": "#f6f7f8", // light mode only
|
|
263
|
+
"buttonLabel": "Get the app", // shown only with buttonUrl
|
|
264
|
+
"buttonUrl": "https://ridealert.example/app"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Changes show within a minute.
|
|
270
|
+
2. **The instance's defaults**: the `notFound` section of `linkforty.config.json` (see [Configuration file](#configuration-file)), with the same fields.
|
|
271
|
+
3. **The built-in page.**
|
|
272
|
+
|
|
273
|
+
URLs must be http(s) and colors hex; anything else is dropped, and all text is escaped. The `notFound` option below decides in code ahead of all three.
|
|
274
|
+
|
|
248
275
|
**Mobile interstitial:** When a link has `appScheme` configured and a store fallback URL (iOS App Store or Google Play), mobile requests receive a smart interstitial page instead of a raw 302 redirect. The interstitial tries to open the app via URI scheme and falls back to the app store after 1.5 seconds. This handles the case where a 302 to a custom URI scheme fails silently when the app is not installed. URL fragments are preserved through the redirect, enabling patterns like E2E encryption where the decryption key lives in the fragment.
|
|
249
276
|
|
|
250
277
|
**Launchpad page (desktop):** A link to app-only content has nowhere to send a desktop visitor. When the chain resolves no web destination (link → template → workspace `appConfig`), a desktop request gets a hosted landing page instead of an error: the link's title, description and image (`ogTitle` / `ogDescription` / `ogImageUrl`, falling back to `title` / `description`), the app's icon and name, the official App Store / Google Play badges (embedded in the page, so it makes no third-party request), and a QR code so the visitor can finish on their phone. The page is `noindex`, sent with `Cache-Control: no-store` and a content-security policy that allows no script without a per-response nonce, and it never navigates on its own — no timers, no automatic scheme attempts.
|
|
@@ -380,6 +407,7 @@ interface ServerOptions {
|
|
|
380
407
|
trustProxy?: boolean | number; // Trust X-Forwarded-For when behind a proxy (default: false)
|
|
381
408
|
redirect?: RedirectRouteOptions; // See below
|
|
382
409
|
sdk?: SdkRouteOptions; // See below
|
|
410
|
+
config?: LinkFortyConfig; // linkforty.config.json, from loadConfig()
|
|
383
411
|
}
|
|
384
412
|
|
|
385
413
|
interface RedirectRouteOptions {
|
|
@@ -406,6 +434,24 @@ interface RedirectRouteOptions {
|
|
|
406
434
|
// association files on that host, so no installed app can claim its URLs.
|
|
407
435
|
// A hook that throws is logged and treated as false.
|
|
408
436
|
isWebLinkRequest?: (request) => Promise<boolean> | boolean;
|
|
437
|
+
// The instance's not-found page or redirect, used when the organization has
|
|
438
|
+
// no settings.notFound of its own. createServer() fills it from the
|
|
439
|
+
// `notFound` section of the config file (ServerOptions.config).
|
|
440
|
+
notFoundDefaults?: NotFoundSettings;
|
|
441
|
+
// Decide the not-found response in code, ahead of the organization's
|
|
442
|
+
// settings and notFoundDefaults: page content (title,
|
|
443
|
+
// message, logoUrl, accentColor, backgroundColor, buttonLabel, buttonUrl — all
|
|
444
|
+
// optional, all escaped; non-http(s) URLs and non-hex colors are dropped),
|
|
445
|
+
// a URL to 302 to (http(s) only), or null to fall through. Called only
|
|
446
|
+
// for requests that accept HTML, and never for a link withdrawn for abuse.
|
|
447
|
+
// `context.reason` is "unknown" | "disabled" | "not_targeted" |
|
|
448
|
+
// "no_destination"; `context.link` is the link row (null for "unknown");
|
|
449
|
+
// `context.scope` is the host's organization, if any. Use the reason to
|
|
450
|
+
// decide whose settings apply, not to vary the page: an expired link must
|
|
451
|
+
// look like a code that never existed. A hook that throws is logged and
|
|
452
|
+
// treated as null.
|
|
453
|
+
notFound?: (request, context: { reason, link, scope }) =>
|
|
454
|
+
Promise<NotFoundResolution | null> | NotFoundResolution | null;
|
|
409
455
|
}
|
|
410
456
|
|
|
411
457
|
interface SdkRouteOptions {
|
|
@@ -422,7 +468,26 @@ interface SdkRouteOptions {
|
|
|
422
468
|
|
|
423
469
|
Install matching only considers clicks from the installing device's own network (its IPv4 /24 or IPv6 /64), since no other click can reach the confidence threshold, so the attribution window holds however much traffic the server takes. It relies on the `idx_fingerprints_ip` index, which `initializeDatabase()` builds concurrently on first start so click writes are never blocked.
|
|
424
470
|
|
|
425
|
-
`renderLaunchpadPage()`, `defaultLaunchpadContent()` and `shouldServeLaunchpadOnDesktop()` are exported so a host can render the same page elsewhere (a settings preview, for instance).
|
|
471
|
+
`renderLaunchpadPage()`, `defaultLaunchpadContent()` and `shouldServeLaunchpadOnDesktop()` are exported so a host can render the same page elsewhere (a settings preview, for instance). `renderNotFoundPage()` is exported for the same reason, and for a host that answers some requests before they reach the redirect route.
|
|
472
|
+
|
|
473
|
+
### Configuration file
|
|
474
|
+
|
|
475
|
+
Core splits its configuration the way most self-hosted platforms do:
|
|
476
|
+
|
|
477
|
+
- **Environment variables** hold infrastructure and secrets: `DATABASE_URL`, `REDIS_URL`, `PORT`, `TRUST_PROXY`, and so on.
|
|
478
|
+
- **`linkforty.config.json`** holds the instance's defaults and behavior, including text a visitor reads. Today that's the `notFound` section; see [linkforty.config.example.json](linkforty.config.example.json) and [DOCKER.md](DOCKER.md#configuration-file) for every setting.
|
|
479
|
+
- **`organizations.settings`** holds per-organization values, which take precedence over the file where both apply.
|
|
480
|
+
|
|
481
|
+
The file is optional. Core reads the path in `LINKFORTY_CONFIG`, or `linkforty.config.json` in the working directory. A file with an invalid value or unknown setting stops the server at startup with the setting named, rather than starting with it ignored. The Docker image does this for you. When you create the server yourself, load it and pass it in:
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
import { createServer, loadConfig } from '@linkforty/core';
|
|
485
|
+
|
|
486
|
+
const { config } = loadConfig(); // throws ConfigError on a bad file
|
|
487
|
+
const server = await createServer({ database: { url: process.env.DATABASE_URL }, config });
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Options passed in code (such as `redirect.notFoundDefaults`) take precedence over the file.
|
|
426
491
|
|
|
427
492
|
### Running behind a reverse proxy
|
|
428
493
|
|
|
@@ -670,6 +735,21 @@ await fastify.register(linkRoutes);
|
|
|
670
735
|
await fastify.listen({ port: 3000 });
|
|
671
736
|
```
|
|
672
737
|
|
|
738
|
+
#### Starting while the database is busy
|
|
739
|
+
|
|
740
|
+
`initializeDatabase()` creates or updates the schema on every start. Postgres takes a table's lock *before* it evaluates `IF NOT EXISTS`, so a statement such as `ALTER TABLE links ADD COLUMN IF NOT EXISTS …` for a column that already exists would still request an exclusive lock on `links` — and if a long statement (a retention job, a large export) holds that table, the request waits, and every later query on the table, redirects included, queues behind it until the long statement ends. To keep a routine start from ever doing that, statements whose effect already exists are answered from the catalog and never sent, and you can bound the wait for the rest:
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
await initializeDatabase({
|
|
744
|
+
url: 'postgresql://localhost/linkforty',
|
|
745
|
+
// A schema change that waits longer than this fails the start with
|
|
746
|
+
// SQLSTATE 55P03 rather than queueing the table. Retry or restart.
|
|
747
|
+
lockTimeoutMs: 5000,
|
|
748
|
+
});
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
The timeout applies only to the initialisation session and is reset before that connection returns to the pool. When adding schema statements of your own, use the idempotent shapes (`ADD COLUMN IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, `DROP CONSTRAINT IF EXISTS`) or a `DO $$ … IF NOT EXISTS (SELECT … FROM information_schema …)` block, never a bare `ALTER` that asks for a lock on every start.
|
|
752
|
+
|
|
673
753
|
## Deployment
|
|
674
754
|
|
|
675
755
|
LinkForty can be deployed in multiple ways depending on your needs:
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { FastifyInstance } from 'fastify';
|
|
|
2
2
|
import { DatabaseOptions } from './lib/database.js';
|
|
3
3
|
import { type RedirectRouteOptions } from './routes/redirect.js';
|
|
4
4
|
import { type SdkRouteOptions } from './routes/sdk.js';
|
|
5
|
+
import type { LinkFortyConfig } from './lib/config.js';
|
|
5
6
|
/**
|
|
6
7
|
* Configuration options for creating a LinkForty server instance.
|
|
7
8
|
*/
|
|
@@ -20,6 +21,11 @@ export interface ServerOptions {
|
|
|
20
21
|
redirect?: RedirectRouteOptions;
|
|
21
22
|
/** Options for the SDK routes: install match scoping. */
|
|
22
23
|
sdk?: SdkRouteOptions;
|
|
24
|
+
/**
|
|
25
|
+
* The instance's settings from linkforty.config.json, as loadConfig() returns
|
|
26
|
+
* them. Options passed in code take precedence over the file.
|
|
27
|
+
*/
|
|
28
|
+
config?: LinkFortyConfig;
|
|
23
29
|
}
|
|
24
30
|
/**
|
|
25
31
|
* Create and configure a LinkForty Fastify server instance.
|
|
@@ -40,6 +46,8 @@ export * from './lib/link-lookup.js';
|
|
|
40
46
|
export * from './lib/portable/index.js';
|
|
41
47
|
export * from './lib/link-safety.js';
|
|
42
48
|
export * from './lib/launchpad.js';
|
|
49
|
+
export * from './lib/not-found-page.js';
|
|
50
|
+
export * from './lib/config.js';
|
|
43
51
|
export * from './lib/event-emitter.js';
|
|
44
52
|
export * from './types/index.js';
|
|
45
53
|
export type { RedirectRouteOptions } from './routes/redirect.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAAsB,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAGjF,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,EAAsB,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAGjF,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAMlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;KAC3B,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oJAAoJ;IACpJ,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,yDAAyD;IACzD,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,aAAkB,kTAoC7D;AAGD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -40,7 +40,10 @@ export async function createServer(options = {}) {
|
|
|
40
40
|
// Routes
|
|
41
41
|
await fastify.register(healthRoutes);
|
|
42
42
|
await fastify.register(wellKnownRoutes);
|
|
43
|
-
await fastify.register(redirectRoutes,
|
|
43
|
+
await fastify.register(redirectRoutes, {
|
|
44
|
+
...options.redirect,
|
|
45
|
+
notFoundDefaults: options.redirect?.notFoundDefaults ?? options.config?.notFound,
|
|
46
|
+
});
|
|
44
47
|
await fastify.register(linkRoutes);
|
|
45
48
|
await fastify.register(analyticsRoutes);
|
|
46
49
|
await fastify.register(sdkRoutes, options.sdk ?? {});
|
|
@@ -59,6 +62,8 @@ export * from './lib/link-lookup.js';
|
|
|
59
62
|
export * from './lib/portable/index.js';
|
|
60
63
|
export * from './lib/link-safety.js';
|
|
61
64
|
export * from './lib/launchpad.js';
|
|
65
|
+
export * from './lib/not-found-page.js';
|
|
66
|
+
export * from './lib/config.js';
|
|
62
67
|
export * from './lib/event-emitter.js';
|
|
63
68
|
export * from './types/index.js';
|
|
64
69
|
export { redirectRoutes, linkRoutes, analyticsRoutes, sdkRoutes, webhookRoutes, templateRoutes, qrRoutes, previewRoutes, debugRoutes, wellKnownRoutes, healthRoutes } from './routes/index.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAA4B,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,eAAe,CAAC;AACjC,OAAO,KAAK,MAAM,gBAAgB,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAmB,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,cAAc,EAA6B,MAAM,sBAAsB,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAwB,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { NotFoundSettings } from './not-found-page.js';
|
|
2
|
+
/** File looked for in the working directory when LINKFORTY_CONFIG is unset. */
|
|
3
|
+
export declare const DEFAULT_CONFIG_FILE = "linkforty.config.json";
|
|
4
|
+
export interface LinkFortyConfig {
|
|
5
|
+
/** What a browser sees when a link can't be served (lib/not-found-page.ts). */
|
|
6
|
+
notFound?: NotFoundSettings;
|
|
7
|
+
}
|
|
8
|
+
export declare class ConfigError extends Error {
|
|
9
|
+
readonly path: string;
|
|
10
|
+
constructor(message: string, path: string);
|
|
11
|
+
}
|
|
12
|
+
/** Validate an already-parsed configuration object. `path` names it in errors. */
|
|
13
|
+
export declare function parseConfig(raw: unknown, path?: string): LinkFortyConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Load the configuration file.
|
|
16
|
+
*
|
|
17
|
+
* Reads the file named by `options.path`, else by LINKFORTY_CONFIG, else
|
|
18
|
+
* `linkforty.config.json` in the working directory. A file named explicitly
|
|
19
|
+
* must exist; the default one is optional. Returns the file's path, or null
|
|
20
|
+
* when none was read.
|
|
21
|
+
*
|
|
22
|
+
* @throws ConfigError when the file can't be read, isn't JSON, or has an
|
|
23
|
+
* invalid or unknown setting.
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadConfig(options?: {
|
|
26
|
+
path?: string;
|
|
27
|
+
env?: Record<string, string | undefined>;
|
|
28
|
+
cwd?: string;
|
|
29
|
+
}): {
|
|
30
|
+
config: LinkFortyConfig;
|
|
31
|
+
path: string | null;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAED,qBAAa,WAAY,SAAQ,KAAK;IAGlC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADrB,OAAO,EAAE,MAAM,EACN,IAAI,EAAE,MAAM;CAKxB;AAiDD,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,SAAsB,GAAG,eAAe,CAMrF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GACtF;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAkBlD"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The instance's configuration file, `linkforty.config.json`.
|
|
3
|
+
*
|
|
4
|
+
* Configuration is split the way most self-hosted platforms split it:
|
|
5
|
+
*
|
|
6
|
+
* - environment variables hold infrastructure and secrets: where the
|
|
7
|
+
* database is, which port to listen on, and the path to this file;
|
|
8
|
+
* - this file holds the instance's defaults and behavior, including text a
|
|
9
|
+
* visitor reads;
|
|
10
|
+
* - `organizations.settings` holds per-organization values, which take
|
|
11
|
+
* precedence over this file where both apply.
|
|
12
|
+
*
|
|
13
|
+
* The file is optional: without one every built-in default applies. A file that
|
|
14
|
+
* exists but is wrong stops the server at startup with a message naming the bad
|
|
15
|
+
* key. The operator wrote it on purpose, so a mistake should show immediately
|
|
16
|
+
* rather than as a page that quietly ignores them.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync } from 'fs';
|
|
19
|
+
import { resolve } from 'path';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
/** File looked for in the working directory when LINKFORTY_CONFIG is unset. */
|
|
22
|
+
export const DEFAULT_CONFIG_FILE = 'linkforty.config.json';
|
|
23
|
+
export class ConfigError extends Error {
|
|
24
|
+
constructor(message, path) {
|
|
25
|
+
super(`${path}: ${message}`);
|
|
26
|
+
this.path = path;
|
|
27
|
+
this.name = 'ConfigError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const httpUrl = z
|
|
31
|
+
.string()
|
|
32
|
+
.trim()
|
|
33
|
+
.refine((value) => {
|
|
34
|
+
try {
|
|
35
|
+
const { protocol } = new URL(value);
|
|
36
|
+
return protocol === 'http:' || protocol === 'https:';
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}, 'must be an http(s) URL');
|
|
42
|
+
const hexColor = z
|
|
43
|
+
.string()
|
|
44
|
+
.trim()
|
|
45
|
+
.regex(/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i, 'must be a hex color such as #0f766e');
|
|
46
|
+
const notFoundSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
redirectUrl: httpUrl.optional(),
|
|
49
|
+
title: z.string().trim().min(1).max(120).optional(),
|
|
50
|
+
message: z.string().trim().min(1).max(500).optional(),
|
|
51
|
+
logoUrl: httpUrl.optional(),
|
|
52
|
+
accentColor: hexColor.optional(),
|
|
53
|
+
backgroundColor: hexColor.optional(),
|
|
54
|
+
buttonLabel: z.string().trim().min(1).max(60).optional(),
|
|
55
|
+
buttonUrl: httpUrl.optional(),
|
|
56
|
+
})
|
|
57
|
+
.strict()
|
|
58
|
+
.refine((value) => !value.buttonLabel === !value.buttonUrl, {
|
|
59
|
+
message: 'buttonLabel and buttonUrl must be set together',
|
|
60
|
+
});
|
|
61
|
+
const configSchema = z
|
|
62
|
+
.object({
|
|
63
|
+
notFound: notFoundSchema.optional(),
|
|
64
|
+
})
|
|
65
|
+
.strict();
|
|
66
|
+
function describeIssue(issue) {
|
|
67
|
+
if (issue.code === 'unrecognized_keys') {
|
|
68
|
+
const where = issue.path.length ? `${issue.path.join('.')}: ` : '';
|
|
69
|
+
return `${where}unknown setting ${issue.keys.map((k) => `"${k}"`).join(', ')}`;
|
|
70
|
+
}
|
|
71
|
+
return issue.path.length ? `${issue.path.join('.')}: ${issue.message}` : issue.message;
|
|
72
|
+
}
|
|
73
|
+
/** Validate an already-parsed configuration object. `path` names it in errors. */
|
|
74
|
+
export function parseConfig(raw, path = DEFAULT_CONFIG_FILE) {
|
|
75
|
+
const result = configSchema.safeParse(raw);
|
|
76
|
+
if (!result.success) {
|
|
77
|
+
throw new ConfigError(result.error.issues.map(describeIssue).join('; '), path);
|
|
78
|
+
}
|
|
79
|
+
return result.data;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Load the configuration file.
|
|
83
|
+
*
|
|
84
|
+
* Reads the file named by `options.path`, else by LINKFORTY_CONFIG, else
|
|
85
|
+
* `linkforty.config.json` in the working directory. A file named explicitly
|
|
86
|
+
* must exist; the default one is optional. Returns the file's path, or null
|
|
87
|
+
* when none was read.
|
|
88
|
+
*
|
|
89
|
+
* @throws ConfigError when the file can't be read, isn't JSON, or has an
|
|
90
|
+
* invalid or unknown setting.
|
|
91
|
+
*/
|
|
92
|
+
export function loadConfig(options = {}) {
|
|
93
|
+
const env = options.env ?? process.env;
|
|
94
|
+
const named = options.path || env.LINKFORTY_CONFIG || '';
|
|
95
|
+
const path = resolve(options.cwd ?? process.cwd(), named || DEFAULT_CONFIG_FILE);
|
|
96
|
+
if (!existsSync(path)) {
|
|
97
|
+
if (named)
|
|
98
|
+
throw new ConfigError('file not found', path);
|
|
99
|
+
return { config: {}, path: null };
|
|
100
|
+
}
|
|
101
|
+
let raw;
|
|
102
|
+
try {
|
|
103
|
+
raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const reason = error instanceof SyntaxError ? `not valid JSON (${error.message})` : 'could not be read';
|
|
107
|
+
throw new ConfigError(reason, path);
|
|
108
|
+
}
|
|
109
|
+
return { config: parseConfig(raw, path), path };
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/lib/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { ConfigError, loadConfig, parseConfig } from './config.js';
|
|
6
|
+
let dir;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
dir = mkdtempSync(join(tmpdir(), 'linkforty-config-'));
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
rmSync(dir, { recursive: true, force: true });
|
|
12
|
+
});
|
|
13
|
+
function write(name, contents) {
|
|
14
|
+
const path = join(dir, name);
|
|
15
|
+
writeFileSync(path, typeof contents === 'string' ? contents : JSON.stringify(contents));
|
|
16
|
+
return path;
|
|
17
|
+
}
|
|
18
|
+
function errorFrom(fn) {
|
|
19
|
+
try {
|
|
20
|
+
fn();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
expect(error).toBeInstanceOf(ConfigError);
|
|
24
|
+
return error;
|
|
25
|
+
}
|
|
26
|
+
throw new Error('expected a ConfigError');
|
|
27
|
+
}
|
|
28
|
+
describe('loadConfig', () => {
|
|
29
|
+
it('uses the defaults when there is no file', () => {
|
|
30
|
+
expect(loadConfig({ cwd: dir, env: {} })).toEqual({ config: {}, path: null });
|
|
31
|
+
});
|
|
32
|
+
it('reads linkforty.config.json from the working directory', () => {
|
|
33
|
+
const path = write('linkforty.config.json', { notFound: { title: 'Gone' } });
|
|
34
|
+
expect(loadConfig({ cwd: dir, env: {} })).toEqual({ config: { notFound: { title: 'Gone' } }, path });
|
|
35
|
+
});
|
|
36
|
+
it('reads the file named by LINKFORTY_CONFIG', () => {
|
|
37
|
+
const path = write('custom.json', { notFound: { redirectUrl: 'https://example.com/' } });
|
|
38
|
+
expect(loadConfig({ cwd: '/', env: { LINKFORTY_CONFIG: path } }).config).toEqual({
|
|
39
|
+
notFound: { redirectUrl: 'https://example.com/' },
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
it('fails when a named file is missing', () => {
|
|
43
|
+
const error = errorFrom(() => loadConfig({ cwd: dir, env: { LINKFORTY_CONFIG: 'missing.json' } }));
|
|
44
|
+
expect(error.message).toContain('missing.json: file not found');
|
|
45
|
+
});
|
|
46
|
+
it('fails on invalid JSON', () => {
|
|
47
|
+
write('linkforty.config.json', '{ "notFound": { title: "x" } }');
|
|
48
|
+
expect(errorFrom(() => loadConfig({ cwd: dir, env: {} })).message).toContain('not valid JSON');
|
|
49
|
+
});
|
|
50
|
+
it('trims values', () => {
|
|
51
|
+
write('linkforty.config.json', { notFound: { title: ' Gone ' } });
|
|
52
|
+
expect(loadConfig({ cwd: dir, env: {} }).config.notFound?.title).toBe('Gone');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('parseConfig', () => {
|
|
56
|
+
it('accepts every notFound setting', () => {
|
|
57
|
+
const notFound = {
|
|
58
|
+
redirectUrl: 'https://example.com/',
|
|
59
|
+
title: 'Gone',
|
|
60
|
+
message: 'Try the homepage.',
|
|
61
|
+
logoUrl: 'https://cdn.example.com/logo.png',
|
|
62
|
+
accentColor: '#0f766e',
|
|
63
|
+
backgroundColor: '#f6f7f8',
|
|
64
|
+
buttonLabel: 'Home',
|
|
65
|
+
buttonUrl: 'https://example.com/',
|
|
66
|
+
};
|
|
67
|
+
expect(parseConfig({ notFound })).toEqual({ notFound });
|
|
68
|
+
});
|
|
69
|
+
it.each([
|
|
70
|
+
[{ notFound: { redirectUrl: 'javascript:alert(1)' } }, 'notFound.redirectUrl: must be an http(s) URL'],
|
|
71
|
+
[{ notFound: { accentColor: 'teal' } }, 'notFound.accentColor: must be a hex color'],
|
|
72
|
+
[{ notFound: { buttonLabel: 'Home' } }, 'buttonLabel and buttonUrl must be set together'],
|
|
73
|
+
[{ notFound: { titel: 'Gone' } }, 'notFound: unknown setting "titel"'],
|
|
74
|
+
[{ notfound: {} }, 'unknown setting "notfound"'],
|
|
75
|
+
[{ notFound: { title: 42 } }, 'notFound.title'],
|
|
76
|
+
[{ notFound: { title: 'x'.repeat(121) } }, 'notFound.title'],
|
|
77
|
+
])('names the bad setting: %j', (raw, expected) => {
|
|
78
|
+
const error = errorFrom(() => parseConfig(raw, 'linkforty.config.json'));
|
|
79
|
+
expect(error.message).toContain('linkforty.config.json: ');
|
|
80
|
+
expect(error.message).toContain(expected);
|
|
81
|
+
});
|
|
82
|
+
it('rejects a file that is not an object', () => {
|
|
83
|
+
expect(() => parseConfig([])).toThrow(ConfigError);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/lib/config.test.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/database.d.ts
CHANGED
|
@@ -5,6 +5,21 @@ export interface DatabaseOptions {
|
|
|
5
5
|
min?: number;
|
|
6
6
|
max?: number;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* How long a schema statement at initialisation may wait for a table lock,
|
|
10
|
+
* in milliseconds. Unset (the default) means it waits as long as Postgres
|
|
11
|
+
* lets it.
|
|
12
|
+
*
|
|
13
|
+
* Postgres takes a table's lock before it evaluates `IF NOT EXISTS`, so an
|
|
14
|
+
* `ALTER TABLE … ADD COLUMN IF NOT EXISTS` for a column that already exists
|
|
15
|
+
* still asks for ACCESS EXCLUSIVE. Statements whose effect already exists
|
|
16
|
+
* are answered from the catalog and never sent, so this only matters for
|
|
17
|
+
* statements that genuinely change the schema. When one of those waits
|
|
18
|
+
* longer than this, `initializeDatabase()` fails with SQLSTATE `55P03`
|
|
19
|
+
* instead of parking a lock request that every later query on the table
|
|
20
|
+
* would queue behind; the host decides whether to retry or restart.
|
|
21
|
+
*/
|
|
22
|
+
lockTimeoutMs?: number;
|
|
8
23
|
}
|
|
9
24
|
export declare let db: pg.Pool;
|
|
10
25
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/lib/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/lib/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAKpB,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;AA4BvB;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,yFAAyF;AACzF,eAAO,MAAM,6BAA6B,UAAU,CAAC;AAErD,oGAAoG;AACpG,eAAO,MAAM,2BAA2B,UAAU,CAAC;AAGnD,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,eAAoB,iBAuwBrE"}
|
package/dist/lib/database.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pg from 'pg';
|
|
2
|
+
import { guardSchemaClient, isLockTimeout } from './schema-guard.js';
|
|
2
3
|
const { Pool } = pg;
|
|
3
4
|
export let db;
|
|
4
5
|
// Helper function to wait for a specified time
|
|
@@ -54,7 +55,15 @@ export async function initializeDatabase(options = {}) {
|
|
|
54
55
|
db.on('error', (error) => {
|
|
55
56
|
console.error('Database connection lost while idle; it will be replaced:', error.message);
|
|
56
57
|
});
|
|
57
|
-
const
|
|
58
|
+
const raw = await connectWithRetry();
|
|
59
|
+
// Idempotent statements whose effect already exists (a column, an index, an
|
|
60
|
+
// absent constraint, a settled NOT NULL) are answered from the catalog and
|
|
61
|
+
// never reach Postgres, so a routine start never asks for a table lock it
|
|
62
|
+
// does not need. See schema-guard.ts for why that matters.
|
|
63
|
+
const client = guardSchemaClient(raw);
|
|
64
|
+
const lockTimeoutMs = Number.isFinite(options.lockTimeoutMs) && options.lockTimeoutMs > 0
|
|
65
|
+
? Math.trunc(options.lockTimeoutMs)
|
|
66
|
+
: null;
|
|
58
67
|
// Serialise schema initialisation across processes.
|
|
59
68
|
//
|
|
60
69
|
// `CREATE TABLE IF NOT EXISTS` is not atomic: the existence check and the
|
|
@@ -79,6 +88,12 @@ export async function initializeDatabase(options = {}) {
|
|
|
79
88
|
try {
|
|
80
89
|
await client.query('SELECT pg_advisory_lock($1)', [SCHEMA_INIT_LOCK_KEY]);
|
|
81
90
|
lockHeld = true;
|
|
91
|
+
// Set after the advisory lock is held: waiting for another instance's
|
|
92
|
+
// initialisation is wanted, waiting for a table lock is not. Session-level
|
|
93
|
+
// on this client only; RESET below before it goes back to the pool.
|
|
94
|
+
if (lockTimeoutMs !== null) {
|
|
95
|
+
await raw.query(`SET lock_timeout = ${lockTimeoutMs}`);
|
|
96
|
+
}
|
|
82
97
|
// Organizations table (must be created before links, which references it).
|
|
83
98
|
//
|
|
84
99
|
// The redirect path LEFT JOINs this table to read `settings.appConfig`, which
|
|
@@ -705,13 +720,32 @@ export async function initializeDatabase(options = {}) {
|
|
|
705
720
|
END IF;
|
|
706
721
|
END $$;
|
|
707
722
|
`);
|
|
708
|
-
|
|
723
|
+
if (client.skipped.length > 0) {
|
|
724
|
+
console.log(`Database schema initialized successfully (${client.skipped.length} statement(s) already applied, not re-sent)`);
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
console.log('Database schema initialized successfully');
|
|
728
|
+
}
|
|
709
729
|
}
|
|
710
730
|
catch (error) {
|
|
731
|
+
if (isLockTimeout(error)) {
|
|
732
|
+
// Say what happened in operator terms and keep the SQLSTATE so a host
|
|
733
|
+
// can recognise it and retry the start with backoff.
|
|
734
|
+
const wrapped = new Error(`Schema initialisation waited ${lockTimeoutMs} ms for a table lock and gave up ` +
|
|
735
|
+
`(another statement is holding the table; retry the start once it finishes): ${error.message}`);
|
|
736
|
+
wrapped.code = error.code;
|
|
737
|
+
console.error('Error initializing database:', wrapped.message);
|
|
738
|
+
throw wrapped;
|
|
739
|
+
}
|
|
711
740
|
console.error('Error initializing database:', error);
|
|
712
741
|
throw error;
|
|
713
742
|
}
|
|
714
743
|
finally {
|
|
744
|
+
if (lockTimeoutMs !== null) {
|
|
745
|
+
// The client returns to the shared pool: the timeout must not follow it
|
|
746
|
+
// into request handling.
|
|
747
|
+
await raw.query('RESET lock_timeout').catch(() => undefined);
|
|
748
|
+
}
|
|
715
749
|
if (lockHeld) {
|
|
716
750
|
// Best effort: a broken connection has already dropped the lock, and
|
|
717
751
|
// failing to unlock must not mask the error that got us here.
|
package/dist/lib/database.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/lib/database.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/lib/database.ts"],"names":[],"mappings":"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"}
|