@h3ravel/url 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/app.globals.d.ts +25 -0
- package/dist/index.cjs +437 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +298 -0
- package/dist/index.d.ts +298 -0
- package/dist/index.js +401 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 h3ravel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# @h3ravel/url
|
|
2
|
+
|
|
3
|
+
Request-aware URI builder and URL manipulation utilities for H3ravel framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @h3ravel/url
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Static factories** for creating URLs from strings, paths, routes, and controller actions
|
|
14
|
+
- **Fluent builder API** for modifying URLs with immutable chaining
|
|
15
|
+
- **Request-aware helpers** for working with current request context
|
|
16
|
+
- **URL signing** capabilities for secure temporary URLs
|
|
17
|
+
- **Type-safe** query parameters and route parameters
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Basic URL Creation
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Url } from '@h3ravel/url'
|
|
25
|
+
|
|
26
|
+
// From string
|
|
27
|
+
const url = Url.of('https://example.com/path')
|
|
28
|
+
|
|
29
|
+
// From path relative to app URL
|
|
30
|
+
const url = Url.to('/users')
|
|
31
|
+
|
|
32
|
+
// From named route
|
|
33
|
+
const url = Url.route('users.show', { id: 1 })
|
|
34
|
+
|
|
35
|
+
// From controller action
|
|
36
|
+
const url = Url.action('UserController@index')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Fluent Builder API
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const url = Url.of('https://example.com')
|
|
43
|
+
.withScheme('http')
|
|
44
|
+
.withHost('test.com')
|
|
45
|
+
.withPort(8000)
|
|
46
|
+
.withPath('/users')
|
|
47
|
+
.withQuery({ page: 2 })
|
|
48
|
+
.withFragment('section-1')
|
|
49
|
+
.toString()
|
|
50
|
+
// -> "http://test.com:8000/users?page=2#section-1"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Request-Aware Helpers
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Get current URL
|
|
57
|
+
url().current()
|
|
58
|
+
|
|
59
|
+
// Get full URL with query string
|
|
60
|
+
url().full()
|
|
61
|
+
|
|
62
|
+
// Get previous URL
|
|
63
|
+
url().previous()
|
|
64
|
+
|
|
65
|
+
// Get previous path only
|
|
66
|
+
url().previousPath()
|
|
67
|
+
|
|
68
|
+
// Get current query parameters
|
|
69
|
+
url().query()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Signed URLs
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// Create signed route URL
|
|
76
|
+
const signedUrl = Url.signedRoute('users.show', { id: 1 })
|
|
77
|
+
|
|
78
|
+
// Create temporary signed route URL (expires in 5 minutes)
|
|
79
|
+
const tempUrl = Url.temporarySignedRoute('users.index', {}, Date.now() + 300000)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## API Reference
|
|
83
|
+
|
|
84
|
+
### Static Methods
|
|
85
|
+
|
|
86
|
+
- `Url.of(string)` - Create from full URL string
|
|
87
|
+
- `Url.to(path)` - Create from path relative to app URL
|
|
88
|
+
- `Url.route(name, params)` - Create from named route
|
|
89
|
+
- `Url.signedRoute(name, params)` - Create signed route URL
|
|
90
|
+
- `Url.temporarySignedRoute(name, params, expiration)` - Create expiring signed route URL
|
|
91
|
+
- `Url.action(controller)` - Create from controller action
|
|
92
|
+
|
|
93
|
+
### Instance Methods
|
|
94
|
+
|
|
95
|
+
- `withScheme(scheme: string)` - Set URL scheme
|
|
96
|
+
- `withHost(host: string)` - Set URL host
|
|
97
|
+
- `withPort(port: number)` - Set URL port
|
|
98
|
+
- `withPath(path: string)` - Set URL path
|
|
99
|
+
- `withQuery(query: Record<string, any>)` - Set query parameters
|
|
100
|
+
- `withFragment(fragment: string)` - Set URL fragment
|
|
101
|
+
- `toString()` - Convert to string representation
|
|
102
|
+
|
|
103
|
+
### Request-Aware Functions
|
|
104
|
+
|
|
105
|
+
- `current()` - Get current request URL
|
|
106
|
+
- `full()` - Get full URL with query string
|
|
107
|
+
- `previous()` - Get previous request URL
|
|
108
|
+
- `previousPath()` - Get previous request path
|
|
109
|
+
- `query()` - Get current query parameters
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ExtractControllerMethods } from '@h3ravel/shared'
|
|
2
|
+
import { RequestAwareHelpers, Url } from '.'
|
|
3
|
+
|
|
4
|
+
export { }
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
/**
|
|
8
|
+
* Create a URL from a named route
|
|
9
|
+
*/
|
|
10
|
+
function route (name: string, params?: Record<string, any>): string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a URL from a controller action
|
|
14
|
+
*/
|
|
15
|
+
function action<C extends new (...args: any) => any> (
|
|
16
|
+
controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],
|
|
17
|
+
params?: Record<string, any>
|
|
18
|
+
): string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get request-aware URL helpers
|
|
22
|
+
*/
|
|
23
|
+
function url (): RequestAwareHelpers;
|
|
24
|
+
function url (path: string): string;
|
|
25
|
+
}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to$1, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to$1, key) && key !== except) __defProp(to$1, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to$1;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
let __h3ravel_core = require("@h3ravel/core");
|
|
25
|
+
__h3ravel_core = __toESM(__h3ravel_core);
|
|
26
|
+
let __h3ravel_support = require("@h3ravel/support");
|
|
27
|
+
__h3ravel_support = __toESM(__h3ravel_support);
|
|
28
|
+
let node_path = require("node:path");
|
|
29
|
+
node_path = __toESM(node_path);
|
|
30
|
+
|
|
31
|
+
//#region src/RequestAwareHelpers.ts
|
|
32
|
+
/**
|
|
33
|
+
* Request-aware URL helper class
|
|
34
|
+
*/
|
|
35
|
+
var RequestAwareHelpers = class {
|
|
36
|
+
baseUrl = "";
|
|
37
|
+
constructor(app) {
|
|
38
|
+
this.app = app;
|
|
39
|
+
try {
|
|
40
|
+
this.baseUrl = config("app.url", "http://localhost:3000");
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get the current request instance
|
|
45
|
+
*/
|
|
46
|
+
getCurrentRequest() {
|
|
47
|
+
const request = this.app.make("http.request");
|
|
48
|
+
if (!request) throw new Error("Request instance not available in current context");
|
|
49
|
+
return request;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the current request URL (path only, no query string)
|
|
53
|
+
*/
|
|
54
|
+
current() {
|
|
55
|
+
const raw = this.getCurrentRequest().getEvent().req.url ?? "/";
|
|
56
|
+
return new URL(raw, "http://localhost").pathname;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the full current URL with query string
|
|
60
|
+
*/
|
|
61
|
+
full() {
|
|
62
|
+
const requestUrl = this.getCurrentRequest().getEvent().req.url ?? "/";
|
|
63
|
+
if (requestUrl.startsWith("http")) return requestUrl;
|
|
64
|
+
return new URL(requestUrl, this.baseUrl).toString();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the previous request URL from session or referrer
|
|
68
|
+
*/
|
|
69
|
+
previous() {
|
|
70
|
+
const headers = this.getCurrentRequest().getEvent()?.node?.req?.headers;
|
|
71
|
+
let referrer = headers?.referer ?? headers?.referrer;
|
|
72
|
+
if (Array.isArray(referrer)) referrer = referrer[0];
|
|
73
|
+
if (referrer) return referrer;
|
|
74
|
+
return this.current();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get the previous request path (without query string)
|
|
78
|
+
*/
|
|
79
|
+
previousPath() {
|
|
80
|
+
const previousUrl = this.previous();
|
|
81
|
+
try {
|
|
82
|
+
return new URL(previousUrl).pathname;
|
|
83
|
+
} catch {
|
|
84
|
+
return previousUrl;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the current query parameters
|
|
89
|
+
*/
|
|
90
|
+
query() {
|
|
91
|
+
return this.getCurrentRequest().query || {};
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Global helper function factory
|
|
96
|
+
*/
|
|
97
|
+
function createUrlHelper(app) {
|
|
98
|
+
return () => new RequestAwareHelpers(app);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/Url.ts
|
|
103
|
+
/**
|
|
104
|
+
* URL builder class with fluent API and request-aware helpers
|
|
105
|
+
*/
|
|
106
|
+
var Url = class Url {
|
|
107
|
+
_scheme;
|
|
108
|
+
_host;
|
|
109
|
+
_port;
|
|
110
|
+
_path;
|
|
111
|
+
_query;
|
|
112
|
+
_fragment;
|
|
113
|
+
app;
|
|
114
|
+
constructor(app, scheme, host, port, path$1 = "/", query = {}, fragment) {
|
|
115
|
+
this.app = app;
|
|
116
|
+
this._scheme = scheme;
|
|
117
|
+
this._host = host;
|
|
118
|
+
this._port = port;
|
|
119
|
+
this._path = path$1.startsWith("/") ? path$1 : `/${path$1}`;
|
|
120
|
+
this._query = { ...query };
|
|
121
|
+
this._fragment = fragment;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Create a URL from a full URL string
|
|
125
|
+
*/
|
|
126
|
+
static of(url$1, app) {
|
|
127
|
+
try {
|
|
128
|
+
const parsed = new URL(url$1);
|
|
129
|
+
const query = {};
|
|
130
|
+
parsed.searchParams.forEach((value, key) => {
|
|
131
|
+
query[key] = value;
|
|
132
|
+
});
|
|
133
|
+
return new Url(app, parsed.protocol.replace(":", ""), parsed.hostname, parsed.port ? parseInt(parsed.port) : void 0, parsed.pathname || "/", query, parsed.hash ? parsed.hash.substring(1) : void 0);
|
|
134
|
+
} catch {
|
|
135
|
+
throw new Error(`Invalid URL: ${url$1}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Create a URL from a path relative to the app URL
|
|
140
|
+
*/
|
|
141
|
+
static to(path$1, app) {
|
|
142
|
+
let baseUrl = "";
|
|
143
|
+
try {
|
|
144
|
+
baseUrl = config("app.url", "http://localhost:3000");
|
|
145
|
+
} catch {}
|
|
146
|
+
const fullUrl = new URL(path$1, baseUrl).toString();
|
|
147
|
+
return Url.of(fullUrl, app);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Create a URL from a named route
|
|
151
|
+
*/
|
|
152
|
+
static route(name, params = {}, app) {
|
|
153
|
+
if (!app) throw new Error("Application instance required for route generation");
|
|
154
|
+
const router = app.make("router");
|
|
155
|
+
if (!router || typeof router.route !== "function") throw new Error("Router not available or does not support route generation");
|
|
156
|
+
if (typeof router.route !== "function") throw new Error("Router does not support route generation");
|
|
157
|
+
const routeUrl = router.route(name, params);
|
|
158
|
+
if (!routeUrl) throw new Error(`Route "${name}" not found`);
|
|
159
|
+
return Url.to(routeUrl, app);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Create a signed URL from a named route
|
|
163
|
+
*/
|
|
164
|
+
static signedRoute(name, params = {}, app) {
|
|
165
|
+
return Url.route(name, params, app).withSignature(app);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create a temporary signed URL from a named route
|
|
169
|
+
*/
|
|
170
|
+
static temporarySignedRoute(name, params = {}, expiration, app) {
|
|
171
|
+
return Url.route(name, params, app).withSignature(app, expiration);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create a URL from a controller action
|
|
175
|
+
*/
|
|
176
|
+
static action(controller, params, app) {
|
|
177
|
+
if (!app) throw new Error("Application instance required for action URL generation");
|
|
178
|
+
const [controllerName, methodName = "index"] = typeof controller === "string" ? controller.split("@") : controller;
|
|
179
|
+
const cname = typeof controllerName === "string" ? controllerName : controllerName.name;
|
|
180
|
+
const routes = app.make("app.routes");
|
|
181
|
+
if (!Array.isArray(routes)) throw new Error("Action URL generation requires router integration - not yet implemented");
|
|
182
|
+
if (routes.length < 1) throw new Error(`No routes available to resolve action: ${controller}`);
|
|
183
|
+
const found = routes.find((route$1) => {
|
|
184
|
+
return route$1.signature?.[0] === cname && (route$1.signature?.[1] || "index") === methodName;
|
|
185
|
+
});
|
|
186
|
+
if (!found) throw new Error(`No route found for ${cname}`);
|
|
187
|
+
const _params = Object.values(params ?? {}).join("/");
|
|
188
|
+
if (_params) return Url.to(node_path.default.join(found.path, _params));
|
|
189
|
+
return Url.to(found.path, app);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Set the scheme (protocol) of the URL
|
|
193
|
+
*/
|
|
194
|
+
withScheme(scheme) {
|
|
195
|
+
return new Url(this.app, scheme, this._host, this._port, this._path, this._query, this._fragment);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Set the host of the URL
|
|
199
|
+
*/
|
|
200
|
+
withHost(host) {
|
|
201
|
+
return new Url(this.app, this._scheme, host, this._port, this._path, this._query, this._fragment);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Set the port of the URL
|
|
205
|
+
*/
|
|
206
|
+
withPort(port) {
|
|
207
|
+
return new Url(this.app, this._scheme, this._host, port, this._path, this._query, this._fragment);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Set the path of the URL
|
|
211
|
+
*/
|
|
212
|
+
withPath(path$1) {
|
|
213
|
+
return new Url(this.app, this._scheme, this._host, this._port, path$1, this._query, this._fragment);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Set the query parameters of the URL
|
|
217
|
+
*/
|
|
218
|
+
withQuery(query) {
|
|
219
|
+
return new Url(this.app, this._scheme, this._host, this._port, this._path, { ...query }, this._fragment);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Merge additional query parameters
|
|
223
|
+
*/
|
|
224
|
+
withQueryParams(params) {
|
|
225
|
+
return new Url(this.app, this._scheme, this._host, this._port, this._path, {
|
|
226
|
+
...this._query,
|
|
227
|
+
...params
|
|
228
|
+
}, this._fragment);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Set the fragment (hash) of the URL
|
|
232
|
+
*/
|
|
233
|
+
withFragment(fragment) {
|
|
234
|
+
return new Url(this.app, this._scheme, this._host, this._port, this._path, this._query, fragment);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Add a signature to the URL for security
|
|
238
|
+
*/
|
|
239
|
+
withSignature(app, expiration) {
|
|
240
|
+
if (!(app || this.app)) throw new Error("Application instance required for URL signing");
|
|
241
|
+
let key = "";
|
|
242
|
+
try {
|
|
243
|
+
key = config("app.key");
|
|
244
|
+
} catch {}
|
|
245
|
+
if (!key) throw new __h3ravel_core.ConfigException("APP_KEY and app.key", "any", this);
|
|
246
|
+
const url$1 = this.toString();
|
|
247
|
+
const queryParams = { ...this._query };
|
|
248
|
+
if (expiration) queryParams.expires = Math.floor(expiration / 1e3);
|
|
249
|
+
const payload = expiration ? `${url$1}?expires=${queryParams.expires}` : url$1;
|
|
250
|
+
queryParams.signature = (0, __h3ravel_support.hmac)(payload, key);
|
|
251
|
+
return this.withQuery(queryParams);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Verify if a URL signature is valid
|
|
255
|
+
*/
|
|
256
|
+
hasValidSignature(app) {
|
|
257
|
+
if (!(app || this.app)) return false;
|
|
258
|
+
const signature = this._query.signature;
|
|
259
|
+
if (!signature) return false;
|
|
260
|
+
if (this._query.expires !== void 0 && this._query.expires !== null) {
|
|
261
|
+
const expiresStr = String(this._query.expires);
|
|
262
|
+
const expirationTime = parseInt(expiresStr, 10) * 1e3;
|
|
263
|
+
if (isNaN(expirationTime) || Date.now() > expirationTime) return false;
|
|
264
|
+
}
|
|
265
|
+
const queryWithoutSignature = { ...this._query };
|
|
266
|
+
delete queryWithoutSignature.signature;
|
|
267
|
+
const urlWithoutSignature = new Url(this.app, this._scheme, this._host, this._port, this._path, queryWithoutSignature, this._fragment).toString();
|
|
268
|
+
const payload = this._query.expires ? `${urlWithoutSignature}?expires=${this._query.expires}` : urlWithoutSignature;
|
|
269
|
+
let key = "";
|
|
270
|
+
try {
|
|
271
|
+
key = config("app.key", "default-key");
|
|
272
|
+
} catch {}
|
|
273
|
+
const expectedSignature = (0, __h3ravel_support.hmac)(payload, key);
|
|
274
|
+
return signature === expectedSignature;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Convert the URL to its string representation
|
|
278
|
+
*/
|
|
279
|
+
toString() {
|
|
280
|
+
let url$1 = "";
|
|
281
|
+
if (this._scheme && this._host) {
|
|
282
|
+
url$1 += `${this._scheme}://${this._host}`;
|
|
283
|
+
if (this._port && !(this._scheme === "http" && this._port === 80 || this._scheme === "https" && this._port === 443)) url$1 += `:${this._port}`;
|
|
284
|
+
}
|
|
285
|
+
if (this._path) {
|
|
286
|
+
if (!this._path.startsWith("/")) url$1 += "/";
|
|
287
|
+
url$1 += this._path;
|
|
288
|
+
}
|
|
289
|
+
const queryEntries = Object.entries(this._query);
|
|
290
|
+
if (queryEntries.length > 0) {
|
|
291
|
+
const queryString = queryEntries.map(([key, value]) => {
|
|
292
|
+
if (Array.isArray(value)) return value.map((v) => `${encodeURIComponent(key)}%5B%5D=${encodeURIComponent(v)}`).join("&");
|
|
293
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;
|
|
294
|
+
}).join("&");
|
|
295
|
+
url$1 += `?${queryString}`;
|
|
296
|
+
}
|
|
297
|
+
if (this._fragment) url$1 += `#${this._fragment}`;
|
|
298
|
+
return url$1;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get the scheme
|
|
302
|
+
*/
|
|
303
|
+
getScheme() {
|
|
304
|
+
return this._scheme;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get the host
|
|
308
|
+
*/
|
|
309
|
+
getHost() {
|
|
310
|
+
return this._host;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get the port
|
|
314
|
+
*/
|
|
315
|
+
getPort() {
|
|
316
|
+
return this._port;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Get the path
|
|
320
|
+
*/
|
|
321
|
+
getPath() {
|
|
322
|
+
return this._path;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get the query parameters
|
|
326
|
+
*/
|
|
327
|
+
getQuery() {
|
|
328
|
+
return { ...this._query };
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get the fragment
|
|
332
|
+
*/
|
|
333
|
+
getFragment() {
|
|
334
|
+
return this._fragment;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/Helpers.ts
|
|
340
|
+
/**
|
|
341
|
+
* Global helper functions for URL manipulation
|
|
342
|
+
*/
|
|
343
|
+
/**
|
|
344
|
+
* Create a URL from a path relative to the app URL
|
|
345
|
+
*/
|
|
346
|
+
function to(path$1, app) {
|
|
347
|
+
return Url.to(path$1, app);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Create a URL from a named route
|
|
351
|
+
*/
|
|
352
|
+
function route(name, params = {}, app) {
|
|
353
|
+
return Url.route(name, params, app);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Create a signed URL from a named route
|
|
357
|
+
*/
|
|
358
|
+
function signedRoute(name, params = {}, app) {
|
|
359
|
+
return Url.signedRoute(name, params, app);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Create a temporary signed URL from a named route
|
|
363
|
+
*/
|
|
364
|
+
function temporarySignedRoute(name, params = {}, expiration, app) {
|
|
365
|
+
return Url.temporarySignedRoute(name, params, expiration, app);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Create a URL from a controller action
|
|
369
|
+
*/
|
|
370
|
+
function action(controller, app) {
|
|
371
|
+
return Url.action(controller, app);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get request-aware URL helpers
|
|
375
|
+
*/
|
|
376
|
+
function url(app) {
|
|
377
|
+
if (!app) throw new Error("Application instance required for request-aware URL helpers");
|
|
378
|
+
return new RequestAwareHelpers(app);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Create URL helpers that are bound to an application instance
|
|
382
|
+
*/
|
|
383
|
+
function createUrlHelpers(app) {
|
|
384
|
+
return {
|
|
385
|
+
to: (path$1) => Url.to(path$1, app),
|
|
386
|
+
route: (name, params = {}) => Url.route(name, params, app).toString(),
|
|
387
|
+
signedRoute: (name, params = {}) => Url.signedRoute(name, params, app),
|
|
388
|
+
temporarySignedRoute: (name, params = {}, expiration) => Url.temporarySignedRoute(name, params, expiration, app),
|
|
389
|
+
action: (controller, params) => Url.action(controller, params, app).toString(),
|
|
390
|
+
url: (path$1) => {
|
|
391
|
+
if (path$1) return Url.to(path$1).toString();
|
|
392
|
+
return new RequestAwareHelpers(app);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/Providers/UrlServiceProvider.ts
|
|
399
|
+
/**
|
|
400
|
+
* Service provider for URL utilities
|
|
401
|
+
*/
|
|
402
|
+
var UrlServiceProvider = class extends __h3ravel_core.ServiceProvider {
|
|
403
|
+
/**
|
|
404
|
+
* Register URL services in the container
|
|
405
|
+
*/
|
|
406
|
+
register() {
|
|
407
|
+
this.app.singleton("app.url", () => Url);
|
|
408
|
+
this.app.singleton("app.url.helper", () => createUrlHelper(this.app));
|
|
409
|
+
this.app.singleton("app.url.helpers", () => createUrlHelpers(this.app));
|
|
410
|
+
if (typeof globalThis !== "undefined") {
|
|
411
|
+
const helpers = createUrlHelpers(this.app);
|
|
412
|
+
Object.assign(globalThis, {
|
|
413
|
+
url: helpers.url,
|
|
414
|
+
route: helpers.route,
|
|
415
|
+
action: helpers.action
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Boot URL services
|
|
421
|
+
*/
|
|
422
|
+
boot() {}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
//#endregion
|
|
426
|
+
exports.RequestAwareHelpers = RequestAwareHelpers;
|
|
427
|
+
exports.Url = Url;
|
|
428
|
+
exports.UrlServiceProvider = UrlServiceProvider;
|
|
429
|
+
exports.action = action;
|
|
430
|
+
exports.createUrlHelper = createUrlHelper;
|
|
431
|
+
exports.createUrlHelpers = createUrlHelpers;
|
|
432
|
+
exports.route = route;
|
|
433
|
+
exports.signedRoute = signedRoute;
|
|
434
|
+
exports.temporarySignedRoute = temporarySignedRoute;
|
|
435
|
+
exports.to = to;
|
|
436
|
+
exports.url = url;
|
|
437
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["app: Application","path","url","query: Record<string, unknown>","routes: RouteDefinition[]","route","ConfigException","queryParams: Record<string, unknown>","path","ServiceProvider"],"sources":["../src/RequestAwareHelpers.ts","../src/Url.ts","../src/Helpers.ts","../src/Providers/UrlServiceProvider.ts"],"sourcesContent":["import type { Application } from '@h3ravel/core'\nimport type { IRequest } from '@h3ravel/shared'\nimport { RouteParams } from './Contracts/UrlContract'\n\n/**\n * Request-aware URL helper class\n */\nexport class RequestAwareHelpers {\n private readonly baseUrl: string = ''\n\n constructor(private app: Application) {\n try {\n this.baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n }\n\n /**\n * Get the current request instance\n */\n private getCurrentRequest (): IRequest {\n const request = this.app.make('http.request')\n if (!request) {\n throw new Error('Request instance not available in current context')\n }\n return request\n }\n\n /**\n * Get the current request URL (path only, no query string)\n */\n current (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the path from the request\n const raw = event.req.url ?? '/'\n const url = new URL(raw, 'http://localhost')\n return url.pathname\n }\n\n /**\n * Get the full current URL with query string\n */\n full (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Get the full URL including query string\n const requestUrl = event.req.url ?? '/'\n\n // If requestUrl is already absolute, use it directly, otherwise combine with baseUrl\n if (requestUrl.startsWith('http')) {\n return requestUrl\n }\n\n const fullUrl = new URL(requestUrl, this.baseUrl)\n return fullUrl.toString()\n }\n\n /**\n * Get the previous request URL from session or referrer\n */\n previous (): string {\n const request = this.getCurrentRequest()\n const event = request.getEvent()\n\n // Try to get from session first (if session is available)\n // For now, fallback to HTTP referrer header\n const headers = (event as any)?.node?.req?.headers as Record<string, string | string[] | undefined> | undefined\n // console.log(headers)\n let referrer = headers?.referer ?? headers?.referrer\n if (Array.isArray(referrer)) referrer = referrer[0]\n if (referrer) return referrer\n\n // Fallback to current URL if no referrer\n return this.current()\n }\n\n /**\n * Get the previous request path (without query string)\n */\n previousPath (): string {\n const previousUrl = this.previous()\n\n try {\n const url = new URL(previousUrl)\n return url.pathname\n } catch {\n // If previous URL is not a valid URL, return as-is\n return previousUrl\n }\n }\n\n /**\n * Get the current query parameters\n */\n query (): RouteParams {\n const request = this.getCurrentRequest()\n return request.query || {}\n }\n}\n\n/**\n * Global helper function factory\n */\nexport function createUrlHelper (app: Application): () => RequestAwareHelpers {\n return () => new RequestAwareHelpers(app)\n}\n","import { ConfigException, type Application } from '@h3ravel/core'\nimport { RouteParams } from './Contracts/UrlContract'\nimport { hmac } from '@h3ravel/support'\nimport { RouteDefinition, ExtractControllerMethods } from '@h3ravel/shared'\nimport path from 'node:path'\n\n/**\n * URL builder class with fluent API and request-aware helpers\n */\nexport class Url {\n private readonly _scheme?: string\n private readonly _host?: string\n private readonly _port?: number\n private readonly _path: string\n private readonly _query: Record<string, unknown>\n private readonly _fragment?: string\n private readonly app?: Application\n\n private constructor(\n app?: Application,\n scheme?: string,\n host?: string,\n port?: number,\n path: string = '/',\n query: Record<string, unknown> = {},\n fragment?: string\n ) {\n this.app = app\n this._scheme = scheme\n this._host = host\n this._port = port\n this._path = path.startsWith('/') ? path : `/${path}`\n this._query = { ...query }\n this._fragment = fragment\n }\n\n /**\n * Create a URL from a full URL string\n */\n static of (url: string, app?: Application): Url {\n try {\n const parsed = new URL(url)\n const query: Record<string, unknown> = {}\n\n // Parse query parameters\n parsed.searchParams.forEach((value, key) => {\n query[key] = value\n })\n\n return new Url(\n app,\n parsed.protocol.replace(':', ''),\n parsed.hostname,\n parsed.port ? parseInt(parsed.port) : undefined,\n parsed.pathname || '/',\n query,\n parsed.hash ? parsed.hash.substring(1) : undefined\n )\n } catch {\n throw new Error(`Invalid URL: ${url}`)\n }\n }\n\n /**\n * Create a URL from a path relative to the app URL\n */\n static to (path: string, app?: Application): Url {\n let baseUrl = ''\n try {\n baseUrl = config('app.url', 'http://localhost:3000')\n } catch {/** */ }\n\n const fullUrl = new URL(path, baseUrl).toString()\n\n return Url.of(fullUrl, app)\n }\n\n /**\n * Create a URL from a named route\n */\n // Route parameter map (declaration-mergeable by consumers)\n static route<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n if (!app) {\n throw new Error('Application instance required for route generation')\n }\n\n // Use (app as any).make to avoid TS error if make is not typed on Application\n const router = app.make('router')\n if (!router || typeof router.route !== 'function') {\n throw new Error('Router not available or does not support route generation')\n }\n\n if (typeof router.route !== 'function') {\n throw new Error('Router does not support route generation')\n }\n\n const routeUrl = router.route(name, params)\n if (!routeUrl) {\n throw new Error(`Route \"${name}\" not found`)\n }\n\n return Url.to(routeUrl, app)\n }\n\n /**\n * Create a signed URL from a named route\n */\n static signedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app)\n }\n\n /**\n * Create a temporary signed URL from a named route\n */\n static temporarySignedRoute<TName extends string = string, TParams extends RouteParams = RouteParams> (\n name: TName,\n params: TParams = {} as TParams,\n expiration: number,\n app?: Application\n ): Url {\n const url = Url.route<TName, TParams>(name, params, app)\n return url.withSignature(app, expiration)\n }\n\n /**\n * Create a URL from a controller action\n */\n static action<C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>,\n app?: Application\n ): Url {\n if (!app) throw new Error('Application instance required for action URL generation')\n\n const [controllerName, methodName = 'index'] = typeof controller === 'string'\n ? controller.split('@')\n : controller\n\n const cname = typeof controllerName === 'string' ? controllerName : controllerName.name\n\n const routes: RouteDefinition[] = app.make('app.routes')\n\n if (!Array.isArray(routes)) {\n // Backward-compatible message expected by existing tests\n throw new Error('Action URL generation requires router integration - not yet implemented')\n }\n\n if (routes.length < 1) throw new Error(`No routes available to resolve action: ${controller}`)\n\n // Search for for the \n const found = routes.find(route => {\n return route.signature?.[0] === cname && (route.signature?.[1] || 'index') === methodName\n })\n\n if (!found) throw new Error(`No route found for ${cname}`)\n\n // Build the route parameters\n const _params = Object.values(params ?? {}).join('/')\n\n if (_params) {\n return Url.to(path.join(found.path, _params))\n }\n\n return Url.to(found.path, app)\n }\n\n /**\n * Set the scheme (protocol) of the URL\n */\n withScheme (scheme: string): Url {\n return new Url(\n this.app,\n scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the host of the URL\n */\n withHost (host: string): Url {\n return new Url(\n this.app,\n this._scheme,\n host,\n this._port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the port of the URL\n */\n withPort (port: number): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n port,\n this._path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the path of the URL\n */\n withPath (path: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n path,\n this._query,\n this._fragment\n )\n }\n\n /**\n * Set the query parameters of the URL\n */\n withQuery (query: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...query },\n this._fragment\n )\n }\n\n /**\n * Merge additional query parameters\n */\n withQueryParams (params: Record<string, unknown>): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n { ...this._query, ...params },\n this._fragment\n )\n }\n\n /**\n * Set the fragment (hash) of the URL\n */\n withFragment (fragment: string): Url {\n return new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n this._query,\n fragment\n )\n }\n\n /**\n * Add a signature to the URL for security\n */\n withSignature (app?: Application, expiration?: number): Url {\n const appInstance = app || this.app\n if (!appInstance) {\n throw new Error('Application instance required for URL signing')\n }\n\n let key = ''\n try {\n key = config('app.key')\n } catch {/** */ }\n\n if (!key) {\n throw new ConfigException('APP_KEY and app.key', 'any', this)\n }\n const url = this.toString()\n const queryParams: Record<string, unknown> = { ...this._query }\n\n if (expiration) {\n queryParams.expires = Math.floor(expiration / 1000)\n }\n\n // Create signature payload\n const payload = expiration\n ? `${url}?expires=${queryParams.expires}`\n : url\n\n const signature = hmac(payload, key)\n queryParams.signature = signature\n\n return this.withQuery(queryParams)\n }\n\n /**\n * Verify if a URL signature is valid\n */\n hasValidSignature (app?: Application): boolean {\n const appInstance = app || this.app\n if (!appInstance) {\n return false\n }\n\n const signature = this._query.signature\n if (!signature) {\n return false\n }\n\n // Check expiration if present\n if (this._query.expires !== undefined && this._query.expires !== null) {\n const expiresStr = String(this._query.expires)\n const expirationTime = parseInt(expiresStr, 10) * 1000\n if (isNaN(expirationTime) || Date.now() > expirationTime) {\n return false\n }\n }\n\n // Recreate URL without signature for verification\n const queryWithoutSignature = { ...this._query }\n delete queryWithoutSignature.signature\n\n const urlWithoutSignature = new Url(\n this.app,\n this._scheme,\n this._host,\n this._port,\n this._path,\n queryWithoutSignature,\n this._fragment\n ).toString()\n\n const payload = this._query.expires\n ? `${urlWithoutSignature}?expires=${this._query.expires}`\n : urlWithoutSignature\n\n let key = ''\n try {\n key = config('app.key', 'default-key')\n } catch {/** */ }\n const expectedSignature = hmac(payload, key)\n\n return signature === expectedSignature\n }\n\n /**\n * Convert the URL to its string representation\n */\n toString (): string {\n let url = ''\n\n // Add scheme and host\n if (this._scheme && this._host) {\n url += `${this._scheme}://${this._host}`\n\n // Add port if specified and not default\n if (this._port &&\n !((this._scheme === 'http' && this._port === 80) ||\n (this._scheme === 'https' && this._port === 443))) {\n url += `:${this._port}`\n }\n }\n\n // Add path\n if (this._path) {\n if (!this._path.startsWith('/')) {\n url += '/'\n }\n url += this._path\n }\n\n // Add query parameters\n const queryEntries = Object.entries(this._query)\n if (queryEntries.length > 0) {\n const queryString = queryEntries\n .map(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(v => `${encodeURIComponent(key)}%5B%5D=${encodeURIComponent(v)}`).join('&')\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`\n })\n .join('&')\n url += `?${queryString}`\n }\n\n // Add fragment\n if (this._fragment) {\n url += `#${this._fragment}`\n }\n\n return url\n }\n\n /**\n * Get the scheme\n */\n getScheme (): string | undefined {\n return this._scheme\n }\n\n /**\n * Get the host\n */\n getHost (): string | undefined {\n return this._host\n }\n\n /**\n * Get the port\n */\n getPort (): number | undefined {\n return this._port\n }\n\n /**\n * Get the path\n */\n getPath (): string {\n return this._path\n }\n\n /**\n * Get the query parameters\n */\n getQuery (): Record<string, unknown> {\n return { ...this._query }\n }\n\n /**\n * Get the fragment\n */\n getFragment (): string | undefined {\n return this._fragment\n }\n}\n","import { Application } from '@h3ravel/core'\nimport { HelpersContract } from './Contracts/UrlContract'\nimport { RequestAwareHelpers } from './RequestAwareHelpers'\nimport { Url } from './Url'\nimport { ExtractControllerMethods } from '@h3ravel/shared'\n\n/**\n * Global helper functions for URL manipulation\n */\n\n/**\n * Create a URL from a path relative to the app URL\n */\nexport function to (\n path: string,\n app?: Application\n): Url {\n return Url.to(path, app)\n}\n\n/**\n * Create a URL from a named route\n */\nexport function route<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.route<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a signed URL from a named route\n */\nexport function signedRoute<TName extends string, TParams extends Record<string, string> = Record<string, string>> (\n name: TName,\n params: TParams = {} as TParams,\n app?: Application\n): Url {\n return Url.signedRoute<TName, TParams>(name, params, app)\n}\n\n/**\n * Create a temporary signed URL from a named route\n */\nexport function temporarySignedRoute (\n name: string,\n params: Record<string, string> = {},\n expiration: number,\n app?: Application\n): Url {\n return Url.temporarySignedRoute(name, params, expiration, app)\n}\n\n/**\n * Create a URL from a controller action\n */\nexport function action (\n controller: string,\n app?: Application\n): Url {\n return Url.action(controller, app)\n}\n\n/**\n * Get request-aware URL helpers\n */\nexport function url (app?: Application): RequestAwareHelpers {\n if (!app) throw new Error('Application instance required for request-aware URL helpers')\n return new RequestAwareHelpers(app)\n}\n\n/**\n * Create URL helpers that are bound to an application instance\n */\nexport function createUrlHelpers (app: Application): HelpersContract {\n return {\n /**\n * Create a URL from a path relative to the app URL\n */\n to: (path: string) => Url.to(path, app),\n\n /**\n * Create a URL from a named route\n */\n route: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.route(name, params, app).toString(),\n\n /**\n * Create a signed URL from a named route\n */\n signedRoute: (\n name: string,\n params: Record<string, any> = {}\n ) => Url.signedRoute(name, params, app),\n\n /**\n * Create a temporary signed URL from a named route\n */\n temporarySignedRoute: (\n name: string,\n params: Record<string, any> = {},\n expiration: number\n ) => Url.temporarySignedRoute(name, params, expiration, app),\n\n /**\n * Create a URL from a controller action\n */\n action: <C extends new (...args: any) => any> (\n controller: string | [C, methodName: ExtractControllerMethods<InstanceType<C>>],\n params?: Record<string, any>\n ) => Url.action(controller, params, app).toString(),\n\n /**\n * Get request-aware URL helpers\n */\n url: (path?: string) => {\n if (path) {\n return Url.to(path).toString() as never\n }\n return new RequestAwareHelpers(app) as never\n }\n }\n}\n","/// <reference path=\"../app.globals.d.ts\" />\nimport { ServiceProvider } from '@h3ravel/core'\nimport { Url } from '../Url'\nimport { createUrlHelper } from '../RequestAwareHelpers'\nimport { createUrlHelpers } from '../Helpers'\n\n/**\n * Service provider for URL utilities\n */\nexport class UrlServiceProvider extends ServiceProvider {\n /**\n * Register URL services in the container\n */\n register (): void {\n // Register the Url class\n this.app.singleton('app.url', () => Url)\n // Register the url() helper function\n this.app.singleton('app.url.helper', () => createUrlHelper(this.app))\n\n // Register bound URL helpers\n this.app.singleton('app.url.helpers', () => createUrlHelpers(this.app))\n\n // Make url() globally available\n if (typeof globalThis !== 'undefined') {\n const helpers = createUrlHelpers(this.app)\n\n Object.assign(globalThis, {\n url: helpers.url,\n route: helpers.route,\n action: helpers.action,\n })\n }\n }\n\n /**\n * Boot URL services\n */\n boot (): void {\n // Any additional setup can be done here\n }\n}\n"],"mappings":"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"}
|