refactor: use fetch cache arg instead of cache busting (#3584)
This commit is contained in:
@ -215,7 +215,13 @@ export default class API {
|
||||
}
|
||||
|
||||
buildRequest = (req: ApiRequest) => {
|
||||
return flow([unsentRequest.withRoot(this.apiRoot), unsentRequest.withTimestamp])(req);
|
||||
const withRoot = unsentRequest.withRoot(this.apiRoot)(req);
|
||||
if (withRoot.has('cache')) {
|
||||
return withRoot;
|
||||
} else {
|
||||
const withNoCache = unsentRequest.withNoCache(withRoot);
|
||||
return withNoCache;
|
||||
}
|
||||
};
|
||||
|
||||
request = (req: ApiRequest): Promise<Response> => {
|
||||
@ -295,7 +301,7 @@ export default class API {
|
||||
return content;
|
||||
};
|
||||
|
||||
async readFileMetadata(path: string, sha: string) {
|
||||
async readFileMetadata(path: string, sha: string | null | undefined) {
|
||||
const fetchFileMetadata = async () => {
|
||||
try {
|
||||
const { values }: { values: BitBucketCommit[] } = await this.requestJSON({
|
||||
|
@ -5,7 +5,6 @@ describe('github API', () => {
|
||||
beforeEach(() => {
|
||||
const fetch = jest.fn();
|
||||
global.fetch = fetch;
|
||||
global.Date = jest.fn(() => ({ getTime: () => 1000 }));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -27,15 +26,13 @@ describe('github API', () => {
|
||||
const result = await api.request('/some-path');
|
||||
expect(result).toEqual('some response');
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
'https://site.netlify.com/.netlify/git/github/some-path?ts=1000',
|
||||
{
|
||||
headers: {
|
||||
Authorization: 'Bearer token',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
expect(fetch).toHaveBeenCalledWith('https://site.netlify.com/.netlify/git/github/some-path', {
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
Authorization: 'Bearer token',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error on not ok response with message property', async () => {
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
AssetProxy,
|
||||
Entry as LibEntry,
|
||||
PersistOptions,
|
||||
readFile,
|
||||
readFileMetadata,
|
||||
CMS_BRANCH_PREFIX,
|
||||
generateContentKey,
|
||||
@ -249,8 +248,7 @@ export default class API {
|
||||
}
|
||||
|
||||
urlFor(path: string, options: Options) {
|
||||
const cacheBuster = new Date().getTime();
|
||||
const params = [`ts=${cacheBuster}`];
|
||||
const params = [];
|
||||
if (options.params) {
|
||||
for (const key in options.params) {
|
||||
params.push(`${key}=${encodeURIComponent(options.params[key] as string)}`);
|
||||
@ -289,6 +287,7 @@ export default class API {
|
||||
options: Options = {},
|
||||
parser = (response: Response) => this.parseResponse(response),
|
||||
) {
|
||||
options = { cache: 'no-cache', ...options };
|
||||
const headers = await this.requestHeaders(options.headers || {});
|
||||
const url = this.urlFor(path, options);
|
||||
let responseStatus = 500;
|
||||
@ -312,6 +311,7 @@ export default class API {
|
||||
}
|
||||
|
||||
async requestAllPages<T>(url: string, options: Options = {}) {
|
||||
options = { cache: 'no-cache', ...options };
|
||||
const headers = await this.requestHeaders(options.headers || {});
|
||||
const processedURL = this.urlFor(url, options);
|
||||
const allResponses = await getAllResponses(
|
||||
@ -344,9 +344,7 @@ export default class API {
|
||||
}
|
||||
|
||||
checkMetadataRef() {
|
||||
return this.request(`${this.repoURL}/git/refs/meta/_netlify_cms`, {
|
||||
cache: 'no-store',
|
||||
})
|
||||
return this.request(`${this.repoURL}/git/refs/meta/_netlify_cms`)
|
||||
.then(response => response.object)
|
||||
.catch(() => {
|
||||
// Meta ref doesn't exist
|
||||
@ -432,7 +430,6 @@ export default class API {
|
||||
const metadataRequestOptions = {
|
||||
params: { ref: 'refs/meta/_netlify_cms' },
|
||||
headers: { Accept: 'application/vnd.github.v3.raw' },
|
||||
cache: 'no-store' as RequestCache,
|
||||
};
|
||||
|
||||
const errorHandler = (err: Error) => {
|
||||
@ -627,12 +624,11 @@ export default class API {
|
||||
if (!sha) {
|
||||
sha = await this.getFileSha(path, { repoURL, branch });
|
||||
}
|
||||
const fetchContent = () => this.fetchBlobContent({ sha: sha as string, repoURL, parseText });
|
||||
const content = await readFile(sha, fetchContent, localForage, parseText);
|
||||
const content = await this.fetchBlobContent({ sha: sha as string, repoURL, parseText });
|
||||
return content;
|
||||
}
|
||||
|
||||
async readFileMetadata(path: string, sha: string) {
|
||||
async readFileMetadata(path: string, sha: string | null | undefined) {
|
||||
const fetchFileMetadata = async () => {
|
||||
try {
|
||||
const result: Octokit.ReposListCommitsResponse = await this.request(
|
||||
@ -655,7 +651,9 @@ export default class API {
|
||||
}
|
||||
|
||||
async fetchBlobContent({ sha, repoURL, parseText }: BlobArgs) {
|
||||
const result: Octokit.GitGetBlobResponse = await this.request(`${repoURL}/git/blobs/${sha}`);
|
||||
const result: Octokit.GitGetBlobResponse = await this.request(`${repoURL}/git/blobs/${sha}`, {
|
||||
cache: 'force-cache',
|
||||
});
|
||||
|
||||
if (parseText) {
|
||||
// treat content as a utf-8 string
|
||||
@ -969,9 +967,7 @@ export default class API {
|
||||
const fileDataPath = encodeURIComponent(directory);
|
||||
const fileDataURL = `${repoURL}/git/trees/${branch}:${fileDataPath}`;
|
||||
|
||||
const result: Octokit.GitGetTreeResponse = await this.request(fileDataURL, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
const result: Octokit.GitGetTreeResponse = await this.request(fileDataURL);
|
||||
const file = result.tree.find(file => file.path === filename);
|
||||
if (file) {
|
||||
return file.sha;
|
||||
|
@ -93,7 +93,6 @@ describe('github API', () => {
|
||||
beforeEach(() => {
|
||||
const fetch = jest.fn();
|
||||
global.fetch = fetch;
|
||||
global.Date = jest.fn(() => ({ getTime: () => 1000 }));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -112,7 +111,8 @@ describe('github API', () => {
|
||||
const result = await api.request('/some-path');
|
||||
expect(result).toEqual('some response');
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
expect(fetch).toHaveBeenCalledWith('https://api.github.com/some-path?ts=1000', {
|
||||
expect(fetch).toHaveBeenCalledWith('https://api.github.com/some-path', {
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
Authorization: 'token token',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
@ -157,7 +157,8 @@ describe('github API', () => {
|
||||
const result = await api.request('/some-path');
|
||||
expect(result).toEqual('some response');
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
expect(fetch).toHaveBeenCalledWith('https://api.github.com/some-path?ts=1000', {
|
||||
expect(fetch).toHaveBeenCalledWith('https://api.github.com/some-path', {
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
Authorization: 'promise-token',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
|
@ -197,7 +197,7 @@ export default class API {
|
||||
}
|
||||
|
||||
withAuthorizationHeaders = (req: ApiRequest) => {
|
||||
const withHeaders: ApiRequest = unsentRequest.withHeaders(
|
||||
const withHeaders = unsentRequest.withHeaders(
|
||||
this.token ? { Authorization: `Bearer ${this.token}` } : {},
|
||||
req,
|
||||
);
|
||||
@ -206,9 +206,14 @@ export default class API {
|
||||
|
||||
buildRequest = async (req: ApiRequest) => {
|
||||
const withRoot: ApiRequest = unsentRequest.withRoot(this.apiRoot)(req);
|
||||
const withAuthorizationHeaders: ApiRequest = await this.withAuthorizationHeaders(withRoot);
|
||||
const withTimestamp: ApiRequest = unsentRequest.withTimestamp(withAuthorizationHeaders);
|
||||
return withTimestamp;
|
||||
const withAuthorizationHeaders = await this.withAuthorizationHeaders(withRoot);
|
||||
|
||||
if (withAuthorizationHeaders.has('cache')) {
|
||||
return withAuthorizationHeaders;
|
||||
} else {
|
||||
const withNoCache: ApiRequest = unsentRequest.withNoCache(withAuthorizationHeaders);
|
||||
return withNoCache;
|
||||
}
|
||||
};
|
||||
|
||||
request = async (req: ApiRequest): Promise<Response> => {
|
||||
@ -286,7 +291,7 @@ export default class API {
|
||||
return content;
|
||||
};
|
||||
|
||||
async readFileMetadata(path: string, sha: string) {
|
||||
async readFileMetadata(path: string, sha: string | null | undefined) {
|
||||
const fetchFileMetadata = async () => {
|
||||
try {
|
||||
const result: GitLabCommit[] = await this.requestJSON({
|
||||
@ -520,7 +525,6 @@ export default class API {
|
||||
method: 'HEAD',
|
||||
url: `${this.repoURL}/repository/files/${encodeURIComponent(path)}`,
|
||||
params: { ref: branch },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
const blobId = request.headers.get('X-Gitlab-Blob-Id') as string;
|
||||
@ -532,7 +536,6 @@ export default class API {
|
||||
method: 'HEAD',
|
||||
url: `${this.repoURL}/repository/files/${encodeURIComponent(path)}`,
|
||||
params: { ref: branch },
|
||||
cache: 'no-store',
|
||||
})
|
||||
.then(() => true)
|
||||
.catch(error => {
|
||||
|
@ -119,19 +119,21 @@ export type FileMetadata = {
|
||||
const getFileMetadataKey = (id: string) => `gh.${id}.meta`;
|
||||
|
||||
export const readFileMetadata = async (
|
||||
id: string,
|
||||
id: string | null | undefined,
|
||||
fetchMetadata: () => Promise<FileMetadata>,
|
||||
localForage: LocalForage,
|
||||
) => {
|
||||
const key = getFileMetadataKey(id);
|
||||
const cached = await localForage.getItem<FileMetadata>(key);
|
||||
const key = id ? getFileMetadataKey(id) : null;
|
||||
const cached = key && (await localForage.getItem<FileMetadata>(key));
|
||||
if (cached) {
|
||||
return cached;
|
||||
} else {
|
||||
const metadata = await fetchMetadata();
|
||||
await localForage.setItem<FileMetadata>(key, metadata);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const metadata = await fetchMetadata();
|
||||
if (key) {
|
||||
await localForage.setItem<FileMetadata>(key, metadata);
|
||||
}
|
||||
return metadata;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -162,7 +162,7 @@ type ReadFile = (
|
||||
options: { parseText: boolean },
|
||||
) => Promise<string | Blob>;
|
||||
|
||||
type ReadFileMetadata = (path: string, id: string) => Promise<FileMetadata>;
|
||||
type ReadFileMetadata = (path: string, id: string | null | undefined) => Promise<FileMetadata>;
|
||||
|
||||
type ReadUnpublishedFile = (
|
||||
key: string,
|
||||
@ -183,9 +183,7 @@ const fetchFiles = async (
|
||||
try {
|
||||
const [data, fileMetadata] = await Promise.all([
|
||||
readFile(file.path, file.id, { parseText: true }),
|
||||
file.id
|
||||
? readFileMetadata(file.path, file.id)
|
||||
: Promise.resolve({ author: '', updatedOn: '' }),
|
||||
readFileMetadata(file.path, file.id),
|
||||
]);
|
||||
resolve({ file: { ...file, ...fileMetadata }, data: data as string });
|
||||
sem.leave();
|
||||
|
@ -49,26 +49,23 @@ const ensureRequestArg = func => req => func(maybeRequestArg(req));
|
||||
const ensureRequestArg2 = func => (arg, req) => func(arg, maybeRequestArg(req));
|
||||
|
||||
// This actually performs the built request object
|
||||
const performRequest = ensureRequestArg(req => fetch(...toFetchArguments(req)));
|
||||
const performRequest = ensureRequestArg(req => {
|
||||
const args = toFetchArguments(req);
|
||||
return fetch(...args);
|
||||
});
|
||||
|
||||
// Each of the following functions takes options and returns another
|
||||
// function that performs the requested action on a request. They each
|
||||
// default to containing an empty object, so you can simply call them
|
||||
// without arguments to generate a request with only those properties.
|
||||
// function that performs the requested action on a request.
|
||||
const getCurriedRequestProcessor = flow([ensureRequestArg2, curry]);
|
||||
const getPropSetFunctions = path => [
|
||||
getCurriedRequestProcessor((val, req) => req.setIn(path, val)),
|
||||
getCurriedRequestProcessor((val, req) => (req.getIn(path) ? req : req.setIn(path, val))),
|
||||
];
|
||||
const getPropMergeFunctions = path => [
|
||||
getCurriedRequestProcessor((obj, req) => req.updateIn(path, (p = Map()) => p.merge(obj))),
|
||||
getCurriedRequestProcessor((obj, req) => req.updateIn(path, (p = Map()) => Map(obj).merge(p))),
|
||||
];
|
||||
const getPropSetFunction = path => getCurriedRequestProcessor((val, req) => req.setIn(path, val));
|
||||
const getPropMergeFunction = path =>
|
||||
getCurriedRequestProcessor((obj, req) => req.updateIn(path, (p = Map()) => p.merge(obj)));
|
||||
|
||||
const [withMethod, withDefaultMethod] = getPropSetFunctions(['method']);
|
||||
const [withBody, withDefaultBody] = getPropSetFunctions(['body']);
|
||||
const [withParams, withDefaultParams] = getPropMergeFunctions(['params']);
|
||||
const [withHeaders, withDefaultHeaders] = getPropMergeFunctions(['headers']);
|
||||
const withMethod = getPropSetFunction(['method']);
|
||||
const withBody = getPropSetFunction(['body']);
|
||||
const withNoCache = getPropSetFunction(['cache'])('no-cache');
|
||||
const withParams = getPropMergeFunction(['params']);
|
||||
const withHeaders = getPropMergeFunction(['headers']);
|
||||
|
||||
// withRoot sets a root URL, unless the URL is already absolute
|
||||
const absolutePath = new RegExp('^(?:[a-z]+:)?//', 'i');
|
||||
@ -83,24 +80,15 @@ const withRoot = getCurriedRequestProcessor((root, req) =>
|
||||
}),
|
||||
);
|
||||
|
||||
// withTimestamp needs no argument and has to run as late as possible,
|
||||
// so it calls `withParams` only when it's actually called with a
|
||||
// request.
|
||||
const withTimestamp = ensureRequestArg(req => withParams({ ts: new Date().getTime() }, req));
|
||||
|
||||
export default {
|
||||
toURL,
|
||||
fromURL,
|
||||
fromFetchArguments,
|
||||
performRequest,
|
||||
withMethod,
|
||||
withDefaultMethod,
|
||||
withBody,
|
||||
withDefaultBody,
|
||||
withHeaders,
|
||||
withDefaultHeaders,
|
||||
withParams,
|
||||
withDefaultParams,
|
||||
withRoot,
|
||||
withTimestamp,
|
||||
withNoCache,
|
||||
};
|
||||
|
Reference in New Issue
Block a user