@drawbridge/drawbridge-utils 0.0.101 → 0.0.103
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/dist/fetch.cjs +57 -2
- package/dist/fetch.d.cts +113 -1
- package/dist/fetch.d.ts +113 -1
- package/dist/fetch.js +55 -1
- package/dist/fields-validate.cjs +121 -0
- package/dist/fields-validate.d.cts +116 -0
- package/dist/fields-validate.d.ts +116 -0
- package/dist/fields-validate.js +94 -0
- package/dist/upload.cjs +27 -2
- package/dist/upload.d.cts +59 -1
- package/dist/upload.d.ts +59 -1
- package/dist/upload.js +23 -1
- package/package.json +9 -3
package/dist/fetch.cjs
CHANGED
|
@@ -32,7 +32,8 @@ __export(fetch_exports, {
|
|
|
32
32
|
isTransientError: () => isTransientError,
|
|
33
33
|
queryString: () => queryString,
|
|
34
34
|
request: () => request,
|
|
35
|
-
setMemoryToken: () => setMemoryToken
|
|
35
|
+
setMemoryToken: () => setMemoryToken,
|
|
36
|
+
upload: () => upload
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(fetch_exports);
|
|
38
39
|
var import_qs = __toESM(require("qs"), 1);
|
|
@@ -122,6 +123,59 @@ var request = async ({
|
|
|
122
123
|
}
|
|
123
124
|
return res;
|
|
124
125
|
};
|
|
126
|
+
var upload = ({
|
|
127
|
+
body,
|
|
128
|
+
endpoint,
|
|
129
|
+
method = "POST",
|
|
130
|
+
onProgress,
|
|
131
|
+
signal,
|
|
132
|
+
token,
|
|
133
|
+
url = process.env.NEXT_PUBLIC_API_URI
|
|
134
|
+
}) => {
|
|
135
|
+
const authToken = token || (typeof window !== "undefined" ? memoryToken : null);
|
|
136
|
+
const csrf = readCsrf();
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const xhr = new XMLHttpRequest();
|
|
139
|
+
xhr.open(method, url + endpoint, true);
|
|
140
|
+
xhr.setRequestHeader("accept", "application/json");
|
|
141
|
+
if (authToken) {
|
|
142
|
+
xhr.setRequestHeader("authorization", "Bearer " + authToken);
|
|
143
|
+
}
|
|
144
|
+
if (csrf) {
|
|
145
|
+
xhr.setRequestHeader("x-drawbridge-csrf", csrf);
|
|
146
|
+
}
|
|
147
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
148
|
+
if (!event.lengthComputable || !onProgress) return;
|
|
149
|
+
onProgress({
|
|
150
|
+
bytesTotal: event.total,
|
|
151
|
+
bytesUploaded: event.loaded
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
xhr.addEventListener("load", () => {
|
|
155
|
+
let data = null;
|
|
156
|
+
try {
|
|
157
|
+
data = JSON.parse(xhr.responseText);
|
|
158
|
+
} catch {
|
|
159
|
+
data = null;
|
|
160
|
+
}
|
|
161
|
+
if (xhr.status >= 200 && xhr.status < 300) return resolve(data);
|
|
162
|
+
const error = new Error((data == null ? void 0 : data.message) || xhr.statusText);
|
|
163
|
+
error.status = xhr.status;
|
|
164
|
+
error.data = data;
|
|
165
|
+
reject(error);
|
|
166
|
+
});
|
|
167
|
+
xhr.addEventListener("abort", () => {
|
|
168
|
+
const error = new Error("Upload cancelled");
|
|
169
|
+
error.name = "AbortError";
|
|
170
|
+
reject(error);
|
|
171
|
+
});
|
|
172
|
+
xhr.addEventListener("error", () => reject(new Error("Network error")));
|
|
173
|
+
if (signal) {
|
|
174
|
+
signal.addEventListener("abort", () => xhr.abort(), { once: true });
|
|
175
|
+
}
|
|
176
|
+
xhr.send(body);
|
|
177
|
+
});
|
|
178
|
+
};
|
|
125
179
|
var isTransientError = (error) => {
|
|
126
180
|
const status = error == null ? void 0 : error.status;
|
|
127
181
|
return !status || status === 429 || status >= 500;
|
|
@@ -131,5 +185,6 @@ var isTransientError = (error) => {
|
|
|
131
185
|
isTransientError,
|
|
132
186
|
queryString,
|
|
133
187
|
request,
|
|
134
|
-
setMemoryToken
|
|
188
|
+
setMemoryToken,
|
|
189
|
+
upload
|
|
135
190
|
});
|
package/dist/fetch.d.cts
CHANGED
|
@@ -124,6 +124,118 @@ const request = async ({
|
|
|
124
124
|
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
+
// fetch() has no upload-progress event and never will, which is the reason every
|
|
128
|
+
// upload in the dashboard could only ever show a spinner. XHR does, so anything
|
|
129
|
+
// that wants a real progress bar posts through here instead.
|
|
130
|
+
//
|
|
131
|
+
// It lives beside request() rather than in the dashboard because `memoryToken`
|
|
132
|
+
// above is module-private on purpose — a copy in app-web could only read it if
|
|
133
|
+
// the token were exported, which is the one thing setMemoryToken exists to
|
|
134
|
+
// prevent. The csrf cookie read is here too.
|
|
135
|
+
//
|
|
136
|
+
// The error shape is deliberately identical to request()'s — an Error carrying
|
|
137
|
+
// .status and .data — so isTransientError() and every existing catch block work
|
|
138
|
+
// against either transport without knowing which ran.
|
|
139
|
+
const upload = ({
|
|
140
|
+
body,
|
|
141
|
+
endpoint,
|
|
142
|
+
method = 'POST',
|
|
143
|
+
onProgress,
|
|
144
|
+
signal,
|
|
145
|
+
token,
|
|
146
|
+
url = process.env.NEXT_PUBLIC_API_URI
|
|
147
|
+
}) => {
|
|
148
|
+
|
|
149
|
+
const authToken = token || ( typeof window !== 'undefined' ? memoryToken : null );
|
|
150
|
+
const csrf = readCsrf();
|
|
151
|
+
|
|
152
|
+
return new Promise( ( resolve, reject ) => {
|
|
153
|
+
|
|
154
|
+
const xhr = new XMLHttpRequest();
|
|
155
|
+
|
|
156
|
+
xhr.open( method, url + endpoint, true );
|
|
157
|
+
|
|
158
|
+
// No content-type, ever. The browser has to write the multipart boundary
|
|
159
|
+
// itself and setting the header by hand drops it, which busboy then
|
|
160
|
+
// rejects. Nor origin: it is a forbidden header name, so the browser
|
|
161
|
+
// ignores setRequestHeader for it ( request() only sets it because its
|
|
162
|
+
// server-side callers run where fetch allows it ).
|
|
163
|
+
xhr.setRequestHeader( 'accept', 'application/json' );
|
|
164
|
+
|
|
165
|
+
if( authToken ){
|
|
166
|
+
|
|
167
|
+
xhr.setRequestHeader( 'authorization', 'Bearer ' + authToken );
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if( csrf ){
|
|
172
|
+
|
|
173
|
+
xhr.setRequestHeader( 'x-drawbridge-csrf', csrf );
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
xhr.upload.addEventListener( 'progress', ( event ) => {
|
|
178
|
+
|
|
179
|
+
if( ! event.lengthComputable || ! onProgress ) return;
|
|
180
|
+
|
|
181
|
+
onProgress({
|
|
182
|
+
bytesTotal : event.total,
|
|
183
|
+
bytesUploaded : event.loaded
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
xhr.addEventListener( 'load', () => {
|
|
189
|
+
|
|
190
|
+
let data = null;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
|
|
194
|
+
data = JSON.parse( xhr.responseText );
|
|
195
|
+
|
|
196
|
+
} catch {
|
|
197
|
+
|
|
198
|
+
data = null;
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if( xhr.status >= 200 && xhr.status < 300 ) return resolve( data );
|
|
203
|
+
|
|
204
|
+
const error = new Error( data?.message || xhr.statusText );
|
|
205
|
+
|
|
206
|
+
error.status = xhr.status;
|
|
207
|
+
error.data = data;
|
|
208
|
+
|
|
209
|
+
reject( error );
|
|
210
|
+
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
xhr.addEventListener( 'abort', () => {
|
|
214
|
+
|
|
215
|
+
const error = new Error( 'Upload cancelled' );
|
|
216
|
+
|
|
217
|
+
error.name = 'AbortError';
|
|
218
|
+
|
|
219
|
+
reject( error );
|
|
220
|
+
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// No status on a network failure, which is exactly how isTransientError
|
|
224
|
+
// classifies it — worth a retry rather than a sign-in bounce.
|
|
225
|
+
xhr.addEventListener( 'error', () => reject( new Error( 'Network error' ) ) );
|
|
226
|
+
|
|
227
|
+
if( signal ){
|
|
228
|
+
|
|
229
|
+
signal.addEventListener( 'abort', () => xhr.abort(), { once : true } );
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
xhr.send( body );
|
|
234
|
+
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
};
|
|
238
|
+
|
|
127
239
|
// Classifies an error thrown by request() — which carries `.status` from the
|
|
128
240
|
// HTTP response, or no status for a network / parse failure. Transient errors
|
|
129
241
|
// ( rate limits, server errors, connection blips ) may succeed on retry, so
|
|
@@ -139,4 +251,4 @@ const isTransientError = ( error ) => {
|
|
|
139
251
|
|
|
140
252
|
};
|
|
141
253
|
|
|
142
|
-
export { isTransientError, queryString, request, setMemoryToken };
|
|
254
|
+
export { isTransientError, queryString, request, setMemoryToken, upload };
|
package/dist/fetch.d.ts
CHANGED
|
@@ -124,6 +124,118 @@ const request = async ({
|
|
|
124
124
|
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
+
// fetch() has no upload-progress event and never will, which is the reason every
|
|
128
|
+
// upload in the dashboard could only ever show a spinner. XHR does, so anything
|
|
129
|
+
// that wants a real progress bar posts through here instead.
|
|
130
|
+
//
|
|
131
|
+
// It lives beside request() rather than in the dashboard because `memoryToken`
|
|
132
|
+
// above is module-private on purpose — a copy in app-web could only read it if
|
|
133
|
+
// the token were exported, which is the one thing setMemoryToken exists to
|
|
134
|
+
// prevent. The csrf cookie read is here too.
|
|
135
|
+
//
|
|
136
|
+
// The error shape is deliberately identical to request()'s — an Error carrying
|
|
137
|
+
// .status and .data — so isTransientError() and every existing catch block work
|
|
138
|
+
// against either transport without knowing which ran.
|
|
139
|
+
const upload = ({
|
|
140
|
+
body,
|
|
141
|
+
endpoint,
|
|
142
|
+
method = 'POST',
|
|
143
|
+
onProgress,
|
|
144
|
+
signal,
|
|
145
|
+
token,
|
|
146
|
+
url = process.env.NEXT_PUBLIC_API_URI
|
|
147
|
+
}) => {
|
|
148
|
+
|
|
149
|
+
const authToken = token || ( typeof window !== 'undefined' ? memoryToken : null );
|
|
150
|
+
const csrf = readCsrf();
|
|
151
|
+
|
|
152
|
+
return new Promise( ( resolve, reject ) => {
|
|
153
|
+
|
|
154
|
+
const xhr = new XMLHttpRequest();
|
|
155
|
+
|
|
156
|
+
xhr.open( method, url + endpoint, true );
|
|
157
|
+
|
|
158
|
+
// No content-type, ever. The browser has to write the multipart boundary
|
|
159
|
+
// itself and setting the header by hand drops it, which busboy then
|
|
160
|
+
// rejects. Nor origin: it is a forbidden header name, so the browser
|
|
161
|
+
// ignores setRequestHeader for it ( request() only sets it because its
|
|
162
|
+
// server-side callers run where fetch allows it ).
|
|
163
|
+
xhr.setRequestHeader( 'accept', 'application/json' );
|
|
164
|
+
|
|
165
|
+
if( authToken ){
|
|
166
|
+
|
|
167
|
+
xhr.setRequestHeader( 'authorization', 'Bearer ' + authToken );
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if( csrf ){
|
|
172
|
+
|
|
173
|
+
xhr.setRequestHeader( 'x-drawbridge-csrf', csrf );
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
xhr.upload.addEventListener( 'progress', ( event ) => {
|
|
178
|
+
|
|
179
|
+
if( ! event.lengthComputable || ! onProgress ) return;
|
|
180
|
+
|
|
181
|
+
onProgress({
|
|
182
|
+
bytesTotal : event.total,
|
|
183
|
+
bytesUploaded : event.loaded
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
xhr.addEventListener( 'load', () => {
|
|
189
|
+
|
|
190
|
+
let data = null;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
|
|
194
|
+
data = JSON.parse( xhr.responseText );
|
|
195
|
+
|
|
196
|
+
} catch {
|
|
197
|
+
|
|
198
|
+
data = null;
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if( xhr.status >= 200 && xhr.status < 300 ) return resolve( data );
|
|
203
|
+
|
|
204
|
+
const error = new Error( data?.message || xhr.statusText );
|
|
205
|
+
|
|
206
|
+
error.status = xhr.status;
|
|
207
|
+
error.data = data;
|
|
208
|
+
|
|
209
|
+
reject( error );
|
|
210
|
+
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
xhr.addEventListener( 'abort', () => {
|
|
214
|
+
|
|
215
|
+
const error = new Error( 'Upload cancelled' );
|
|
216
|
+
|
|
217
|
+
error.name = 'AbortError';
|
|
218
|
+
|
|
219
|
+
reject( error );
|
|
220
|
+
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// No status on a network failure, which is exactly how isTransientError
|
|
224
|
+
// classifies it — worth a retry rather than a sign-in bounce.
|
|
225
|
+
xhr.addEventListener( 'error', () => reject( new Error( 'Network error' ) ) );
|
|
226
|
+
|
|
227
|
+
if( signal ){
|
|
228
|
+
|
|
229
|
+
signal.addEventListener( 'abort', () => xhr.abort(), { once : true } );
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
xhr.send( body );
|
|
234
|
+
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
};
|
|
238
|
+
|
|
127
239
|
// Classifies an error thrown by request() — which carries `.status` from the
|
|
128
240
|
// HTTP response, or no status for a network / parse failure. Transient errors
|
|
129
241
|
// ( rate limits, server errors, connection blips ) may succeed on retry, so
|
|
@@ -139,4 +251,4 @@ const isTransientError = ( error ) => {
|
|
|
139
251
|
|
|
140
252
|
};
|
|
141
253
|
|
|
142
|
-
export { isTransientError, queryString, request, setMemoryToken };
|
|
254
|
+
export { isTransientError, queryString, request, setMemoryToken, upload };
|
package/dist/fetch.js
CHANGED
|
@@ -86,6 +86,59 @@ var request = async ({
|
|
|
86
86
|
}
|
|
87
87
|
return res;
|
|
88
88
|
};
|
|
89
|
+
var upload = ({
|
|
90
|
+
body,
|
|
91
|
+
endpoint,
|
|
92
|
+
method = "POST",
|
|
93
|
+
onProgress,
|
|
94
|
+
signal,
|
|
95
|
+
token,
|
|
96
|
+
url = process.env.NEXT_PUBLIC_API_URI
|
|
97
|
+
}) => {
|
|
98
|
+
const authToken = token || (typeof window !== "undefined" ? memoryToken : null);
|
|
99
|
+
const csrf = readCsrf();
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const xhr = new XMLHttpRequest();
|
|
102
|
+
xhr.open(method, url + endpoint, true);
|
|
103
|
+
xhr.setRequestHeader("accept", "application/json");
|
|
104
|
+
if (authToken) {
|
|
105
|
+
xhr.setRequestHeader("authorization", "Bearer " + authToken);
|
|
106
|
+
}
|
|
107
|
+
if (csrf) {
|
|
108
|
+
xhr.setRequestHeader("x-drawbridge-csrf", csrf);
|
|
109
|
+
}
|
|
110
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
111
|
+
if (!event.lengthComputable || !onProgress) return;
|
|
112
|
+
onProgress({
|
|
113
|
+
bytesTotal: event.total,
|
|
114
|
+
bytesUploaded: event.loaded
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
xhr.addEventListener("load", () => {
|
|
118
|
+
let data = null;
|
|
119
|
+
try {
|
|
120
|
+
data = JSON.parse(xhr.responseText);
|
|
121
|
+
} catch {
|
|
122
|
+
data = null;
|
|
123
|
+
}
|
|
124
|
+
if (xhr.status >= 200 && xhr.status < 300) return resolve(data);
|
|
125
|
+
const error = new Error((data == null ? void 0 : data.message) || xhr.statusText);
|
|
126
|
+
error.status = xhr.status;
|
|
127
|
+
error.data = data;
|
|
128
|
+
reject(error);
|
|
129
|
+
});
|
|
130
|
+
xhr.addEventListener("abort", () => {
|
|
131
|
+
const error = new Error("Upload cancelled");
|
|
132
|
+
error.name = "AbortError";
|
|
133
|
+
reject(error);
|
|
134
|
+
});
|
|
135
|
+
xhr.addEventListener("error", () => reject(new Error("Network error")));
|
|
136
|
+
if (signal) {
|
|
137
|
+
signal.addEventListener("abort", () => xhr.abort(), { once: true });
|
|
138
|
+
}
|
|
139
|
+
xhr.send(body);
|
|
140
|
+
});
|
|
141
|
+
};
|
|
89
142
|
var isTransientError = (error) => {
|
|
90
143
|
const status = error == null ? void 0 : error.status;
|
|
91
144
|
return !status || status === 429 || status >= 500;
|
|
@@ -94,5 +147,6 @@ export {
|
|
|
94
147
|
isTransientError,
|
|
95
148
|
queryString,
|
|
96
149
|
request,
|
|
97
|
-
setMemoryToken
|
|
150
|
+
setMemoryToken,
|
|
151
|
+
upload
|
|
98
152
|
};
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
// lib/fields-validate.js
|
|
20
|
+
var fields_validate_exports = {};
|
|
21
|
+
__export(fields_validate_exports, {
|
|
22
|
+
errors: () => errors,
|
|
23
|
+
validators: () => validators
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(fields_validate_exports);
|
|
26
|
+
|
|
27
|
+
// lib/phone.js
|
|
28
|
+
var import_libphonenumber_js = require("libphonenumber-js");
|
|
29
|
+
var isValid = (value, country) => {
|
|
30
|
+
if (!value) return false;
|
|
31
|
+
try {
|
|
32
|
+
return (0, import_libphonenumber_js.isValidPhoneNumber)(String(value), country);
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// lib/fields-validate.js
|
|
39
|
+
var errors = {
|
|
40
|
+
country: "Field is not an accepted value",
|
|
41
|
+
email: "Field value must be a valid email",
|
|
42
|
+
length: (length) => "Field must be less than or equal to " + length + " characters",
|
|
43
|
+
max: (val) => "Field value must be less than or equal to " + val,
|
|
44
|
+
min: (val) => "Field value must be greater than or equal to " + val,
|
|
45
|
+
option: "Field is not an accepted value",
|
|
46
|
+
phone: "Field value is not valid",
|
|
47
|
+
required: "Field is required"
|
|
48
|
+
};
|
|
49
|
+
var required = (field, schema) => {
|
|
50
|
+
var _a;
|
|
51
|
+
return ((_a = field == null ? void 0 : field.attributes) == null ? void 0 : _a.required) === false ? schema.nullable() : schema.required(errors.required);
|
|
52
|
+
};
|
|
53
|
+
var validators = {
|
|
54
|
+
// An agreement that is not required is not an agreement - consent stays
|
|
55
|
+
// affirmative regardless of the toggle.
|
|
56
|
+
agreement: (field, yup) => {
|
|
57
|
+
return yup.bool().required(errors.required).oneOf([true], errors.required);
|
|
58
|
+
},
|
|
59
|
+
email: (field, yup) => {
|
|
60
|
+
const max = 320;
|
|
61
|
+
return required(field, yup.string().email(errors.email).max(max, errors.length(max)));
|
|
62
|
+
},
|
|
63
|
+
name: (field, yup) => {
|
|
64
|
+
var _a;
|
|
65
|
+
const max = 150;
|
|
66
|
+
const object = (((_a = field == null ? void 0 : field.settings) == null ? void 0 : _a.capture) || []).reduce(
|
|
67
|
+
(accumulator, item) => {
|
|
68
|
+
accumulator[item] = yup.string().required(errors.required).max(max, errors.length(max));
|
|
69
|
+
return accumulator;
|
|
70
|
+
},
|
|
71
|
+
{}
|
|
72
|
+
);
|
|
73
|
+
return required(field, yup.object(object));
|
|
74
|
+
},
|
|
75
|
+
number: (field, yup) => {
|
|
76
|
+
var _a, _b, _c, _d, _e, _f;
|
|
77
|
+
let validate = yup.number();
|
|
78
|
+
if ((_a = field == null ? void 0 : field.attributes) == null ? void 0 : _a.min) {
|
|
79
|
+
validate = validate.min((_b = field == null ? void 0 : field.attributes) == null ? void 0 : _b.min, errors.min((_c = field == null ? void 0 : field.attributes) == null ? void 0 : _c.min));
|
|
80
|
+
}
|
|
81
|
+
;
|
|
82
|
+
if ((_d = field == null ? void 0 : field.attributes) == null ? void 0 : _d.max) {
|
|
83
|
+
validate = validate.max((_e = field == null ? void 0 : field.attributes) == null ? void 0 : _e.max, errors.max((_f = field == null ? void 0 : field.attributes) == null ? void 0 : _f.max));
|
|
84
|
+
}
|
|
85
|
+
;
|
|
86
|
+
return required(field, validate);
|
|
87
|
+
},
|
|
88
|
+
phone: (field, yup) => {
|
|
89
|
+
var _a;
|
|
90
|
+
return required(
|
|
91
|
+
field,
|
|
92
|
+
yup.object({
|
|
93
|
+
country: yup.string().required(errors.required).oneOf((_a = field == null ? void 0 : field.settings) == null ? void 0 : _a.countries, errors.country),
|
|
94
|
+
number: yup.string().required(errors.required)
|
|
95
|
+
}).test("phone", errors.phone, (value) => isValid(value == null ? void 0 : value.number, value == null ? void 0 : value.country))
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
// The in-list check the share form never had: the answer must be one of the
|
|
99
|
+
// field's own options. null rides the whitelist so an optional select can be
|
|
100
|
+
// left blank - required() still rejects null when the field is required.
|
|
101
|
+
select: (field, yup) => {
|
|
102
|
+
var _a;
|
|
103
|
+
const options = (((_a = field == null ? void 0 : field.settings) == null ? void 0 : _a.options) || []).map((item) => item == null ? void 0 : item.option);
|
|
104
|
+
return required(field, yup.string().oneOf([...options, null], errors.option));
|
|
105
|
+
},
|
|
106
|
+
text: (field, yup) => {
|
|
107
|
+
var _a;
|
|
108
|
+
const max = ((_a = field == null ? void 0 : field.attributes) == null ? void 0 : _a.max) || 150;
|
|
109
|
+
return required(field, yup.string().max(max, errors.length(max)));
|
|
110
|
+
},
|
|
111
|
+
textarea: (field, yup) => {
|
|
112
|
+
var _a;
|
|
113
|
+
const max = ((_a = field == null ? void 0 : field.attributes) == null ? void 0 : _a.max) || 150;
|
|
114
|
+
return required(field, yup.string().max(max, errors.length(max)));
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
118
|
+
0 && (module.exports = {
|
|
119
|
+
errors,
|
|
120
|
+
validators
|
|
121
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isValid } from './phone.cjs';
|
|
2
|
+
import 'libphonenumber-js';
|
|
3
|
+
|
|
4
|
+
// Field validators shared by the share form (client) and the api submission
|
|
5
|
+
// route (server). They lived only in drawbridge-share, so every additional-field
|
|
6
|
+
// answer - number, select, text, textarea - was validated in the browser and
|
|
7
|
+
// stored as submitted; the api only ever validated lead fields.
|
|
8
|
+
//
|
|
9
|
+
// yup is passed in rather than imported so each consumer keeps its own copy
|
|
10
|
+
// (share bundles one, api has its own in lib/validate).
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const errors = {
|
|
14
|
+
country : 'Field is not an accepted value',
|
|
15
|
+
email : 'Field value must be a valid email',
|
|
16
|
+
length : ( length ) => 'Field must be less than or equal to ' + length + ' characters',
|
|
17
|
+
max : ( val ) => 'Field value must be less than or equal to ' + val,
|
|
18
|
+
min : ( val ) => 'Field value must be greater than or equal to ' + val,
|
|
19
|
+
option : 'Field is not an accepted value',
|
|
20
|
+
phone : 'Field value is not valid',
|
|
21
|
+
required : 'Field is required'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Every stored attributes.required is currently true, so this is a no-op today -
|
|
25
|
+
// it is here so the optional-field toggle does not need a second pass over
|
|
26
|
+
// every builder.
|
|
27
|
+
const required = ( field, schema ) => ( field?.attributes?.required === false ) ? schema.nullable() : schema.required( errors.required );
|
|
28
|
+
|
|
29
|
+
const validators = {
|
|
30
|
+
// An agreement that is not required is not an agreement - consent stays
|
|
31
|
+
// affirmative regardless of the toggle.
|
|
32
|
+
agreement : ( field, yup ) => {
|
|
33
|
+
|
|
34
|
+
return yup.bool().required( errors.required ).oneOf( [ true ], errors.required );
|
|
35
|
+
|
|
36
|
+
},
|
|
37
|
+
email : ( field, yup ) => {
|
|
38
|
+
|
|
39
|
+
const max = 320;
|
|
40
|
+
|
|
41
|
+
return required( field, yup.string().email( errors.email ).max( max, errors.length( max ) ) );
|
|
42
|
+
|
|
43
|
+
},
|
|
44
|
+
name : ( field, yup ) => {
|
|
45
|
+
|
|
46
|
+
const max = 150;
|
|
47
|
+
|
|
48
|
+
const object = ( field?.settings?.capture || [] ).reduce(
|
|
49
|
+
( accumulator, item ) => {
|
|
50
|
+
|
|
51
|
+
accumulator[ item ] = yup.string().required( errors.required ).max( max, errors.length( max ) );
|
|
52
|
+
|
|
53
|
+
return accumulator;
|
|
54
|
+
|
|
55
|
+
},
|
|
56
|
+
{}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return required( field, yup.object( object ) );
|
|
60
|
+
|
|
61
|
+
},
|
|
62
|
+
number : ( field, yup ) => {
|
|
63
|
+
|
|
64
|
+
let validate = yup.number();
|
|
65
|
+
|
|
66
|
+
if( field?.attributes?.min ){
|
|
67
|
+
|
|
68
|
+
validate = validate.min( field?.attributes?.min, errors.min( field?.attributes?.min ) );
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
if( field?.attributes?.max ){
|
|
72
|
+
|
|
73
|
+
validate = validate.max( field?.attributes?.max, errors.max( field?.attributes?.max ) );
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
return required( field, validate );
|
|
77
|
+
|
|
78
|
+
},
|
|
79
|
+
phone : ( field, yup ) => {
|
|
80
|
+
|
|
81
|
+
return required(
|
|
82
|
+
field,
|
|
83
|
+
yup.object({
|
|
84
|
+
country : yup.string().required( errors.required ).oneOf( field?.settings?.countries, errors.country ),
|
|
85
|
+
number : yup.string().required( errors.required )
|
|
86
|
+
}).test( 'phone', errors.phone, ( value ) => isValid( value?.number, value?.country ) )
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
},
|
|
90
|
+
// The in-list check the share form never had: the answer must be one of the
|
|
91
|
+
// field's own options. null rides the whitelist so an optional select can be
|
|
92
|
+
// left blank - required() still rejects null when the field is required.
|
|
93
|
+
select : ( field, yup ) => {
|
|
94
|
+
|
|
95
|
+
const options = ( field?.settings?.options || [] ).map( ( item ) => item?.option );
|
|
96
|
+
|
|
97
|
+
return required( field, yup.string().oneOf( [ ...options, null ], errors.option ) );
|
|
98
|
+
|
|
99
|
+
},
|
|
100
|
+
text : ( field, yup ) => {
|
|
101
|
+
|
|
102
|
+
const max = field?.attributes?.max || 150;
|
|
103
|
+
|
|
104
|
+
return required( field, yup.string().max( max, errors.length( max ) ) );
|
|
105
|
+
|
|
106
|
+
},
|
|
107
|
+
textarea : ( field, yup ) => {
|
|
108
|
+
|
|
109
|
+
const max = field?.attributes?.max || 150;
|
|
110
|
+
|
|
111
|
+
return required( field, yup.string().max( max, errors.length( max ) ) );
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export { errors, validators };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isValid } from './phone.js';
|
|
2
|
+
import 'libphonenumber-js';
|
|
3
|
+
|
|
4
|
+
// Field validators shared by the share form (client) and the api submission
|
|
5
|
+
// route (server). They lived only in drawbridge-share, so every additional-field
|
|
6
|
+
// answer - number, select, text, textarea - was validated in the browser and
|
|
7
|
+
// stored as submitted; the api only ever validated lead fields.
|
|
8
|
+
//
|
|
9
|
+
// yup is passed in rather than imported so each consumer keeps its own copy
|
|
10
|
+
// (share bundles one, api has its own in lib/validate).
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const errors = {
|
|
14
|
+
country : 'Field is not an accepted value',
|
|
15
|
+
email : 'Field value must be a valid email',
|
|
16
|
+
length : ( length ) => 'Field must be less than or equal to ' + length + ' characters',
|
|
17
|
+
max : ( val ) => 'Field value must be less than or equal to ' + val,
|
|
18
|
+
min : ( val ) => 'Field value must be greater than or equal to ' + val,
|
|
19
|
+
option : 'Field is not an accepted value',
|
|
20
|
+
phone : 'Field value is not valid',
|
|
21
|
+
required : 'Field is required'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Every stored attributes.required is currently true, so this is a no-op today -
|
|
25
|
+
// it is here so the optional-field toggle does not need a second pass over
|
|
26
|
+
// every builder.
|
|
27
|
+
const required = ( field, schema ) => ( field?.attributes?.required === false ) ? schema.nullable() : schema.required( errors.required );
|
|
28
|
+
|
|
29
|
+
const validators = {
|
|
30
|
+
// An agreement that is not required is not an agreement - consent stays
|
|
31
|
+
// affirmative regardless of the toggle.
|
|
32
|
+
agreement : ( field, yup ) => {
|
|
33
|
+
|
|
34
|
+
return yup.bool().required( errors.required ).oneOf( [ true ], errors.required );
|
|
35
|
+
|
|
36
|
+
},
|
|
37
|
+
email : ( field, yup ) => {
|
|
38
|
+
|
|
39
|
+
const max = 320;
|
|
40
|
+
|
|
41
|
+
return required( field, yup.string().email( errors.email ).max( max, errors.length( max ) ) );
|
|
42
|
+
|
|
43
|
+
},
|
|
44
|
+
name : ( field, yup ) => {
|
|
45
|
+
|
|
46
|
+
const max = 150;
|
|
47
|
+
|
|
48
|
+
const object = ( field?.settings?.capture || [] ).reduce(
|
|
49
|
+
( accumulator, item ) => {
|
|
50
|
+
|
|
51
|
+
accumulator[ item ] = yup.string().required( errors.required ).max( max, errors.length( max ) );
|
|
52
|
+
|
|
53
|
+
return accumulator;
|
|
54
|
+
|
|
55
|
+
},
|
|
56
|
+
{}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return required( field, yup.object( object ) );
|
|
60
|
+
|
|
61
|
+
},
|
|
62
|
+
number : ( field, yup ) => {
|
|
63
|
+
|
|
64
|
+
let validate = yup.number();
|
|
65
|
+
|
|
66
|
+
if( field?.attributes?.min ){
|
|
67
|
+
|
|
68
|
+
validate = validate.min( field?.attributes?.min, errors.min( field?.attributes?.min ) );
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
if( field?.attributes?.max ){
|
|
72
|
+
|
|
73
|
+
validate = validate.max( field?.attributes?.max, errors.max( field?.attributes?.max ) );
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
return required( field, validate );
|
|
77
|
+
|
|
78
|
+
},
|
|
79
|
+
phone : ( field, yup ) => {
|
|
80
|
+
|
|
81
|
+
return required(
|
|
82
|
+
field,
|
|
83
|
+
yup.object({
|
|
84
|
+
country : yup.string().required( errors.required ).oneOf( field?.settings?.countries, errors.country ),
|
|
85
|
+
number : yup.string().required( errors.required )
|
|
86
|
+
}).test( 'phone', errors.phone, ( value ) => isValid( value?.number, value?.country ) )
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
},
|
|
90
|
+
// The in-list check the share form never had: the answer must be one of the
|
|
91
|
+
// field's own options. null rides the whitelist so an optional select can be
|
|
92
|
+
// left blank - required() still rejects null when the field is required.
|
|
93
|
+
select : ( field, yup ) => {
|
|
94
|
+
|
|
95
|
+
const options = ( field?.settings?.options || [] ).map( ( item ) => item?.option );
|
|
96
|
+
|
|
97
|
+
return required( field, yup.string().oneOf( [ ...options, null ], errors.option ) );
|
|
98
|
+
|
|
99
|
+
},
|
|
100
|
+
text : ( field, yup ) => {
|
|
101
|
+
|
|
102
|
+
const max = field?.attributes?.max || 150;
|
|
103
|
+
|
|
104
|
+
return required( field, yup.string().max( max, errors.length( max ) ) );
|
|
105
|
+
|
|
106
|
+
},
|
|
107
|
+
textarea : ( field, yup ) => {
|
|
108
|
+
|
|
109
|
+
const max = field?.attributes?.max || 150;
|
|
110
|
+
|
|
111
|
+
return required( field, yup.string().max( max, errors.length( max ) ) );
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export { errors, validators };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// lib/phone.js
|
|
2
|
+
import { parsePhoneNumberFromString, isValidPhoneNumber } from "libphonenumber-js";
|
|
3
|
+
var isValid = (value, country) => {
|
|
4
|
+
if (!value) return false;
|
|
5
|
+
try {
|
|
6
|
+
return isValidPhoneNumber(String(value), country);
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// lib/fields-validate.js
|
|
13
|
+
var errors = {
|
|
14
|
+
country: "Field is not an accepted value",
|
|
15
|
+
email: "Field value must be a valid email",
|
|
16
|
+
length: (length) => "Field must be less than or equal to " + length + " characters",
|
|
17
|
+
max: (val) => "Field value must be less than or equal to " + val,
|
|
18
|
+
min: (val) => "Field value must be greater than or equal to " + val,
|
|
19
|
+
option: "Field is not an accepted value",
|
|
20
|
+
phone: "Field value is not valid",
|
|
21
|
+
required: "Field is required"
|
|
22
|
+
};
|
|
23
|
+
var required = (field, schema) => {
|
|
24
|
+
var _a;
|
|
25
|
+
return ((_a = field == null ? void 0 : field.attributes) == null ? void 0 : _a.required) === false ? schema.nullable() : schema.required(errors.required);
|
|
26
|
+
};
|
|
27
|
+
var validators = {
|
|
28
|
+
// An agreement that is not required is not an agreement - consent stays
|
|
29
|
+
// affirmative regardless of the toggle.
|
|
30
|
+
agreement: (field, yup) => {
|
|
31
|
+
return yup.bool().required(errors.required).oneOf([true], errors.required);
|
|
32
|
+
},
|
|
33
|
+
email: (field, yup) => {
|
|
34
|
+
const max = 320;
|
|
35
|
+
return required(field, yup.string().email(errors.email).max(max, errors.length(max)));
|
|
36
|
+
},
|
|
37
|
+
name: (field, yup) => {
|
|
38
|
+
var _a;
|
|
39
|
+
const max = 150;
|
|
40
|
+
const object = (((_a = field == null ? void 0 : field.settings) == null ? void 0 : _a.capture) || []).reduce(
|
|
41
|
+
(accumulator, item) => {
|
|
42
|
+
accumulator[item] = yup.string().required(errors.required).max(max, errors.length(max));
|
|
43
|
+
return accumulator;
|
|
44
|
+
},
|
|
45
|
+
{}
|
|
46
|
+
);
|
|
47
|
+
return required(field, yup.object(object));
|
|
48
|
+
},
|
|
49
|
+
number: (field, yup) => {
|
|
50
|
+
var _a, _b, _c, _d, _e, _f;
|
|
51
|
+
let validate = yup.number();
|
|
52
|
+
if ((_a = field == null ? void 0 : field.attributes) == null ? void 0 : _a.min) {
|
|
53
|
+
validate = validate.min((_b = field == null ? void 0 : field.attributes) == null ? void 0 : _b.min, errors.min((_c = field == null ? void 0 : field.attributes) == null ? void 0 : _c.min));
|
|
54
|
+
}
|
|
55
|
+
;
|
|
56
|
+
if ((_d = field == null ? void 0 : field.attributes) == null ? void 0 : _d.max) {
|
|
57
|
+
validate = validate.max((_e = field == null ? void 0 : field.attributes) == null ? void 0 : _e.max, errors.max((_f = field == null ? void 0 : field.attributes) == null ? void 0 : _f.max));
|
|
58
|
+
}
|
|
59
|
+
;
|
|
60
|
+
return required(field, validate);
|
|
61
|
+
},
|
|
62
|
+
phone: (field, yup) => {
|
|
63
|
+
var _a;
|
|
64
|
+
return required(
|
|
65
|
+
field,
|
|
66
|
+
yup.object({
|
|
67
|
+
country: yup.string().required(errors.required).oneOf((_a = field == null ? void 0 : field.settings) == null ? void 0 : _a.countries, errors.country),
|
|
68
|
+
number: yup.string().required(errors.required)
|
|
69
|
+
}).test("phone", errors.phone, (value) => isValid(value == null ? void 0 : value.number, value == null ? void 0 : value.country))
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
// The in-list check the share form never had: the answer must be one of the
|
|
73
|
+
// field's own options. null rides the whitelist so an optional select can be
|
|
74
|
+
// left blank - required() still rejects null when the field is required.
|
|
75
|
+
select: (field, yup) => {
|
|
76
|
+
var _a;
|
|
77
|
+
const options = (((_a = field == null ? void 0 : field.settings) == null ? void 0 : _a.options) || []).map((item) => item == null ? void 0 : item.option);
|
|
78
|
+
return required(field, yup.string().oneOf([...options, null], errors.option));
|
|
79
|
+
},
|
|
80
|
+
text: (field, yup) => {
|
|
81
|
+
var _a;
|
|
82
|
+
const max = ((_a = field == null ? void 0 : field.attributes) == null ? void 0 : _a.max) || 150;
|
|
83
|
+
return required(field, yup.string().max(max, errors.length(max)));
|
|
84
|
+
},
|
|
85
|
+
textarea: (field, yup) => {
|
|
86
|
+
var _a;
|
|
87
|
+
const max = ((_a = field == null ? void 0 : field.attributes) == null ? void 0 : _a.max) || 150;
|
|
88
|
+
return required(field, yup.string().max(max, errors.length(max)));
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
export {
|
|
92
|
+
errors,
|
|
93
|
+
validators
|
|
94
|
+
};
|
package/dist/upload.cjs
CHANGED
|
@@ -22,7 +22,10 @@ __export(upload_exports, {
|
|
|
22
22
|
allowedMimes: () => allowedMimes,
|
|
23
23
|
allowedUploadTypes: () => allowedUploadTypes,
|
|
24
24
|
isAllowedMime: () => isAllowedMime,
|
|
25
|
-
|
|
25
|
+
maximumUploadBytes: () => maximumUploadBytes,
|
|
26
|
+
resolveUploadType: () => resolveUploadType,
|
|
27
|
+
uploadPartCount: () => uploadPartCount,
|
|
28
|
+
uploadPartSize: () => uploadPartSize
|
|
26
29
|
});
|
|
27
30
|
module.exports = __toCommonJS(upload_exports);
|
|
28
31
|
var allowedUploadTypes = {
|
|
@@ -36,6 +39,11 @@ var allowedUploadTypes = {
|
|
|
36
39
|
webp: "image/webp"
|
|
37
40
|
},
|
|
38
41
|
video: {
|
|
42
|
+
// iPhones record .mov (HEVC) by default, so this is the single most
|
|
43
|
+
// likely video a merchant uploads. sync's storage.js already computes
|
|
44
|
+
// needsReencode for hevc/h265 and re-encodes to h264, so allowing it
|
|
45
|
+
// here is the only thing the pipeline was waiting on.
|
|
46
|
+
mov: "video/quicktime",
|
|
39
47
|
mp4: "video/mp4"
|
|
40
48
|
}
|
|
41
49
|
};
|
|
@@ -56,10 +64,27 @@ var resolveUploadType = (type, extension) => {
|
|
|
56
64
|
return mime;
|
|
57
65
|
};
|
|
58
66
|
var isAllowedMime = (mime) => allowedMimes.has(mime);
|
|
67
|
+
var megabyte = 1024 * 1024;
|
|
68
|
+
var maximumUploadBytes = 2048 * megabyte;
|
|
69
|
+
var maximumParts = 9e3;
|
|
70
|
+
var minimumPartSize = 10 * megabyte;
|
|
71
|
+
var uploadPartSize = (bytes) => {
|
|
72
|
+
const size = Number(bytes) || 0;
|
|
73
|
+
const required = Math.ceil(size / maximumParts);
|
|
74
|
+
const rounded = Math.ceil(required / megabyte) * megabyte;
|
|
75
|
+
return Math.max(minimumPartSize, rounded);
|
|
76
|
+
};
|
|
77
|
+
var uploadPartCount = (bytes) => {
|
|
78
|
+
const size = Number(bytes) || 0;
|
|
79
|
+
return Math.max(1, Math.ceil(size / uploadPartSize(size)));
|
|
80
|
+
};
|
|
59
81
|
// Annotate the CommonJS export names for ESM import in node:
|
|
60
82
|
0 && (module.exports = {
|
|
61
83
|
allowedMimes,
|
|
62
84
|
allowedUploadTypes,
|
|
63
85
|
isAllowedMime,
|
|
64
|
-
|
|
86
|
+
maximumUploadBytes,
|
|
87
|
+
resolveUploadType,
|
|
88
|
+
uploadPartCount,
|
|
89
|
+
uploadPartSize
|
|
65
90
|
});
|
package/dist/upload.d.cts
CHANGED
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Why both helpers and not just one: api routes know (type, extension) but not
|
|
17
17
|
// the canonical mime; sync workers have the mime but not the original type tuple.
|
|
18
|
+
//
|
|
19
|
+
// A third consumer joined with presigned multipart uploads: the dashboard reads
|
|
20
|
+
// this same object to build its file picker's accept attribute and the uploader's
|
|
21
|
+
// client-side restrictions, so a wrong type is refused before two gigabytes move
|
|
22
|
+
// rather than after. That is the whole reason the list is not copied into
|
|
23
|
+
// app-web — four views of one list can't drift.
|
|
18
24
|
|
|
19
25
|
const allowedUploadTypes = {
|
|
20
26
|
application : {
|
|
@@ -27,6 +33,11 @@ const allowedUploadTypes = {
|
|
|
27
33
|
webp : 'image/webp'
|
|
28
34
|
},
|
|
29
35
|
video : {
|
|
36
|
+
// iPhones record .mov (HEVC) by default, so this is the single most
|
|
37
|
+
// likely video a merchant uploads. sync's storage.js already computes
|
|
38
|
+
// needsReencode for hevc/h265 and re-encodes to h264, so allowing it
|
|
39
|
+
// here is the only thing the pipeline was waiting on.
|
|
40
|
+
mov : 'video/quicktime',
|
|
30
41
|
mp4 : 'video/mp4'
|
|
31
42
|
}
|
|
32
43
|
};
|
|
@@ -55,4 +66,51 @@ const resolveUploadType = ( type, extension ) => {
|
|
|
55
66
|
|
|
56
67
|
const isAllowedMime = ( mime ) => allowedMimes.has( mime );
|
|
57
68
|
|
|
58
|
-
|
|
69
|
+
const megabyte = ( 1024 * 1024 );
|
|
70
|
+
|
|
71
|
+
// The ceiling for a presigned multipart upload. Video streams browser-to-bucket
|
|
72
|
+
// on that path rather than through the api, so this is a product decision
|
|
73
|
+
// rather than the fleet-protection one that keeps the single-request busboy
|
|
74
|
+
// route at 50MB.
|
|
75
|
+
//
|
|
76
|
+
// It lives here because both sides have to agree on it exactly: the dashboard
|
|
77
|
+
// rejects before transferring and the api rejects authoritatively, and if the
|
|
78
|
+
// two numbers drift the client blocks files the api would happily accept.
|
|
79
|
+
const maximumUploadBytes = ( 2048 * megabyte );
|
|
80
|
+
|
|
81
|
+
// S3 caps a multipart upload at 10,000 parts and requires every part except the
|
|
82
|
+
// last to be at least 5MB. A fixed part size therefore puts a silent ceiling on
|
|
83
|
+
// the maximum file, so the size is derived from the declared length with
|
|
84
|
+
// headroom against the hard limit.
|
|
85
|
+
//
|
|
86
|
+
// The 10MB floor is a request-volume decision, not an S3 one: a 2GB upload at
|
|
87
|
+
// 5MB parts is 410 signing calls, at 10MB it is 205.
|
|
88
|
+
//
|
|
89
|
+
// Both the api and the dashboard call this, and the dashboard *must* — Uppy
|
|
90
|
+
// builds its chunk list in the MultipartUploader constructor, which runs before
|
|
91
|
+
// createMultipartUpload is ever called, so a part size returned from the create
|
|
92
|
+
// endpoint arrives too late to chunk with. One function, two callers, no
|
|
93
|
+
// possibility of disagreeing about where the part boundaries fall.
|
|
94
|
+
const maximumParts = 9000;
|
|
95
|
+
const minimumPartSize = ( 10 * megabyte );
|
|
96
|
+
|
|
97
|
+
const uploadPartSize = ( bytes ) => {
|
|
98
|
+
|
|
99
|
+
const size = Number( bytes ) || 0;
|
|
100
|
+
const required = Math.ceil( size / maximumParts );
|
|
101
|
+
const rounded = Math.ceil( required / megabyte ) * megabyte;
|
|
102
|
+
|
|
103
|
+
return Math.max( minimumPartSize, rounded );
|
|
104
|
+
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// An empty upload is still one part — S3 has no zero-part upload.
|
|
108
|
+
const uploadPartCount = ( bytes ) => {
|
|
109
|
+
|
|
110
|
+
const size = Number( bytes ) || 0;
|
|
111
|
+
|
|
112
|
+
return Math.max( 1, Math.ceil( size / uploadPartSize( size ) ) );
|
|
113
|
+
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export { allowedMimes, allowedUploadTypes, isAllowedMime, maximumUploadBytes, resolveUploadType, uploadPartCount, uploadPartSize };
|
package/dist/upload.d.ts
CHANGED
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Why both helpers and not just one: api routes know (type, extension) but not
|
|
17
17
|
// the canonical mime; sync workers have the mime but not the original type tuple.
|
|
18
|
+
//
|
|
19
|
+
// A third consumer joined with presigned multipart uploads: the dashboard reads
|
|
20
|
+
// this same object to build its file picker's accept attribute and the uploader's
|
|
21
|
+
// client-side restrictions, so a wrong type is refused before two gigabytes move
|
|
22
|
+
// rather than after. That is the whole reason the list is not copied into
|
|
23
|
+
// app-web — four views of one list can't drift.
|
|
18
24
|
|
|
19
25
|
const allowedUploadTypes = {
|
|
20
26
|
application : {
|
|
@@ -27,6 +33,11 @@ const allowedUploadTypes = {
|
|
|
27
33
|
webp : 'image/webp'
|
|
28
34
|
},
|
|
29
35
|
video : {
|
|
36
|
+
// iPhones record .mov (HEVC) by default, so this is the single most
|
|
37
|
+
// likely video a merchant uploads. sync's storage.js already computes
|
|
38
|
+
// needsReencode for hevc/h265 and re-encodes to h264, so allowing it
|
|
39
|
+
// here is the only thing the pipeline was waiting on.
|
|
40
|
+
mov : 'video/quicktime',
|
|
30
41
|
mp4 : 'video/mp4'
|
|
31
42
|
}
|
|
32
43
|
};
|
|
@@ -55,4 +66,51 @@ const resolveUploadType = ( type, extension ) => {
|
|
|
55
66
|
|
|
56
67
|
const isAllowedMime = ( mime ) => allowedMimes.has( mime );
|
|
57
68
|
|
|
58
|
-
|
|
69
|
+
const megabyte = ( 1024 * 1024 );
|
|
70
|
+
|
|
71
|
+
// The ceiling for a presigned multipart upload. Video streams browser-to-bucket
|
|
72
|
+
// on that path rather than through the api, so this is a product decision
|
|
73
|
+
// rather than the fleet-protection one that keeps the single-request busboy
|
|
74
|
+
// route at 50MB.
|
|
75
|
+
//
|
|
76
|
+
// It lives here because both sides have to agree on it exactly: the dashboard
|
|
77
|
+
// rejects before transferring and the api rejects authoritatively, and if the
|
|
78
|
+
// two numbers drift the client blocks files the api would happily accept.
|
|
79
|
+
const maximumUploadBytes = ( 2048 * megabyte );
|
|
80
|
+
|
|
81
|
+
// S3 caps a multipart upload at 10,000 parts and requires every part except the
|
|
82
|
+
// last to be at least 5MB. A fixed part size therefore puts a silent ceiling on
|
|
83
|
+
// the maximum file, so the size is derived from the declared length with
|
|
84
|
+
// headroom against the hard limit.
|
|
85
|
+
//
|
|
86
|
+
// The 10MB floor is a request-volume decision, not an S3 one: a 2GB upload at
|
|
87
|
+
// 5MB parts is 410 signing calls, at 10MB it is 205.
|
|
88
|
+
//
|
|
89
|
+
// Both the api and the dashboard call this, and the dashboard *must* — Uppy
|
|
90
|
+
// builds its chunk list in the MultipartUploader constructor, which runs before
|
|
91
|
+
// createMultipartUpload is ever called, so a part size returned from the create
|
|
92
|
+
// endpoint arrives too late to chunk with. One function, two callers, no
|
|
93
|
+
// possibility of disagreeing about where the part boundaries fall.
|
|
94
|
+
const maximumParts = 9000;
|
|
95
|
+
const minimumPartSize = ( 10 * megabyte );
|
|
96
|
+
|
|
97
|
+
const uploadPartSize = ( bytes ) => {
|
|
98
|
+
|
|
99
|
+
const size = Number( bytes ) || 0;
|
|
100
|
+
const required = Math.ceil( size / maximumParts );
|
|
101
|
+
const rounded = Math.ceil( required / megabyte ) * megabyte;
|
|
102
|
+
|
|
103
|
+
return Math.max( minimumPartSize, rounded );
|
|
104
|
+
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// An empty upload is still one part — S3 has no zero-part upload.
|
|
108
|
+
const uploadPartCount = ( bytes ) => {
|
|
109
|
+
|
|
110
|
+
const size = Number( bytes ) || 0;
|
|
111
|
+
|
|
112
|
+
return Math.max( 1, Math.ceil( size / uploadPartSize( size ) ) );
|
|
113
|
+
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export { allowedMimes, allowedUploadTypes, isAllowedMime, maximumUploadBytes, resolveUploadType, uploadPartCount, uploadPartSize };
|
package/dist/upload.js
CHANGED
|
@@ -10,6 +10,11 @@ var allowedUploadTypes = {
|
|
|
10
10
|
webp: "image/webp"
|
|
11
11
|
},
|
|
12
12
|
video: {
|
|
13
|
+
// iPhones record .mov (HEVC) by default, so this is the single most
|
|
14
|
+
// likely video a merchant uploads. sync's storage.js already computes
|
|
15
|
+
// needsReencode for hevc/h265 and re-encodes to h264, so allowing it
|
|
16
|
+
// here is the only thing the pipeline was waiting on.
|
|
17
|
+
mov: "video/quicktime",
|
|
13
18
|
mp4: "video/mp4"
|
|
14
19
|
}
|
|
15
20
|
};
|
|
@@ -30,9 +35,26 @@ var resolveUploadType = (type, extension) => {
|
|
|
30
35
|
return mime;
|
|
31
36
|
};
|
|
32
37
|
var isAllowedMime = (mime) => allowedMimes.has(mime);
|
|
38
|
+
var megabyte = 1024 * 1024;
|
|
39
|
+
var maximumUploadBytes = 2048 * megabyte;
|
|
40
|
+
var maximumParts = 9e3;
|
|
41
|
+
var minimumPartSize = 10 * megabyte;
|
|
42
|
+
var uploadPartSize = (bytes) => {
|
|
43
|
+
const size = Number(bytes) || 0;
|
|
44
|
+
const required = Math.ceil(size / maximumParts);
|
|
45
|
+
const rounded = Math.ceil(required / megabyte) * megabyte;
|
|
46
|
+
return Math.max(minimumPartSize, rounded);
|
|
47
|
+
};
|
|
48
|
+
var uploadPartCount = (bytes) => {
|
|
49
|
+
const size = Number(bytes) || 0;
|
|
50
|
+
return Math.max(1, Math.ceil(size / uploadPartSize(size)));
|
|
51
|
+
};
|
|
33
52
|
export {
|
|
34
53
|
allowedMimes,
|
|
35
54
|
allowedUploadTypes,
|
|
36
55
|
isAllowedMime,
|
|
37
|
-
|
|
56
|
+
maximumUploadBytes,
|
|
57
|
+
resolveUploadType,
|
|
58
|
+
uploadPartCount,
|
|
59
|
+
uploadPartSize
|
|
38
60
|
};
|
package/package.json
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
"tinycolor2": "1.6.0"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@drawbridge/drawbridge-agents": "0.1.
|
|
16
|
+
"@drawbridge/drawbridge-agents": "0.1.37",
|
|
17
17
|
"@node-oauth/oauth2-server": "5.3.0",
|
|
18
18
|
"tsup": "8.5.1",
|
|
19
|
-
"typescript": "5.9.3"
|
|
19
|
+
"typescript": "5.9.3",
|
|
20
|
+
"yup": "1.7.1"
|
|
20
21
|
},
|
|
21
22
|
"peerDependencies": {
|
|
22
23
|
"@node-oauth/oauth2-server": "^5.3.0"
|
|
@@ -161,6 +162,11 @@
|
|
|
161
162
|
"types": "./dist/plans.d.ts",
|
|
162
163
|
"import": "./dist/plans.js",
|
|
163
164
|
"require": "./dist/plans.cjs"
|
|
165
|
+
},
|
|
166
|
+
"./fields-validate": {
|
|
167
|
+
"types": "./dist/fields-validate.d.ts",
|
|
168
|
+
"import": "./dist/fields-validate.js",
|
|
169
|
+
"require": "./dist/fields-validate.cjs"
|
|
164
170
|
}
|
|
165
171
|
},
|
|
166
172
|
"files": [
|
|
@@ -179,5 +185,5 @@
|
|
|
179
185
|
"test": ". \"$HOME/.nvm/nvm.sh\" && nvm use && node --test"
|
|
180
186
|
},
|
|
181
187
|
"types": "dist/index.d.ts",
|
|
182
|
-
"version": "0.0.
|
|
188
|
+
"version": "0.0.103"
|
|
183
189
|
}
|