@stackbit/cms-contentful
Version:
Stackbit Contentful CMS Interface
180 lines • 7.83 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContentPoller = void 0;
const lodash_1 = __importDefault(require("lodash"));
const contentful_1 = require("contentful");
const contentful_api_client_1 = require("./contentful-api-client");
const utils_1 = require("./utils");
class ContentPoller {
constructor({ spaceId, environment, previewToken, managementToken, previewHost, managementHost, uploadHost, pollingIntervalMs = 1000, pollType, syncContext, notificationCallback, logger }) {
this.logger = logger;
this.nextSyncToken = null;
this.running = false;
this.pollTimeout = null;
this.pollingIntervalMs = pollingIntervalMs;
this.pollType = pollType ?? 'date';
this.notificationCallback = notificationCallback;
this.setSyncContext(syncContext);
this.client = (0, contentful_1.createClient)({
space: spaceId,
environment: environment,
accessToken: previewToken,
host: previewHost ?? 'preview.contentful.com'
});
this.managementClient = (0, contentful_api_client_1.createPlainApiClient)({
accessToken: managementToken,
spaceId,
environment,
managementHost,
uploadHost
});
this.handleTimeout = this.handleTimeout.bind(this);
}
start() {
this.logger.debug(`start polling contentful for content changes using ${this.pollType}`);
if (!this.running) {
this.running = true;
this.setPollTimeout();
}
}
stop() {
this.logger.debug(`stop polling contentful for content changes`);
if (this.pollTimeout) {
clearTimeout(this.pollTimeout);
this.pollTimeout = null;
}
this.running = false;
}
setSyncContext(syncContext) {
const now = new Date().toISOString();
this.syncContext = {
lastUpdatedEntryDate: syncContext?.lastUpdatedEntryDate ?? now,
lastUpdatedAssetDate: syncContext?.lastUpdatedAssetDate ?? now,
lastUpdatedContentTypeDate: syncContext?.lastUpdatedContentTypeDate ?? now
};
}
setPollTimeout() {
if (this.pollTimeout) {
clearTimeout(this.pollTimeout);
}
this.pollTimeout = setTimeout(this.handleTimeout, this.pollingIntervalMs);
}
async handleTimeout() {
this.pollTimeout = null;
try {
if (this.pollType === 'sync') {
await this.pollSync();
}
else {
await this.pollDate();
}
}
catch (error) {
this.logger.warn('error polling', { error: error.message });
}
if (this.running) {
this.setPollTimeout();
}
}
async pollDate() {
const prevSyncContext = this.syncContext;
const entries = await (0, contentful_api_client_1.fetchEntriesUpdatedAfter)(this.managementClient, this.syncContext.lastUpdatedEntryDate);
const assets = await (0, contentful_api_client_1.fetchAssetsUpdatedAfter)(this.managementClient, this.syncContext.lastUpdatedAssetDate);
const contentTypes = await (0, contentful_api_client_1.fetchContentTypesUpdatedAfter)(this.managementClient, this.syncContext.lastUpdatedContentTypeDate);
// Check that syncContext wasn't updated externally via setSyncContext() while the content was fetched.
// If it was updated (prevSyncContext != this.syncContext), then ignore the dates from the fetched content
// and keep the syncContext that was set externally via setSyncContext().
if (lodash_1.default.isEqual(prevSyncContext, this.syncContext)) {
this.syncContext = lodash_1.default.defaults({
lastUpdatedEntryDate: (0, utils_1.getLastUpdatedEntityDate)(entries),
lastUpdatedAssetDate: (0, utils_1.getLastUpdatedEntityDate)(assets),
lastUpdatedContentTypeDate: (0, utils_1.getLastUpdatedEntityDate)(contentTypes)
}, this.syncContext);
}
if (contentTypes.length || entries.length || assets.length) {
this.logger.info(`poll date response: got ` +
`${entries.length} changed entries, ` +
`${assets.length} changed assets, ` +
`${contentTypes.length} changed content types`);
await this.notificationCallback({
contentTypes,
entries,
assets,
deletedEntries: [],
deletedAssets: []
});
}
}
async pollSync() {
const initial = this.nextSyncToken === null;
let hasMoreItems = true;
let hasItems = false;
const result = {
entries: [],
assets: [],
deletedEntries: [],
deletedAssets: [],
contentTypes: []
};
if (initial) {
this.logger.info('Running initial sync...');
}
while (hasMoreItems) {
const response = await this.client.sync({
initial: this.nextSyncToken === null,
nextSyncToken: this.nextSyncToken,
resolveLinks: false,
limit: 1000
});
this.logger.info(`sync response: ${response.entries.length}`);
const isEmptyResponse = lodash_1.default.every(lodash_1.default.pick(response, ['entries', 'assets', 'deletedEntries', 'deletedAssets']), lodash_1.default.isEmpty);
if (this.nextSyncToken === response.nextSyncToken || isEmptyResponse) {
hasMoreItems = false;
}
else {
if (!initial) {
// refetch entries from management api to get full sys data
const { entries, assets } = await this.batchRefetchData({
entryIds: response.entries.map((entry) => entry.sys.id),
assetIds: response.assets.map((entry) => entry.sys.id)
});
hasItems = true;
result.entries = result.entries.concat(entries);
result.assets = result.assets.concat(assets);
result.deletedEntries = result.deletedEntries.concat(response.deletedEntries);
result.deletedAssets = result.deletedAssets.concat(response.deletedAssets);
}
}
this.nextSyncToken = response.nextSyncToken;
}
if (!initial && hasItems) {
await this.notificationCallback(result);
}
if (initial) {
this.logger.info('Initial sync done.');
}
}
async batchRefetchData({ entryIds, assetIds }) {
const limit = 300;
const entryChunks = lodash_1.default.chunk(entryIds, limit);
const entries = lodash_1.default.flatMap(await Promise.all(entryChunks.map((chunk) => this.managementClient.entry.getMany({
query: {
limit: limit,
'sys.id[in]': chunk.join(',')
}
}))), (result) => result.items);
const assetChunks = lodash_1.default.chunk(assetIds, limit);
const assets = lodash_1.default.flatMap(await Promise.all(assetChunks.map((chunk) => this.managementClient.asset.getMany({
query: {
limit: limit,
'sys.id[in]': chunk.join(',')
}
}))), (result) => result.items);
return { entries, assets };
}
}
exports.ContentPoller = ContentPoller;
//# sourceMappingURL=content-poller.js.map