@streamscloud/kit 0.36.0 → 0.38.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/dist/core/files/upload-media-store.svelte.d.ts +10 -2
- package/dist/core/files/upload-media-store.svelte.js +10 -2
- package/dist/core/utils/href-validator.d.ts +5 -1
- package/dist/core/utils/href-validator.js +11 -0
- package/dist/core/validation/validation-schemas/index.d.ts +2 -1
- package/dist/core/validation/validation-schemas/index.js +1 -0
- package/dist/core/validation/validation-schemas/types.d.ts +10 -0
- package/dist/core/validation/validation-schemas/url-validation.d.ts +3 -0
- package/dist/core/validation/validation-schemas/url-validation.js +13 -0
- package/dist/core/validation/validation-schemas/validation-localization.d.ts +1 -0
- package/dist/core/validation/validation-schemas/validation-localization.js +7 -0
- package/package.json +1 -1
|
@@ -9,6 +9,12 @@ export type AddOptions = {
|
|
|
9
9
|
/** Stage an object URL for local preview. @default 'never' */
|
|
10
10
|
preview?: UploadPreviewMode;
|
|
11
11
|
};
|
|
12
|
+
export type UploadOutcome = {
|
|
13
|
+
/** Files whose upload ended in error, at the moment this call settled. */
|
|
14
|
+
failed: FailedUpload[];
|
|
15
|
+
/** Files that reached `success`. */
|
|
16
|
+
succeeded: SuccessfulUpload[];
|
|
17
|
+
};
|
|
12
18
|
export type UploadMediaStoreOptions = {
|
|
13
19
|
/**
|
|
14
20
|
* Auto-resize images before upload. Pass `false` to disable; pass an object to override the
|
|
@@ -47,7 +53,8 @@ export type UploadMediaStoreOptions = {
|
|
|
47
53
|
* on: { blobId: (f, id) => { avatarBlobId = id; } }
|
|
48
54
|
* });
|
|
49
55
|
* store.add(rawFile);
|
|
50
|
-
* await store.upload();
|
|
56
|
+
* const { failed } = await store.upload();
|
|
57
|
+
* if (failed.length > 0) { ... }
|
|
51
58
|
* ```
|
|
52
59
|
*/
|
|
53
60
|
export declare class UploadMediaStore {
|
|
@@ -94,7 +101,8 @@ export declare class UploadMediaStore {
|
|
|
94
101
|
* itself in `AppUploadActivity` for its duration, so a mounted `UploadProgressToaster` picks it up
|
|
95
102
|
* with no extra wiring.
|
|
96
103
|
*/
|
|
97
|
-
upload(): Promise<
|
|
104
|
+
upload(): Promise<UploadOutcome>;
|
|
105
|
+
private outcome;
|
|
98
106
|
private settled;
|
|
99
107
|
private runQueued;
|
|
100
108
|
private maybeResize;
|
|
@@ -29,7 +29,8 @@ const revokePreviewUrl = (file) => {
|
|
|
29
29
|
* on: { blobId: (f, id) => { avatarBlobId = id; } }
|
|
30
30
|
* });
|
|
31
31
|
* store.add(rawFile);
|
|
32
|
-
* await store.upload();
|
|
32
|
+
* const { failed } = await store.upload();
|
|
33
|
+
* if (failed.length > 0) { ... }
|
|
33
34
|
* ```
|
|
34
35
|
*/
|
|
35
36
|
export class UploadMediaStore {
|
|
@@ -89,7 +90,7 @@ export class UploadMediaStore {
|
|
|
89
90
|
const queued = this._files.filter((f) => f.status === 'queued');
|
|
90
91
|
if (queued.length === 0) {
|
|
91
92
|
await this.settled();
|
|
92
|
-
return;
|
|
93
|
+
return this.outcome();
|
|
93
94
|
}
|
|
94
95
|
// Claim before the first await, or a second upload() during the strategy call re-claims the same files.
|
|
95
96
|
for (const entry of queued) {
|
|
@@ -109,6 +110,13 @@ export class UploadMediaStore {
|
|
|
109
110
|
})();
|
|
110
111
|
this._inFlight.set(runId, run);
|
|
111
112
|
await this.settled();
|
|
113
|
+
return this.outcome();
|
|
114
|
+
}
|
|
115
|
+
outcome() {
|
|
116
|
+
return {
|
|
117
|
+
failed: this._files.filter((f) => f.status === 'error'),
|
|
118
|
+
succeeded: this._files.filter((f) => f.status === 'success')
|
|
119
|
+
};
|
|
112
120
|
}
|
|
113
121
|
async settled() {
|
|
114
122
|
while (this._inFlight.size > 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ValidationError = 'NotString' | 'Empty' | 'ControlChars' | 'DisallowedScheme' | 'MissingHost' | 'EmbeddedCredentials' | 'Relative' | 'BareRelative';
|
|
1
|
+
export type ValidationError = 'NotString' | 'Empty' | 'ControlChars' | 'DisallowedScheme' | 'MissingHost' | 'UnknownHost' | 'EmbeddedCredentials' | 'Relative' | 'BareRelative';
|
|
2
2
|
type DetailedResult = {
|
|
3
3
|
valid: true;
|
|
4
4
|
} | {
|
|
@@ -8,6 +8,10 @@ type DetailedResult = {
|
|
|
8
8
|
export type ValidateOptions = {
|
|
9
9
|
allowRelative?: 'all' | 'prefixed';
|
|
10
10
|
allowedSchemes?: Set<string>;
|
|
11
|
+
/** Rejects a syntactically valid but hostless-looking host (`https://sdf`) — requires a public label+TLD shape, `localhost`, an IPv4 literal, or a bracketed IPv6 literal. */
|
|
12
|
+
requireNamedHost?: boolean;
|
|
13
|
+
/** With `requireNamedHost`, also accepts `localhost` / IPv4 / bracketed IPv6 as known hosts. */
|
|
14
|
+
allowLocalHosts?: boolean;
|
|
11
15
|
};
|
|
12
16
|
export declare const validateHref: (input: unknown, opts?: ValidateOptions) => DetailedResult;
|
|
13
17
|
export {};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
const DEFAULT_ALLOWED_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:']);
|
|
2
|
+
// Shape check only, not full RFC 5891 punycode validation — a non-ICANN xn-- label (e.g. xn--1234) still passes.
|
|
3
|
+
const PUBLIC_HOST = /^[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,}|xn--[a-z0-9-]{2,})$/i;
|
|
4
|
+
const IPV4_HOST = /^\d{1,3}(\.\d{1,3}){3}$/;
|
|
5
|
+
// A leading '[' is safe to trust here only because `new URL()` already parsed and validated the bracketed IPv6 literal.
|
|
6
|
+
const isLocalHost = (hostname) => hostname === 'localhost' || IPV4_HOST.test(hostname) || hostname.startsWith('[');
|
|
2
7
|
export const validateHref = (input, opts = {}) => {
|
|
3
8
|
if (typeof input !== 'string') {
|
|
4
9
|
return { valid: false, error: 'NotString' };
|
|
@@ -25,6 +30,12 @@ export const validateHref = (input, opts = {}) => {
|
|
|
25
30
|
if (u.username || u.password) {
|
|
26
31
|
return { valid: false, error: 'EmbeddedCredentials' };
|
|
27
32
|
}
|
|
33
|
+
if (opts.requireNamedHost && (proto === 'http:' || proto === 'https:')) {
|
|
34
|
+
const knownHost = PUBLIC_HOST.test(u.hostname) || (!!opts.allowLocalHosts && isLocalHost(u.hostname));
|
|
35
|
+
if (!knownHost) {
|
|
36
|
+
return { valid: false, error: 'UnknownHost' };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
28
39
|
return { valid: true };
|
|
29
40
|
}
|
|
30
41
|
catch {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export type { TextValidation, TextWithFormatValidation, NumberValidation, MinNumberValidation, ArrayValidation } from './types';
|
|
1
|
+
export type { TextValidation, TextWithFormatValidation, NumberValidation, MinNumberValidation, ArrayValidation, UrlValidation } from './types';
|
|
2
2
|
export { emailValidationSchema, nullableEmailValidationSchema } from './email-validation';
|
|
3
3
|
export { textValidationSchema, formattedTextValidationSchema } from './text-validations';
|
|
4
4
|
export { handleValidationSchema } from './handle-validations';
|
|
5
5
|
export { numberValidationSchema, minNumberValidationSchema } from './number-validations';
|
|
6
6
|
export { arrayValidationSchema } from './array-validations';
|
|
7
|
+
export { urlValidationSchema } from './url-validation';
|
|
@@ -3,3 +3,4 @@ export { textValidationSchema, formattedTextValidationSchema } from './text-vali
|
|
|
3
3
|
export { handleValidationSchema } from './handle-validations';
|
|
4
4
|
export { numberValidationSchema, minNumberValidationSchema } from './number-validations';
|
|
5
5
|
export { arrayValidationSchema } from './array-validations';
|
|
6
|
+
export { urlValidationSchema } from './url-validation';
|
|
@@ -5,6 +5,16 @@ export type TextValidation = {
|
|
|
5
5
|
export type TextWithFormatValidation = TextValidation & {
|
|
6
6
|
format: string;
|
|
7
7
|
};
|
|
8
|
+
export type UrlValidation = {
|
|
9
|
+
/** @default false */
|
|
10
|
+
required?: boolean;
|
|
11
|
+
maxLength?: number;
|
|
12
|
+
schemes?: Set<string>;
|
|
13
|
+
/** @default true */
|
|
14
|
+
requireNamedHost?: boolean;
|
|
15
|
+
/** @default true */
|
|
16
|
+
allowLocalHosts?: boolean;
|
|
17
|
+
};
|
|
8
18
|
export type NumberValidation = {
|
|
9
19
|
minValue: number | null;
|
|
10
20
|
maxValue: number | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { validateHref } from '../../utils';
|
|
2
|
+
import { ValidationLocalization } from './validation-localization';
|
|
3
|
+
import * as yup from 'yup';
|
|
4
|
+
const DEFAULT_SCHEMES = new Set(['http:', 'https:']);
|
|
5
|
+
export const urlValidationSchema = (rules = {}) => {
|
|
6
|
+
const msg = new ValidationLocalization();
|
|
7
|
+
const allowedSchemes = rules.schemes ?? DEFAULT_SCHEMES;
|
|
8
|
+
const requireNamedHost = rules.requireNamedHost ?? true;
|
|
9
|
+
const allowLocalHosts = rules.allowLocalHosts ?? true;
|
|
10
|
+
const schema = yup.string().test('url', msg.url, (value) => !value || validateHref(value, { allowedSchemes, requireNamedHost, allowLocalHosts }).valid);
|
|
11
|
+
const withMaxLength = rules.maxLength === undefined ? schema : schema.max(rules.maxLength, msg.maxLength(rules.maxLength));
|
|
12
|
+
return rules.required ? withMaxLength.required(msg.required) : withMaxLength;
|
|
13
|
+
};
|
|
@@ -27,6 +27,9 @@ export class ValidationLocalization {
|
|
|
27
27
|
get badFormat() {
|
|
28
28
|
return loc.badFormat[AppLocale.current];
|
|
29
29
|
}
|
|
30
|
+
get url() {
|
|
31
|
+
return loc.url[AppLocale.current];
|
|
32
|
+
}
|
|
30
33
|
}
|
|
31
34
|
const loc = {
|
|
32
35
|
required: {
|
|
@@ -64,5 +67,9 @@ const loc = {
|
|
|
64
67
|
badFormat: {
|
|
65
68
|
en: 'Invalid format',
|
|
66
69
|
no: 'Ugyldig format'
|
|
70
|
+
},
|
|
71
|
+
url: {
|
|
72
|
+
en: 'Please enter a valid URL',
|
|
73
|
+
no: 'Vennligst skriv inn en gyldig URL'
|
|
67
74
|
}
|
|
68
75
|
};
|