@learncard/helpers 1.3.5 → 1.3.7
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 +5 -5
- package/dist/helpers.cjs.development.cjs +9837 -7556
- package/dist/helpers.cjs.development.cjs.map +3 -3
- package/dist/helpers.cjs.production.min.cjs +111 -27
- package/dist/helpers.cjs.production.min.cjs.map +3 -3
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.esm.js +4374 -2485
- package/dist/helpers.esm.js.map +3 -3
- package/package.json +68 -66
- package/src/Utilities.ts +71 -0
- package/src/app-install/index.ts +168 -0
- package/src/arrays/index.ts +20 -0
- package/src/bitstring-status-list/index.ts +69 -0
- package/src/credential-format.ts +508 -0
- package/src/images/discord.helpers.ts +103 -0
- package/src/images/filestack.helpers.ts +179 -0
- package/src/images/images.helpers.ts +75 -0
- package/src/images/index.ts +1 -0
- package/src/images/unsplash.helpers.ts +110 -0
- package/src/images/url.helpers.ts +46 -0
- package/src/index.ts +105 -0
- package/src/numbers/index.ts +21 -0
- package/src/state.ts +576 -0
- package/src/strings/index.ts +4 -0
- package/src/types/index.ts +5 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { getUrlsFromSrcSet } from './images.helpers';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates an array of Filestack URL Parameters
|
|
5
|
+
*
|
|
6
|
+
* @param url Filestack URL
|
|
7
|
+
*
|
|
8
|
+
* @return Filestack URL Parameters
|
|
9
|
+
*/
|
|
10
|
+
export const getUrlParams = (url: string): string[] => url.split('.com/')[1].split('/');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Converts an array of Filestack URL Parameters to a valid Filestack URL
|
|
14
|
+
*
|
|
15
|
+
* @param urlParams Filestack URL Parameters
|
|
16
|
+
*
|
|
17
|
+
* @return Filestack URL
|
|
18
|
+
*/
|
|
19
|
+
export const getUrlFromUrlParams = (urlParams: string[]): string =>
|
|
20
|
+
`https://cdn.filestackcontent.com/${urlParams.join('/')}`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Gets the handle from a Filestack URL
|
|
24
|
+
*
|
|
25
|
+
* @param url Filestack URL
|
|
26
|
+
*
|
|
27
|
+
* @return Filestack handle
|
|
28
|
+
*/
|
|
29
|
+
export const getFilestackHandle = (url: string): string => {
|
|
30
|
+
const urlParams = getUrlParams(url);
|
|
31
|
+
|
|
32
|
+
return urlParams[urlParams.length - 1];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getFileType = async (url: string): Promise<string> => {
|
|
36
|
+
const handle = getFilestackHandle(url);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const data = (await (
|
|
40
|
+
await fetch(`https://www.filestackapi.com/api/file/${handle}/metadata`)
|
|
41
|
+
).json()) as { mimetype?: string };
|
|
42
|
+
|
|
43
|
+
return data.mimetype ?? '';
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(error);
|
|
46
|
+
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const fileTypeSupportsPreview = (fileType: string): boolean => {
|
|
52
|
+
const unsupportedFileTypes = [
|
|
53
|
+
'application/vnd.apple.pages', // .pages
|
|
54
|
+
'application/vnd.apple.numbers', // .numbers
|
|
55
|
+
'application/x-iwork-keynote-sffkey', // .key
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
return !unsupportedFileTypes.includes(fileType);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fixes image rotation based on EXIF data and uses auto_image on a given URL
|
|
63
|
+
*
|
|
64
|
+
* @param url URL to fix
|
|
65
|
+
* @param mimetype MIME type (e.g. image/gif)
|
|
66
|
+
* @param webp flag to indicate whether we should manually specify conversion to webp
|
|
67
|
+
*
|
|
68
|
+
* @return fixed URL
|
|
69
|
+
*/
|
|
70
|
+
export const fixUrl = (url: string, mimetype?: string, webp = false): string => {
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
72
|
+
if (url.split(' ').length > 1) return fixSrcSetString(url, mimetype, webp);
|
|
73
|
+
|
|
74
|
+
const urlParams = getUrlParams(url).filter(param => param !== 'rotate=deg:exif');
|
|
75
|
+
|
|
76
|
+
urlParams.splice(0, 0, 'rotate=deg:exif');
|
|
77
|
+
|
|
78
|
+
if (mimetype !== 'image/gif') urlParams.splice(-1, 0, 'auto_image');
|
|
79
|
+
if (webp) urlParams.splice(-1, 0, 'output=format:webp');
|
|
80
|
+
|
|
81
|
+
return getUrlFromUrlParams(urlParams);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fixes image rotation based on EXIF data and uses auto_image on a given srcset string
|
|
86
|
+
*
|
|
87
|
+
* @param srcSetString srcset string to fix
|
|
88
|
+
* @param mimetype MIME type (e.g. image/gif)
|
|
89
|
+
* @param webp flag to indicate whether we should manually specify conversion to webp
|
|
90
|
+
*
|
|
91
|
+
* @return fixed srcset string
|
|
92
|
+
*/
|
|
93
|
+
export const fixSrcSetString = (srcSetString: string, mimetype?: string, webp = false): string => {
|
|
94
|
+
const urls = getUrlsFromSrcSet(srcSetString);
|
|
95
|
+
|
|
96
|
+
return urls.map(url => `${fixUrl(url[0], mimetype, webp)} ${url[1]}`).join(', ');
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resizes a filestack image to the given size
|
|
101
|
+
*
|
|
102
|
+
* @param url Filestack URL to resize
|
|
103
|
+
* @param size Target output width
|
|
104
|
+
*
|
|
105
|
+
* @return Filestack URL
|
|
106
|
+
*/
|
|
107
|
+
export const resizeUrl = (url: string, size: number): string => {
|
|
108
|
+
const urlParams = getUrlParams(url).filter(param => !param.match(/resize.*/));
|
|
109
|
+
|
|
110
|
+
if (urlParams.includes('rotate=deg:exif')) urlParams.splice(1, 0, `resize=width:${size}`);
|
|
111
|
+
else urlParams.splice(0, 0, `resize=width:${size}`);
|
|
112
|
+
|
|
113
|
+
return getUrlFromUrlParams(urlParams);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Changes a filestack image's quality to the given value
|
|
118
|
+
*
|
|
119
|
+
* @param url Filestack URL to change
|
|
120
|
+
* @param quality quality value (1-100)
|
|
121
|
+
*
|
|
122
|
+
* @return Filestack URL
|
|
123
|
+
*/
|
|
124
|
+
export const changeQuality = (url: string, quality: number): string => {
|
|
125
|
+
const urlParams = getUrlParams(url).filter(param => !param.match(/quality.*/));
|
|
126
|
+
|
|
127
|
+
if (urlParams.includes('rotate=deg:exif')) urlParams.splice(1, 0, `quality=value:${quality}`);
|
|
128
|
+
else urlParams.splice(0, 0, `quality=value:${quality}`);
|
|
129
|
+
|
|
130
|
+
return getUrlFromUrlParams(urlParams);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resizes and changes a filestack image's quality
|
|
135
|
+
*
|
|
136
|
+
* @param url Filestack URL to change
|
|
137
|
+
* @param size Target output width
|
|
138
|
+
* @param quality quality value (1-100)
|
|
139
|
+
*
|
|
140
|
+
* @return Filestack URL
|
|
141
|
+
*/
|
|
142
|
+
export const resizeAndChangeQuality = (
|
|
143
|
+
url: string,
|
|
144
|
+
size: number,
|
|
145
|
+
quality: number,
|
|
146
|
+
{
|
|
147
|
+
mimetype,
|
|
148
|
+
fix = false,
|
|
149
|
+
webp = false,
|
|
150
|
+
}: { mimetype?: string; fix?: boolean; webp?: boolean } = {}
|
|
151
|
+
) => {
|
|
152
|
+
const updatedUrl = changeQuality(resizeUrl(url, size), quality);
|
|
153
|
+
|
|
154
|
+
return fix ? fixUrl(updatedUrl, mimetype, webp) : updatedUrl;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Generates a responsive srcset string from a Filestack URL and array of resolutions
|
|
159
|
+
*
|
|
160
|
+
* @param url Filestack URL
|
|
161
|
+
* @param resolutions list of resolutions
|
|
162
|
+
*
|
|
163
|
+
* @return srcset string
|
|
164
|
+
*/
|
|
165
|
+
export const generateSrcSet = (
|
|
166
|
+
url: string,
|
|
167
|
+
resolutions: number[],
|
|
168
|
+
{
|
|
169
|
+
mimetype,
|
|
170
|
+
fix = false,
|
|
171
|
+
webp = false,
|
|
172
|
+
}: { mimetype?: string; fix?: boolean; webp?: boolean } = {}
|
|
173
|
+
) => {
|
|
174
|
+
const srcSet = resolutions
|
|
175
|
+
.map(resolution => `${resizeUrl(url, resolution)} ${resolution}w`)
|
|
176
|
+
.join(', ');
|
|
177
|
+
|
|
178
|
+
return fix ? fixSrcSetString(srcSet, mimetype, webp) : srcSet;
|
|
179
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as filestack from './filestack.helpers';
|
|
2
|
+
import * as unsplash from './unsplash.helpers';
|
|
3
|
+
import * as discord from './discord.helpers';
|
|
4
|
+
|
|
5
|
+
const Providers = { filestack, unsplash, discord };
|
|
6
|
+
|
|
7
|
+
export const getProvider = (url?: string): keyof typeof Providers | null => {
|
|
8
|
+
if (url?.includes('cdn.filestackcontent.com')) return 'filestack';
|
|
9
|
+
|
|
10
|
+
if (url?.includes('images.unsplash.com')) return 'unsplash';
|
|
11
|
+
|
|
12
|
+
if (url?.includes('cdn.discordapp.com')) return 'discord';
|
|
13
|
+
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const changeQuality = (url: string, quality: number): string => {
|
|
18
|
+
const provider = getProvider(url);
|
|
19
|
+
|
|
20
|
+
if (!provider) return url;
|
|
21
|
+
|
|
22
|
+
return Providers[provider].changeQuality(url, quality);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const fixUrl = (url: string, mimetype?: string, webp = false): string => {
|
|
26
|
+
const provider = getProvider(url);
|
|
27
|
+
|
|
28
|
+
if (!provider) return url;
|
|
29
|
+
|
|
30
|
+
return Providers[provider].fixUrl(url, mimetype, webp);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const generateSrcSet = (
|
|
34
|
+
url: string,
|
|
35
|
+
resolutions: number[],
|
|
36
|
+
options: { mimetype?: string; fix?: boolean; webp?: boolean } = {}
|
|
37
|
+
): string => {
|
|
38
|
+
const provider = getProvider(url);
|
|
39
|
+
|
|
40
|
+
if (!provider) return url;
|
|
41
|
+
|
|
42
|
+
return Providers[provider].generateSrcSet(url, resolutions, options);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const resizeAndChangeQuality = (
|
|
46
|
+
url: string,
|
|
47
|
+
size: number,
|
|
48
|
+
quality: number,
|
|
49
|
+
options: { mimetype?: string; fix?: boolean; webp?: boolean } = {}
|
|
50
|
+
): string => {
|
|
51
|
+
const provider = getProvider(url);
|
|
52
|
+
|
|
53
|
+
if (!provider) return url;
|
|
54
|
+
|
|
55
|
+
return Providers[provider].resizeAndChangeQuality(url, size, quality, options);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const DEFAULT_RESOLUTIONS = [200, 400, 600];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Gets an array of URLs from a srcSet string
|
|
62
|
+
*
|
|
63
|
+
* @param srcSet HTML srcSet string (e.g. 'test.com/img 100w, test.com/img2 200w')
|
|
64
|
+
*
|
|
65
|
+
* @return URLs
|
|
66
|
+
*/
|
|
67
|
+
export const getUrlsFromSrcSet = (srcSet: string): [string, string][] => {
|
|
68
|
+
return srcSet.split(/,? /).reduce<[string, string][]>((accumulator, current, index, array) => {
|
|
69
|
+
if (index % 2 === 0) return accumulator;
|
|
70
|
+
|
|
71
|
+
accumulator.push([array[index - 1], current]);
|
|
72
|
+
|
|
73
|
+
return accumulator;
|
|
74
|
+
}, []);
|
|
75
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './images.helpers';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getUrlsFromSrcSet } from './images.helpers';
|
|
2
|
+
import { parseUrl, stringifyUrl } from './url.helpers';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fixes image rotation based on EXIF data and uses auto_image on a given srcset string
|
|
6
|
+
*
|
|
7
|
+
* @param srcSetString srcset string to fix
|
|
8
|
+
* @param mimetype MIME type (e.g. image/gif)
|
|
9
|
+
* @param webp flag to indicate whether we should manually specify conversion to webp
|
|
10
|
+
*
|
|
11
|
+
* @return fixed srcset string
|
|
12
|
+
*/
|
|
13
|
+
export const fixSrcSetString = (
|
|
14
|
+
srcSetString: string,
|
|
15
|
+
_mimetype?: string,
|
|
16
|
+
_webp = false
|
|
17
|
+
): string => {
|
|
18
|
+
const urls = getUrlsFromSrcSet(srcSetString);
|
|
19
|
+
|
|
20
|
+
return urls.map(([url, width]) => `${fixUrl(url)} ${width}`).join(', ');
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns the best format on a given URL
|
|
25
|
+
*
|
|
26
|
+
* @param url URL to fix
|
|
27
|
+
*
|
|
28
|
+
* @return fixed URL
|
|
29
|
+
*/
|
|
30
|
+
export const fixUrl = (url: string, _mimetype?: string, _webp = false): string => {
|
|
31
|
+
if (url.split(' ').length > 1) return fixSrcSetString(url);
|
|
32
|
+
|
|
33
|
+
const parsedUrl = parseUrl(url);
|
|
34
|
+
|
|
35
|
+
parsedUrl.query.auto = 'format';
|
|
36
|
+
|
|
37
|
+
return stringifyUrl(parsedUrl);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resizes an unsplash image to the given size
|
|
42
|
+
*
|
|
43
|
+
* @param url Unsplash URL to resize
|
|
44
|
+
* @param size Target output width
|
|
45
|
+
*
|
|
46
|
+
* @return Unsplash URL
|
|
47
|
+
*/
|
|
48
|
+
export const resizeUrl = (url: string, size: number): string => {
|
|
49
|
+
const parsedUrl = parseUrl(url);
|
|
50
|
+
|
|
51
|
+
parsedUrl.query.w = size.toString();
|
|
52
|
+
|
|
53
|
+
return stringifyUrl(parsedUrl);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Changes an unsplash image's quality to the given value
|
|
58
|
+
*
|
|
59
|
+
* @param url Unsplash URL to change
|
|
60
|
+
* @param quality quality value (1-100)
|
|
61
|
+
*
|
|
62
|
+
* @return Unsplash URL
|
|
63
|
+
*/
|
|
64
|
+
export const changeQuality = (url: string, quality: number): string => {
|
|
65
|
+
const parsedUrl = parseUrl(url);
|
|
66
|
+
|
|
67
|
+
parsedUrl.query.q = quality.toString();
|
|
68
|
+
|
|
69
|
+
return stringifyUrl(parsedUrl);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resizes and changes an unsplash image's quality
|
|
74
|
+
*
|
|
75
|
+
* @param url Unsplash URL to change
|
|
76
|
+
* @param size Target output width
|
|
77
|
+
* @param quality quality value (1-100)
|
|
78
|
+
*
|
|
79
|
+
* @return Unsplash URL
|
|
80
|
+
*/
|
|
81
|
+
export const resizeAndChangeQuality = (
|
|
82
|
+
url: string,
|
|
83
|
+
size: number,
|
|
84
|
+
quality: number,
|
|
85
|
+
{ fix = false }: { mimetype?: string; fix?: boolean; webp?: boolean } = {}
|
|
86
|
+
): string => {
|
|
87
|
+
const updatedUrl = changeQuality(resizeUrl(url, size), quality);
|
|
88
|
+
|
|
89
|
+
return fix ? fixUrl(updatedUrl) : updatedUrl;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generates a responsive srcset string from an Unsplash URL and array of resolutions
|
|
94
|
+
*
|
|
95
|
+
* @param url Unsplash URL
|
|
96
|
+
* @param resolutions list of resolutions
|
|
97
|
+
*
|
|
98
|
+
* @return srcset string
|
|
99
|
+
*/
|
|
100
|
+
export const generateSrcSet = (
|
|
101
|
+
url: string,
|
|
102
|
+
resolutions: number[],
|
|
103
|
+
{ fix = false }: { mimetype?: string; fix?: boolean; webp?: boolean } = {}
|
|
104
|
+
): string => {
|
|
105
|
+
const srcSet = resolutions
|
|
106
|
+
.map(resolution => `${resizeUrl(url, resolution)} ${resolution}w`)
|
|
107
|
+
.join(', ');
|
|
108
|
+
|
|
109
|
+
return fix ? fixSrcSetString(srcSet) : srcSet;
|
|
110
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal URL query helpers backed by `URLSearchParams`, replacing the two
|
|
3
|
+
* `query-string` call sites in the image helpers. `query-string@9` is ESM-only
|
|
4
|
+
* and pulls in ESM-only transitive deps, which broke the CommonJS build of
|
|
5
|
+
* `@learncard/helpers` for external consumers on Node 18.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type ParsedUrl = {
|
|
9
|
+
url: string;
|
|
10
|
+
query: Record<string, string>;
|
|
11
|
+
fragmentIdentifier?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const parseUrl = (input: string): ParsedUrl => {
|
|
15
|
+
const hashIndex = input.indexOf('#');
|
|
16
|
+
const fragmentIdentifier = hashIndex === -1 ? undefined : input.slice(hashIndex + 1);
|
|
17
|
+
const withoutFragment = hashIndex === -1 ? input : input.slice(0, hashIndex);
|
|
18
|
+
|
|
19
|
+
const queryIndex = withoutFragment.indexOf('?');
|
|
20
|
+
const url = queryIndex === -1 ? withoutFragment : withoutFragment.slice(0, queryIndex);
|
|
21
|
+
const search = queryIndex === -1 ? '' : withoutFragment.slice(queryIndex + 1);
|
|
22
|
+
|
|
23
|
+
const query: Record<string, string> = {};
|
|
24
|
+
for (const [key, value] of new URLSearchParams(search)) query[key] = value;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
url,
|
|
28
|
+
query,
|
|
29
|
+
...(fragmentIdentifier !== undefined ? { fragmentIdentifier } : {}),
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const stringifyUrl = (parsed: ParsedUrl): string => {
|
|
34
|
+
const params = new URLSearchParams();
|
|
35
|
+
for (const [key, value] of Object.entries(parsed.query)) {
|
|
36
|
+
if (value === undefined || value === null) continue;
|
|
37
|
+
params.append(key, String(value));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// URLSearchParams encodes spaces as `+`; normalize to `%20` so output stays
|
|
41
|
+
// byte-stable for consumers that use the URL as a cache key.
|
|
42
|
+
const search = params.toString().replace(/\+/g, '%20');
|
|
43
|
+
const fragment = parsed.fragmentIdentifier ? `#${parsed.fragmentIdentifier}` : '';
|
|
44
|
+
|
|
45
|
+
return `${parsed.url}${search ? `?${search}` : ''}${fragment}`;
|
|
46
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { JWE, JWEValidator, UnsignedVC, VC } from '@learncard/types';
|
|
2
|
+
import type { DataTransformer } from '@trpc/server';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Determines whether or not a string is a valid hexadecimal string
|
|
6
|
+
*
|
|
7
|
+
* E.g. 'abc123' is valid hex, 'zzz' is not
|
|
8
|
+
*/
|
|
9
|
+
export const isHex = (str: string) => /^[0-9a-f]+$/i.test(str);
|
|
10
|
+
|
|
11
|
+
/** Determines whether or not an object is an encrypted JWE */
|
|
12
|
+
export const isEncrypted = (item: Record<string, any>): item is JWE => {
|
|
13
|
+
return JWEValidator.safeParse(item).success;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* tRPC data transformer that handles RegExp serialization/deserialization
|
|
18
|
+
*/
|
|
19
|
+
export const RegExpTransformer: DataTransformer = {
|
|
20
|
+
serialize(object: any): any {
|
|
21
|
+
return JSON.stringify(object, (_key, value) => {
|
|
22
|
+
if (value instanceof RegExp) return value.toString(); // Converts to format /pattern/flags
|
|
23
|
+
|
|
24
|
+
return value;
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
deserialize(object: any): any {
|
|
29
|
+
// Old clients will for some reason already be deserialized, so this checks for that to retain
|
|
30
|
+
// backwards compat
|
|
31
|
+
if (typeof object !== 'string') return object;
|
|
32
|
+
|
|
33
|
+
return JSON.parse(object, (_key, value) => {
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
const match = value.match(/^\/(.*)\/([gimsuy]*)$/);
|
|
36
|
+
|
|
37
|
+
if (match) {
|
|
38
|
+
try {
|
|
39
|
+
return new RegExp(match[1], match[2]);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.warn(`Failed to parse RegExp: ${error}`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Determines if a credential uses VC 2.0 format by checking the context array
|
|
53
|
+
*/
|
|
54
|
+
export const isVC2Format = (credential: UnsignedVC | VC): boolean => {
|
|
55
|
+
if (!credential['@context'] || !Array.isArray(credential['@context'])) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return credential['@context'].some(
|
|
60
|
+
context => context === 'https://www.w3.org/ns/credentials/v2'
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Unwraps a boost credential from a CertifiedBoostCredential, if it is one */
|
|
65
|
+
export const unwrapBoostCredential = (vc?: VC | UnsignedVC) => {
|
|
66
|
+
if (vc?.type?.includes('CertifiedBoostCredential') && vc?.boostCredential) {
|
|
67
|
+
return vc.boostCredential;
|
|
68
|
+
} else {
|
|
69
|
+
return vc;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Export helpers from shared-helpers migration
|
|
74
|
+
export * from './images';
|
|
75
|
+
export * from './arrays';
|
|
76
|
+
export * from './types';
|
|
77
|
+
export * from './strings';
|
|
78
|
+
export * from './numbers';
|
|
79
|
+
export * from './state';
|
|
80
|
+
export * from './bitstring-status-list';
|
|
81
|
+
|
|
82
|
+
// Export utilities from shared-types migration
|
|
83
|
+
export * from './Utilities';
|
|
84
|
+
|
|
85
|
+
// Export app install helpers
|
|
86
|
+
export * from './app-install';
|
|
87
|
+
export * from './credential-format';
|
|
88
|
+
|
|
89
|
+
// ADR-0001 Phase 1: format-tagged credential storage projector
|
|
90
|
+
export * from './credential-format';
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Checks if a DID is an app-specific did:web
|
|
94
|
+
*
|
|
95
|
+
* App did:webs follow the pattern: `did:web:learncard.app:app:<slug>`
|
|
96
|
+
*
|
|
97
|
+
* @param did - The DID to check
|
|
98
|
+
* @returns true if the DID is an app did:web, false otherwise
|
|
99
|
+
*/
|
|
100
|
+
export const isAppDidWeb = (did?: string): boolean => {
|
|
101
|
+
if (!did) return false;
|
|
102
|
+
// Matches did:web:<slug>:app:<slug> pattern
|
|
103
|
+
const LCN_APP_DID_WEB_REGEX = /^did:web:.*:app:([^:]+)$/;
|
|
104
|
+
return LCN_APP_DID_WEB_REGEX.test(did);
|
|
105
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a number into a friendlier string i.e. 1,234 becomes 1.2K
|
|
3
|
+
*/
|
|
4
|
+
export const formatNumber = (num: number): string => {
|
|
5
|
+
const abbreviations = [
|
|
6
|
+
{ value: 1e9, symbol: 'B' },
|
|
7
|
+
{ value: 1e6, symbol: 'M' },
|
|
8
|
+
{ value: 1e3, symbol: 'K' }
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
for (const { value, symbol } of abbreviations) {
|
|
12
|
+
if (num >= value) {
|
|
13
|
+
// Round to 1 decimal place and remove trailing zero
|
|
14
|
+
const result = (Math.floor(num / (value / 10)) / 10).toFixed(1);
|
|
15
|
+
return result.replace(/\.0$/, '') + symbol;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// For numbers less than 1000, return as is, but without decimal places
|
|
20
|
+
return Math.floor(num).toString();
|
|
21
|
+
}
|