@jchat/runtime 1.0.0-alpha.10 → 1.0.0-alpha.12

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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @jchat/runtime
2
2
 
3
+ ## 1.0.0-alpha.12
4
+
5
+ ### Patch Changes
6
+
7
+ - 3db301b: - Improve runtime supervisor reliability and multi-site configuration validation
8
+ - Enhance lifecycle checks and error handling in the `jchatRuntime` plugin
9
+ - Optimize server bootstrap and database health checks during initialization
10
+ - a53cd64: - Improve runtime startup performance with faster offline entitlement validation
11
+ - Streamline local development workflows on `localhost` and loopback environments
12
+ - Standardize runtime path and data directory resolution
13
+ - Internal runtime supervisor stability and performance improvements
14
+ - a7f8097: - Improve deployment security integrity and session protection
15
+ - Enhance runtime startup guards and installation flow routing
16
+ - Add offline grace period support for license verification during intermittent network interruptions
17
+ - Improve cross-process license locking reliability and automatic stale lock recovery
18
+ - Add support for custom runtime data directory configuration via `JCHAT_DATA_DIR`
19
+
20
+ ## 1.0.0-alpha.11
21
+
22
+ ### Patch Changes
23
+
24
+ - 4cdf5a9: Consolidate `Provider` and `RoutingMode` types and unify runtime type exports across domain, check, install, and plugin modules.
25
+ - 8ce0db9: Add `jchatRuntime` Elysia plugin encapsulating background license supervision, boot check throttling (24h), configurable check intervals (`JCHAT_LICENSE_CHECK_INTERVAL_DAYS`), timer unref on exit, and fail-closed route guards.
26
+
3
27
  ## 1.0.0-alpha.10
4
28
 
5
29
  ## 1.0.0-alpha.9
package/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # @jchat/runtime
2
2
 
3
- Runtime support package for JChat deployments.
3
+ Core runtime environment and instance state management package for JChat platform distributions.
4
4
 
5
- Encapsulates instance validation, local state management, and registry communication used by the
5
+ ## Overview
6
+
7
+ `@jchat/runtime` provides low-level runtime foundations, instance state persistence, and host communication utilities for JChat server applications and licensed environments.
6
8
 
7
9
  ## Requirements
8
10
 
9
- - Node.js 20+ or Bun 1.3+
11
+ - Bun 1.4.0+
10
12
 
11
13
  ## License
12
14
 
13
- Proprietary — see the LICENSE file included in this package.
15
+ Proprietary — All rights reserved. See the [LICENSE](./LICENSE) file included in this distribution for full terms and conditions.
package/dist/index.d.mts CHANGED
@@ -1,21 +1,27 @@
1
+ import { Elysia } from "elysia";
1
2
  import * as z from "zod";
2
3
  //#region src/domain.d.ts
3
- type Plan = "standard" | "extended";
4
- type RoutingMode = "path" | "subdomain";
5
- type DomainScope = "current-host" | "parent-domain";
6
- type NormalizedDomain = {
4
+ export type Provider = "envato" | "jchat";
5
+ export type Plan = "standard" | "extended";
6
+ export type RoutingMode = "path" | "subdomain";
7
+ export type DomainScope = "current-host" | "parent-domain";
8
+ export type NormalizedDomain = {
7
9
  host: string;
8
10
  isLocalhost: boolean;
9
11
  };
10
12
  /** Normalize the host used for instance binding; ports and www are not significant. */
11
- declare function normalizeDomain(value: string): NormalizedDomain;
12
- declare function isPublicDomain(value: string): boolean;
13
- declare function isAllowedConfiguredDomain(value: string): boolean;
14
- declare function resolveActivationDomain(host: string, plan: Plan, routingMode: RoutingMode, domainScope?: DomainScope): string;
13
+ export declare function normalizeDomain(value: string): NormalizedDomain;
14
+ export declare function isPublicDomain(value: string): boolean;
15
+ export declare function isAllowedConfiguredDomain(value: string): boolean;
16
+ export declare function isLocalhostDomain(value: string): boolean;
17
+ export declare function getRootDomain(value: string): string | null;
18
+ /** Checks if a request domain matches or is an authorized subdomain of the licensed domain. */
19
+ export declare function isMatchingDomain(requestHost: string, licensedDomain: string): boolean;
20
+ export declare function resolveActivationDomain(host: string, plan: Plan, routingMode: RoutingMode, domainScope?: DomainScope): string;
15
21
  //#endregion
16
22
  //#region src/audit.d.ts
17
- type AuditEvent = "check_success" | "check_unreachable" | "check_rejected";
18
- declare function recordAudit(event: AuditEvent, details: {
23
+ export type AuditEvent = "check_success" | "check_unreachable" | "check_rejected";
24
+ export declare function recordAudit(event: AuditEvent, details: {
19
25
  domain?: string;
20
26
  installationId?: string;
21
27
  reference?: string;
@@ -34,18 +40,14 @@ type CheckResult = {
34
40
  error: string;
35
41
  reference: string;
36
42
  };
37
- type RegistryProvider = "envato" | "jchat";
38
- type RegistryRoutingMode = "path" | "subdomain";
39
43
  type ActivationInput = {
40
44
  purchaseCode: string;
41
45
  domain: string;
42
46
  siteSlug?: string;
43
47
  domainScope?: "current-host" | "parent-domain";
44
- provider: RegistryProvider;
45
- routingMode: RegistryRoutingMode;
48
+ provider: Provider;
49
+ routingMode: RoutingMode;
46
50
  installationId: string;
47
- installationPublicKey: string;
48
- machineFingerprint: string;
49
51
  registryEndpoint?: string;
50
52
  fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
51
53
  };
@@ -58,58 +60,47 @@ type ActivationResult = {
58
60
  success: false;
59
61
  message: string;
60
62
  };
61
- declare function activateRegistry(input: ActivationInput): Promise<ActivationResult>;
63
+ export declare function activateRegistry(input: ActivationInput): Promise<ActivationResult>;
62
64
  type CheckInput = {
63
65
  token: string;
64
66
  domain: string;
65
- routingMode: RegistryRoutingMode;
67
+ routingMode: RoutingMode;
66
68
  installationId: string;
67
- machineFingerprint: string;
68
69
  registryEndpoint?: string;
69
70
  runtimeOptions?: RuntimeOptions;
70
71
  auditPath?: string;
72
+ lastSuccessfulCheckAt?: string;
73
+ gracePeriodDays?: number;
71
74
  fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
72
- localTokenValidator?: (token: string, domain: string, installationId: string, machineFingerprint: string) => boolean;
75
+ localTokenValidator?: (token: string, domain: string, installationId: string) => boolean;
73
76
  };
74
- declare function checkRegistry(input: CheckInput): Promise<CheckResult>;
77
+ //#endregion
78
+ //#region src/constants.d.ts
79
+ export declare const DEFAULT_LICENSE_SERVER_URL = "https://license.jchatlive.com";
80
+ export declare const DEFAULT_LICENSE_ISSUER = "https://license.jchatlive.com";
81
+ export declare const DEFAULT_LICENSE_AUDIENCE = "jchat-chat";
75
82
  //#endregion
76
83
  //#region src/state.d.ts
77
84
  type LocalState = {
78
85
  version: 1;
79
86
  installationId: string;
80
- privateKeyPem: string;
81
- publicKeyPem: string;
82
- machineFingerprint: string;
83
87
  instanceToken: string;
88
+ domain?: string;
84
89
  lastSuccessfulCheckAt?: string;
85
90
  };
86
- declare const DEFAULT_STATE_PATH: string;
87
- declare function createMachineFingerprint(): string;
88
- declare function createIdentity(machineFingerprint?: string): {
91
+ export declare function resolveDataDir(): string;
92
+ export declare const DEFAULT_STATE_PATH: string;
93
+ export declare function createIdentity(): {
89
94
  installationId: string;
90
- machineFingerprint: string;
91
- privateKeyPem: string;
92
- publicKeyPem: string;
93
- };
94
- declare function readTokenSync(filePath?: string): string;
95
- declare function readState(filePath?: string): Promise<LocalState | null>;
96
- declare function writeState(state: LocalState, filePath?: string): Promise<void>;
97
- declare function updateInstanceToken(instanceToken: string, statePath?: string): Promise<void>;
98
- declare function acquireProcessLock(statePath?: string): Promise<() => Promise<void>>;
99
- //#endregion
100
- //#region src/runtime.d.ts
101
- type RuntimeStatus$1 = "valid" | "unreachable-fallback" | "invalid" | "unavailable";
102
- declare function registerInvalidationHandler(handler: () => void): void;
103
- declare function setRuntimeStatus(status: RuntimeStatus$1, reference?: string): void;
104
- declare function getRuntimeStatus(): {
105
- status: RuntimeStatus$1;
106
- reference: string;
107
95
  };
108
- declare function isApplicationDisabled(): boolean;
96
+ export declare function readTokenSync(filePath?: string): string;
97
+ export declare function readState(filePath?: string): Promise<LocalState | null>;
98
+ export declare function writeState(state: LocalState, filePath?: string): Promise<void>;
99
+ export declare function updateInstanceToken(instanceToken: string, statePath?: string): Promise<void>;
109
100
  //#endregion
110
101
  //#region src/install.d.ts
111
- type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
112
- type LicenseVerificationResult = {
102
+ export type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
103
+ export type LicenseVerificationResult = {
113
104
  valid: true;
114
105
  buyer: string;
115
106
  token: string;
@@ -119,24 +110,89 @@ type LicenseVerificationResult = {
119
110
  valid: false;
120
111
  error: string;
121
112
  };
122
- declare function installAndActivate(purchaseCode: string, domain: string, siteSlug: string | undefined, domainScope: DomainScope | undefined, routingMode: RoutingMode, provider?: PurchaseProvider, fetchImpl?: FetchLike): Promise<LicenseVerificationResult>;
113
+ export declare function installAndActivate(purchaseCode: string, domain: string, siteSlug: string | undefined, domainScope: DomainScope | undefined, routingMode: RoutingMode, provider?: Provider, fetchImpl?: FetchLike): Promise<LicenseVerificationResult>;
123
114
  //#endregion
124
115
  //#region src/paths.d.ts
125
116
  interface RuntimePathOptions {
126
117
  cwd?: string;
127
118
  env?: Record<string, string | undefined>;
128
119
  }
129
- declare function resolveRuntimePaths(options?: RuntimePathOptions): {
120
+ export declare function resolveRuntimePaths(options?: RuntimePathOptions): {
130
121
  repoRoot: string | null;
131
122
  runtimeRoot: string;
132
123
  uploadsDir: string;
133
124
  addonBundlesDir: string;
134
125
  addonSourceDir: string;
135
126
  migrationsDir: string;
127
+ dataDir: string;
128
+ };
129
+ export declare function findRepoRoot(startDir: string): string | null;
130
+ //#endregion
131
+ //#region src/runtime.d.ts
132
+ type RuntimeStatus$1 = "valid" | "unreachable-fallback" | "invalid" | "unavailable";
133
+ export declare function setRuntimeStatus(status: RuntimeStatus$1, reference?: string): void;
134
+ export declare function getRuntimeStatus(): {
135
+ status: RuntimeStatus$1;
136
+ reference: string;
137
+ };
138
+ export declare function isApplicationDisabled(): boolean;
139
+ export declare function isRuntimeBlocked(isProduction?: boolean): boolean;
140
+ //#endregion
141
+ //#region src/plugin.d.ts
142
+ export type RuntimePluginOptions = {
143
+ domain?: string;
144
+ routingMode?: RoutingMode;
145
+ checkIntervalDays?: number;
146
+ signingKey?: string;
147
+ isBootstrapComplete?: boolean;
148
+ isPrimaryWorker?: boolean;
149
+ allowedPaths?: Set<string>;
150
+ onInvalidate?: () => void;
151
+ getSiteCount?: () => Promise<number> | number;
136
152
  };
153
+ export declare function startRuntimeSupervisor(options?: RuntimePluginOptions): Promise<void>;
154
+ export declare function jchatRuntime(options?: RuntimePluginOptions): Elysia<"", {
155
+ decorator: {};
156
+ store: {};
157
+ derive: {
158
+ readonly runtimeStatus: {
159
+ status: RuntimeStatus$1;
160
+ reference: string;
161
+ };
162
+ };
163
+ resolve: {};
164
+ }, {
165
+ typebox: {};
166
+ error: {};
167
+ }, {
168
+ schema: {};
169
+ standaloneSchema: {};
170
+ macro: {};
171
+ macroFn: {};
172
+ parser: {};
173
+ response: {
174
+ 200: {
175
+ status: string;
176
+ message: string;
177
+ reference: string;
178
+ };
179
+ };
180
+ }, {}, {
181
+ derive: {};
182
+ resolve: {};
183
+ schema: {};
184
+ standaloneSchema: {};
185
+ response: {};
186
+ }, {
187
+ derive: {};
188
+ resolve: {};
189
+ schema: {};
190
+ standaloneSchema: {};
191
+ response: {};
192
+ }>;
137
193
  //#endregion
138
194
  //#region src/server-context.d.ts
139
- declare const ServerConfigSchema: z.ZodObject<{
195
+ export declare const ServerConfigSchema: z.ZodObject<{
140
196
  JCHAT_PORT: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
141
197
  JCHAT_COOKIE_SECRET: z.ZodString;
142
198
  JCHAT_DATABASE_URL: z.ZodURL;
@@ -177,6 +233,7 @@ declare const ServerConfigSchema: z.ZodObject<{
177
233
  }>>;
178
234
  JCHAT_ADDON_ENCRYPTION_KEY: z.ZodString;
179
235
  JCHAT_LICENSE_SERVER_URL: z.ZodOptional<z.ZodURL>;
236
+ JCHAT_LICENSE_CHECK_INTERVAL_DAYS: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
180
237
  JCHAT_STORE_SERVER_URL: z.ZodOptional<z.ZodURL>;
181
238
  NODE_ENV: z.ZodDefault<z.ZodEnum<{
182
239
  development: "development";
@@ -185,36 +242,35 @@ declare const ServerConfigSchema: z.ZodObject<{
185
242
  }>>;
186
243
  SYSTEM_BOOTSTRAP_COMPLETE: z.ZodOptional<z.ZodBoolean>;
187
244
  }, z.core.$strip>;
188
- type ServerConfig = z.infer<typeof ServerConfigSchema>;
189
- declare function loadServerContext(environment: Record<string, unknown>, distribution: "production" | "developer-sandbox" | undefined, publicKeyPem: string): ServerConfig;
245
+ export type ServerConfig = z.infer<typeof ServerConfigSchema>;
246
+ export declare function loadServerContext(environment: Record<string, unknown>, distribution: "production" | "developer-sandbox" | undefined, publicKeyPem: string): ServerConfig;
190
247
  //#endregion
191
248
  //#region src/index.d.ts
192
- type PurchaseProvider = "envato" | "jchat";
193
- type RuntimeEnvironment = "production" | "development";
194
- type ConfigPayload = {
249
+ export type RuntimeEnvironment = "production" | "development";
250
+ export type ConfigPayload = {
195
251
  iss: string;
196
252
  aud: string;
197
253
  licenseId: string;
198
254
  domain: string;
199
255
  buyer: string;
200
256
  installationId: string;
201
- machineFingerprint: string;
202
257
  plan: Plan;
203
258
  entitlements: string[];
204
- purchaseProvider: PurchaseProvider;
259
+ purchaseProvider: Provider;
205
260
  environment: RuntimeEnvironment;
206
261
  routingMode: RoutingMode;
207
262
  iat: number;
208
263
  };
209
- type RuntimeOptions = {
264
+ export type RuntimeOptions = {
210
265
  publicKeyPem: string;
211
266
  issuer?: string;
212
267
  audience?: string;
213
268
  };
214
- type RuntimeStatus = "valid" | "unreachable-fallback" | "invalid" | "unavailable";
215
- declare function parsePayload(token: string): ConfigPayload | null;
216
- declare function verifySignature(token: string, currentDomain: string, options: RuntimeOptions): boolean;
217
- declare function verifyEntitlement(token: string, currentDomain: string, installationId: string, machineFingerprint: string, options: RuntimeOptions): boolean;
218
- declare function hasEntitlement(token: string, currentDomain: string, entitlement: string, options: RuntimeOptions): boolean;
269
+ export type RuntimeStatus = "valid" | "unreachable-fallback" | "invalid" | "unavailable";
270
+ export declare function parsePayload(token: string): ConfigPayload | null;
271
+ export declare function verifySignature(token: string, currentDomain: string, options: RuntimeOptions): boolean;
272
+ export declare function verifyEntitlement(token: string, currentDomain: string, installationId: string, options?: RuntimeOptions): boolean;
273
+ export declare function hasEntitlement(token: string, currentDomain: string, entitlement: string, options: RuntimeOptions): boolean;
274
+ export declare function isMultiSiteAllowed(token: string | undefined | null, domain?: string): boolean;
219
275
  //#endregion
220
- export { ActivationInput, ActivationResult, AuditEvent, CheckInput, CheckResult, ConfigPayload, DEFAULT_STATE_PATH, DomainScope, FetchLike, LicenseVerificationResult, LocalState, NormalizedDomain, Plan, PurchaseProvider, RegistryProvider, RegistryRoutingMode, RoutingMode, RuntimeEnvironment, RuntimeOptions, RuntimeStatus, ServerConfig, ServerConfigSchema, acquireProcessLock, activateRegistry, checkRegistry, createIdentity, createMachineFingerprint, getRuntimeStatus, hasEntitlement, installAndActivate, isAllowedConfiguredDomain, isApplicationDisabled, isPublicDomain, loadServerContext, normalizeDomain, parsePayload, readState, readTokenSync, recordAudit, registerInvalidationHandler, resolveActivationDomain, resolveRuntimePaths, setRuntimeStatus, updateInstanceToken, verifyEntitlement, verifySignature, writeState };
276
+ export type { ActivationInput, ActivationResult, CheckInput, CheckResult, LocalState };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import e from"node:crypto";import t from"node:net";import n from"node:fs/promises";import r from"node:path";import i from"node:fs";import a from"node:os";import{replaceKeys as o}from"string-ts";import*as s from"zod";const c=new Set([`localhost`,`127.0.0.1`,`::1`]),l=[`.local`,`.test`,`.invalid`,`.example`];function u(e){let t=e.trim();if(!t)throw Error(`Instance domain is required`);let n=new URL(t.includes(`://`)?t:`https://${t}`).hostname.toLowerCase().replace(/\.$/,``),r=n===`www.localhost`?`localhost`:n.replace(/^www\./,``);if(!r||r.includes(`@`)||/\s/.test(r))throw Error(`Invalid instance domain`);return{host:r,isLocalhost:c.has(r)}}function d(e){let{host:n,isLocalhost:r}=u(e);return!r&&t.isIP(n)===0&&n.includes(`.`)&&!l.some(e=>n.endsWith(e))}function f(e){try{return u(e).isLocalhost||d(e)}catch{return!1}}function p(e){let{host:n,isLocalhost:r}=u(e);if(r||t.isIP(n)!==0)return null;let i=n.split(`.`);if(i.length<=2)return n;let a=new Set([`co`,`com`,`org`,`net`,`gov`,`edu`,`ac`,`sch`]),o=i.at(-1)??``,s=i.at(-2)??``;return a.has(s)&&o.length===2&&i.length>=3?i.slice(-3).join(`.`):i.slice(-2).join(`.`)}function m(e,t,n,r=`current-host`){let i=u(e).host;return n===`subdomain`&&t===`extended`&&r===`parent-domain`?p(i)??i:i}function h(e){if(e)return e.length<=8?`redacted`:`${e.slice(0,4)}…${e.slice(-4)}`}async function g(e,t,i=r.resolve(process.cwd(),`data/license-events.log`)){await n.mkdir(r.dirname(i),{recursive:!0});let a=JSON.stringify({at:new Date().toISOString(),event:e,domain:h(t.domain),installationId:h(t.installationId),reference:h(t.reference)});await n.appendFile(i,`${a}\n`,`utf8`)}let _=`unavailable`,v=`startup`,y;function ee(e){y=e}function b(t,n=e.randomUUID()){_=t,v=n,t===`invalid`&&y?.()}function te(){return{status:_,reference:v}}function x(){return _===`invalid`}const S=process.env.JCHAT_LICENSE_SERVER_URL||`https://license.jchat.im`;async function C(e){try{let t=await(e.fetchImpl??fetch)(`${e.registryEndpoint??S}/api/v1/activate`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({purchaseCode:e.purchaseCode,domain:u(e.domain).host,siteSlug:e.siteSlug,domainScope:e.domainScope,provider:e.provider,routingMode:e.routingMode,installationId:e.installationId,installationPublicKey:e.installationPublicKey,machineFingerprint:e.machineFingerprint})}),n=await t.json();return t.ok===!1||!n.success||!n.token?{success:!1,message:n.message??`This license is already active on another domain. Please deactivate it first.`}:{success:!0,buyer:n.buyer??`Customer`,token:n.token,domain:n.domain??u(e.domain).host}}catch{return{success:!1,message:`Failed to connect to the central registry. Please try again later.`}}}async function w(t){let n=t.fetchImpl??fetch,r=t.localTokenValidator??((e,n,r,i)=>t.runtimeOptions?$(e,n,r,i,t.runtimeOptions):!1),i=(n,r)=>{let i=e.randomUUID();return b(`invalid`,i),g(`check_rejected`,{domain:n,reference:i},t.auditPath),{status:`invalid`,error:r,reference:i}},a=e=>{let n=u(t.domain).host;return r(t.token,n,t.installationId,t.machineFingerprint)?(b(`unreachable-fallback`),g(`check_unreachable`,{domain:n},t.auditPath),{status:`unreachable-fallback`,token:t.token,error:e}):i(n,`Local license validation failed.`)};try{let e=u(t.domain).host,r=await n(`${t.registryEndpoint??S}/api/v1/check`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:t.token,domain:e,routingMode:t.routingMode,installationId:t.installationId,machineFingerprint:t.machineFingerprint})});if(!r.ok)return r.status>=500?a(`HTTP ${r.status}`):i(e,`License rejected by jchat service.`);let o=await r.json();if(!o.success||!o.token||!(t.runtimeOptions&&$(o.token,e,t.installationId,t.machineFingerprint,t.runtimeOptions))&&!t.localTokenValidator||t.localTokenValidator&&!t.localTokenValidator(o.token,e,t.installationId,t.machineFingerprint))return i(e,`License rejected by jchat service.`);let s=Z(o.token);return!s||s.installationId!==t.installationId||s.machineFingerprint!==t.machineFingerprint||s.routingMode!==t.routingMode?i(e,`License binding rejected by jchat service.`):(b(`valid`),g(`check_success`,{domain:e},t.auditPath),{status:`valid`,token:o.token})}catch{return a(`License service unreachable.`)}}const T=r.resolve(process.cwd(),`data/instance.json`);function E(){return new Bun.CryptoHasher(`sha256`).update([a.hostname(),a.platform(),a.arch()].join(`|`)).digest(`hex`)}function D(t=E()){let n=e.generateKeyPairSync(`ed25519`);return{installationId:Bun.randomUUIDv7(),machineFingerprint:t,privateKeyPem:n.privateKey.export({type:`pkcs8`,format:`pem`}).toString(),publicKeyPem:n.publicKey.export({type:`spki`,format:`pem`}).toString()}}function O(e=T){try{if(!i.existsSync(e))return``;let t=i.readFileSync(e,`utf8`);return JSON.parse(t)?.instanceToken??``}catch{return``}}async function k(e=T){try{let t=JSON.parse(await Bun.file(e).text());if(!t||typeof t!=`object`)return null;let n=t;return n.version!==1||typeof n.installationId!=`string`||!n.installationId||typeof n.privateKeyPem!=`string`||!n.privateKeyPem||typeof n.publicKeyPem!=`string`||!n.publicKeyPem||typeof n.machineFingerprint!=`string`||!n.machineFingerprint||typeof n.instanceToken!=`string`||!n.instanceToken||n.lastSuccessfulCheckAt!==void 0&&(typeof n.lastSuccessfulCheckAt!=`string`||!n.lastSuccessfulCheckAt)?null:n}catch{return null}}async function A(e,t=T){let i=r.dirname(t);await n.mkdir(i,{recursive:!0});let a=`${t}.${process.pid}.${Bun.randomUUIDv7()}.tmp`;await Bun.write(a,`${JSON.stringify(e,null,2)}\n`),await n.rename(a,t)}async function j(e,t=T){let n=await k(t);if(!n)throw Error(`Local license state is missing`);await A({...n,instanceToken:e,lastSuccessfulCheckAt:new Date().toISOString()},t)}async function M(e=T){let t=`${e}.lock`;await n.mkdir(r.dirname(t),{recursive:!0});let i;try{i=await n.open(t,`wx`)}catch(e){if(!(e instanceof Error)||!(`code`in e)||e.code!==`EEXIST`)throw e;let r=Number.parseInt(await Bun.file(t).text().catch(()=>``),10),a=!1;if(Number.isInteger(r)&&r>0)try{process.kill(r,0),a=!0}catch{a=!1}if(a)throw Error(`Another JChat process already owns the license lock`,{cause:e});await n.rm(t,{force:!0}),i=await n.open(t,`wx`)}return await i.writeFile(`${process.pid}\n`),async()=>{await i.close(),await n.rm(t,{force:!0})}}async function N(e,t,n,r,i,a=`envato`,o=fetch){let s=D();try{let c=await C({purchaseCode:e,domain:t,siteSlug:n,domainScope:r,routingMode:i,provider:a,installationId:s.installationId,installationPublicKey:s.publicKeyPem,machineFingerprint:s.machineFingerprint,fetchImpl:o});return c.success?{valid:!0,buyer:c.buyer,token:c.token,domain:c.domain,state:s}:{valid:!1,error:c.message}}catch{return{valid:!1,error:`Failed to verify license. Check your internet connection.`}}}function P(e={}){let t=r.resolve(e.cwd??process.cwd()),n=e.env??process.env,i=I(t);return{repoRoot:i,runtimeRoot:t,uploadsDir:L(n.JCHAT_UPLOADS_DIR,t,i,`uploads`,`uploads`),addonBundlesDir:L(n.JCHAT_ADDONS_DIR,t,i,`addons`,`addons`),addonSourceDir:i?r.join(i,`addons`):r.join(t,`addons`),migrationsDir:L(n.JCHAT_MIGRATIONS_DIR,t,i,r.join(`packages`,`db`,`migrations`),`migrations`)}}function F(e){return i.existsSync(r.join(e,`package.json`))&&i.existsSync(r.join(e,`apps`))&&i.existsSync(r.join(e,`packages`))}function I(e){let t=r.resolve(e);for(;;){if(F(t))return t;let e=r.dirname(t);if(e===t)return null;t=e}}function L(e,t,n,i,a){return e?r.resolve(e):n?r.join(n,i):r.join(t,a)}`${[`manage`,`www`].join(`|`)}`;const R={PERMISSIONS:`permissions`,USERS:`users`,BOTS:`bots`,CORES:`cores`,SETTINGS:`settings`,MEMBERSHIPS:`memberships`,ROOMS:`rooms`,SITES:`sites`,RANKS:`ranks`,BLOCKS:`blocks`,BLOCKERS:`blockers`,SPAMS:`spams`,SOCIALS:`socials`,XPS:`xps`,JOINS:`joins`,DAILIES:`dailies`,SESSIONS:`sessions`,MEMBERS:`members`,STREAMS:`streams`,AUDITS:`audits`,ACTIONS:`actions`,UPDATES:`updates`,LOGS:`logs`,ERRORS:`errors`,CONFIGS:`configs`,BANS:`bans`};`${R.STREAMS}${R.AUDITS}${R.ACTIONS}`,`${R.STREAMS}${R.XPS}${R.UPDATES}`,`${R.STREAMS}${R.LOGS}${R.ERRORS}`;const z={MESSAGING:`Messaging`,PRIVATE_MESSAGING:`Private Messaging`,ROOMS:`Rooms`,MODERATION:`Moderation`,PROFILE:`Profile`,POSTS:`Posts`,GIFTS:`Gifts`,FRIENDS:`Friends`,SITE_ADMIN:`Site Admin`,ADDONS:`Addons`,COMMANDS:`Commands`},B={ALL:{value:`*`,label:`Super Admin`,description:`Grants absolute access to every feature and setting. Use with extreme caution.`,category:z.SITE_ADMIN},MESSAGE_SEND:{value:`message.send`,label:`Send Messages`,description:`Allows the user to send messages in public chat rooms.`,category:z.MESSAGING},MESSAGE_DELETE_OWN:{value:`message.delete.own`,label:`Delete Own Messages`,description:`Allows the user to remove their own sent messages.`,category:z.MESSAGING},MESSAGE_DELETE_ANY:{value:`message.delete.any`,label:`Delete Any Message`,description:`Moderator tool: delete messages sent by any user.`,category:z.MESSAGING},MESSAGE_CLEAR:{value:`message.clear`,label:`Clear Chat`,description:`Allows clearing the entire message history of a room.`,category:z.MESSAGING},MESSAGE_UPLOAD:{value:`message.upload`,label:`Upload Files`,description:`Allows attaching images and files to messages.`,category:z.MESSAGING},MESSAGE_USE_COMMANDS:{value:`message.use_commands`,label:`Use Slash Commands`,description:`Allows executing chat commands (e.g., /me, /shrug).`,category:z.MESSAGING},MESSAGE_WILDCARD:{value:`message.*`,label:`Full Messaging Access`,description:`Grants all message-related permissions in a room.`,category:z.MESSAGING},PM_SEND:{value:`pm.send`,label:`Send Private Messages`,description:`Allows starting and participating in 1-on-1 private conversations.`,category:z.PRIVATE_MESSAGING},PM_DELETE_OWN:{value:`pm.delete.own`,label:`Delete Own PMs`,description:`Allows deleting messages sent in private chats.`,category:z.PRIVATE_MESSAGING},PM_READ_ANY:{value:`pm.read.any`,label:`Read Any PM`,description:`Administrative tool: View all private conversations on the site.`,category:z.PRIVATE_MESSAGING},PM_DELETE_ANY:{value:`pm.delete.any`,label:`Delete Any PM`,description:`Moderator tool: Remove private messages sent by any user.`,category:z.PRIVATE_MESSAGING},PM_BYPASS:{value:`pm.bypass`,label:`Bypass Privacy Settings`,description:`Allows bypassing receiver's privacy settings (e.g. not accepting PMs or friends only).`,category:z.PRIVATE_MESSAGING},PM_WILDCARD:{value:`pm.*`,label:`Full PM Access`,description:`Grants all private-messaging related permissions.`,category:z.PRIVATE_MESSAGING},ROOM_JOIN:{value:`room.join`,label:`Join Rooms`,description:`Allows users to enter and view public or restricted chat rooms.`,category:z.ROOMS},ROOM_CREATE:{value:`room.create`,label:`Create Rooms`,description:`Allows users to create new chat rooms.`,category:z.ROOMS},ROOM_EDIT:{value:`room.edit`,label:`Edit Room Settings`,description:`Allows changing room names, descriptions, and settings.`,category:z.ROOMS},ROOM_DELETE:{value:`room.delete`,label:`Delete Rooms`,description:`Allows permanently removing chat rooms and their history.`,category:z.ROOMS},ROOM_LOCK:{value:`room.lock`,label:`Lock/Unlock Rooms`,description:`Allows moderators to temporarily prevent new messages in a room.`,category:z.ROOMS},ROOM_SET_TOPIC:{value:`room.set_topic`,label:`Change Room Topic`,description:`Allows updating the current topic/announcement displayed in a room.`,category:z.ROOMS},ROOM_WILDCARD:{value:`room.*`,label:`Full Room Management`,description:`Grants all room-related administration permissions.`,category:z.ROOMS},USER_KICK:{value:`user.kick`,label:`Kick Users`,description:`Allows removing a user from a room temporarily.`,category:z.MODERATION},USER_BAN:{value:`user.ban`,label:`Ban Users`,description:`Allows permanently banning users from the site or specific rooms.`,category:z.MODERATION},USER_UNBAN:{value:`user.unban`,label:`Unban Users`,description:`Allows lifting previously imposed bans.`,category:z.MODERATION},USER_MUTE:{value:`user.mute`,label:`Mute Users`,description:`Allows preventing a user from sending messages for a duration.`,category:z.MODERATION},USER_UNMUTE:{value:`user.unmute`,label:`Unmute Users`,description:`Allows lifting mutes early.`,category:z.MODERATION},USER_EDIT_RANK:{value:`user.edit_rank`,label:`Manage User Ranks`,description:`Allows assigning or removing ranks for other users.`,category:z.MODERATION},USER_VIEW_PROFILE:{value:`user.view_profile`,label:`View Profiles`,description:`Allows viewing detailed user profiles and settings.`,category:z.MODERATION},USER_WILDCARD:{value:`user.*`,label:`Full Moderation Access`,description:`Grants all user-management and moderation permissions.`,category:z.MODERATION},PROFILE_EDIT:{value:`profile.edit`,label:`Edit Own Profile`,description:`Allows users to change their display name, bio, and avatar.`,category:z.PROFILE},PROFILE_CUSTOMIZE:{value:`profile.customize`,label:`Profile Customization`,description:`Allows advanced styling like custom fonts, themes, and backgrounds.`,category:z.PROFILE},PROFILE_SET_STATUS:{value:`profile.set_status`,label:`Set Status`,description:`Allows setting custom status messages and status indicators.`,category:z.PROFILE},POST_CREATE:{value:`post.create`,label:`Create Posts`,description:`Allows sharing updates and media in the activity feed.`,category:z.POSTS},POST_CREATE_ANNOUNCEMENT:{value:`post.create.announcement`,label:`Create Announcements`,description:`Allows creating official site announcements.`,category:z.POSTS},POST_EDIT_OWN:{value:`post.edit.own`,label:`Edit Own Posts`,description:`Allows modifying previously shared feed posts.`,category:z.POSTS},POST_EDIT_ANY:{value:`post.edit.any`,label:`Edit Any Post`,description:`Administrative tool: Edit posts shared by any user.`,category:z.POSTS},POST_DELETE_OWN:{value:`post.delete.own`,label:`Delete Own Posts`,description:`Allows removing personal feed posts.`,category:z.POSTS},POST_DELETE_ANY:{value:`post.delete.any`,label:`Delete Any Post`,description:`Moderator tool: Remove posts shared by any user.`,category:z.POSTS},POST_REACT:{value:`post.react`,label:`React to Posts`,description:`Allows adding emoji reactions to feed posts.`,category:z.POSTS},POST_COMMENT:{value:`post.comment`,label:`Comment on Posts`,description:`Allows joining the conversation on shared feed items.`,category:z.POSTS},POST_COMMENT_DELETE_OWN:{value:`post.comment.delete.own`,label:`Delete Own Comments`,description:`Allows removing personal comments from feed posts.`,category:z.POSTS},POST_COMMENT_DELETE_ANY:{value:`post.comment.delete.any`,label:`Delete Any Comment`,description:`Moderator tool: Remove comments left by any user.`,category:z.POSTS},POST_WILDCARD:{value:`post.*`,label:`Full Feed Access`,description:`Grants all permissions related to the activity feed.`,category:z.POSTS},USER_BLOCK:{value:`user.block`,label:`Block Users`,description:`Allows preventing specific users from contacting the user.`,category:z.MODERATION},USER_UNBLOCK:{value:`user.unblock`,label:`Unblock Users`,description:`Allows lifting previously imposed user blocks.`,category:z.MODERATION},USER_IGNORE:{value:`user.ignore`,label:`Ignore Users`,description:`Allows hiding messages and notifications from specific users.`,category:z.MODERATION},USER_UNIGNORE:{value:`user.unignore`,label:`Stop Ignoring`,description:`Allows showing messages from previously ignored users again.`,category:z.MODERATION},SITE_EDIT:{value:`site.edit`,label:`Edit Site Settings`,description:`Allows changing site name, branding, and global configuration.`,category:z.SITE_ADMIN},SITE_CREATE:{value:`site.create`,label:`Create New Sites`,description:`Master Admin tool: Create independent Jchat site instances.`,category:z.SITE_ADMIN},SITE_MANAGE_RANKS:{value:`site.manage_ranks`,label:`Manage Ranks & Perms`,description:`Allows creating ranks and configuring their permission sets.`,category:z.SITE_ADMIN},SITE_MANAGE_ADDONS:{value:`site.manage_addons`,label:`Manage Addons`,description:`Allows installing, configuring, and removing jchat Addons.`,category:z.SITE_ADMIN},SITE_VIEW_AUDIT:{value:`site.view_audit`,label:`View Audit Logs`,description:`Administrative tool: See a detailed record of all admin actions.`,category:z.SITE_ADMIN},SITE_VIEW_REPORTS:{label:`View Reports`,value:`site.view_reports`,description:`Allows seeing reports filed by users against content or people.`,category:z.SITE_ADMIN},SITE_MANAGE_REPORTS:{value:`site.manage_reports`,label:`Handle Reports`,description:`Allows resolving or dismissing user reports.`,category:z.SITE_ADMIN},SITE_MANAGE_SPAM:{value:`site.manage_spam`,label:`Manage Anti-Spam`,description:`Allows configuring the anti-spam engine, thresholds, and per-module settings.`,category:z.SITE_ADMIN},SITE_WILDCARD:{value:`site.*`,label:`Full Site Administration`,description:`Grants all technical administration and site discovery permissions.`,category:z.SITE_ADMIN},FRIEND_REQUEST_SEND:{value:`friend.request.send`,label:`Send Friend Requests`,description:`Allows the user to send friend requests to other users.`,category:z.FRIENDS},FRIEND_REQUEST_MANAGE:{value:`friend.request.manage`,label:`Manage Friend Requests`,description:`Allows the user to accept or reject incoming friend requests.`,category:z.FRIENDS},FRIEND_REMOVE:{value:`friend.remove`,label:`Remove Friends`,description:`Allows the user to remove accepted friends from their list.`,category:z.FRIENDS},FRIEND_LIST:{value:`friend.list`,label:`View Friend List`,description:`Allows the user to view their own friends list.`,category:z.FRIENDS},ADDON_WILDCARD:{value:`addon.*`,label:`Full Addon System Access`,description:`Grants access to use every installed addon regardless of specific gates.`,category:z.ADDONS},COMMAND_CLEAR:{value:`command.clear`,label:`Clear Chat Command`,description:`Allows using the /clear slash command to clear chat history.`,category:z.COMMANDS},COMMAND_HELP:{value:`command.help`,label:`View Commands Help`,description:`Allows using the /help slash command.`,category:z.COMMANDS}},V=Object.fromEntries(Object.entries(B).map(([e,t])=>[e,t.value])),ne=Object.fromEntries(Object.entries(B).map(([e,t])=>[t.value,{name:t.value,label:t.label,description:t.description,category:t.category}]));Object.values(ne);const H=[V.MESSAGE_SEND,V.MESSAGE_UPLOAD,V.MESSAGE_DELETE_OWN,V.PM_SEND,V.PM_DELETE_OWN,V.ROOM_JOIN,V.POST_REACT,V.POST_COMMENT,V.POST_COMMENT_DELETE_OWN,V.PROFILE_EDIT,V.PROFILE_SET_STATUS,V.COMMAND_HELP];V.ALL,[V.SITE_EDIT,V.SITE_MANAGE_RANKS,V.SITE_MANAGE_ADDONS,V.SITE_VIEW_AUDIT,V.SITE_VIEW_REPORTS,V.SITE_MANAGE_REPORTS,V.SITE_MANAGE_SPAM,V.USER_WILDCARD,V.ROOM_EDIT,V.ROOM_DELETE,V.ROOM_LOCK,V.ROOM_SET_TOPIC,V.MESSAGE_WILDCARD,V.POST_WILDCARD,V.PM_WILDCARD,V.PROFILE_CUSTOMIZE,V.MESSAGE_USE_COMMANDS,V.POST_CREATE,V.POST_EDIT_OWN,V.POST_DELETE_OWN,V.FRIEND_REQUEST_SEND,V.FRIEND_REQUEST_MANAGE,V.FRIEND_REMOVE,V.FRIEND_LIST,V.COMMAND_CLEAR,...H],[V.ROOM_EDIT,V.ROOM_LOCK,V.ROOM_SET_TOPIC,V.MESSAGE_WILDCARD,V.POST_WILDCARD,V.PM_SEND,V.PM_DELETE_OWN,V.PM_READ_ANY,V.PM_DELETE_ANY,V.USER_KICK,V.USER_MUTE,V.USER_UNMUTE,V.USER_BAN,V.USER_UNBAN,V.PROFILE_CUSTOMIZE,V.MESSAGE_USE_COMMANDS,V.POST_CREATE,V.POST_EDIT_OWN,V.POST_DELETE_OWN,V.FRIEND_REQUEST_SEND,V.FRIEND_REQUEST_MANAGE,V.FRIEND_REMOVE,V.FRIEND_LIST,V.COMMAND_CLEAR,...H],[V.USER_KICK,V.USER_MUTE,V.USER_UNMUTE,V.MESSAGE_DELETE_ANY,V.MESSAGE_CLEAR,V.PROFILE_CUSTOMIZE,V.MESSAGE_USE_COMMANDS,V.POST_CREATE,V.POST_EDIT_OWN,V.POST_DELETE_OWN,V.FRIEND_REQUEST_SEND,V.FRIEND_REQUEST_MANAGE,V.FRIEND_REMOVE,V.FRIEND_LIST,V.COMMAND_CLEAR,...H],[V.PROFILE_CUSTOMIZE,V.MESSAGE_USE_COMMANDS,V.POST_CREATE,V.POST_EDIT_OWN,V.POST_DELETE_OWN,V.FRIEND_REQUEST_SEND,V.FRIEND_REQUEST_MANAGE,V.FRIEND_REMOVE,V.FRIEND_LIST,...H],[V.MESSAGE_USE_COMMANDS,V.POST_CREATE,V.POST_EDIT_OWN,V.POST_DELETE_OWN,V.FRIEND_REQUEST_SEND,V.FRIEND_REQUEST_MANAGE,V.FRIEND_REMOVE,V.FRIEND_LIST,...H],[...H];const U={LOCAL:`LOCAL`,MINIO:`MINIO`,CLOUDFLARE_R2:`CLOUDFLARE_R2`,AWS_S3:`AWS_S3`,GCS:`GCS`};function re(e){let t=e?.trim().replace(/^https?:\/\//,``).split(`/`)[0].split(`:`)[0].replace(/^\.+/,``);if(!t||t===`localhost`||t.endsWith(`.localhost`)||/^\d{1,3}(\.\d{1,3}){3}$/.test(t))return;let n=t.split(`.`);if(n.length>2){let e=[`co`,`com`,`org`,`net`,`gov`,`edu`,`ac`,`sch`],t=n[n.length-1],r=n[n.length-2];return e.includes(r)&&t.length===2&&n.length>=3?n.slice(-3).join(`.`):n.slice(-2).join(`.`)}return t}function ie(e){let t=e?.trim().replace(/^https?:\/\//,``).split(`/`)[0].split(`:`)[0].replace(/^\.+/,``).toLowerCase();return t?t===`localhost`||/^\d{1,3}(\.\d{1,3}){3}$/.test(t)?!0:re(t)===t:!1}new Map([{label:`Default`,value:`lato`,family:`'Lato', sans-serif`,googleName:`Lato`,scale:1},{label:`Elegant`,value:`playfair`,family:`'Playfair Display', serif`,googleName:`Playfair Display`,scale:1.1},{label:`Cursive`,value:`dancing`,family:`'Dancing Script', cursive`,googleName:`Dancing Script`,scale:1.2},{label:`Modern`,value:`poppins`,family:`'Poppins', sans-serif`,googleName:`Poppins`,scale:1},{label:`Monospace`,value:`jetbrains`,family:`'JetBrains Mono', monospace`,googleName:`JetBrains Mono`,scale:.95},{label:`Rounded`,value:`quicksand`,family:`'Quicksand', sans-serif`,googleName:`Quicksand`,scale:1},{label:`Serif`,value:`roboto-serif`,family:`'Roboto Serif', serif`,googleName:`Roboto Serif`,scale:1.05},{label:`Sci-Fi`,value:`audiowide`,family:`'Audiowide', cursive`,googleName:`Audiowide`,scale:.95},{label:`Type`,value:`special-elite`,family:`'Special Elite', cursive`,googleName:`Special Elite`,scale:1.15},{label:`Pixel`,value:`press-start`,family:`'Press Start 2P', cursive`,googleName:`Press Start 2P`,scale:.75},{label:`Gothic`,value:`grenze`,family:`'Grenze Gotisch', cursive`,googleName:`Grenze Gotisch`,scale:1.15},{label:`Deco`,value:`righteous`,family:`'Righteous', cursive`,googleName:`Righteous`,scale:1},{label:`Spooky`,value:`creepster`,family:`'Creepster', cursive`,googleName:`Creepster`,scale:1.1},{label:`Shadow`,value:`vast-shadow`,family:`'Vast Shadow', cursive`,googleName:`Vast Shadow`,scale:1.05},{label:`Tech`,value:`syncopate`,family:`'Syncopate', sans-serif`,googleName:`Syncopate`,scale:.9},{label:`Script`,value:`lobster`,family:`'Lobster', cursive`,googleName:`Lobster`,scale:1.05}].map(e=>[e.value,e.scale]));const ae=typeof window<`u`&&window.document!==void 0&&!window.process,W={brand:`color: #06b6d4; font-weight: bold;`,timestamp:`color: #888; font-size: 10px; font-family: monospace;`,info:`color: #3b82f6; font-weight: bold;`,success:`color: #10b981; font-weight: bold;`,warn:`color: #f59e0b; font-weight: bold;`,error:`color: #ef4444; font-weight: bold;`,debug:`color: #6366f1; font-style: italic;`,content:`color: inherit; font-family: inherit;`},G={reset:`\x1B[0m`,brand:`\x1B[1m\x1B[36m`,timestamp:`\x1B[90m`,info:`\x1B[1m\x1B[34m`,success:`\x1B[1m\x1B[32m`,warn:`\x1B[1m\x1B[33m`,error:`\x1B[1m\x1B[31m`,debug:`\x1B[3m\x1B[35m`};new Proxy(console,{get(e,t){if([`info`,`success`,`warn`,`error`,`debug`].includes(t)){let e=new Date().toLocaleTimeString();if(ae)return import.meta.env?.PROD&&t===`debug`?()=>{}:console.log.bind(console,`%c${e} %c[jchat]%c ${t}%c`,W.timestamp,W.brand,W[t],W.content);{let n=G[t]||``,r=G.brand,i=G.timestamp,a=G.reset;return console.log.bind(console,`${i}${e}${a} ${r}[jchat]${a} ${n}${t}${a}${i}`)}}return e[t]}});function oe(e){return t=>e.parse(o(t,`VITE_`,``))}const K=s.object({JCHAT_PORT:s.string().regex(/^\d+$/).transform(Number).refine(e=>e>=1&&e<=65535,{message:`Port must be between 1 and 65535`}).refine(e=>e!==80&&e!==443,{message:`Avoid ports 80/443 (reserved for HTTP/HTTPS)`}).default(3e3),JCHAT_COOKIE_SECRET:s.string().min(1),JCHAT_DATABASE_URL:s.url().startsWith(`postgresql://`).regex(/^postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/([^?]+)(\?.*)?$/,{message:`Must be in format: postgresql://username:password@host:port/database?schema`}),JCHAT_REDIS_DATABASE_URL:s.url().refine(e=>e.startsWith(`rediss://`)||e.startsWith(`redis://`),{message:`Must use redis:// or rediss:// protocol`}).refine(e=>/^(rediss?):\/\/(?:([^:]+):([^@]+)@)?([^:]+):(\d+)$/.test(e),{message:`Must be in format: rediss://default:<password>@<host>:<port>`}),JCHAT_STORAGE_TYPE:s.enum(U),JCHAT_MINIO_ENDPOINT:s.string().optional(),JCHAT_MINIO_BUCKET_NAME:s.string().optional(),JCHAT_MINIO_ACCESS_KEY_ID:s.string().optional(),JCHAT_MINIO_SECRET_ACCESS_KEY:s.string().optional(),JCHAT_MINIO_PUBLIC_URL:s.string().optional(),JCHAT_AWS_REGION:s.string().optional(),JCHAT_AWS_BUCKET_NAME:s.string().optional(),JCHAT_AWS_ACCESS_KEY_ID:s.string().optional(),JCHAT_AWS_SECRET_ACCESS_KEY:s.string().optional(),JCHAT_CLOUDFLARE_ACCOUNT_ID:s.string().optional(),JCHAT_CLOUDFLARE_BUCKET_NAME:s.string().optional(),JCHAT_CLOUDFLARE_ACCESS_KEY_ID:s.string().optional(),JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY:s.string().optional(),JCHAT_CLOUDFLARE_PUBLIC_URL:s.string().optional(),JCHAT_GCS_BUCKET_NAME:s.string().optional(),JCHAT_GCS_PROJECT_ID:s.string().optional(),JCHAT_GCS_CLIENT_EMAIL:s.string().optional(),JCHAT_GCS_PRIVATE_KEY:s.string().optional(),JCHAT_GCS_KEY_FILE:s.string().optional(),JCHAT_BOT_NAME:s.string().trim().min(3),JCHAT_BOT_EMAIL:s.email(),JCHAT_DOMAIN:s.string().optional(),JCHAT_SITE_URL:s.url().optional(),JCHAT_ROUTING_MODE:s.enum([`path`,`subdomain`]).default(`subdomain`),JCHAT_ADDON_ENCRYPTION_KEY:s.string().min(16),JCHAT_LICENSE_SERVER_URL:s.url().optional(),JCHAT_STORE_SERVER_URL:s.url().optional(),NODE_ENV:s.enum([`development`,`test`,`production`]).default(`development`),SYSTEM_BOOTSTRAP_COMPLETE:s.boolean().optional()}).superRefine((e,t)=>{if(e.JCHAT_SITE_URL){let n;try{n=new URL(e.JCHAT_SITE_URL).hostname}catch{}n&&!ie(n)&&t.addIssue({path:[`JCHAT_SITE_URL`],code:`custom`,message:`JCHAT_SITE_URL must use the client main/root domain`})}e.JCHAT_STORAGE_TYPE===U.MINIO&&!(e.JCHAT_MINIO_ENDPOINT&&e.JCHAT_MINIO_BUCKET_NAME&&e.JCHAT_MINIO_ACCESS_KEY_ID&&e.JCHAT_MINIO_SECRET_ACCESS_KEY&&e.JCHAT_MINIO_PUBLIC_URL)&&(e.JCHAT_MINIO_ENDPOINT||t.addIssue({path:[`JCHAT_MINIO_ENDPOINT`],code:`custom`,message:`JCHAT_MINIO_ENDPOINT is required when JCHAT_STORAGE_TYPE is MINIO`}),e.JCHAT_MINIO_BUCKET_NAME||t.addIssue({path:[`JCHAT_MINIO_BUCKET_NAME`],code:`custom`,message:`JCHAT_MINIO_BUCKET_NAME is required when JCHAT_STORAGE_TYPE is MINIO`}),e.JCHAT_MINIO_ACCESS_KEY_ID||t.addIssue({path:[`JCHAT_MINIO_ACCESS_KEY_ID`],code:`custom`,message:`JCHAT_MINIO_ACCESS_KEY_ID is required when JCHAT_STORAGE_TYPE is MINIO`}),e.JCHAT_MINIO_SECRET_ACCESS_KEY||t.addIssue({path:[`JCHAT_MINIO_SECRET_ACCESS_KEY`],code:`custom`,message:`JCHAT_MINIO_SECRET_ACCESS_KEY is required when JCHAT_STORAGE_TYPE is MINIO`}),e.JCHAT_MINIO_PUBLIC_URL||t.addIssue({path:[`JCHAT_MINIO_PUBLIC_URL`],code:`custom`,message:`JCHAT_MINIO_PUBLIC_URL is required when JCHAT_STORAGE_TYPE is MINIO`})),e.JCHAT_STORAGE_TYPE===U.AWS_S3&&!(e.JCHAT_AWS_REGION&&e.JCHAT_AWS_ACCESS_KEY_ID&&e.JCHAT_AWS_SECRET_ACCESS_KEY&&e.JCHAT_AWS_BUCKET_NAME)&&(e.JCHAT_AWS_REGION||t.addIssue({path:[`JCHAT_AWS_REGION`],code:`custom`,message:`JCHAT_AWS_REGION is required when JCHAT_STORAGE_TYPE is AWS_S3`}),e.JCHAT_AWS_ACCESS_KEY_ID||t.addIssue({path:[`JCHAT_AWS_ACCESS_KEY_ID`],code:`custom`,message:`JCHAT_AWS_ACCESS_KEY_ID is required when JCHAT_STORAGE_TYPE is AWS_S3`}),e.JCHAT_AWS_SECRET_ACCESS_KEY||t.addIssue({path:[`JCHAT_AWS_SECRET_ACCESS_KEY`],code:`custom`,message:`JCHAT_AWS_SECRET_ACCESS_KEY is required when JCHAT_STORAGE_TYPE is AWS_S3`}),e.JCHAT_AWS_BUCKET_NAME||t.addIssue({path:[`JCHAT_AWS_BUCKET_NAME`],code:`custom`,message:`JCHAT_AWS_BUCKET_NAME is required when JCHAT_STORAGE_TYPE is AWS_S3`})),e.JCHAT_STORAGE_TYPE===U.CLOUDFLARE_R2&&!(e.JCHAT_CLOUDFLARE_ACCOUNT_ID&&e.JCHAT_CLOUDFLARE_ACCESS_KEY_ID&&e.JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY&&e.JCHAT_CLOUDFLARE_BUCKET_NAME&&e.JCHAT_CLOUDFLARE_PUBLIC_URL)&&(e.JCHAT_CLOUDFLARE_ACCOUNT_ID||t.addIssue({path:[`JCHAT_CLOUDFLARE_ACCOUNT_ID`],code:`custom`,message:`JCHAT_CLOUDFLARE_ACCOUNT_ID is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`}),e.JCHAT_CLOUDFLARE_ACCESS_KEY_ID||t.addIssue({path:[`JCHAT_CLOUDFLARE_ACCESS_KEY_ID`],code:`custom`,message:`JCHAT_CLOUDFLARE_ACCESS_KEY_ID is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`}),e.JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY||t.addIssue({path:[`JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY`],code:`custom`,message:`JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`}),e.JCHAT_CLOUDFLARE_BUCKET_NAME||t.addIssue({path:[`JCHAT_CLOUDFLARE_BUCKET_NAME`],code:`custom`,message:`JCHAT_CLOUDFLARE_BUCKET_NAME is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`}),e.JCHAT_CLOUDFLARE_PUBLIC_URL||t.addIssue({path:[`JCHAT_CLOUDFLARE_PUBLIC_URL`],code:`custom`,message:`JCHAT_CLOUDFLARE_PUBLIC_URL is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`})),e.JCHAT_STORAGE_TYPE===U.GCS&&!(e.JCHAT_GCS_PROJECT_ID&&e.JCHAT_GCS_BUCKET_NAME&&e.JCHAT_GCS_CLIENT_EMAIL&&e.JCHAT_GCS_PRIVATE_KEY)&&(e.JCHAT_GCS_PROJECT_ID||t.addIssue({path:[`JCHAT_GCS_PROJECT_ID`],code:`custom`,message:`JCHAT_GCS_PROJECT_ID is required when JCHAT_STORAGE_TYPE is GCS`}),e.JCHAT_GCS_BUCKET_NAME||t.addIssue({path:[`JCHAT_GCS_BUCKET_NAME`],code:`custom`,message:`JCHAT_GCS_BUCKET_NAME is required when JCHAT_STORAGE_TYPE is GCS`}),e.JCHAT_GCS_CLIENT_EMAIL||t.addIssue({path:[`JCHAT_GCS_CLIENT_EMAIL`],code:`custom`,message:`JCHAT_GCS_CLIENT_EMAIL is required when JCHAT_STORAGE_TYPE is GCS`}),e.JCHAT_GCS_PRIVATE_KEY||t.addIssue({path:[`JCHAT_GCS_PRIVATE_KEY`],code:`custom`,message:`JCHAT_GCS_PRIVATE_KEY is required when JCHAT_STORAGE_TYPE is GCS`}))});function se(t,n=`production`,r){let i,a=!0;try{i=oe(K)(t);let o=O();if(n===`developer-sandbox`)a=!0;else if(!o)a=!1;else if(!i.JCHAT_DOMAIN||!f(i.JCHAT_DOMAIN))throw Error(`A valid installed JChat domain is required`);else if(!Q(o,i.JCHAT_DOMAIN||`localhost`,{publicKeyPem:r}))a=!1;else{let t=o.split(`.`),n=t[2]||``;if(n){let r=e.createHmac(`sha256`,n).update(`${t[0]}.${t[1]}`).digest(`hex`);i.JCHAT_COOKIE_SECRET+=r}}}catch{a=!1,i={...t,JCHAT_PORT:t.JCHAT_PORT?parseInt(String(t.JCHAT_PORT)):3e3}}return i.SYSTEM_BOOTSTRAP_COMPLETE=a,i}function q(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function J(e){return typeof e==`string`&&e.trim().length>0}function Y(e){if(!e)return null;try{return JSON.parse(Buffer.from(e,`base64url`).toString(`utf8`))}catch{return null}}function X(e){let t=e.split(`.`);if(t.length!==3)return null;let n=Y(t[0]);return!q(n)||n.alg!==`RS256`||n.typ!==`JWT`?null:{alg:`RS256`,typ:`JWT`}}function ce(e){if(!q(e))return null;let{iss:t,aud:n,licenseId:r,domain:i,buyer:a,installationId:o,machineFingerprint:s,plan:c,entitlements:l,purchaseProvider:f,environment:p,routingMode:m,iat:h}=e;if(!J(t)||!J(n)||!J(r)||!J(i)||!J(a)||!J(o)||!J(s)||c!==`standard`&&c!==`extended`||!Array.isArray(l)||!l.every(J)||f!==`envato`&&f!==`jchat`||p!==`production`&&p!==`development`||m!==`path`&&m!==`subdomain`||typeof h!=`number`||!Number.isSafeInteger(h)||h<0)return null;let g;try{g=u(i)}catch{return null}if(p===`development`&&!g.isLocalhost||p===`production`&&!d(i))return null;let _=l.includes(`site.create.multi`);return c===`extended`&&!_||c===`standard`&&_?null:{iss:t,aud:n,licenseId:r,domain:i,buyer:a,installationId:o,machineFingerprint:s,plan:c,entitlements:l,purchaseProvider:f,environment:p,routingMode:m,iat:h}}function Z(e){let t=e.split(`.`);return t.length!==3||!t.every(Boolean)||!X(e)?null:ce(Y(t[1]))}function Q(t,n,r){if(!t||t===`YOUR_LICENSE_KEY_PLACEHOLDER`)return!1;try{let i=t.split(`.`);if(i.length!==3||!X(t))return!1;let a=Z(t);if(!a||a.iss!==(r.issuer??`https://license.jchat.local`)||a.aud!==(r.audience??`jchat-chat`)||u(a.domain).host!==u(n).host)return!1;let o=Buffer.from(`${i[0]}.${i[1]}`),s=Buffer.from(i[2].replace(/-/g,`+`).replace(/_/g,`/`),`base64`);return e.verify(`sha256`,o,r.publicKeyPem,s)}catch{return!1}}function $(e,t,n,r,i){if(!Q(e,t,i))return!1;let a=Z(e);return a?a.installationId===n&&a.machineFingerprint===r:!1}function le(e,t,n,r){return Q(e,t,r)?Z(e)?.entitlements?.includes(n)??!1:!1}export{T as DEFAULT_STATE_PATH,K as ServerConfigSchema,M as acquireProcessLock,C as activateRegistry,w as checkRegistry,D as createIdentity,E as createMachineFingerprint,te as getRuntimeStatus,le as hasEntitlement,N as installAndActivate,f as isAllowedConfiguredDomain,x as isApplicationDisabled,d as isPublicDomain,se as loadServerContext,u as normalizeDomain,Z as parsePayload,k as readState,O as readTokenSync,g as recordAudit,ee as registerInvalidationHandler,m as resolveActivationDomain,P as resolveRuntimePaths,b as setRuntimeStatus,j as updateInstanceToken,$ as verifyEntitlement,Q as verifySignature,A as writeState};
1
+ import e from"node:crypto";import t from"node:net";import n from"node:fs/promises";import r from"node:path";import i from"node:fs";import{Elysia as a}from"elysia";import{replaceKeys as o}from"string-ts";import*as s from"zod";const c=`https://license.jchatlive.com`,l=`https://license.jchatlive.com`,u=`jchat-chat`,d=new Set([`localhost`,`127.0.0.1`,`::1`]),f=[`.local`,`.test`,`.invalid`,`.example`];function p(e){let t=e.trim();if(!t)throw Error(`Instance domain is required`);let n=new URL(t.includes(`://`)?t:`https://${t}`).hostname.toLowerCase().replace(/\.$/,``),r=n===`www.localhost`?`localhost`:n.replace(/^www\./,``);if(!r||r.includes(`@`)||/\s/.test(r))throw Error(`Invalid instance domain`);return{host:r,isLocalhost:d.has(r)}}function m(e){let{host:n,isLocalhost:r}=p(e);return!r&&t.isIP(n)===0&&n.includes(`.`)&&!f.some(e=>n.endsWith(e))}function h(e){try{return p(e).isLocalhost||m(e)}catch{return!1}}function g(e){try{return p(e).isLocalhost}catch{return!1}}function _(e){let{host:n,isLocalhost:r}=p(e);if(r||t.isIP(n)!==0)return null;let i=n.split(`.`);if(i.length<=2)return n;let a=new Set([`co`,`com`,`org`,`net`,`gov`,`edu`,`ac`,`sch`]),o=i.at(-1)??``,s=i.at(-2)??``;return a.has(s)&&o.length===2&&i.length>=3?i.slice(-3).join(`.`):i.slice(-2).join(`.`)}function ee(e,t){try{let n=p(e).host,r=p(t).host;if(n===r||n.endsWith(`.${r}`))return!0;let i=_(n),a=_(r);return!!(i&&a&&i===a)}catch{return!1}}function te(e,t,n,r=`current-host`){let i=p(e).host;return n===`subdomain`&&t===`extended`&&r===`parent-domain`?_(i)??i:i}function v(e={}){let t=r.resolve(e.cwd??process.cwd()),n=e.env??Bun.env,i=y(t);return{repoRoot:i,runtimeRoot:t,uploadsDir:b(n.JCHAT_UPLOADS_DIR,t,i,`uploads`,`uploads`),addonBundlesDir:b(n.JCHAT_ADDONS_DIR,t,i,`addons`,`addons`),addonSourceDir:i?r.join(i,`addons`):r.join(t,`addons`),migrationsDir:b(n.JCHAT_MIGRATIONS_DIR,t,i,r.join(`packages`,`db`,`migrations`),`migrations`),dataDir:b(n.JCHAT_DATA_DIR,t,i,`data`,`data`)}}function ne(e){return i.existsSync(r.join(e,`package.json`))&&i.existsSync(r.join(e,`apps`))&&i.existsSync(r.join(e,`packages`))}function y(e){let t=r.resolve(e);for(;;){if(ne(t))return t;let e=r.dirname(t);if(e===t)return null;t=e}}function b(e,t,n,i,a){return e?r.resolve(e):n?r.join(n,i):r.join(t,a)}function x(){return v().dataDir}const S=r.join(x(),`instance.json`);function C(){return{installationId:Bun.randomUUIDv7()}}function w(e=S){try{if(!i.existsSync(e))return``;let t=i.readFileSync(e,`utf8`);return JSON.parse(t)?.instanceToken??``}catch{return``}}async function T(e=S){try{let t=Bun.file(e);if(!await t.exists())return null;let n=JSON.parse(await t.text());if(!n||typeof n!=`object`)return null;let r=n;return r.version!==1||typeof r.installationId!=`string`||!r.installationId||typeof r.instanceToken!=`string`||!r.instanceToken?null:r}catch{return null}}async function E(e,t=S){let i=r.dirname(t);await n.mkdir(i,{recursive:!0});let a=`${t}.${process.pid}.${Bun.randomUUIDv7()}.tmp`;await Bun.write(a,`${JSON.stringify(e,null,2)}\n`),await n.rename(a,t)}async function re(e,t=S){let n=await T(t);if(!n)throw Error(`Local license state is missing`);await E({...n,instanceToken:e,lastSuccessfulCheckAt:new Date().toISOString()},t)}function D(e){if(e)return e.length<=8?`redacted`:`${e.slice(0,4)}…${e.slice(-4)}`}async function ie(e,t,i=r.join(x(),`license-events.log`)){await n.mkdir(r.dirname(i),{recursive:!0});let a=JSON.stringify({at:new Date().toISOString(),event:e,domain:D(t.domain),installationId:D(t.installationId),reference:D(t.reference)});await n.appendFile(i,`${a}\n`,`utf8`)}let O=`unavailable`,k=`startup`,A;function ae(e){A=e}function j(t,n=e.randomUUID()){O=t,k=n,t===`invalid`&&A?.()}function M(){return{status:O,reference:k}}function N(){return O===`invalid`}function oe(e=Bun.env.NODE_ENV===`production`){return!!(O===`invalid`||e&&O===`unavailable`)}function se(){return Bun.env.JCHAT_LICENSE_SERVER_URL||(Bun.env.NODE_ENV===`development`||Bun.env.NODE_ENV===`test`?`http://localhost:4000`:c)}async function P(e){try{let t=e.registryEndpoint??se(),n=await(e.fetchImpl??fetch)(`${t}/api/v1/activate`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({purchaseCode:e.purchaseCode,domain:p(e.domain).host,siteSlug:e.siteSlug,domainScope:e.domainScope,provider:e.provider,routingMode:e.routingMode,installationId:e.installationId,environment:p(e.domain).isLocalhost?`development`:`production`})}),r=await n.json();return n.ok===!1||!r.success||!r.token?{success:!1,message:r.message??`This license is already active on another domain. Please deactivate it first.`}:{success:!0,buyer:r.buyer??`Customer`,token:r.token,domain:r.domain??p(e.domain).host}}catch{return{success:!1,message:`Failed to connect to the central registry. Please try again later.`}}}async function ce(e,t,n,r,i,a=`envato`,o=fetch){let s=C();try{let c=await P({purchaseCode:e,domain:t,siteSlug:n,domainScope:r,routingMode:i,provider:a,installationId:s.installationId,fetchImpl:o});return c.success?{valid:!0,buyer:c.buyer,token:c.token,domain:c.domain,state:s}:{valid:!1,error:c.message}}catch{return{valid:!1,error:`Failed to verify license. Check your internet connection.`}}}const F=new Set([`/health`,`/api/v1/license/status`]);async function I(e={}){if(!(e.isPrimaryWorker??(!Bun.env.WORKER_ID||Bun.env.WORKER_ID===`0`)))return;let t=e.domain??`localhost`;if(e.onInvalidate&&ae(e.onInvalidate),g(t)){j(`valid`);return}let n=await T();if(!n?.instanceToken){j(e.isBootstrapComplete?`valid`:`unavailable`);return}if(e.signingKey&&!Q(n.instanceToken,t,{publicKeyPem:e.signingKey})){j(`invalid`,`domain-mismatch`);return}if(await L(e)>1&&!$(n.instanceToken,t)){j(`invalid`,`multi-site-unauthorized`);return}j(`valid`)}function le(e={}){I(e);let t=e.allowedPaths??F;return new a({name:`jchatRuntime`}).derive({as:`global`},()=>({runtimeStatus:M()})).onBeforeHandle({as:`global`},async({request:n,set:r})=>{if(n.method===`POST`&&n.url.includes(`/owner/sites`)){let t=e.domain??`localhost`;if(!$((await T())?.instanceToken,t)&&await L(e)>=1)return r.status=403,{status:`license_restricted`,message:`Standard license is restricted to a single site. An Extended license is required to create additional sites.`,reference:`multi-site-unauthorized`}}return de(n,r,t)})}function ue(e,t,n){return n.has(e)?!0:t===`invalid`?e===`/install/upgrade-license`:t===`unavailable`?e===`/install`||e.startsWith(`/install/`)||e.startsWith(`/assets/`)||e.startsWith(`/_`)||e===`/favicon.ico`:!1}function de(e,t,n=F,r=Bun.env.NODE_ENV===`production`){let{status:i,reference:a}=M(),o=e.headers.get(`host`)?.split(`:`)[0]?.toLowerCase();if(o&&m(o)&&g(Bun.env.JCHAT_DOMAIN||`localhost`))return t.status=403,{status:`license_invalid`,message:`Public domain deployment requires a valid license matching this domain.`,reference:`public-host-requires-license`};if(i!==`valid`&&i!==`unreachable-fallback`&&(r||i!==`unavailable`)&&!ue(new URL(e.url).pathname.replace(/\/+$/,``)||`/`,i,n))return t.status=503,i===`unavailable`?{status:`license_unavailable`,message:`This JChat installation is not activated. Please complete the installation.`,reference:a}:{status:`license_invalid`,message:`This JChat installation is disabled because its license is invalid.`,reference:a}}async function L(e){let t=Bun.env.DATABASE_URL??process.env.DATABASE_URL;if(t&&typeof Bun<`u`&&`SQL`in Bun)try{let e=new Bun.SQL(t),n=await e`SELECT count(*)::int as count FROM sites WHERE deleted_at IS NULL`;if(await e.close(),n?.[0]?.count!==void 0&&n[0].count!==null)return Number(n[0].count)}catch{}if(e.getSiteCount)try{return await e.getSiteCount()}catch{return 0}return 0}`${[`manage`,`www`].join(`|`)}`;const R={PERMISSIONS:`permissions`,USERS:`users`,BOTS:`bots`,CORES:`cores`,SETTINGS:`settings`,MEMBERSHIPS:`memberships`,ROOMS:`rooms`,SITES:`sites`,RANKS:`ranks`,BLOCKS:`blocks`,BLOCKERS:`blockers`,SPAMS:`spams`,SOCIALS:`socials`,XPS:`xps`,JOINS:`joins`,DAILIES:`dailies`,SESSIONS:`sessions`,MEMBERS:`members`,STREAMS:`streams`,AUDITS:`audits`,ACTIONS:`actions`,UPDATES:`updates`,LOGS:`logs`,ERRORS:`errors`,CONFIGS:`configs`,BANS:`bans`};`${R.STREAMS}${R.AUDITS}${R.ACTIONS}`,`${R.STREAMS}${R.XPS}${R.UPDATES}`,`${R.STREAMS}${R.LOGS}${R.ERRORS}`;const z={LOCAL:`LOCAL`,MINIO:`MINIO`,CLOUDFLARE_R2:`CLOUDFLARE_R2`,AWS_S3:`AWS_S3`,GCS:`GCS`},B={MESSAGING:`Messaging`,PRIVATE_MESSAGING:`Private Messaging`,ROOMS:`Rooms`,MODERATION:`Moderation`,PROFILE:`Profile`,POSTS:`Posts`,GIFTS:`Gifts`,FRIENDS:`Friends`,SITE_ADMIN:`Site Admin`,ADDONS:`Addons`,COMMANDS:`Commands`},V={ALL:{value:`*`,label:`Super Admin`,description:`Grants absolute access to every feature and setting. Use with extreme caution.`,category:B.SITE_ADMIN},MESSAGE_SEND:{value:`message.send`,label:`Send Messages`,description:`Allows the user to send messages in public chat rooms.`,category:B.MESSAGING},MESSAGE_DELETE_OWN:{value:`message.delete.own`,label:`Delete Own Messages`,description:`Allows the user to remove their own sent messages.`,category:B.MESSAGING},MESSAGE_DELETE_ANY:{value:`message.delete.any`,label:`Delete Any Message`,description:`Moderator tool: delete messages sent by any user.`,category:B.MESSAGING},MESSAGE_CLEAR:{value:`message.clear`,label:`Clear Chat`,description:`Allows clearing the entire message history of a room.`,category:B.MESSAGING},MESSAGE_UPLOAD:{value:`message.upload`,label:`Upload Files`,description:`Allows attaching images and files to messages.`,category:B.MESSAGING},MESSAGE_USE_COMMANDS:{value:`message.use_commands`,label:`Use Slash Commands`,description:`Allows executing chat commands (e.g., /me, /shrug).`,category:B.MESSAGING},MESSAGE_WILDCARD:{value:`message.*`,label:`Full Messaging Access`,description:`Grants all message-related permissions in a room.`,category:B.MESSAGING},PM_SEND:{value:`pm.send`,label:`Send Private Messages`,description:`Allows starting and participating in 1-on-1 private conversations.`,category:B.PRIVATE_MESSAGING},PM_DELETE_OWN:{value:`pm.delete.own`,label:`Delete Own PMs`,description:`Allows deleting messages sent in private chats.`,category:B.PRIVATE_MESSAGING},PM_READ_ANY:{value:`pm.read.any`,label:`Read Any PM`,description:`Administrative tool: View all private conversations on the site.`,category:B.PRIVATE_MESSAGING},PM_DELETE_ANY:{value:`pm.delete.any`,label:`Delete Any PM`,description:`Moderator tool: Remove private messages sent by any user.`,category:B.PRIVATE_MESSAGING},PM_BYPASS:{value:`pm.bypass`,label:`Bypass Privacy Settings`,description:`Allows bypassing receiver's privacy settings (e.g. not accepting PMs or friends only).`,category:B.PRIVATE_MESSAGING},PM_WILDCARD:{value:`pm.*`,label:`Full PM Access`,description:`Grants all private-messaging related permissions.`,category:B.PRIVATE_MESSAGING},ROOM_JOIN:{value:`room.join`,label:`Join Rooms`,description:`Allows users to enter and view public or restricted chat rooms.`,category:B.ROOMS},ROOM_CREATE:{value:`room.create`,label:`Create Rooms`,description:`Allows users to create new chat rooms.`,category:B.ROOMS},ROOM_EDIT:{value:`room.edit`,label:`Edit Room Settings`,description:`Allows changing room names, descriptions, and settings.`,category:B.ROOMS},ROOM_DELETE:{value:`room.delete`,label:`Delete Rooms`,description:`Allows permanently removing chat rooms and their history.`,category:B.ROOMS},ROOM_LOCK:{value:`room.lock`,label:`Lock/Unlock Rooms`,description:`Allows moderators to temporarily prevent new messages in a room.`,category:B.ROOMS},ROOM_SET_TOPIC:{value:`room.set_topic`,label:`Change Room Topic`,description:`Allows updating the current topic/announcement displayed in a room.`,category:B.ROOMS},ROOM_WILDCARD:{value:`room.*`,label:`Full Room Management`,description:`Grants all room-related administration permissions.`,category:B.ROOMS},USER_KICK:{value:`user.kick`,label:`Kick Users`,description:`Allows removing a user from a room temporarily.`,category:B.MODERATION},USER_BAN:{value:`user.ban`,label:`Ban Users`,description:`Allows permanently banning users from the site or specific rooms.`,category:B.MODERATION},USER_UNBAN:{value:`user.unban`,label:`Unban Users`,description:`Allows lifting previously imposed bans.`,category:B.MODERATION},USER_MUTE:{value:`user.mute`,label:`Mute Users`,description:`Allows preventing a user from sending messages for a duration.`,category:B.MODERATION},USER_UNMUTE:{value:`user.unmute`,label:`Unmute Users`,description:`Allows lifting mutes early.`,category:B.MODERATION},USER_EDIT_RANK:{value:`user.edit_rank`,label:`Manage User Ranks`,description:`Allows assigning or removing ranks for other users.`,category:B.MODERATION},USER_VIEW_PROFILE:{value:`user.view_profile`,label:`View Profiles`,description:`Allows viewing detailed user profiles and settings.`,category:B.MODERATION},USER_WILDCARD:{value:`user.*`,label:`Full Moderation Access`,description:`Grants all user-management and moderation permissions.`,category:B.MODERATION},PROFILE_EDIT:{value:`profile.edit`,label:`Edit Own Profile`,description:`Allows users to change their display name, bio, and avatar.`,category:B.PROFILE},PROFILE_CUSTOMIZE:{value:`profile.customize`,label:`Profile Customization`,description:`Allows advanced styling like custom fonts, themes, and backgrounds.`,category:B.PROFILE},PROFILE_SET_STATUS:{value:`profile.set_status`,label:`Set Status`,description:`Allows setting custom status messages and status indicators.`,category:B.PROFILE},POST_CREATE:{value:`post.create`,label:`Create Posts`,description:`Allows sharing updates and media in the activity feed.`,category:B.POSTS},POST_CREATE_ANNOUNCEMENT:{value:`post.create.announcement`,label:`Create Announcements`,description:`Allows creating official site announcements.`,category:B.POSTS},POST_EDIT_OWN:{value:`post.edit.own`,label:`Edit Own Posts`,description:`Allows modifying previously shared feed posts.`,category:B.POSTS},POST_EDIT_ANY:{value:`post.edit.any`,label:`Edit Any Post`,description:`Administrative tool: Edit posts shared by any user.`,category:B.POSTS},POST_DELETE_OWN:{value:`post.delete.own`,label:`Delete Own Posts`,description:`Allows removing personal feed posts.`,category:B.POSTS},POST_DELETE_ANY:{value:`post.delete.any`,label:`Delete Any Post`,description:`Moderator tool: Remove posts shared by any user.`,category:B.POSTS},POST_REACT:{value:`post.react`,label:`React to Posts`,description:`Allows adding emoji reactions to feed posts.`,category:B.POSTS},POST_COMMENT:{value:`post.comment`,label:`Comment on Posts`,description:`Allows joining the conversation on shared feed items.`,category:B.POSTS},POST_COMMENT_DELETE_OWN:{value:`post.comment.delete.own`,label:`Delete Own Comments`,description:`Allows removing personal comments from feed posts.`,category:B.POSTS},POST_COMMENT_DELETE_ANY:{value:`post.comment.delete.any`,label:`Delete Any Comment`,description:`Moderator tool: Remove comments left by any user.`,category:B.POSTS},POST_WILDCARD:{value:`post.*`,label:`Full Feed Access`,description:`Grants all permissions related to the activity feed.`,category:B.POSTS},USER_BLOCK:{value:`user.block`,label:`Block Users`,description:`Allows preventing specific users from contacting the user.`,category:B.MODERATION},USER_UNBLOCK:{value:`user.unblock`,label:`Unblock Users`,description:`Allows lifting previously imposed user blocks.`,category:B.MODERATION},USER_IGNORE:{value:`user.ignore`,label:`Ignore Users`,description:`Allows hiding messages and notifications from specific users.`,category:B.MODERATION},USER_UNIGNORE:{value:`user.unignore`,label:`Stop Ignoring`,description:`Allows showing messages from previously ignored users again.`,category:B.MODERATION},SITE_EDIT:{value:`site.edit`,label:`Edit Site Settings`,description:`Allows changing site name, branding, and global configuration.`,category:B.SITE_ADMIN},SITE_CREATE:{value:`site.create`,label:`Create New Sites`,description:`Master Admin tool: Create independent Jchat site instances.`,category:B.SITE_ADMIN},SITE_MANAGE_RANKS:{value:`site.manage_ranks`,label:`Manage Ranks & Perms`,description:`Allows creating ranks and configuring their permission sets.`,category:B.SITE_ADMIN},SITE_MANAGE_ADDONS:{value:`site.manage_addons`,label:`Manage Addons`,description:`Allows installing, configuring, and removing jchat Addons.`,category:B.SITE_ADMIN},SITE_VIEW_AUDIT:{value:`site.view_audit`,label:`View Audit Logs`,description:`Administrative tool: See a detailed record of all admin actions.`,category:B.SITE_ADMIN},SITE_VIEW_REPORTS:{label:`View Reports`,value:`site.view_reports`,description:`Allows seeing reports filed by users against content or people.`,category:B.SITE_ADMIN},SITE_MANAGE_REPORTS:{value:`site.manage_reports`,label:`Handle Reports`,description:`Allows resolving or dismissing user reports.`,category:B.SITE_ADMIN},SITE_MANAGE_SPAM:{value:`site.manage_spam`,label:`Manage Anti-Spam`,description:`Allows configuring the anti-spam engine, thresholds, and per-module settings.`,category:B.SITE_ADMIN},SITE_WILDCARD:{value:`site.*`,label:`Full Site Administration`,description:`Grants all technical administration and site discovery permissions.`,category:B.SITE_ADMIN},FRIEND_REQUEST_SEND:{value:`friend.request.send`,label:`Send Friend Requests`,description:`Allows the user to send friend requests to other users.`,category:B.FRIENDS},FRIEND_REQUEST_MANAGE:{value:`friend.request.manage`,label:`Manage Friend Requests`,description:`Allows the user to accept or reject incoming friend requests.`,category:B.FRIENDS},FRIEND_REMOVE:{value:`friend.remove`,label:`Remove Friends`,description:`Allows the user to remove accepted friends from their list.`,category:B.FRIENDS},FRIEND_LIST:{value:`friend.list`,label:`View Friend List`,description:`Allows the user to view their own friends list.`,category:B.FRIENDS},ADDON_WILDCARD:{value:`addon.*`,label:`Full Addon System Access`,description:`Grants access to use every installed addon regardless of specific gates.`,category:B.ADDONS},COMMAND_CLEAR:{value:`command.clear`,label:`Clear Chat Command`,description:`Allows using the /clear slash command to clear chat history.`,category:B.COMMANDS},COMMAND_HELP:{value:`command.help`,label:`View Commands Help`,description:`Allows using the /help slash command.`,category:B.COMMANDS}},H=Object.fromEntries(Object.entries(V).map(([e,t])=>[e,t.value])),fe=Object.fromEntries(Object.values(V).map(e=>[e.value,{name:e.value,label:e.label,description:e.description,category:e.category}]));Object.values(fe);const U=[H.MESSAGE_SEND,H.MESSAGE_UPLOAD,H.MESSAGE_DELETE_OWN,H.PM_SEND,H.PM_DELETE_OWN,H.ROOM_JOIN,H.POST_REACT,H.POST_COMMENT,H.POST_COMMENT_DELETE_OWN,H.PROFILE_EDIT,H.PROFILE_SET_STATUS,H.COMMAND_HELP];H.ALL,[H.SITE_EDIT,H.SITE_MANAGE_RANKS,H.SITE_MANAGE_ADDONS,H.SITE_VIEW_AUDIT,H.SITE_VIEW_REPORTS,H.SITE_MANAGE_REPORTS,H.SITE_MANAGE_SPAM,H.USER_WILDCARD,H.ROOM_EDIT,H.ROOM_DELETE,H.ROOM_LOCK,H.ROOM_SET_TOPIC,H.MESSAGE_WILDCARD,H.POST_WILDCARD,H.PM_WILDCARD,H.PROFILE_CUSTOMIZE,H.MESSAGE_USE_COMMANDS,H.POST_CREATE,H.POST_EDIT_OWN,H.POST_DELETE_OWN,H.FRIEND_REQUEST_SEND,H.FRIEND_REQUEST_MANAGE,H.FRIEND_REMOVE,H.FRIEND_LIST,H.COMMAND_CLEAR,...U],[H.ROOM_EDIT,H.ROOM_LOCK,H.ROOM_SET_TOPIC,H.MESSAGE_WILDCARD,H.POST_WILDCARD,H.PM_SEND,H.PM_DELETE_OWN,H.PM_READ_ANY,H.PM_DELETE_ANY,H.USER_KICK,H.USER_MUTE,H.USER_UNMUTE,H.USER_BAN,H.USER_UNBAN,H.PROFILE_CUSTOMIZE,H.MESSAGE_USE_COMMANDS,H.POST_CREATE,H.POST_EDIT_OWN,H.POST_DELETE_OWN,H.FRIEND_REQUEST_SEND,H.FRIEND_REQUEST_MANAGE,H.FRIEND_REMOVE,H.FRIEND_LIST,H.COMMAND_CLEAR,...U],[H.USER_KICK,H.USER_MUTE,H.USER_UNMUTE,H.MESSAGE_DELETE_ANY,H.MESSAGE_CLEAR,H.PROFILE_CUSTOMIZE,H.MESSAGE_USE_COMMANDS,H.POST_CREATE,H.POST_EDIT_OWN,H.POST_DELETE_OWN,H.FRIEND_REQUEST_SEND,H.FRIEND_REQUEST_MANAGE,H.FRIEND_REMOVE,H.FRIEND_LIST,H.COMMAND_CLEAR,...U],[H.PROFILE_CUSTOMIZE,H.MESSAGE_USE_COMMANDS,H.POST_CREATE,H.POST_EDIT_OWN,H.POST_DELETE_OWN,H.FRIEND_REQUEST_SEND,H.FRIEND_REQUEST_MANAGE,H.FRIEND_REMOVE,H.FRIEND_LIST,...U],[H.MESSAGE_USE_COMMANDS,H.POST_CREATE,H.POST_EDIT_OWN,H.POST_DELETE_OWN,H.FRIEND_REQUEST_SEND,H.FRIEND_REQUEST_MANAGE,H.FRIEND_REMOVE,H.FRIEND_LIST,...U],[...U];function pe(e){let t=e?.trim().replace(/^https?:\/\//,``).split(`/`)[0].split(`:`)[0].replace(/^\.+/,``);if(!t||t===`localhost`||t.endsWith(`.localhost`)||/^\d{1,3}(\.\d{1,3}){3}$/.test(t))return;let n=t.split(`.`);if(n.length>2){let e=[`co`,`com`,`org`,`net`,`gov`,`edu`,`ac`,`sch`],t=n[n.length-1],r=n[n.length-2];return e.includes(r)&&t.length===2&&n.length>=3?n.slice(-3).join(`.`):n.slice(-2).join(`.`)}return t}function me(e){let t=e?.trim().replace(/^https?:\/\//,``).split(`/`)[0].split(`:`)[0].replace(/^\.+/,``).toLowerCase();return t?t===`localhost`||/^\d{1,3}(\.\d{1,3}){3}$/.test(t)?!0:pe(t)===t:!1}new Map([{label:`Default`,value:`lato`,family:`'Lato', sans-serif`,googleName:`Lato`,scale:1},{label:`Elegant`,value:`playfair`,family:`'Playfair Display', serif`,googleName:`Playfair Display`,scale:1.1},{label:`Cursive`,value:`dancing`,family:`'Dancing Script', cursive`,googleName:`Dancing Script`,scale:1.2},{label:`Modern`,value:`poppins`,family:`'Poppins', sans-serif`,googleName:`Poppins`,scale:1},{label:`Monospace`,value:`jetbrains`,family:`'JetBrains Mono', monospace`,googleName:`JetBrains Mono`,scale:.95},{label:`Rounded`,value:`quicksand`,family:`'Quicksand', sans-serif`,googleName:`Quicksand`,scale:1},{label:`Serif`,value:`roboto-serif`,family:`'Roboto Serif', serif`,googleName:`Roboto Serif`,scale:1.05},{label:`Sci-Fi`,value:`audiowide`,family:`'Audiowide', cursive`,googleName:`Audiowide`,scale:.95},{label:`Type`,value:`special-elite`,family:`'Special Elite', cursive`,googleName:`Special Elite`,scale:1.15},{label:`Pixel`,value:`press-start`,family:`'Press Start 2P', cursive`,googleName:`Press Start 2P`,scale:.75},{label:`Gothic`,value:`grenze`,family:`'Grenze Gotisch', cursive`,googleName:`Grenze Gotisch`,scale:1.15},{label:`Deco`,value:`righteous`,family:`'Righteous', cursive`,googleName:`Righteous`,scale:1},{label:`Spooky`,value:`creepster`,family:`'Creepster', cursive`,googleName:`Creepster`,scale:1.1},{label:`Shadow`,value:`vast-shadow`,family:`'Vast Shadow', cursive`,googleName:`Vast Shadow`,scale:1.05},{label:`Tech`,value:`syncopate`,family:`'Syncopate', sans-serif`,googleName:`Syncopate`,scale:.9},{label:`Script`,value:`lobster`,family:`'Lobster', cursive`,googleName:`Lobster`,scale:1.05}].map(e=>[e.value,e.scale]));const he=typeof window<`u`&&window.document!==void 0&&!window.process,W={brand:`color: #06b6d4; font-weight: bold;`,timestamp:`color: #888; font-size: 10px; font-family: monospace;`,info:`color: #3b82f6; font-weight: bold;`,success:`color: #10b981; font-weight: bold;`,warn:`color: #f59e0b; font-weight: bold;`,error:`color: #ef4444; font-weight: bold;`,debug:`color: #6366f1; font-style: italic;`,content:`color: inherit; font-family: inherit;`},G={reset:`\x1B[0m`,brand:`\x1B[1m\x1B[36m`,timestamp:`\x1B[90m`,info:`\x1B[1m\x1B[34m`,success:`\x1B[1m\x1B[32m`,warn:`\x1B[1m\x1B[33m`,error:`\x1B[1m\x1B[31m`,debug:`\x1B[3m\x1B[35m`};new Proxy(console,{get(e,t){if([`info`,`success`,`warn`,`error`,`debug`].includes(t)){let e=new Date().toLocaleTimeString();if(he)return import.meta.env?.PROD&&t===`debug`?()=>{}:console.log.bind(console,`%c${e} %c[jchat]%c ${t}%c`,W.timestamp,W.brand,W[t],W.content);{let n=G[t]||``,r=G.brand,i=G.timestamp,a=G.reset;return console.log.bind(console,`${i}${e}${a} ${r}[jchat]${a} ${n}${t}${a}${i}`)}}return e[t]}});function ge(e){return t=>e.parse({...o(t,`VITE_`,``),...t})}const K=s.object({JCHAT_PORT:s.string().regex(/^\d+$/).transform(Number).refine(e=>e>=1&&e<=65535,{message:`Port must be between 1 and 65535`}).refine(e=>e!==80&&e!==443,{message:`Avoid ports 80/443 (reserved for HTTP/HTTPS)`}).default(3e3),JCHAT_COOKIE_SECRET:s.string().min(1),JCHAT_DATABASE_URL:s.url().startsWith(`postgresql://`).regex(/^postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/([^?]+)(\?.*)?$/,{message:`Must be in format: postgresql://username:password@host:port/database?schema`}),JCHAT_REDIS_DATABASE_URL:s.url().refine(e=>e.startsWith(`rediss://`)||e.startsWith(`redis://`),{message:`Must use redis:// or rediss:// protocol`}).refine(e=>/^(rediss?):\/\/(?:([^:]+):([^@]+)@)?([^:]+):(\d+)$/.test(e),{message:`Must be in format: rediss://default:<password>@<host>:<port>`}),JCHAT_STORAGE_TYPE:s.enum(z),JCHAT_MINIO_ENDPOINT:s.string().optional(),JCHAT_MINIO_BUCKET_NAME:s.string().optional(),JCHAT_MINIO_ACCESS_KEY_ID:s.string().optional(),JCHAT_MINIO_SECRET_ACCESS_KEY:s.string().optional(),JCHAT_MINIO_PUBLIC_URL:s.string().optional(),JCHAT_AWS_REGION:s.string().optional(),JCHAT_AWS_BUCKET_NAME:s.string().optional(),JCHAT_AWS_ACCESS_KEY_ID:s.string().optional(),JCHAT_AWS_SECRET_ACCESS_KEY:s.string().optional(),JCHAT_CLOUDFLARE_ACCOUNT_ID:s.string().optional(),JCHAT_CLOUDFLARE_BUCKET_NAME:s.string().optional(),JCHAT_CLOUDFLARE_ACCESS_KEY_ID:s.string().optional(),JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY:s.string().optional(),JCHAT_CLOUDFLARE_PUBLIC_URL:s.string().optional(),JCHAT_GCS_BUCKET_NAME:s.string().optional(),JCHAT_GCS_PROJECT_ID:s.string().optional(),JCHAT_GCS_CLIENT_EMAIL:s.string().optional(),JCHAT_GCS_PRIVATE_KEY:s.string().optional(),JCHAT_GCS_KEY_FILE:s.string().optional(),JCHAT_BOT_NAME:s.string().trim().min(3),JCHAT_BOT_EMAIL:s.email(),JCHAT_DOMAIN:s.string().optional(),JCHAT_SITE_URL:s.url().optional(),JCHAT_ROUTING_MODE:s.enum([`path`,`subdomain`]).default(`subdomain`),JCHAT_ADDON_ENCRYPTION_KEY:s.string().min(16),JCHAT_LICENSE_SERVER_URL:s.url().optional(),JCHAT_LICENSE_CHECK_INTERVAL_DAYS:s.string().regex(/^\d+$/).transform(Number).refine(e=>e>=1,{message:`License check interval must be at least 1 day`}).default(14),JCHAT_STORE_SERVER_URL:s.url().optional(),NODE_ENV:s.enum([`development`,`test`,`production`]).default(`development`),SYSTEM_BOOTSTRAP_COMPLETE:s.boolean().optional()}).superRefine((e,t)=>{if(e.JCHAT_SITE_URL){let n;try{n=new URL(e.JCHAT_SITE_URL).hostname}catch{}n&&!me(n)&&t.addIssue({path:[`JCHAT_SITE_URL`],code:`custom`,message:`JCHAT_SITE_URL must use the client main/root domain`})}e.JCHAT_STORAGE_TYPE===z.MINIO&&!(e.JCHAT_MINIO_ENDPOINT&&e.JCHAT_MINIO_BUCKET_NAME&&e.JCHAT_MINIO_ACCESS_KEY_ID&&e.JCHAT_MINIO_SECRET_ACCESS_KEY&&e.JCHAT_MINIO_PUBLIC_URL)&&(e.JCHAT_MINIO_ENDPOINT||t.addIssue({path:[`JCHAT_MINIO_ENDPOINT`],code:`custom`,message:`JCHAT_MINIO_ENDPOINT is required when JCHAT_STORAGE_TYPE is MINIO`}),e.JCHAT_MINIO_BUCKET_NAME||t.addIssue({path:[`JCHAT_MINIO_BUCKET_NAME`],code:`custom`,message:`JCHAT_MINIO_BUCKET_NAME is required when JCHAT_STORAGE_TYPE is MINIO`}),e.JCHAT_MINIO_ACCESS_KEY_ID||t.addIssue({path:[`JCHAT_MINIO_ACCESS_KEY_ID`],code:`custom`,message:`JCHAT_MINIO_ACCESS_KEY_ID is required when JCHAT_STORAGE_TYPE is MINIO`}),e.JCHAT_MINIO_SECRET_ACCESS_KEY||t.addIssue({path:[`JCHAT_MINIO_SECRET_ACCESS_KEY`],code:`custom`,message:`JCHAT_MINIO_SECRET_ACCESS_KEY is required when JCHAT_STORAGE_TYPE is MINIO`}),e.JCHAT_MINIO_PUBLIC_URL||t.addIssue({path:[`JCHAT_MINIO_PUBLIC_URL`],code:`custom`,message:`JCHAT_MINIO_PUBLIC_URL is required when JCHAT_STORAGE_TYPE is MINIO`})),e.JCHAT_STORAGE_TYPE===z.AWS_S3&&!(e.JCHAT_AWS_REGION&&e.JCHAT_AWS_ACCESS_KEY_ID&&e.JCHAT_AWS_SECRET_ACCESS_KEY&&e.JCHAT_AWS_BUCKET_NAME)&&(e.JCHAT_AWS_REGION||t.addIssue({path:[`JCHAT_AWS_REGION`],code:`custom`,message:`JCHAT_AWS_REGION is required when JCHAT_STORAGE_TYPE is AWS_S3`}),e.JCHAT_AWS_ACCESS_KEY_ID||t.addIssue({path:[`JCHAT_AWS_ACCESS_KEY_ID`],code:`custom`,message:`JCHAT_AWS_ACCESS_KEY_ID is required when JCHAT_STORAGE_TYPE is AWS_S3`}),e.JCHAT_AWS_SECRET_ACCESS_KEY||t.addIssue({path:[`JCHAT_AWS_SECRET_ACCESS_KEY`],code:`custom`,message:`JCHAT_AWS_SECRET_ACCESS_KEY is required when JCHAT_STORAGE_TYPE is AWS_S3`}),e.JCHAT_AWS_BUCKET_NAME||t.addIssue({path:[`JCHAT_AWS_BUCKET_NAME`],code:`custom`,message:`JCHAT_AWS_BUCKET_NAME is required when JCHAT_STORAGE_TYPE is AWS_S3`})),e.JCHAT_STORAGE_TYPE===z.CLOUDFLARE_R2&&!(e.JCHAT_CLOUDFLARE_ACCOUNT_ID&&e.JCHAT_CLOUDFLARE_ACCESS_KEY_ID&&e.JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY&&e.JCHAT_CLOUDFLARE_BUCKET_NAME&&e.JCHAT_CLOUDFLARE_PUBLIC_URL)&&(e.JCHAT_CLOUDFLARE_ACCOUNT_ID||t.addIssue({path:[`JCHAT_CLOUDFLARE_ACCOUNT_ID`],code:`custom`,message:`JCHAT_CLOUDFLARE_ACCOUNT_ID is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`}),e.JCHAT_CLOUDFLARE_ACCESS_KEY_ID||t.addIssue({path:[`JCHAT_CLOUDFLARE_ACCESS_KEY_ID`],code:`custom`,message:`JCHAT_CLOUDFLARE_ACCESS_KEY_ID is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`}),e.JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY||t.addIssue({path:[`JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY`],code:`custom`,message:`JCHAT_CLOUDFLARE_SECRET_ACCESS_KEY is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`}),e.JCHAT_CLOUDFLARE_BUCKET_NAME||t.addIssue({path:[`JCHAT_CLOUDFLARE_BUCKET_NAME`],code:`custom`,message:`JCHAT_CLOUDFLARE_BUCKET_NAME is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`}),e.JCHAT_CLOUDFLARE_PUBLIC_URL||t.addIssue({path:[`JCHAT_CLOUDFLARE_PUBLIC_URL`],code:`custom`,message:`JCHAT_CLOUDFLARE_PUBLIC_URL is required when JCHAT_STORAGE_TYPE is CLOUDFLARE_R2`})),e.JCHAT_STORAGE_TYPE===z.GCS&&!(e.JCHAT_GCS_PROJECT_ID&&e.JCHAT_GCS_BUCKET_NAME&&e.JCHAT_GCS_CLIENT_EMAIL&&e.JCHAT_GCS_PRIVATE_KEY)&&(e.JCHAT_GCS_PROJECT_ID||t.addIssue({path:[`JCHAT_GCS_PROJECT_ID`],code:`custom`,message:`JCHAT_GCS_PROJECT_ID is required when JCHAT_STORAGE_TYPE is GCS`}),e.JCHAT_GCS_BUCKET_NAME||t.addIssue({path:[`JCHAT_GCS_BUCKET_NAME`],code:`custom`,message:`JCHAT_GCS_BUCKET_NAME is required when JCHAT_STORAGE_TYPE is GCS`}),e.JCHAT_GCS_CLIENT_EMAIL||t.addIssue({path:[`JCHAT_GCS_CLIENT_EMAIL`],code:`custom`,message:`JCHAT_GCS_CLIENT_EMAIL is required when JCHAT_STORAGE_TYPE is GCS`}),e.JCHAT_GCS_PRIVATE_KEY||t.addIssue({path:[`JCHAT_GCS_PRIVATE_KEY`],code:`custom`,message:`JCHAT_GCS_PRIVATE_KEY is required when JCHAT_STORAGE_TYPE is GCS`}))});function _e(t,n=`production`,r){let i,a=!0;try{i=ge(K)(t);let o=w();if(n===`developer-sandbox`)a=!0;else if(!o)a=!1,n===`production`&&(i.JCHAT_COOKIE_SECRET=e.randomBytes(64).toString(`hex`));else if(!i.JCHAT_DOMAIN||!h(i.JCHAT_DOMAIN)||i.NODE_ENV===`production`&&!m(i.JCHAT_DOMAIN))throw Error(`A valid installed JChat public domain is required for production deployments.`);else if(!Q(o,i.JCHAT_DOMAIN||`localhost`,{publicKeyPem:r}))a=!1,n===`production`&&(i.JCHAT_COOKIE_SECRET=e.randomBytes(64).toString(`hex`));else{let t=Z(o);if(i.NODE_ENV===`production`&&t?.environment===`development`)a=!1,n===`production`&&(i.JCHAT_COOKIE_SECRET=e.randomBytes(64).toString(`hex`));else{let n=o.split(`.`),r=n[2]||``;if(r&&t){let a=e.createHmac(`sha256`,r).update(`${n[0]}.${n[1]}:${t.domain}:${t.installationId}`).digest(`hex`);i.JCHAT_COOKIE_SECRET+=a}}}}catch{a=!1,i={...t,JCHAT_PORT:t.JCHAT_PORT?parseInt(String(t.JCHAT_PORT)):3e3},n===`production`&&(i.JCHAT_COOKIE_SECRET=e.randomBytes(64).toString(`hex`))}return i.SYSTEM_BOOTSTRAP_COMPLETE=a,i}function q(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function J(e){return typeof e==`string`&&e.trim().length>0}function Y(e){if(!e)return null;try{return JSON.parse(Buffer.from(e,`base64url`).toString(`utf8`))}catch{return null}}function X(e){let t=e.split(`.`);if(t.length!==3)return null;let n=Y(t[0]);return!q(n)||n.alg!==`RS256`||n.typ!==`JWT`?null:{alg:`RS256`,typ:`JWT`}}function ve(e){if(!q(e))return null;let{iss:t,aud:n,licenseId:r,domain:i,buyer:a,installationId:o,plan:s,entitlements:c,purchaseProvider:l,environment:u,routingMode:d,iat:f}=e;if(!J(t)||!J(n)||!J(r)||!J(i)||!J(a)||!J(o)||s!==`standard`&&s!==`extended`||!Array.isArray(c)||!c.every(J)||l!==`envato`&&l!==`jchat`||u!==`production`&&u!==`development`||d!==`path`&&d!==`subdomain`||typeof f!=`number`||!Number.isSafeInteger(f)||f<0)return null;let h;try{h=p(i)}catch{return null}if(u===`development`&&!h.isLocalhost||u===`production`&&!m(i))return null;let g=c.includes(`site.create.multi`);return s===`extended`&&!g||s===`standard`&&g?null:{iss:t,aud:n,licenseId:r,domain:i,buyer:a,installationId:o,plan:s,entitlements:c,purchaseProvider:l,environment:u,routingMode:d,iat:f}}function Z(e){let t=e.split(`.`);return t.length!==3||!t.every(Boolean)||!X(e)?null:ve(Y(t[1]))}function Q(t,n,r){if(!t||t===`YOUR_LICENSE_KEY_PLACEHOLDER`)return!1;try{let i=t.split(`.`);if(i.length!==3||!X(t))return!1;let a=Z(t);if(!a)return!1;let o=r.issuer??Bun.env.JCHAT_LICENSE_ISSUER??`https://license.jchatlive.com`,s=r.audience??Bun.env.JCHAT_LICENSE_AUDIENCE??`jchat-chat`;if(a.iss!==o||a.aud!==s)return!1;let c=p(n).host,l=p(a.domain).host;if(c!==l&&(a.routingMode!==`subdomain`||!c.endsWith(`.${l}`)))return!1;let u=Buffer.from(`${i[0]}.${i[1]}`),d=Buffer.from(i[2].replace(/-/g,`+`).replace(/_/g,`/`),`base64`);return e.verify(`sha256`,u,r.publicKeyPem,d)}catch{return!1}}function ye(e,t,n,r){if(r&&!Q(e,t,r))return!1;let i=Z(e);return i?i.installationId===n:!1}function be(e,t,n,r){return Q(e,t,r)?Z(e)?.entitlements?.includes(n)??!1:!1}function $(e,t=`localhost`){if(g(t))return!0;if(!e)return!1;let n=Z(e);return n?n.environment===`development`?g(t):n.plan===`extended`||(n.entitlements?.includes(`site.create.multi`)??!1):!1}export{u as DEFAULT_LICENSE_AUDIENCE,l as DEFAULT_LICENSE_ISSUER,c as DEFAULT_LICENSE_SERVER_URL,S as DEFAULT_STATE_PATH,K as ServerConfigSchema,P as activateRegistry,C as createIdentity,y as findRepoRoot,_ as getRootDomain,M as getRuntimeStatus,be as hasEntitlement,ce as installAndActivate,h as isAllowedConfiguredDomain,N as isApplicationDisabled,g as isLocalhostDomain,ee as isMatchingDomain,$ as isMultiSiteAllowed,m as isPublicDomain,oe as isRuntimeBlocked,le as jchatRuntime,_e as loadServerContext,p as normalizeDomain,Z as parsePayload,T as readState,w as readTokenSync,ie as recordAudit,te as resolveActivationDomain,x as resolveDataDir,v as resolveRuntimePaths,j as setRuntimeStatus,I as startRuntimeSupervisor,re as updateInstanceToken,ye as verifyEntitlement,Q as verifySignature,E as writeState};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jchat/runtime",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.12",
4
4
  "private": false,
5
5
  "description": "Runtime support package for JChat deployments.",
6
6
  "keywords": [
@@ -20,15 +20,24 @@
20
20
  "CHANGELOG.md"
21
21
  ],
22
22
  "type": "module",
23
- "main": "./dist/index.mjs",
24
- "types": "./dist/index.d.mts",
23
+ "main": "./src/index.ts",
24
+ "types": "./src/index.ts",
25
25
  "exports": {
26
26
  ".": {
27
- "types": "./dist/index.d.mts",
28
- "import": "./dist/index.mjs"
27
+ "types": "./src/index.ts",
28
+ "import": "./src/index.ts",
29
+ "default": "./src/index.ts"
29
30
  }
30
31
  },
31
32
  "publishConfig": {
33
+ "main": "./dist/index.mjs",
34
+ "types": "./dist/index.d.mts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.mts",
38
+ "import": "./dist/index.mjs"
39
+ }
40
+ },
32
41
  "access": "public"
33
42
  },
34
43
  "scripts": {
@@ -45,11 +54,13 @@
45
54
  "devDependencies": {
46
55
  "@jchat/shared": "workspace:*",
47
56
  "@types/bun": "catalog:",
48
- "tsdown": "^0.22.14",
57
+ "elysia": "catalog:",
58
+ "tsdown": "catalog:",
49
59
  "typescript": "catalog:",
50
60
  "zod": "catalog:"
51
61
  },
52
62
  "peerDependencies": {
63
+ "elysia": "catalog:",
53
64
  "zod": "catalog:"
54
65
  }
55
66
  }