UNPKG

@stackbit/cms-contentful

Version:

Stackbit Contentful CMS Interface

180 lines 7.83 kB
"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