decap-cms-lib-util 3.2.0 → 3.3.1
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 +1 -1
- package/dist/decap-cms-lib-util.js +1 -1
- package/dist/decap-cms-lib-util.js.LICENSE.txt +9 -0
- package/dist/decap-cms-lib-util.js.map +1 -1
- package/dist/esm/API.js +17 -25
- package/dist/esm/APIError.js +0 -7
- package/dist/esm/AccessTokenError.js +0 -4
- package/dist/esm/Cursor.js +4 -9
- package/dist/esm/EditorialWorkflowError.js +0 -5
- package/dist/esm/backendUtil.js +6 -7
- package/dist/esm/getBlobSHA.js +3 -3
- package/dist/esm/git-lfs.js +21 -29
- package/dist/esm/implementation.js +14 -17
- package/dist/esm/unsentRequest.js +9 -12
- package/package.json +5 -2
- package/CHANGELOG.md +0 -382
- package/dist/esm/stega.js +0 -129
- package/src/API.ts +0 -376
- package/src/APIError.ts +0 -17
- package/src/APIUtils.ts +0 -38
- package/src/AccessTokenError.ts +0 -11
- package/src/Cursor.ts +0 -178
- package/src/EditorialWorkflowError.ts +0 -12
- package/src/__tests__/api.spec.js +0 -13
- package/src/__tests__/apiUtils.spec.js +0 -74
- package/src/__tests__/asyncLock.spec.js +0 -85
- package/src/__tests__/backendUtil.spec.js +0 -97
- package/src/__tests__/implementation.spec.js +0 -58
- package/src/__tests__/path.spec.js +0 -53
- package/src/__tests__/unsentRequest.spec.js +0 -19
- package/src/asyncLock.ts +0 -43
- package/src/backendUtil.ts +0 -120
- package/src/getBlobSHA.ts +0 -12
- package/src/git-lfs.ts +0 -133
- package/src/implementation.ts +0 -575
- package/src/index.ts +0 -213
- package/src/loadScript.js +0 -24
- package/src/localForage.ts +0 -21
- package/src/path.ts +0 -86
- package/src/promise.ts +0 -21
- package/src/stega.ts +0 -134
- package/src/types/semaphore.d.ts +0 -5
- package/src/types.ts +0 -9
- package/src/unsentRequest.js +0 -133
- package/webpack.config.js +0 -3
package/src/index.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import APIError from './APIError';
|
|
2
|
-
import Cursor, { CURSOR_COMPATIBILITY_SYMBOL } from './Cursor';
|
|
3
|
-
import EditorialWorkflowError, { EDITORIAL_WORKFLOW_ERROR } from './EditorialWorkflowError';
|
|
4
|
-
import AccessTokenError from './AccessTokenError';
|
|
5
|
-
import localForage from './localForage';
|
|
6
|
-
import { isAbsolutePath, basename, fileExtensionWithSeparator, fileExtension } from './path';
|
|
7
|
-
import { onlySuccessfulPromises, flowAsync, then } from './promise';
|
|
8
|
-
import unsentRequest from './unsentRequest';
|
|
9
|
-
import {
|
|
10
|
-
filterByExtension,
|
|
11
|
-
getAllResponses,
|
|
12
|
-
parseLinkHeader,
|
|
13
|
-
parseResponse,
|
|
14
|
-
responseParser,
|
|
15
|
-
getPathDepth,
|
|
16
|
-
} from './backendUtil';
|
|
17
|
-
import loadScript from './loadScript';
|
|
18
|
-
import getBlobSHA from './getBlobSHA';
|
|
19
|
-
import { asyncLock } from './asyncLock';
|
|
20
|
-
import {
|
|
21
|
-
entriesByFiles,
|
|
22
|
-
entriesByFolder,
|
|
23
|
-
unpublishedEntries,
|
|
24
|
-
getMediaDisplayURL,
|
|
25
|
-
getMediaAsBlob,
|
|
26
|
-
runWithLock,
|
|
27
|
-
blobToFileObj,
|
|
28
|
-
allEntriesByFolder,
|
|
29
|
-
} from './implementation';
|
|
30
|
-
import {
|
|
31
|
-
readFile,
|
|
32
|
-
readFileMetadata,
|
|
33
|
-
isPreviewContext,
|
|
34
|
-
getPreviewStatus,
|
|
35
|
-
PreviewState,
|
|
36
|
-
requestWithBackoff,
|
|
37
|
-
getDefaultBranchName,
|
|
38
|
-
throwOnConflictingBranches,
|
|
39
|
-
} from './API';
|
|
40
|
-
import {
|
|
41
|
-
CMS_BRANCH_PREFIX,
|
|
42
|
-
generateContentKey,
|
|
43
|
-
isCMSLabel,
|
|
44
|
-
labelToStatus,
|
|
45
|
-
statusToLabel,
|
|
46
|
-
DEFAULT_PR_BODY,
|
|
47
|
-
MERGE_COMMIT_MESSAGE,
|
|
48
|
-
parseContentKey,
|
|
49
|
-
branchFromContentKey,
|
|
50
|
-
contentKeyFromBranch,
|
|
51
|
-
} from './APIUtils';
|
|
52
|
-
import {
|
|
53
|
-
createPointerFile,
|
|
54
|
-
getLargeMediaFilteredMediaFiles,
|
|
55
|
-
getLargeMediaPatternsFromGitAttributesFile,
|
|
56
|
-
parsePointerFile,
|
|
57
|
-
getPointerFileForMediaFileObj,
|
|
58
|
-
} from './git-lfs';
|
|
59
|
-
|
|
60
|
-
import type { PointerFile as PF } from './git-lfs';
|
|
61
|
-
import type { FetchError as FE, ApiRequest as AR } from './API';
|
|
62
|
-
import type {
|
|
63
|
-
Implementation as I,
|
|
64
|
-
ImplementationEntry as IE,
|
|
65
|
-
UnpublishedEntryDiff as UED,
|
|
66
|
-
UnpublishedEntry as UE,
|
|
67
|
-
ImplementationMediaFile as IMF,
|
|
68
|
-
ImplementationFile as IF,
|
|
69
|
-
DisplayURLObject as DUO,
|
|
70
|
-
DisplayURL as DU,
|
|
71
|
-
Credentials as Cred,
|
|
72
|
-
User as U,
|
|
73
|
-
Entry as E,
|
|
74
|
-
PersistOptions as PO,
|
|
75
|
-
AssetProxy as AP,
|
|
76
|
-
Config as C,
|
|
77
|
-
UnpublishedEntryMediaFile as UEMF,
|
|
78
|
-
DataFile as DF,
|
|
79
|
-
} from './implementation';
|
|
80
|
-
import type { AsyncLock as AL } from './asyncLock';
|
|
81
|
-
|
|
82
|
-
export type AsyncLock = AL;
|
|
83
|
-
export type Implementation = I;
|
|
84
|
-
export type ImplementationEntry = IE;
|
|
85
|
-
export type UnpublishedEntryDiff = UED;
|
|
86
|
-
export type UnpublishedEntry = UE;
|
|
87
|
-
export type ImplementationMediaFile = IMF;
|
|
88
|
-
export type ImplementationFile = IF;
|
|
89
|
-
export type DisplayURL = DU;
|
|
90
|
-
export type DisplayURLObject = DUO;
|
|
91
|
-
export type Credentials = Cred;
|
|
92
|
-
export type User = U;
|
|
93
|
-
export type Entry = E;
|
|
94
|
-
export type UnpublishedEntryMediaFile = UEMF;
|
|
95
|
-
export type PersistOptions = PO;
|
|
96
|
-
export type AssetProxy = AP;
|
|
97
|
-
export type ApiRequest = AR;
|
|
98
|
-
export type Config = C;
|
|
99
|
-
export type FetchError = FE;
|
|
100
|
-
export type PointerFile = PF;
|
|
101
|
-
export type DataFile = DF;
|
|
102
|
-
|
|
103
|
-
export const DecapCmsLibUtil = {
|
|
104
|
-
APIError,
|
|
105
|
-
Cursor,
|
|
106
|
-
CURSOR_COMPATIBILITY_SYMBOL,
|
|
107
|
-
EditorialWorkflowError,
|
|
108
|
-
EDITORIAL_WORKFLOW_ERROR,
|
|
109
|
-
localForage,
|
|
110
|
-
basename,
|
|
111
|
-
fileExtensionWithSeparator,
|
|
112
|
-
fileExtension,
|
|
113
|
-
onlySuccessfulPromises,
|
|
114
|
-
flowAsync,
|
|
115
|
-
then,
|
|
116
|
-
unsentRequest,
|
|
117
|
-
filterByExtension,
|
|
118
|
-
parseLinkHeader,
|
|
119
|
-
parseResponse,
|
|
120
|
-
responseParser,
|
|
121
|
-
loadScript,
|
|
122
|
-
getBlobSHA,
|
|
123
|
-
getPathDepth,
|
|
124
|
-
entriesByFiles,
|
|
125
|
-
entriesByFolder,
|
|
126
|
-
unpublishedEntries,
|
|
127
|
-
getMediaDisplayURL,
|
|
128
|
-
getMediaAsBlob,
|
|
129
|
-
readFile,
|
|
130
|
-
readFileMetadata,
|
|
131
|
-
CMS_BRANCH_PREFIX,
|
|
132
|
-
generateContentKey,
|
|
133
|
-
isCMSLabel,
|
|
134
|
-
labelToStatus,
|
|
135
|
-
statusToLabel,
|
|
136
|
-
DEFAULT_PR_BODY,
|
|
137
|
-
MERGE_COMMIT_MESSAGE,
|
|
138
|
-
isPreviewContext,
|
|
139
|
-
getPreviewStatus,
|
|
140
|
-
runWithLock,
|
|
141
|
-
PreviewState,
|
|
142
|
-
parseContentKey,
|
|
143
|
-
createPointerFile,
|
|
144
|
-
getLargeMediaFilteredMediaFiles,
|
|
145
|
-
getLargeMediaPatternsFromGitAttributesFile,
|
|
146
|
-
parsePointerFile,
|
|
147
|
-
getPointerFileForMediaFileObj,
|
|
148
|
-
branchFromContentKey,
|
|
149
|
-
contentKeyFromBranch,
|
|
150
|
-
blobToFileObj,
|
|
151
|
-
requestWithBackoff,
|
|
152
|
-
getDefaultBranchName,
|
|
153
|
-
allEntriesByFolder,
|
|
154
|
-
AccessTokenError,
|
|
155
|
-
throwOnConflictingBranches,
|
|
156
|
-
};
|
|
157
|
-
export {
|
|
158
|
-
APIError,
|
|
159
|
-
Cursor,
|
|
160
|
-
CURSOR_COMPATIBILITY_SYMBOL,
|
|
161
|
-
EditorialWorkflowError,
|
|
162
|
-
EDITORIAL_WORKFLOW_ERROR,
|
|
163
|
-
localForage,
|
|
164
|
-
basename,
|
|
165
|
-
fileExtensionWithSeparator,
|
|
166
|
-
fileExtension,
|
|
167
|
-
onlySuccessfulPromises,
|
|
168
|
-
flowAsync,
|
|
169
|
-
then,
|
|
170
|
-
unsentRequest,
|
|
171
|
-
filterByExtension,
|
|
172
|
-
parseLinkHeader,
|
|
173
|
-
getAllResponses,
|
|
174
|
-
parseResponse,
|
|
175
|
-
responseParser,
|
|
176
|
-
loadScript,
|
|
177
|
-
getBlobSHA,
|
|
178
|
-
asyncLock,
|
|
179
|
-
isAbsolutePath,
|
|
180
|
-
getPathDepth,
|
|
181
|
-
entriesByFiles,
|
|
182
|
-
entriesByFolder,
|
|
183
|
-
unpublishedEntries,
|
|
184
|
-
getMediaDisplayURL,
|
|
185
|
-
getMediaAsBlob,
|
|
186
|
-
readFile,
|
|
187
|
-
readFileMetadata,
|
|
188
|
-
CMS_BRANCH_PREFIX,
|
|
189
|
-
generateContentKey,
|
|
190
|
-
isCMSLabel,
|
|
191
|
-
labelToStatus,
|
|
192
|
-
statusToLabel,
|
|
193
|
-
DEFAULT_PR_BODY,
|
|
194
|
-
MERGE_COMMIT_MESSAGE,
|
|
195
|
-
isPreviewContext,
|
|
196
|
-
getPreviewStatus,
|
|
197
|
-
runWithLock,
|
|
198
|
-
PreviewState,
|
|
199
|
-
parseContentKey,
|
|
200
|
-
createPointerFile,
|
|
201
|
-
getLargeMediaFilteredMediaFiles,
|
|
202
|
-
getLargeMediaPatternsFromGitAttributesFile,
|
|
203
|
-
parsePointerFile,
|
|
204
|
-
getPointerFileForMediaFileObj,
|
|
205
|
-
branchFromContentKey,
|
|
206
|
-
contentKeyFromBranch,
|
|
207
|
-
blobToFileObj,
|
|
208
|
-
requestWithBackoff,
|
|
209
|
-
getDefaultBranchName,
|
|
210
|
-
allEntriesByFolder,
|
|
211
|
-
AccessTokenError,
|
|
212
|
-
throwOnConflictingBranches,
|
|
213
|
-
};
|
package/src/loadScript.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple script loader that returns a promise.
|
|
3
|
-
*/
|
|
4
|
-
export default function loadScript(url) {
|
|
5
|
-
return new Promise((resolve, reject) => {
|
|
6
|
-
let done = false;
|
|
7
|
-
const head = document.getElementsByTagName('head')[0];
|
|
8
|
-
const script = document.createElement('script');
|
|
9
|
-
script.src = url;
|
|
10
|
-
script.onload = script.onreadystatechange = function () {
|
|
11
|
-
if (
|
|
12
|
-
!done &&
|
|
13
|
-
(!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')
|
|
14
|
-
) {
|
|
15
|
-
done = true;
|
|
16
|
-
resolve();
|
|
17
|
-
} else {
|
|
18
|
-
reject();
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
script.onerror = error => reject(error);
|
|
22
|
-
head.appendChild(script);
|
|
23
|
-
});
|
|
24
|
-
}
|
package/src/localForage.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import localForage from 'localforage';
|
|
2
|
-
|
|
3
|
-
function localForageTest() {
|
|
4
|
-
const testKey = 'localForageTest';
|
|
5
|
-
localForage
|
|
6
|
-
.setItem(testKey, { expires: Date.now() + 300000 })
|
|
7
|
-
.then(() => {
|
|
8
|
-
localForage.removeItem(testKey);
|
|
9
|
-
})
|
|
10
|
-
.catch(err => {
|
|
11
|
-
if (err.code === 22) {
|
|
12
|
-
const message = 'Unable to set localStorage key. Quota exceeded! Full disk?';
|
|
13
|
-
console.warn(message);
|
|
14
|
-
}
|
|
15
|
-
console.log(err);
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
localForageTest();
|
|
20
|
-
|
|
21
|
-
export default localForage;
|
package/src/path.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
const absolutePath = new RegExp('^(?:[a-z]+:)?//', 'i');
|
|
2
|
-
|
|
3
|
-
function normalizePath(path: string) {
|
|
4
|
-
return path.replace(/[\\/]+/g, '/');
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function isAbsolutePath(path: string) {
|
|
8
|
-
return absolutePath.test(path);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Return the last portion of a path. Similar to the Unix basename command.
|
|
13
|
-
* @example Usage example
|
|
14
|
-
* path.basename('/foo/bar/baz/asdf/quux.html')
|
|
15
|
-
* // returns
|
|
16
|
-
* 'quux.html'
|
|
17
|
-
*
|
|
18
|
-
* path.basename('/foo/bar/baz/asdf/quux.html', '.html')
|
|
19
|
-
* // returns
|
|
20
|
-
* 'quux'
|
|
21
|
-
*/
|
|
22
|
-
export function basename(p: string, ext = '') {
|
|
23
|
-
// Special case: Normalize will modify this to '.'
|
|
24
|
-
if (p === '') {
|
|
25
|
-
return p;
|
|
26
|
-
}
|
|
27
|
-
// Normalize the string first to remove any weirdness.
|
|
28
|
-
p = normalizePath(p);
|
|
29
|
-
// Get the last part of the string.
|
|
30
|
-
const sections = p.split('/');
|
|
31
|
-
const lastPart = sections[sections.length - 1];
|
|
32
|
-
// Special case: If it's empty, then we have a string like so: foo/
|
|
33
|
-
// Meaning, 'foo' is guaranteed to be a directory.
|
|
34
|
-
if (lastPart === '' && sections.length > 1) {
|
|
35
|
-
return sections[sections.length - 2];
|
|
36
|
-
}
|
|
37
|
-
// Remove the extension, if need be.
|
|
38
|
-
if (ext.length > 0) {
|
|
39
|
-
const lastPartExt = lastPart.slice(-ext.length);
|
|
40
|
-
if (lastPartExt === ext) {
|
|
41
|
-
return lastPart.slice(0, -ext.length);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return lastPart;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Return the extension of the path, from the last '.' to end of string in the
|
|
49
|
-
* last portion of the path. If there is no '.' in the last portion of the path
|
|
50
|
-
* or the first character of it is '.', then it returns an empty string.
|
|
51
|
-
* @example Usage example
|
|
52
|
-
* path.fileExtensionWithSeparator('index.html')
|
|
53
|
-
* // returns
|
|
54
|
-
* '.html'
|
|
55
|
-
*/
|
|
56
|
-
export function fileExtensionWithSeparator(p: string) {
|
|
57
|
-
p = normalizePath(p);
|
|
58
|
-
const sections = p.split('/');
|
|
59
|
-
p = sections.pop() as string;
|
|
60
|
-
// Special case: foo/file.ext/ should return '.ext'
|
|
61
|
-
if (p === '' && sections.length > 0) {
|
|
62
|
-
p = sections.pop() as string;
|
|
63
|
-
}
|
|
64
|
-
if (p === '..') {
|
|
65
|
-
return '';
|
|
66
|
-
}
|
|
67
|
-
const i = p.lastIndexOf('.');
|
|
68
|
-
if (i === -1 || i === 0) {
|
|
69
|
-
return '';
|
|
70
|
-
}
|
|
71
|
-
return p.slice(i);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Return the extension of the path, from after the last '.' to end of string in the
|
|
76
|
-
* last portion of the path. If there is no '.' in the last portion of the path
|
|
77
|
-
* or the first character of it is '.', then it returns an empty string.
|
|
78
|
-
* @example Usage example
|
|
79
|
-
* path.fileExtension('index.html')
|
|
80
|
-
* // returns
|
|
81
|
-
* 'html'
|
|
82
|
-
*/
|
|
83
|
-
export function fileExtension(p: string) {
|
|
84
|
-
const ext = fileExtensionWithSeparator(p);
|
|
85
|
-
return ext === '' ? ext : ext.slice(1);
|
|
86
|
-
}
|
package/src/promise.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import flow from 'lodash/flow';
|
|
2
|
-
|
|
3
|
-
export function then<T, V>(fn: (r: T) => V) {
|
|
4
|
-
return (p: Promise<T>) => Promise.resolve(p).then(fn);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const filterPromiseSymbol = Symbol('filterPromiseSymbol');
|
|
8
|
-
|
|
9
|
-
export function onlySuccessfulPromises(promises: Promise<unknown>[]) {
|
|
10
|
-
return Promise.all(promises.map(p => p.catch(() => filterPromiseSymbol))).then(results =>
|
|
11
|
-
results.filter(result => result !== filterPromiseSymbol),
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function wrapFlowAsync(fn: Function) {
|
|
16
|
-
return async (arg: unknown) => fn(await arg);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function flowAsync(fns: Function[]) {
|
|
20
|
-
return flow(fns.map(fn => wrapFlowAsync(fn)));
|
|
21
|
-
}
|
package/src/stega.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { vercelStegaEncode } from '@vercel/stega';
|
|
2
|
-
|
|
3
|
-
import { isImmutableMap, isImmutableList } from './types';
|
|
4
|
-
|
|
5
|
-
import type { Map as ImmutableMap, List } from 'immutable';
|
|
6
|
-
import type { CmsField } from 'decap-cms-core';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Context passed to encode functions, containing the current state of the encoding process
|
|
10
|
-
*/
|
|
11
|
-
interface EncodeContext {
|
|
12
|
-
fields: CmsField[]; // Available CMS fields at current level
|
|
13
|
-
path: string; // Path to current value in object tree
|
|
14
|
-
visit: (value: unknown, fields: CmsField[], path: string) => unknown; // Visitor for recursive traversal
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get the fields that should be used for encoding nested values
|
|
19
|
-
*/
|
|
20
|
-
function getNestedFields(f?: CmsField): CmsField[] {
|
|
21
|
-
if (f) {
|
|
22
|
-
if ('types' in f) {
|
|
23
|
-
return f.types ?? [];
|
|
24
|
-
}
|
|
25
|
-
if ('fields' in f) {
|
|
26
|
-
return f.fields ?? [];
|
|
27
|
-
}
|
|
28
|
-
if ('field' in f) {
|
|
29
|
-
return f.field ? [f.field] : [];
|
|
30
|
-
}
|
|
31
|
-
return [f];
|
|
32
|
-
}
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Encode a string value by appending steganographic data
|
|
38
|
-
* For markdown fields, encode each paragraph separately
|
|
39
|
-
*/
|
|
40
|
-
function encodeString(value: string, { fields, path }: EncodeContext): string {
|
|
41
|
-
const stega = vercelStegaEncode({ decap: path });
|
|
42
|
-
const isMarkdown = fields[0]?.widget === 'markdown';
|
|
43
|
-
|
|
44
|
-
if (isMarkdown && value.includes('\n\n')) {
|
|
45
|
-
const blocks = value.split(/(\n\n+)/);
|
|
46
|
-
return blocks.map(block => (block.trim() ? block + stega : block)).join('');
|
|
47
|
-
}
|
|
48
|
-
return value + stega;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Encode a list of values, handling both simple values and nested objects/lists
|
|
53
|
-
* For typed lists, use the type field to determine which fields to use
|
|
54
|
-
*/
|
|
55
|
-
function encodeList(list: List<unknown>, ctx: EncodeContext): List<unknown> {
|
|
56
|
-
let newList = list;
|
|
57
|
-
for (let i = 0; i < newList.size; i++) {
|
|
58
|
-
const item = newList.get(i);
|
|
59
|
-
if (isImmutableMap(item)) {
|
|
60
|
-
const itemType = item.get('type');
|
|
61
|
-
if (typeof itemType === 'string') {
|
|
62
|
-
// For typed items, look up fields based on type
|
|
63
|
-
const field = ctx.fields.find(f => f.name === itemType);
|
|
64
|
-
const newItem = ctx.visit(item, getNestedFields(field), `${ctx.path}.${i}`);
|
|
65
|
-
newList = newList.set(i, newItem);
|
|
66
|
-
} else {
|
|
67
|
-
// For untyped items, use current fields
|
|
68
|
-
const newItem = ctx.visit(item, ctx.fields, `${ctx.path}.${i}`);
|
|
69
|
-
newList = newList.set(i, newItem);
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
// For simple values, use first field if available
|
|
73
|
-
const field = ctx.fields[0];
|
|
74
|
-
const newItem = ctx.visit(item, field ? [field] : [], `${ctx.path}.${i}`);
|
|
75
|
-
if (newItem !== item) {
|
|
76
|
-
newList = newList.set(i, newItem);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return newList;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Encode a map of values, looking up the appropriate field for each key
|
|
85
|
-
* and recursively encoding nested values
|
|
86
|
-
*/
|
|
87
|
-
function encodeMap(
|
|
88
|
-
map: ImmutableMap<string, unknown>,
|
|
89
|
-
ctx: EncodeContext,
|
|
90
|
-
): ImmutableMap<string, unknown> {
|
|
91
|
-
let newMap = map;
|
|
92
|
-
for (const [key, val] of newMap.entrySeq().toArray()) {
|
|
93
|
-
const field = ctx.fields.find(f => f.name === key);
|
|
94
|
-
if (field) {
|
|
95
|
-
const fields = getNestedFields(field);
|
|
96
|
-
const newVal = ctx.visit(val, fields, ctx.path ? `${ctx.path}.${key}` : key);
|
|
97
|
-
if (newVal !== val) {
|
|
98
|
-
newMap = newMap.set(key, newVal);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return newMap;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Main entry point for encoding steganographic data into entry values
|
|
107
|
-
* Uses a visitor pattern with caching to handle recursive structures
|
|
108
|
-
*/
|
|
109
|
-
export function encodeEntry(value: unknown, fields: List<ImmutableMap<string, unknown>>) {
|
|
110
|
-
const plainFields = fields.toJS() as CmsField[];
|
|
111
|
-
const cache = new Map();
|
|
112
|
-
|
|
113
|
-
function visit(value: unknown, fields: CmsField[], path = '') {
|
|
114
|
-
const cached = cache.get(path);
|
|
115
|
-
if (cached === value) return value;
|
|
116
|
-
|
|
117
|
-
const ctx: EncodeContext = { fields, path, visit };
|
|
118
|
-
let result;
|
|
119
|
-
if (isImmutableList(value)) {
|
|
120
|
-
result = encodeList(value, ctx);
|
|
121
|
-
} else if (isImmutableMap(value)) {
|
|
122
|
-
result = encodeMap(value, ctx);
|
|
123
|
-
} else if (typeof value === 'string') {
|
|
124
|
-
result = encodeString(value, ctx);
|
|
125
|
-
} else {
|
|
126
|
-
result = value;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
cache.set(path, result);
|
|
130
|
-
return result;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return visit(value, plainFields);
|
|
134
|
-
}
|
package/src/types/semaphore.d.ts
DELETED
package/src/types.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Map as ImmutableMap, List } from 'immutable';
|
|
2
|
-
|
|
3
|
-
export function isImmutableMap(value: unknown): value is ImmutableMap<string, unknown> {
|
|
4
|
-
return ImmutableMap.isMap(value);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function isImmutableList(value: unknown): value is List<unknown> {
|
|
8
|
-
return List.isList(value);
|
|
9
|
-
}
|
package/src/unsentRequest.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { fromJS, List, Map } from 'immutable';
|
|
2
|
-
import curry from 'lodash/curry';
|
|
3
|
-
import flow from 'lodash/flow';
|
|
4
|
-
import isString from 'lodash/isString';
|
|
5
|
-
|
|
6
|
-
function isAbortControllerSupported() {
|
|
7
|
-
if (typeof window !== 'undefined') {
|
|
8
|
-
return !!window.AbortController;
|
|
9
|
-
}
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const timeout = 60;
|
|
14
|
-
|
|
15
|
-
function fetchWithTimeout(input, init) {
|
|
16
|
-
if ((init && init.signal) || !isAbortControllerSupported()) {
|
|
17
|
-
return fetch(input, init);
|
|
18
|
-
}
|
|
19
|
-
const controller = new AbortController();
|
|
20
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout * 1000);
|
|
21
|
-
return fetch(input, { ...init, signal: controller.signal })
|
|
22
|
-
.then(res => {
|
|
23
|
-
clearTimeout(timeoutId);
|
|
24
|
-
return res;
|
|
25
|
-
})
|
|
26
|
-
.catch(e => {
|
|
27
|
-
if (e.name === 'AbortError' || e.name === 'DOMException') {
|
|
28
|
-
throw new Error(`Request timed out after ${timeout} seconds`);
|
|
29
|
-
}
|
|
30
|
-
throw e;
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function decodeParams(paramsString) {
|
|
35
|
-
return List(paramsString.split('&'))
|
|
36
|
-
.map(s => List(s.split('=')).map(decodeURIComponent))
|
|
37
|
-
.update(Map);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function fromURL(wholeURL) {
|
|
41
|
-
const [url, allParamsString] = wholeURL.split('?');
|
|
42
|
-
return Map({ url, ...(allParamsString ? { params: decodeParams(allParamsString) } : {}) });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function fromFetchArguments(wholeURL, options) {
|
|
46
|
-
return fromURL(wholeURL).merge(
|
|
47
|
-
(options ? fromJS(options) : Map()).remove('url').remove('params'),
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function encodeParams(params) {
|
|
52
|
-
return params
|
|
53
|
-
.entrySeq()
|
|
54
|
-
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
55
|
-
.join('&');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function toURL(req) {
|
|
59
|
-
return `${req.get('url')}${req.get('params') ? `?${encodeParams(req.get('params'))}` : ''}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function toFetchArguments(req) {
|
|
63
|
-
return [toURL(req), req.remove('url').remove('params').toJS()];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function maybeRequestArg(req) {
|
|
67
|
-
if (isString(req)) {
|
|
68
|
-
return fromURL(req);
|
|
69
|
-
}
|
|
70
|
-
if (req) {
|
|
71
|
-
return fromJS(req);
|
|
72
|
-
}
|
|
73
|
-
return Map();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function ensureRequestArg(func) {
|
|
77
|
-
return req => func(maybeRequestArg(req));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function ensureRequestArg2(func) {
|
|
81
|
-
return (arg, req) => func(arg, maybeRequestArg(req));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// This actually performs the built request object
|
|
85
|
-
const performRequest = ensureRequestArg(req => {
|
|
86
|
-
const args = toFetchArguments(req);
|
|
87
|
-
return fetchWithTimeout(...args);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Each of the following functions takes options and returns another
|
|
91
|
-
// function that performs the requested action on a request.
|
|
92
|
-
const getCurriedRequestProcessor = flow([ensureRequestArg2, curry]);
|
|
93
|
-
|
|
94
|
-
function getPropSetFunction(path) {
|
|
95
|
-
return getCurriedRequestProcessor((val, req) => req.setIn(path, val));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function getPropMergeFunction(path) {
|
|
99
|
-
return getCurriedRequestProcessor((obj, req) => req.updateIn(path, (p = Map()) => p.merge(obj)));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const withMethod = getPropSetFunction(['method']);
|
|
103
|
-
const withBody = getPropSetFunction(['body']);
|
|
104
|
-
const withNoCache = getPropSetFunction(['cache'])('no-cache');
|
|
105
|
-
const withParams = getPropMergeFunction(['params']);
|
|
106
|
-
const withHeaders = getPropMergeFunction(['headers']);
|
|
107
|
-
|
|
108
|
-
// withRoot sets a root URL, unless the URL is already absolute
|
|
109
|
-
const absolutePath = new RegExp('^(?:[a-z]+:)?//', 'i');
|
|
110
|
-
const withRoot = getCurriedRequestProcessor((root, req) =>
|
|
111
|
-
req.update('url', p => {
|
|
112
|
-
if (absolutePath.test(p)) {
|
|
113
|
-
return p;
|
|
114
|
-
}
|
|
115
|
-
return root && p && p[0] !== '/' && root[root.length - 1] !== '/'
|
|
116
|
-
? `${root}/${p}`
|
|
117
|
-
: `${root}${p}`;
|
|
118
|
-
}),
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
export default {
|
|
122
|
-
toURL,
|
|
123
|
-
fromURL,
|
|
124
|
-
fromFetchArguments,
|
|
125
|
-
performRequest,
|
|
126
|
-
withMethod,
|
|
127
|
-
withBody,
|
|
128
|
-
withHeaders,
|
|
129
|
-
withParams,
|
|
130
|
-
withRoot,
|
|
131
|
-
withNoCache,
|
|
132
|
-
fetchWithTimeout,
|
|
133
|
-
};
|
package/webpack.config.js
DELETED