filestack-js
Version:
Official JavaScript library for Filestack
748 lines (747 loc) • 82.5 kB
JavaScript
/*
* Copyright (c) 2019 by Filestack.
* Some rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { __assign, __awaiter, __generator } from "tslib";
import { config } from './../../config';
import { CloudClient, PICKER_KEY } from './cloud';
import * as utils from './../utils/index.node';
import nock from 'nock';
import { Store, STORE_TYPE } from '../utils/store';
var testApiKey = 'API_KEY';
var testTokSession = 'TOK_SESSION';
var testTokArchiveId = 'ARCHIVE_ID';
var testCloudToken = 'testCloudToken';
var testSecurity = {
policy: 'examplePolicy',
signature: 'exampleSignature',
};
var sessionURls = config.urls;
var testSession = {
apikey: testApiKey,
urls: sessionURls,
};
var scope = nock(sessionURls.cloudApiUrl);
scope.defaultReplyHeaders({
'access-control-allow-origin': function (req) { var _a; return (_a = req.getHeader('origin')) === null || _a === void 0 ? void 0 : _a.toString(); },
'access-control-allow-methods': function (req) { var _a; return (_a = req.getHeader('access-control-request-method')) === null || _a === void 0 ? void 0 : _a.toString(); },
'access-control-allow-headers': function (req) { var _a; return (_a = req.getHeader('access-control-request-headers')) === null || _a === void 0 ? void 0 : _a.toString(); },
'content-type': 'application/json',
});
var mockTokInit = jest
.fn()
.mockName('tokInit')
.mockReturnValue('init');
var mockTokStart = jest
.fn()
.mockName('tokStart')
.mockReturnValue('start');
var mockTokStop = jest
.fn()
.mockName('tokStop')
.mockReturnValue('stop');
var mockMetadata = jest
.fn()
.mockName('metadata')
.mockReturnValue('metadata');
var mockList = jest
.fn()
.mockName('list')
.mockImplementation(function (data) {
if (data && data.clouds.token) {
return { token: testCloudToken };
}
return data;
});
var mockLogout = jest
.fn()
.mockName('logout')
.mockImplementation(function (url, params) {
if (params.clouds && params.clouds.token) {
return { token: testCloudToken };
}
return params;
});
var mockStore = jest
.fn()
.mockName('store')
.mockImplementation(function (params) {
if (params && params.clouds && params.clouds.token) {
return JSON.stringify({ token: testCloudToken });
}
return JSON.stringify(params);
});
describe('cloud', function () {
beforeEach(function () {
scope
.persist()
.options(/.*/)
.reply(204, '', {
'access-control-allow-headers': 'filestack-source,filestack-trace-id,filestack-trace-span',
'access-control-allow-methods': '*',
'access-control-allow-origin': '*',
});
scope.post('/auth/logout').reply(200, mockLogout);
scope.post('/folder/list').reply(200, function (_, data) { return mockList(data); });
scope.post('/store/').reply(200, function (_, data) { return mockStore(data); });
scope.post('/metadata').reply(200, mockMetadata);
scope.post(/\/recording\/(audio|video)\/init/).reply(200, mockTokInit);
scope.post(/\/recording\/(audio|video)\/start/).reply(200, mockTokStart);
scope.post(/\/recording\/(audio|video)\/stop/).reply(200, mockTokStop);
});
afterEach(function () {
nock.cleanAll();
jest.clearAllMocks();
localStorage.clear();
});
describe('cancelToken', function () {
var testDomain = 'http://delay.filestack.test';
var scopeD;
beforeEach(function () {
scopeD = nock(testDomain);
scopeD
.post('/store/')
.delay(4000)
.reply(200);
scopeD
.post('/folder/list')
.delay(4000)
.reply(200);
scopeD
.persist()
.options(/.*/)
.reply(204, '', {
'access-control-allow-headers': 'filestack-source,filestack-trace-id,filestack-trace-span',
'access-control-allow-methods': '*',
'access-control-allow-origin': '*',
});
});
afterEach(function () {
nock.cleanAll();
jest.clearAllMocks();
localStorage.clear();
});
it('Should cancel store request', function (done) {
var sessionClone = JSON.parse(JSON.stringify(testSession));
sessionClone.urls.cloudApiUrl = testDomain;
var token = {};
new CloudClient(sessionClone)
.store('google', 'test', { filename: '1', location: 'gcs' }, {}, token)
.then(function () {
done('Request not canceled');
})
.catch(function (err) {
expect(err).toEqual(expect.any(Error));
done();
});
setTimeout(function () {
// @ts-ignore
token.cancel();
}, 500);
});
it('Should cancel list request', function (done) {
var sessionClone = JSON.parse(JSON.stringify(testSession));
sessionClone.urls.cloudApiUrl = testDomain;
var token = {};
new CloudClient(sessionClone)
.list('google', token)
.then(function () {
done('Request not canceled');
})
.catch(function (err) {
expect(err).toEqual(expect.any(Error));
done();
});
setTimeout(function () {
// @ts-ignore
token.cancel();
}, 500);
});
});
describe('facebook inapp browser', function () {
it('should set token to sessionStore when inapp browser is detected', function () { return __awaiter(void 0, void 0, void 0, function () {
var client, token, store;
return __generator(this, function (_a) {
jest.spyOn(utils, 'isFacebook').mockReturnValue(true);
client = new CloudClient(Object.assign({}, testSession, {
prefetch: {
settings: {
inapp_browser: true,
},
pickerOptions: {},
},
}));
token = 'test';
client.token = token;
store = new Store();
expect(store.getItem(PICKER_KEY, STORE_TYPE.SESSION)).toEqual(token);
store.setItem(PICKER_KEY, undefined, STORE_TYPE.SESSION);
return [2 /*return*/];
});
}); });
it('should get token from sessionStore when inapp browser is detected', function () { return __awaiter(void 0, void 0, void 0, function () {
var client, store, token;
return __generator(this, function (_a) {
jest.spyOn(utils, 'isFacebook').mockReturnValue(true);
client = new CloudClient(Object.assign({}, testSession, {
prefetch: {
settings: {
inapp_browser: true,
},
pickerOptions: {},
},
}));
store = new Store();
token = 'test';
store.setItem(PICKER_KEY, token, STORE_TYPE.SESSION);
expect(client.token).toEqual(token);
store.setItem(PICKER_KEY, undefined, STORE_TYPE.SESSION);
return [2 /*return*/];
});
}); });
it('should send appurl in list action', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, client, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { test: true };
client = new CloudClient(Object.assign({}, testSession, {
prefetch: {
settings: {
inapp_browser: true,
},
pickerOptions: {},
},
}));
return [4 /*yield*/, client.list(__assign({}, clouds))];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
appurl: 'http://localhost/?fs-tab=init',
clouds: clouds,
token: null,
});
return [2 /*return*/];
}
});
}); });
it('should send pass mimetypes to backend as string', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, client, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { test: true };
client = new CloudClient(Object.assign({}, testSession, {
prefetch: {
settings: {
inapp_browser: true,
},
pickerOptions: {},
},
}));
return [4 /*yield*/, client.list(__assign({}, clouds), {}, 'image/*')];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
appurl: 'http://localhost/?fs-tab=init',
clouds: clouds,
token: null,
accept: ['image/*'],
});
return [2 /*return*/];
}
});
}); });
it('should send pass mimetypes to backend as array', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, client, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { test: true };
client = new CloudClient(Object.assign({}, testSession, {
prefetch: {
settings: {
inapp_browser: true,
},
pickerOptions: {},
},
}));
return [4 /*yield*/, client.list(__assign({}, clouds), {}, ['image/*'])];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
appurl: 'http://localhost/?fs-tab=init',
clouds: clouds,
token: null,
accept: ['image/*'],
});
return [2 /*return*/];
}
});
}); });
});
it('should not send app url if urlsearch params is undefined', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, before, client, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { test: true };
before = window.URLSearchParams;
window.URLSearchParams = undefined;
client = new CloudClient(Object.assign({}, testSession, {
prefetch: {
settings: {
inapp_browser: true,
},
pickerOptions: {},
},
}));
return [4 /*yield*/, client.list(__assign({}, clouds))];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
clouds: clouds,
token: null,
});
window.URLSearchParams = before;
return [2 /*return*/];
}
});
}); });
describe('list', function () {
it('should make correct list request', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { test: true };
return [4 /*yield*/, new CloudClient(testSession).list(__assign({}, clouds))];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
clouds: clouds,
});
return [2 /*return*/];
}
});
}); });
it('should make correct list request with session cache', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { test: true };
localStorage.setItem(PICKER_KEY, testCloudToken);
return [4 /*yield*/, new CloudClient(testSession, {
sessionCache: true,
}).list(__assign({}, clouds))];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
token: testCloudToken,
clouds: clouds,
});
return [2 /*return*/];
}
});
}); });
it('should set token on api token response', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { token: true };
return [4 /*yield*/, new CloudClient(testSession).list(__assign({}, clouds))];
case 1:
res = _a.sent();
expect(res).toEqual({ token: testCloudToken });
return [2 /*return*/];
}
});
}); });
it('should cache session token to local storage', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { token: true };
return [4 /*yield*/, new CloudClient(testSession, { sessionCache: true }).list(__assign({}, clouds))];
case 1:
res = _a.sent();
expect(localStorage.setItem).toHaveBeenCalledWith(PICKER_KEY, testCloudToken);
expect(res).toEqual({ token: testCloudToken });
return [2 /*return*/];
}
});
}); });
it('should make correct list request with security', function () { return __awaiter(void 0, void 0, void 0, function () {
var clouds, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
clouds = { test: true };
return [4 /*yield*/, new CloudClient(__assign(__assign({}, testSession), testSecurity)).list(__assign({}, clouds))];
case 1:
res = _a.sent();
expect(res).toEqual(__assign({ apikey: testApiKey, flow: 'web', clouds: clouds }, testSecurity));
return [2 /*return*/];
}
});
}); });
});
describe('store', function () {
it('should make correct basic request', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(testSession).store('google', 'test', { filename: '1' })];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
clouds: {
google: {
path: 'test',
store: {
filename: '1',
location: 's3',
},
},
},
});
return [2 /*return*/];
}
});
}); });
it('should respect store location param', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(testSession).store('google', 'test', { filename: '1', location: 'gcs' })];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
clouds: {
google: {
path: 'test',
store: {
filename: '1',
location: 'gcs',
},
},
},
});
return [2 /*return*/];
}
});
}); });
it('should respect store upload tags', function () { return __awaiter(void 0, void 0, void 0, function () {
var uploadTags, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
uploadTags = { testTag: 'testTag' };
return [4 /*yield*/, new CloudClient(testSession).store('google', 'test', {}, null, null, uploadTags)];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
upload_tags: uploadTags,
clouds: {
google: {
path: 'test',
store: {
location: 's3',
},
},
},
});
return [2 /*return*/];
}
});
}); });
it('should make correct basic with security', function () { return __awaiter(void 0, void 0, void 0, function () {
var res, excepted;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(__assign(__assign({}, testSession), testSecurity)).store('token', 'test', { filename: '1' })];
case 1:
res = _a.sent();
excepted = __assign(__assign({}, testSecurity), { apikey: testApiKey, flow: 'web', clouds: {
token: {
path: 'test',
store: {
filename: '1',
location: 's3',
},
},
} });
expect(mockStore).toHaveBeenCalledWith(excepted);
expect(res).toEqual(testCloudToken);
return [2 /*return*/];
}
});
}); });
it('should handle custom source', function () { return __awaiter(void 0, void 0, void 0, function () {
var customSource, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
customSource = {
customSourcePath: 'cs_path',
customSourceContainer: 'cs_container',
};
return [4 /*yield*/, new CloudClient(testSession).store('customsource', 'test', { filename: '1' }, customSource)];
case 1:
res = _a.sent();
expect(res).toEqual({
apikey: testApiKey,
flow: 'web',
clouds: {
customsource: __assign(__assign({}, customSource), { path: 'test', store: {
filename: '1',
location: 's3',
} }),
},
});
return [2 /*return*/];
}
});
}); });
});
describe('logout', function () {
it('should make correct request to logout', function () { return __awaiter(void 0, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = expect;
return [4 /*yield*/, new CloudClient(testSession).logout()];
case 1:
_a.apply(void 0, [_b.sent()]).toEqual({ apikey: 'API_KEY', flow: 'web' });
return [2 /*return*/];
}
});
}); });
it('should make correct request to logout with provided cloud', function () { return __awaiter(void 0, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = expect;
return [4 /*yield*/, new CloudClient(testSession).logout('google')];
case 1:
_a.apply(void 0, [_b.sent()]).toEqual({ apikey: 'API_KEY', flow: 'web', clouds: { google: {} } });
return [2 /*return*/];
}
});
}); });
it('should make correct request to logout and return correct response when cloud name is returned', function () { return __awaiter(void 0, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = expect;
return [4 /*yield*/, new CloudClient(testSession).logout('token')];
case 1:
_a.apply(void 0, [_b.sent()]).toEqual('testCloudToken');
return [2 /*return*/];
}
});
}); });
it('should make correct request to logout and clean session cache ', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
localStorage.setItem(PICKER_KEY, testCloudToken);
return [4 /*yield*/, new CloudClient(testSession, { sessionCache: true }).logout()];
case 1:
res = _a.sent();
expect(localStorage.removeItem).toHaveBeenCalledWith(PICKER_KEY);
expect(res).toEqual({ apikey: 'API_KEY', flow: 'web', token: testCloudToken });
return [2 /*return*/];
}
});
}); });
});
describe('metadata', function () {
it('should make correct request', function () { return __awaiter(void 0, void 0, void 0, function () {
var testUrl, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
testUrl = 'http://test.com';
return [4 /*yield*/, new CloudClient(testSession).metadata(testUrl)];
case 1:
res = _a.sent();
expect(mockMetadata).toHaveBeenCalledWith(expect.any(String), {
apikey: testApiKey,
url: testUrl,
});
expect(res).toEqual('metadata');
return [2 /*return*/];
}
});
}); });
it('should make correct request with security', function () { return __awaiter(void 0, void 0, void 0, function () {
var testUrl, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
testUrl = 'http://test.com';
return [4 /*yield*/, new CloudClient(__assign(__assign({}, testSession), testSecurity)).metadata(testUrl)];
case 1:
res = _a.sent();
expect(mockMetadata).toHaveBeenCalledWith(expect.any(String), __assign({ apikey: testApiKey, url: testUrl }, testSecurity));
expect(res).toEqual('metadata');
return [2 /*return*/];
}
});
}); });
});
describe('OpenTok', function () {
describe('tokInit', function () {
it('should make correct request to api (audio)', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(testSession).tokInit('audio')];
case 1:
res = _a.sent();
expect(mockTokInit).toHaveBeenCalledWith(expect.any(String), '');
expect(res).toEqual('init');
return [2 /*return*/];
}
});
}); });
it('should make correct request to api (video)', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(testSession).tokInit('audio')];
case 1:
res = _a.sent();
expect(mockTokInit).toHaveBeenCalledWith(expect.any(String), '');
expect(res).toEqual('init');
return [2 /*return*/];
}
});
}); });
it('should throw on wrong type', function () { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
expect(function () {
new CloudClient(testSession)
.tokInit('videoa')
.then(function () {
console.log('init');
})
.catch(function () {
console.log('err');
});
}).toThrowError();
return [2 /*return*/];
});
}); });
});
describe('tokStart', function () {
it('should make correct request to api (audio)', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(testSession).tokStart('audio', 'key', testTokSession)];
case 1:
res = _a.sent();
expect(mockTokStart).toHaveBeenCalledWith(expect.any(String), { apikey: 'key', session_id: testTokSession });
expect(res).toEqual('start');
return [2 /*return*/];
}
});
}); });
it('should make correct request to api (video)', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(testSession).tokStart('video', 'key', testTokSession)];
case 1:
res = _a.sent();
expect(mockTokStart).toHaveBeenCalledWith(expect.any(String), { apikey: 'key', session_id: testTokSession });
expect(res).toEqual('start');
return [2 /*return*/];
}
});
}); });
it('should throw on wrong type', function () {
expect(function () { return new CloudClient(testSession).tokStart('videoa', 'key', testTokSession); }).toThrowError();
});
});
describe('tokStop', function () {
it('should make correct request to api (audio)', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(testSession).tokStop('audio', 'key', testTokSession, testTokArchiveId)];
case 1:
res = _a.sent();
expect(mockTokStop).toHaveBeenCalledWith(expect.any(String), {
apikey: 'key',
session_id: testTokSession,
archive_id: testTokArchiveId,
});
expect(res).toEqual('stop');
return [2 /*return*/];
}
});
}); });
it('should make correct request to api (video)', function () { return __awaiter(void 0, void 0, void 0, function () {
var res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, new CloudClient(testSession).tokStop('video', 'key', testTokSession, testTokArchiveId)];
case 1:
res = _a.sent();
expect(mockTokStop).toHaveBeenCalledWith(expect.any(String), {
apikey: 'key',
session_id: testTokSession,
archive_id: testTokArchiveId,
});
expect(res).toEqual('stop');
return [2 /*return*/];
}
});
}); });
it('should throw on wrong type', function () {
expect(function () { return new CloudClient(testSession).tokStop('videoa', 'key', testTokSession, testTokArchiveId); }).toThrowError();
});
});
});
});
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9saWIvYXBpL2Nsb3VkLnNwZWMuYnJvd3Nlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7O0FBRUgsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ2xELE9BQU8sS0FBSyxLQUFLLE1BQU0sdUJBQXVCLENBQUM7QUFDL0MsT0FBTyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQ3hCLE9BQU8sRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFbkQsSUFBTSxVQUFVLEdBQUcsU0FBUyxDQUFDO0FBQzdCLElBQU0sY0FBYyxHQUFHLGFBQWEsQ0FBQztBQUNyQyxJQUFNLGdCQUFnQixHQUFHLFlBQVksQ0FBQztBQUV0QyxJQUFNLGNBQWMsR0FBRyxnQkFBZ0IsQ0FBQztBQUV4QyxJQUFNLFlBQVksR0FBRztJQUNuQixNQUFNLEVBQUUsZUFBZTtJQUN2QixTQUFTLEVBQUUsa0JBQWtCO0NBQzlCLENBQUM7QUFFRixJQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDO0FBQ2hDLElBQU0sV0FBVyxHQUFHO0lBQ2xCLE1BQU0sRUFBRSxVQUFVO0lBQ2xCLElBQUksRUFBRSxXQUFXO0NBQ2xCLENBQUM7QUFFRixJQUFJLEtBQUssR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBRTFDLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQztJQUN4Qiw2QkFBNkIsRUFBRSxVQUFVLEdBQUcsWUFBSSxPQUFPLE1BQUEsR0FBRyxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsMENBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQzdGLDhCQUE4QixFQUFFLFVBQVUsR0FBRyxZQUFJLE9BQU8sTUFBQSxHQUFHLENBQUMsU0FBUyxDQUFDLCtC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