@drawbridge/drawbridge-utils 0.0.10 → 0.0.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/README.md +38 -1
- package/dist/axios.cjs +6 -1
- package/dist/axios.d.cts +12 -1
- package/dist/axios.d.ts +12 -1
- package/dist/axios.js +6 -1
- package/dist/redirect.cjs +42 -0
- package/dist/redirect.d.cts +32 -0
- package/dist/redirect.d.ts +32 -0
- package/dist/redirect.js +18 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -21,7 +21,8 @@ Exports:
|
|
|
21
21
|
- `formatNumber( number, digits )` — Intl-based number formatter with fixed-digit min/max.
|
|
22
22
|
- `getPlanFeature( plan, key )` — `{ granted, message }` lookup.
|
|
23
23
|
- `infinite`, `isInfinite( value )`, `megabyte`, `gigabyte` — size constants.
|
|
24
|
-
- `
|
|
24
|
+
- `nanoid()` — 8-char base36 random ID.
|
|
25
|
+
- `percentage( value1, value2, decimals )` — `(value1 / value2) * 100`, fixed decimals (default 1).
|
|
25
26
|
- `reducers.fields` — reducer for grouping fields by `additional` / `lead` type.
|
|
26
27
|
- `regex.url` / `regex.protocols` / `regex.domain` — shared regex constants.
|
|
27
28
|
- `shareUrls({ formUri, organization, page })` — builds share URLs from the configured form base URI. Falls back to `process.env.APP_CLIENT_FORM_URI` if `formUri` is not passed (server-only).
|
|
@@ -35,6 +36,42 @@ const { encrypt, decrypt } = require( '@drawbridge/drawbridge-utils/encrypt' );
|
|
|
35
36
|
|
|
36
37
|
Server-only because it uses Node `crypto` and reads `process.env.ENCRYPT_CONNECTION_SECRET`. Exposed as a sub-export so client bundles don't pull Node built-ins.
|
|
37
38
|
|
|
39
|
+
## Axios entry (server-only)
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const { safe, axios } = require( '@drawbridge/drawbridge-utils/axios' );
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `safe.get( url, axiosOptions )` — SSRF-protected GET request.
|
|
46
|
+
- `axios` — raw axios passthrough for advanced usage.
|
|
47
|
+
|
|
48
|
+
## Circuit entry (server-only)
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
const { circuit } = require( '@drawbridge/drawbridge-utils/circuit' );
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `circuit({ name, threshold, timeout })` — circuit breaker for async operations.
|
|
55
|
+
|
|
56
|
+
## Upload entry
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
const { allowedUploadTypes, allowedMimes, resolveUploadType, isAllowedMime } = require( '@drawbridge/drawbridge-utils/upload' );
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- `allowedUploadTypes` / `allowedMimes` — shared whitelist constants.
|
|
63
|
+
- `resolveUploadType( type, extension )` — resolves an upload type from MIME + extension.
|
|
64
|
+
- `isAllowedMime( mime )` — predicate for the MIME whitelist.
|
|
65
|
+
|
|
66
|
+
## Nanoid entry (server-only)
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
const { nanoid, retry } = require( '@drawbridge/drawbridge-utils/nanoid' );
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `nanoid()` — 8-char base36 random ID (same as the main export).
|
|
73
|
+
- `retry({ field, operation, maxRetries })` — runs an async operation that generates a nanoid, retrying on Mongo duplicate-key errors.
|
|
74
|
+
|
|
38
75
|
## Build & publish
|
|
39
76
|
|
|
40
77
|
```bash
|
package/dist/axios.cjs
CHANGED
|
@@ -103,7 +103,12 @@ var safeGet = async (url, axiosOptions = {}) => {
|
|
|
103
103
|
const pinned = records[0];
|
|
104
104
|
const agent = new import_https.default.Agent({
|
|
105
105
|
lookup: (hostname, options, callback) => {
|
|
106
|
-
|
|
106
|
+
if (options == null ? void 0 : options.all) {
|
|
107
|
+
callback(null, [{ address: pinned.address, family: pinned.family }]);
|
|
108
|
+
} else {
|
|
109
|
+
callback(null, pinned.address, pinned.family);
|
|
110
|
+
}
|
|
111
|
+
;
|
|
107
112
|
}
|
|
108
113
|
});
|
|
109
114
|
return import_axios.default.get(url, {
|
package/dist/axios.d.cts
CHANGED
|
@@ -113,8 +113,19 @@ const safeGet = async ( url, axiosOptions = {} ) => {
|
|
|
113
113
|
const agent = new https.Agent({
|
|
114
114
|
lookup : ( hostname, options, callback ) => {
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
// Node's https.Agent (Happy Eyeballs) calls this with options.all = true
|
|
117
|
+
// and expects callback( err, [ { address, family } ] ); the legacy
|
|
118
|
+
// callback( err, address, family ) form only applies when options.all
|
|
119
|
+
// is falsy.
|
|
120
|
+
if( options?.all ){
|
|
117
121
|
|
|
122
|
+
callback( null, [ { address : pinned.address, family : pinned.family } ] );
|
|
123
|
+
|
|
124
|
+
} else {
|
|
125
|
+
|
|
126
|
+
callback( null, pinned.address, pinned.family );
|
|
127
|
+
|
|
128
|
+
}
|
|
118
129
|
}
|
|
119
130
|
});
|
|
120
131
|
|
package/dist/axios.d.ts
CHANGED
|
@@ -113,8 +113,19 @@ const safeGet = async ( url, axiosOptions = {} ) => {
|
|
|
113
113
|
const agent = new https.Agent({
|
|
114
114
|
lookup : ( hostname, options, callback ) => {
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
// Node's https.Agent (Happy Eyeballs) calls this with options.all = true
|
|
117
|
+
// and expects callback( err, [ { address, family } ] ); the legacy
|
|
118
|
+
// callback( err, address, family ) form only applies when options.all
|
|
119
|
+
// is falsy.
|
|
120
|
+
if( options?.all ){
|
|
117
121
|
|
|
122
|
+
callback( null, [ { address : pinned.address, family : pinned.family } ] );
|
|
123
|
+
|
|
124
|
+
} else {
|
|
125
|
+
|
|
126
|
+
callback( null, pinned.address, pinned.family );
|
|
127
|
+
|
|
128
|
+
}
|
|
118
129
|
}
|
|
119
130
|
});
|
|
120
131
|
|
package/dist/axios.js
CHANGED
|
@@ -68,7 +68,12 @@ var safeGet = async (url, axiosOptions = {}) => {
|
|
|
68
68
|
const pinned = records[0];
|
|
69
69
|
const agent = new https.Agent({
|
|
70
70
|
lookup: (hostname, options, callback) => {
|
|
71
|
-
|
|
71
|
+
if (options == null ? void 0 : options.all) {
|
|
72
|
+
callback(null, [{ address: pinned.address, family: pinned.family }]);
|
|
73
|
+
} else {
|
|
74
|
+
callback(null, pinned.address, pinned.family);
|
|
75
|
+
}
|
|
76
|
+
;
|
|
72
77
|
}
|
|
73
78
|
});
|
|
74
79
|
return axiosLib.get(url, {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// redirect.js
|
|
20
|
+
var redirect_exports = {};
|
|
21
|
+
__export(redirect_exports, {
|
|
22
|
+
safeRedirect: () => safeRedirect
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(redirect_exports);
|
|
25
|
+
var SAFE = /^\/(?!\/)[^\\\r\n]*$/;
|
|
26
|
+
var safeRedirect = (raw, fallback = "/") => {
|
|
27
|
+
if (typeof raw !== "string") return fallback;
|
|
28
|
+
if (raw.length > 512) return fallback;
|
|
29
|
+
if (!SAFE.test(raw)) return fallback;
|
|
30
|
+
try {
|
|
31
|
+
const url = new URL(raw, "https://placeholder.invalid");
|
|
32
|
+
if (url.origin !== "https://placeholder.invalid") return fallback;
|
|
33
|
+
return url.pathname + url.search + url.hash;
|
|
34
|
+
} catch {
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
37
|
+
;
|
|
38
|
+
};
|
|
39
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
40
|
+
0 && (module.exports = {
|
|
41
|
+
safeRedirect
|
|
42
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Internal-path-only matcher: must start with `/`, second char must not be
|
|
2
|
+
// `/` (rejects protocol-relative `//evil.com`), no backslashes, no newlines.
|
|
3
|
+
const SAFE = /^\/(?!\/)[^\\\r\n]*$/;
|
|
4
|
+
|
|
5
|
+
// Normalize an untrusted `redirectTo` value to a same-site path or the
|
|
6
|
+
// fallback. Used at every /auth/* endpoint that consumes a redirect param
|
|
7
|
+
// and at every web-side handler that follows the API's value.
|
|
8
|
+
//
|
|
9
|
+
// Rejects: http(s)://, //host (protocol-relative), \ tricks, anything over
|
|
10
|
+
// 512 chars, anything that URL() can't parse back to a same-origin path.
|
|
11
|
+
const safeRedirect = ( raw, fallback = '/' ) => {
|
|
12
|
+
|
|
13
|
+
if( typeof raw !== 'string' ) return fallback;
|
|
14
|
+
if( raw.length > 512 ) return fallback;
|
|
15
|
+
if( ! SAFE.test( raw ) ) return fallback;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
|
|
19
|
+
const url = new URL( raw, 'https://placeholder.invalid' );
|
|
20
|
+
|
|
21
|
+
if( url.origin !== 'https://placeholder.invalid' ) return fallback;
|
|
22
|
+
|
|
23
|
+
return url.pathname + url.search + url.hash;
|
|
24
|
+
|
|
25
|
+
} catch {
|
|
26
|
+
|
|
27
|
+
return fallback;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { safeRedirect };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Internal-path-only matcher: must start with `/`, second char must not be
|
|
2
|
+
// `/` (rejects protocol-relative `//evil.com`), no backslashes, no newlines.
|
|
3
|
+
const SAFE = /^\/(?!\/)[^\\\r\n]*$/;
|
|
4
|
+
|
|
5
|
+
// Normalize an untrusted `redirectTo` value to a same-site path or the
|
|
6
|
+
// fallback. Used at every /auth/* endpoint that consumes a redirect param
|
|
7
|
+
// and at every web-side handler that follows the API's value.
|
|
8
|
+
//
|
|
9
|
+
// Rejects: http(s)://, //host (protocol-relative), \ tricks, anything over
|
|
10
|
+
// 512 chars, anything that URL() can't parse back to a same-origin path.
|
|
11
|
+
const safeRedirect = ( raw, fallback = '/' ) => {
|
|
12
|
+
|
|
13
|
+
if( typeof raw !== 'string' ) return fallback;
|
|
14
|
+
if( raw.length > 512 ) return fallback;
|
|
15
|
+
if( ! SAFE.test( raw ) ) return fallback;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
|
|
19
|
+
const url = new URL( raw, 'https://placeholder.invalid' );
|
|
20
|
+
|
|
21
|
+
if( url.origin !== 'https://placeholder.invalid' ) return fallback;
|
|
22
|
+
|
|
23
|
+
return url.pathname + url.search + url.hash;
|
|
24
|
+
|
|
25
|
+
} catch {
|
|
26
|
+
|
|
27
|
+
return fallback;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { safeRedirect };
|
package/dist/redirect.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// redirect.js
|
|
2
|
+
var SAFE = /^\/(?!\/)[^\\\r\n]*$/;
|
|
3
|
+
var safeRedirect = (raw, fallback = "/") => {
|
|
4
|
+
if (typeof raw !== "string") return fallback;
|
|
5
|
+
if (raw.length > 512) return fallback;
|
|
6
|
+
if (!SAFE.test(raw)) return fallback;
|
|
7
|
+
try {
|
|
8
|
+
const url = new URL(raw, "https://placeholder.invalid");
|
|
9
|
+
if (url.origin !== "https://placeholder.invalid") return fallback;
|
|
10
|
+
return url.pathname + url.search + url.hash;
|
|
11
|
+
} catch {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
;
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
safeRedirect
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"types": "./dist/nanoid.d.ts",
|
|
38
38
|
"import": "./dist/nanoid.js",
|
|
39
39
|
"require": "./dist/nanoid.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./redirect": {
|
|
42
|
+
"types": "./dist/redirect.d.ts",
|
|
43
|
+
"import": "./dist/redirect.js",
|
|
44
|
+
"require": "./dist/redirect.cjs"
|
|
40
45
|
}
|
|
41
46
|
},
|
|
42
47
|
"files": [
|
|
@@ -53,5 +58,5 @@
|
|
|
53
58
|
"build": "tsup && npm publish"
|
|
54
59
|
},
|
|
55
60
|
"types": "dist/index.d.ts",
|
|
56
|
-
"version": "0.0.
|
|
61
|
+
"version": "0.0.12"
|
|
57
62
|
}
|