@portalshq/capabilities-redirect 0.0.2

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/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@portalshq/capabilities-redirect",
3
+ "version": "0.0.2",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/portalshq/portals-cloud"
7
+ },
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "tsc -p tsconfig.json",
13
+ "typecheck": "tsc -p tsconfig.json --noEmit"
14
+ },
15
+ "peerDependencies": {
16
+ "express": "^4.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.4.0",
20
+ "@types/express": "^4.17.21",
21
+ "@types/node": "^20.0.0"
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,69 @@
1
+ import type { Request, Response, NextFunction } from "express";
2
+
3
+ /**
4
+ * Anything that can answer "what's live right now, in priority order" can
5
+ * back the redirect. Today there's at most one live channel, so order is
6
+ * moot -- but this is deliberately the same shape a future ranked feed
7
+ * (the "scroll between channels" experience) would implement, so wiring
8
+ * that in later doesn't require touching this middleware at all.
9
+ */
10
+ export interface LiveChannelRegistry {
11
+ /** Live channel ids, most-preferred first. Empty array if nothing is live. */
12
+ listLive(): string[];
13
+ }
14
+
15
+ /**
16
+ * Minimal in-memory implementation of LiveChannelRegistry. Intended to be
17
+ * driven directly by your RealtimeEngine's onActivate/onTick/onDeactivate
18
+ * callbacks (markLive when a session starts, markEnded when it ends) --
19
+ * see the integration guide. Swappable later for a ranked-feed
20
+ * implementation without changing any caller.
21
+ */
22
+ export class InMemoryLiveChannelRegistry implements LiveChannelRegistry {
23
+ private live = new Set<string>();
24
+
25
+ markLive(channelId: string): void {
26
+ this.live.add(channelId);
27
+ }
28
+
29
+ markEnded(channelId: string): void {
30
+ this.live.delete(channelId);
31
+ }
32
+
33
+ listLive(): string[] {
34
+ return [...this.live];
35
+ }
36
+ }
37
+
38
+ export interface InstantRedirectOptions {
39
+ registry: LiveChannelRegistry;
40
+ /** Default: (id) => `/channel/${id}` */
41
+ channelPath?: (channelId: string) => string;
42
+ /** Default: "/upcoming" */
43
+ fallbackPath?: string;
44
+ /** Which incoming path triggers the redirect check. Default "/" */
45
+ matchPath?: string;
46
+ }
47
+
48
+ /**
49
+ * Express middleware that redirects GET "/" straight to a live channel
50
+ * (or "/upcoming" if nothing's live) using only an in-memory lookup --
51
+ * no DB query, no client JS needs to run first. Mount this BEFORE your
52
+ * static/SPA-catch-all handlers.
53
+ */
54
+ export function createInstantRedirectMiddleware(opts: InstantRedirectOptions) {
55
+ const channelPath = opts.channelPath ?? ((id: string) => `/channel/${id}`);
56
+ const fallbackPath = opts.fallbackPath ?? "/upcoming";
57
+ const matchPath = opts.matchPath ?? "/";
58
+
59
+ return function instantRedirect(req: Request, res: Response, next: NextFunction): void {
60
+ if (req.method !== "GET" || req.path !== matchPath) {
61
+ next();
62
+ return;
63
+ }
64
+
65
+ const [preferredChannelId] = opts.registry.listLive();
66
+ const destination = preferredChannelId ? channelPath(preferredChannelId) : fallbackPath;
67
+ res.redirect(302, destination);
68
+ };
69
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "types": ["node"],
7
+ "downlevelIteration": true
8
+ },
9
+ "include": ["src"]
10
+ }