@objectstack/plugin-hono-server 4.0.4 → 4.1.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.
@@ -1,456 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { Plugin, PluginContext, IHttpServer, IDataEngine } from '@objectstack/core';
4
- import {
5
- RestServerConfig,
6
- } from '@objectstack/spec/api';
7
- import { HonoHttpServer, HonoCorsOptions } from './adapter';
8
- import { cors } from 'hono/cors';
9
- import { serveStatic } from '@hono/node-server/serve-static';
10
- import * as fs from 'fs';
11
- import * as path from 'path';
12
-
13
- export interface StaticMount {
14
- root: string;
15
- path?: string;
16
- rewrite?: boolean;
17
- spa?: boolean;
18
- }
19
-
20
- export interface HonoPluginOptions {
21
- port?: number;
22
- staticRoot?: string;
23
- /**
24
- * Multiple static resource mounts
25
- */
26
- staticMounts?: StaticMount[];
27
- /**
28
- * REST server configuration
29
- * Controls automatic endpoint generation and API behavior
30
- */
31
- restConfig?: RestServerConfig;
32
- /**
33
- * Whether to register standard ObjectStack CRUD endpoints
34
- * @default true
35
- */
36
- registerStandardEndpoints?: boolean;
37
- /**
38
- * Whether to load endpoints from API Registry
39
- * @default true
40
- */
41
- useApiRegistry?: boolean;
42
-
43
- /**
44
- * Whether to enable SPA fallback
45
- * If true, returns index.html for non-API 404s
46
- * @default false
47
- */
48
- spaFallback?: boolean;
49
-
50
- /**
51
- * CORS configuration. Set to `false` to disable entirely.
52
- * Enabled by default with origin '*'.
53
- * Can also be controlled via environment variables:
54
- * CORS_ENABLED, CORS_ORIGIN, CORS_CREDENTIALS, CORS_MAX_AGE
55
- */
56
- cors?: HonoCorsOptions | false;
57
- }
58
-
59
- /**
60
- * Hono Server Plugin
61
- *
62
- * Provides HTTP server capabilities using Hono framework.
63
- * Registers the IHttpServer service so other plugins can register routes.
64
- *
65
- * Route registration is handled by plugins:
66
- * - `@objectstack/rest` → CRUD, metadata, discovery, UI, batch
67
- * - `createDispatcherPlugin()` → auth, graphql, analytics, packages, etc.
68
- */
69
- /**
70
- * Check if an origin matches a pattern with wildcards.
71
- * Supports patterns like:
72
- * - "https://*.example.com" - matches any subdomain
73
- * - "http://localhost:*" - matches any port
74
- * - "https://*.objectui.org,https://*.objectstack.ai" - comma-separated patterns
75
- *
76
- * @param origin The origin to check (e.g., "https://app.example.com")
77
- * @param pattern The pattern to match against (supports * wildcard)
78
- * @returns true if origin matches the pattern
79
- */
80
- function matchOriginPattern(origin: string, pattern: string): boolean {
81
- if (pattern === '*') return true;
82
- if (pattern === origin) return true;
83
-
84
- // Convert wildcard pattern to regex
85
- // Escape special regex characters except *
86
- const regexPattern = pattern
87
- .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special chars
88
- .replace(/\*/g, '.*'); // Convert * to .*
89
-
90
- const regex = new RegExp(`^${regexPattern}$`);
91
- return regex.test(origin);
92
- }
93
-
94
- /**
95
- * Create a CORS origin matcher function that supports wildcard patterns.
96
- *
97
- * @param patterns Single pattern, array of patterns, or comma-separated patterns
98
- * @returns Function that returns the origin if it matches, or null/undefined
99
- */
100
- function createOriginMatcher(
101
- patterns: string | string[]
102
- ): (origin: string) => string | undefined | null {
103
- // Normalize to array
104
- let patternList: string[];
105
- if (typeof patterns === 'string') {
106
- // Handle comma-separated patterns
107
- patternList = patterns.includes(',')
108
- ? patterns.split(',').map(s => s.trim()).filter(Boolean)
109
- : [patterns];
110
- } else {
111
- patternList = patterns;
112
- }
113
-
114
- // Return matcher function
115
- return (requestOrigin: string) => {
116
- for (const pattern of patternList) {
117
- if (matchOriginPattern(requestOrigin, pattern)) {
118
- return requestOrigin;
119
- }
120
- }
121
- return null;
122
- };
123
- }
124
-
125
- export class HonoServerPlugin implements Plugin {
126
- name = 'com.objectstack.server.hono';
127
- type = 'server';
128
- version = '0.9.0';
129
-
130
- // Constants
131
- private static readonly DEFAULT_ENDPOINT_PRIORITY = 100;
132
- private static readonly CORE_ENDPOINT_PRIORITY = 950;
133
- private static readonly DISCOVERY_ENDPOINT_PRIORITY = 900;
134
-
135
- private options: HonoPluginOptions;
136
- private server: HonoHttpServer;
137
-
138
- constructor(options: HonoPluginOptions = {}) {
139
- this.options = {
140
- port: 3000,
141
- registerStandardEndpoints: true,
142
- useApiRegistry: true,
143
- spaFallback: false,
144
- ...options
145
- };
146
- // We handle static root manually in start() to support SPA fallback
147
- this.server = new HonoHttpServer(this.options.port);
148
- }
149
-
150
- /**
151
- * Init phase - Setup HTTP server and register as service
152
- */
153
- init = async (ctx: PluginContext) => {
154
- ctx.logger.debug('Initializing Hono server plugin', {
155
- port: this.options.port,
156
- staticRoot: this.options.staticRoot
157
- });
158
-
159
- // Register HTTP server service as IHttpServer
160
- // Register as 'http.server' to match core requirements
161
- ctx.registerService('http.server', this.server);
162
- // Alias 'http-server' for backward compatibility
163
- ctx.registerService('http-server', this.server);
164
- ctx.logger.debug('HTTP server service registered', { serviceName: 'http.server' });
165
-
166
- // ─── CORS Middleware ──────────────────────────────────────────────────
167
- // Enabled by default. Controlled via options.cors or environment variables.
168
- const corsDisabledByEnv = process.env.CORS_ENABLED === 'false';
169
- if (this.options.cors !== false && !corsDisabledByEnv) {
170
- const corsOpts = typeof this.options.cors === 'object' ? this.options.cors : {};
171
- const enabled = corsOpts.enabled ?? true;
172
-
173
- if (enabled) {
174
- let configuredOrigin: string | string[];
175
- if (corsOpts.origins) {
176
- configuredOrigin = corsOpts.origins;
177
- } else if (process.env.CORS_ORIGIN) {
178
- const envOrigin = process.env.CORS_ORIGIN.trim();
179
- configuredOrigin = envOrigin.includes(',') ? envOrigin.split(',').map(s => s.trim()) : envOrigin;
180
- } else {
181
- configuredOrigin = '*';
182
- }
183
-
184
- const credentials = corsOpts.credentials ?? (process.env.CORS_CREDENTIALS !== 'false');
185
- const maxAge = corsOpts.maxAge ?? (process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400);
186
-
187
- // Determine origin handler based on configuration
188
- let origin: string | string[] | ((origin: string) => string | undefined | null);
189
-
190
- // Check if patterns contain wildcards (*, subdomain patterns, port patterns)
191
- const hasWildcard = (patterns: string | string[]): boolean => {
192
- const list = Array.isArray(patterns) ? patterns : [patterns];
193
- return list.some(p => p.includes('*'));
194
- };
195
-
196
- // When credentials is true, browsers reject wildcard '*' for Access-Control-Allow-Origin.
197
- // For wildcard patterns (like "https://*.example.com"), always use a matcher function.
198
- // For exact origins, we can pass them directly as string/array.
199
- if (configuredOrigin === '*' && credentials) {
200
- // Credentials mode with '*' - reflect the request origin
201
- origin = (requestOrigin: string) => requestOrigin || '*';
202
- } else if (hasWildcard(configuredOrigin)) {
203
- // Wildcard patterns (including better-auth style patterns like "https://*.objectui.org")
204
- // Use pattern matcher to support subdomain and port wildcards
205
- origin = createOriginMatcher(configuredOrigin);
206
- } else {
207
- // Exact origin(s) - pass through as-is
208
- origin = configuredOrigin;
209
- }
210
-
211
- const rawApp = this.server.getRawApp();
212
- rawApp.use('*', cors({
213
- origin: origin as any,
214
- allowMethods: corsOpts.methods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
215
- allowHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
216
- exposeHeaders: [],
217
- credentials,
218
- maxAge,
219
- }));
220
-
221
- ctx.logger.debug('CORS middleware enabled', { origin: configuredOrigin, credentials });
222
- }
223
- }
224
- }
225
-
226
- /**
227
- * Start phase - Configure static files and start listening
228
- */
229
- start = async (ctx: PluginContext) => {
230
- ctx.logger.debug('Starting Hono server plugin');
231
-
232
- // Configure Static Files & SPA Fallback
233
- const mounts: StaticMount[] = this.options.staticMounts || [];
234
-
235
- // Auto-discover UI Plugins
236
- try {
237
- const rawKernel = ctx.getKernel() as any;
238
- if (rawKernel.plugins) {
239
- const loadedPlugins = rawKernel.plugins instanceof Map
240
- ? Array.from(rawKernel.plugins.values())
241
- : Array.isArray(rawKernel.plugins) ? rawKernel.plugins : Object.values(rawKernel.plugins);
242
-
243
- for (const plugin of (loadedPlugins as any[])) {
244
- // Check for UI Plugin signature
245
- // Support legacy 'ui-plugin' and new 'ui' type
246
- if ((plugin.type === 'ui' || plugin.type === 'ui-plugin') && plugin.staticPath) {
247
- // Derive base route from name: @org/console -> console
248
- const slug = plugin.slug || plugin.name.split('/').pop();
249
- const baseRoute = `/${slug}`;
250
-
251
- ctx.logger.debug(`Auto-mounting UI Plugin: ${plugin.name}`, {
252
- path: baseRoute,
253
- root: plugin.staticPath
254
- });
255
-
256
- mounts.push({
257
- root: plugin.staticPath,
258
- path: baseRoute,
259
- rewrite: true, // Strip prefix: /console/assets/x -> /assets/x
260
- spa: true
261
- });
262
-
263
- // Handle Default Plugin Redirect
264
- if (plugin.default || plugin.isDefault) {
265
- const rawApp = this.server.getRawApp();
266
- rawApp.get('/', (c) => c.redirect(baseRoute));
267
- ctx.logger.debug(`Set default UI redirect: / -> ${baseRoute}`);
268
- }
269
- }
270
- }
271
- }
272
- } catch (err: any) {
273
- ctx.logger.warn('Failed to auto-discover UI plugins', { error: err.message || err });
274
- }
275
-
276
- // Backward compatibility for staticRoot
277
- if (this.options.staticRoot) {
278
- mounts.push({
279
- root: this.options.staticRoot,
280
- path: '/',
281
- rewrite: false,
282
- spa: this.options.spaFallback
283
- });
284
- }
285
-
286
- if (mounts.length > 0) {
287
- const rawApp = this.server.getRawApp();
288
-
289
- for (const mount of mounts) {
290
- const mountRoot = path.resolve(process.cwd(), mount.root);
291
-
292
- if (!fs.existsSync(mountRoot)) {
293
- ctx.logger.warn(`Static mount root not found: ${mountRoot}. Skipping.`);
294
- continue;
295
- }
296
-
297
- const mountPath = mount.path || '/';
298
- const normalizedPath = mountPath.startsWith('/') ? mountPath : `/${mountPath}`;
299
- const routePattern = normalizedPath === '/' ? '/*' : `${normalizedPath.replace(/\/$/, '')}/*`;
300
-
301
- // Routes to register: both /mount and /mount/*
302
- const routes = normalizedPath === '/' ? [routePattern] : [normalizedPath, routePattern];
303
-
304
- ctx.logger.debug('Mounting static files', {
305
- to: routes,
306
- from: mountRoot,
307
- rewrite: mount.rewrite,
308
- spa: mount.spa
309
- });
310
-
311
- routes.forEach(route => {
312
- // 1. Serve Static Files
313
- rawApp.get(
314
- route,
315
- serveStatic({
316
- root: mount.root,
317
- rewriteRequestPath: (reqPath) => {
318
- if (mount.rewrite && normalizedPath !== '/') {
319
- // /console/assets/style.css -> /assets/style.css
320
- if (reqPath.startsWith(normalizedPath)) {
321
- return reqPath.substring(normalizedPath.length) || '/';
322
- }
323
- }
324
- return reqPath;
325
- }
326
- })
327
- );
328
-
329
- // 2. SPA Fallback (Scoped)
330
- if (mount.spa) {
331
- rawApp.get(route, async (c, next) => {
332
- // Skip if API path check
333
- const config = this.options.restConfig || {};
334
- const basePath = config.api?.basePath || '/api';
335
-
336
- if (c.req.path.startsWith(basePath)) {
337
- return next();
338
- }
339
-
340
- return serveStatic({
341
- root: mount.root,
342
- rewriteRequestPath: () => 'index.html'
343
- })(c, next);
344
- });
345
- }
346
- });
347
- }
348
- }
349
-
350
- // Start server on kernel:ready hook
351
- ctx.hook('kernel:ready', async () => {
352
- // Register standard endpoints before starting to listen
353
- if (this.options.registerStandardEndpoints) {
354
- this.registerDiscoveryAndCrudEndpoints(ctx);
355
- }
356
-
357
- const port = this.options.port ?? 3000;
358
- ctx.logger.debug('Starting HTTP server', { port });
359
-
360
- await this.server.listen(port);
361
-
362
- const actualPort = this.server.getPort();
363
- if (actualPort !== port) {
364
- ctx.logger.warn(`Port ${port} is in use, using port ${actualPort} instead`);
365
- }
366
- ctx.logger.info('HTTP server started successfully', {
367
- port: actualPort,
368
- url: `http://localhost:${actualPort}`
369
- });
370
- });
371
- }
372
-
373
- /**
374
- * Register discovery and basic CRUD endpoints.
375
- * Called when `registerStandardEndpoints` is true, before the server starts listening.
376
- */
377
- private registerDiscoveryAndCrudEndpoints(ctx: PluginContext) {
378
- const rawApp = this.server.getRawApp();
379
- const prefix = '/api/v1';
380
-
381
- // Build the standard discovery response
382
- const discovery = {
383
- version: 'v1',
384
- apiName: 'ObjectStack API',
385
- routes: {
386
- data: `${prefix}/data`,
387
- metadata: `${prefix}/meta`,
388
- auth: `${prefix}/auth`,
389
- packages: `${prefix}/packages`,
390
- analytics: `${prefix}/analytics`,
391
- realtime: `${prefix}/realtime`,
392
- workflow: `${prefix}/workflow`,
393
- automation: `${prefix}/automation`,
394
- ai: `${prefix}/ai`,
395
- notifications: `${prefix}/notifications`,
396
- i18n: `${prefix}/i18n`,
397
- storage: `${prefix}/storage`,
398
- ui: `${prefix}/ui`,
399
- },
400
- };
401
-
402
- // Discovery endpoints
403
- rawApp.get('/.well-known/objectstack', (c: any) => c.redirect(`${prefix}/discovery`));
404
- rawApp.get(`${prefix}/discovery`, (c: any) => c.json({ data: discovery }));
405
-
406
- ctx.logger.info('Registered discovery endpoints', { prefix });
407
-
408
- // Basic CRUD data endpoints — delegate to ObjectQL service directly
409
- const getObjectQL = () => ctx.getService<IDataEngine>('objectql');
410
-
411
- // Create
412
- rawApp.post(`${prefix}/data/:object`, async (c: any) => {
413
- const ql = getObjectQL();
414
- if (!ql) return c.json({ error: 'Data service not available' }, 503);
415
- const object = c.req.param('object');
416
- const data = await c.req.json().catch(() => ({}));
417
- const res = await ql.insert(object, data);
418
- const record = { ...data, ...res };
419
- return c.json({ object, id: record.id, record });
420
- });
421
-
422
- // Get by ID
423
- rawApp.get(`${prefix}/data/:object/:id`, async (c: any) => {
424
- const ql = getObjectQL();
425
- if (!ql) return c.json({ error: 'Data service not available' }, 503);
426
- const object = c.req.param('object');
427
- const id = c.req.param('id');
428
- let all = await ql.find(object);
429
- if (!all) all = [];
430
- const match = all.find((i: any) => i.id === id);
431
- return match ? c.json({ object, id, record: match }) : c.json({ error: 'Not found' }, 404);
432
- });
433
-
434
- // Find / List
435
- rawApp.get(`${prefix}/data/:object`, async (c: any) => {
436
- const ql = getObjectQL();
437
- if (!ql) return c.json({ error: 'Data service not available' }, 503);
438
- const object = c.req.param('object');
439
- let all = await ql.find(object);
440
- if (!Array.isArray(all) && all && (all as any).value) all = (all as any).value;
441
- if (!all) all = [];
442
- return c.json({ object, records: all, total: all.length });
443
- });
444
-
445
- ctx.logger.debug('Registered standard CRUD data endpoints', { prefix });
446
- }
447
-
448
- /**
449
- * Destroy phase - Stop server
450
- */
451
- async destroy() {
452
- this.server.close();
453
- // Note: Can't use ctx.logger here since we're in destroy
454
- console.log('[HonoServerPlugin] Server stopped');
455
- }
456
- }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- export * from './hono-plugin';
4
- export * from './adapter';
5
-
@@ -1,180 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
-
3
- /**
4
- * Check if an origin matches a pattern with wildcards.
5
- * Supports patterns like:
6
- * - "https://*.example.com" - matches any subdomain
7
- * - "http://localhost:*" - matches any port
8
- * - "https://*.objectui.org,https://*.objectstack.ai" - comma-separated patterns
9
- *
10
- * @param origin The origin to check (e.g., "https://app.example.com")
11
- * @param pattern The pattern to match against (supports * wildcard)
12
- * @returns true if origin matches the pattern
13
- */
14
- function matchOriginPattern(origin: string, pattern: string): boolean {
15
- if (pattern === '*') return true;
16
- if (pattern === origin) return true;
17
-
18
- // Convert wildcard pattern to regex
19
- // Escape special regex characters except *
20
- const regexPattern = pattern
21
- .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special chars
22
- .replace(/\*/g, '.*'); // Convert * to .*
23
-
24
- const regex = new RegExp(`^${regexPattern}$`);
25
- return regex.test(origin);
26
- }
27
-
28
- /**
29
- * Create a CORS origin matcher function that supports wildcard patterns.
30
- *
31
- * @param patterns Single pattern, array of patterns, or comma-separated patterns
32
- * @returns Function that returns the origin if it matches, or null/undefined
33
- */
34
- function createOriginMatcher(
35
- patterns: string | string[]
36
- ): (origin: string) => string | undefined | null {
37
- // Normalize to array
38
- let patternList: string[];
39
- if (typeof patterns === 'string') {
40
- // Handle comma-separated patterns
41
- patternList = patterns.includes(',')
42
- ? patterns.split(',').map(s => s.trim()).filter(Boolean)
43
- : [patterns];
44
- } else {
45
- patternList = patterns;
46
- }
47
-
48
- // Return matcher function
49
- return (requestOrigin: string) => {
50
- for (const pattern of patternList) {
51
- if (matchOriginPattern(requestOrigin, pattern)) {
52
- return requestOrigin;
53
- }
54
- }
55
- return null;
56
- };
57
- }
58
-
59
- describe('matchOriginPattern', () => {
60
- describe('exact matching', () => {
61
- it('should match exact origin', () => {
62
- expect(matchOriginPattern('https://app.example.com', 'https://app.example.com')).toBe(true);
63
- });
64
-
65
- it('should not match different origins', () => {
66
- expect(matchOriginPattern('https://app.example.com', 'https://api.example.com')).toBe(false);
67
- });
68
-
69
- it('should match wildcard "*"', () => {
70
- expect(matchOriginPattern('https://any.domain.com', '*')).toBe(true);
71
- });
72
- });
73
-
74
- describe('subdomain wildcard matching', () => {
75
- it('should match subdomain with wildcard pattern', () => {
76
- expect(matchOriginPattern('https://app.objectui.org', 'https://*.objectui.org')).toBe(true);
77
- expect(matchOriginPattern('https://api.objectui.org', 'https://*.objectui.org')).toBe(true);
78
- expect(matchOriginPattern('https://studio.objectui.org', 'https://*.objectui.org')).toBe(true);
79
- });
80
-
81
- it('should match multi-level subdomains', () => {
82
- expect(matchOriginPattern('https://app.dev.objectui.org', 'https://*.objectui.org')).toBe(true);
83
- expect(matchOriginPattern('https://api.staging.objectui.org', 'https://*.objectui.org')).toBe(true);
84
- });
85
-
86
- it('should not match different domain', () => {
87
- expect(matchOriginPattern('https://app.example.com', 'https://*.objectui.org')).toBe(false);
88
- });
89
-
90
- it('should not match different protocol', () => {
91
- expect(matchOriginPattern('http://app.objectui.org', 'https://*.objectui.org')).toBe(false);
92
- });
93
- });
94
-
95
- describe('port wildcard matching', () => {
96
- it('should match localhost with any port', () => {
97
- expect(matchOriginPattern('http://localhost:3000', 'http://localhost:*')).toBe(true);
98
- expect(matchOriginPattern('http://localhost:8080', 'http://localhost:*')).toBe(true);
99
- expect(matchOriginPattern('http://localhost:5173', 'http://localhost:*')).toBe(true);
100
- });
101
-
102
- it('should not match different host', () => {
103
- expect(matchOriginPattern('http://example.com:3000', 'http://localhost:*')).toBe(false);
104
- });
105
- });
106
-
107
- describe('multiple wildcard patterns', () => {
108
- it('should match wildcard in multiple positions', () => {
109
- expect(matchOriginPattern('https://app.objectui.org', 'https://*.objectui.*')).toBe(true);
110
- expect(matchOriginPattern('https://api.objectui.com', 'https://*.objectui.*')).toBe(true);
111
- });
112
- });
113
- });
114
-
115
- describe('createOriginMatcher', () => {
116
- describe('single pattern', () => {
117
- it('should create matcher for single string pattern', () => {
118
- const matcher = createOriginMatcher('https://*.objectui.org');
119
-
120
- expect(matcher('https://app.objectui.org')).toBe('https://app.objectui.org');
121
- expect(matcher('https://api.objectui.org')).toBe('https://api.objectui.org');
122
- expect(matcher('https://example.com')).toBe(null);
123
- });
124
- });
125
-
126
- describe('array of patterns', () => {
127
- it('should create matcher for array of patterns', () => {
128
- const matcher = createOriginMatcher([
129
- 'https://*.objectui.org',
130
- 'https://*.objectstack.ai'
131
- ]);
132
-
133
- expect(matcher('https://app.objectui.org')).toBe('https://app.objectui.org');
134
- expect(matcher('https://api.objectstack.ai')).toBe('https://api.objectstack.ai');
135
- expect(matcher('https://example.com')).toBe(null);
136
- });
137
- });
138
-
139
- describe('comma-separated patterns', () => {
140
- it('should parse comma-separated patterns', () => {
141
- const matcher = createOriginMatcher('https://*.objectui.org,https://*.objectstack.ai');
142
-
143
- expect(matcher('https://app.objectui.org')).toBe('https://app.objectui.org');
144
- expect(matcher('https://api.objectstack.ai')).toBe('https://api.objectstack.ai');
145
- expect(matcher('https://example.com')).toBe(null);
146
- });
147
-
148
- it('should handle whitespace in comma-separated patterns', () => {
149
- const matcher = createOriginMatcher('https://*.objectui.org, https://*.objectstack.ai , http://localhost:*');
150
-
151
- expect(matcher('https://app.objectui.org')).toBe('https://app.objectui.org');
152
- expect(matcher('https://api.objectstack.ai')).toBe('https://api.objectstack.ai');
153
- expect(matcher('http://localhost:3000')).toBe('http://localhost:3000');
154
- expect(matcher('https://example.com')).toBe(null);
155
- });
156
- });
157
-
158
- describe('mixed exact and wildcard patterns', () => {
159
- it('should match both exact and wildcard patterns', () => {
160
- const matcher = createOriginMatcher([
161
- 'https://app.example.com',
162
- 'https://*.objectui.org'
163
- ]);
164
-
165
- expect(matcher('https://app.example.com')).toBe('https://app.example.com');
166
- expect(matcher('https://dev.objectui.org')).toBe('https://dev.objectui.org');
167
- expect(matcher('https://other.com')).toBe(null);
168
- });
169
- });
170
-
171
- describe('localhost patterns', () => {
172
- it('should match localhost with port wildcard', () => {
173
- const matcher = createOriginMatcher('http://localhost:*');
174
-
175
- expect(matcher('http://localhost:3000')).toBe('http://localhost:3000');
176
- expect(matcher('http://localhost:8080')).toBe('http://localhost:8080');
177
- expect(matcher('http://127.0.0.1:3000')).toBe(null);
178
- });
179
- });
180
- });
package/tsconfig.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "ignoreDeprecations": "6.0",
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "declaration": true,
10
- "outDir": "./dist",
11
- "types": [
12
- "node"
13
- ],
14
- "rootDir": "./src"
15
- },
16
- "include": [
17
- "src/**/*"
18
- ],
19
- "exclude": [
20
- "node_modules",
21
- "dist",
22
- "**/*.test.ts"
23
- ]
24
- }