Skip to content

Commit 48e044e

Browse files
committed
feat: notifiy the editor about subscriptions
1 parent d339897 commit 48e044e

File tree

3 files changed

+133
-7
lines changed

3 files changed

+133
-7
lines changed

src/__tests__/liveUpdates.spec.ts

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
1-
import { describe, it, vi, expect } from 'vitest';
1+
import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest';
22

3-
import { clone } from '../helpers';
3+
import * as helpers from '../helpers';
44
import { LiveUpdates } from '../liveUpdates';
55
import assetFromEntryEditor from './fixtures/assetFromEntryEditor.json';
66
import landingPageContentType from './fixtures/landingPageContentType.json';
77
import nestedCollectionFromPreviewApp from './fixtures/nestedCollectionFromPreviewApp.json';
88
import nestedDataFromPreviewApp from './fixtures/nestedDataFromPreviewApp.json';
99
import pageInsideCollectionFromEntryEditor from './fixtures/pageInsideCollectionFromEntryEditor.json';
1010

11+
vi.mock('../helpers/debug');
12+
1113
describe('LiveUpdates', () => {
14+
const sendMessage = vi.spyOn(helpers, 'sendMessageToEditor');
15+
16+
beforeEach(() => {
17+
sendMessage.mockImplementation(() => {
18+
// noop
19+
});
20+
});
21+
22+
afterEach(() => {
23+
vi.clearAllMocks();
24+
});
25+
1226
const contentType = {
1327
fields: [
1428
{
@@ -51,6 +65,34 @@ describe('LiveUpdates', () => {
5165
});
5266
});
5367

68+
describe('invalid subscription data', () => {
69+
it('should notifiy because sys information is missing', () => {
70+
const liveUpdates = new LiveUpdates();
71+
const data = { title: 'Data 1', __typename: 'Demo' };
72+
const cb = vi.fn();
73+
74+
liveUpdates.subscribe(data, locale, cb);
75+
76+
expect(helpers.debug.error).toHaveBeenCalledWith(
77+
'Live Updates requires the "sys.id" to be present on the provided data',
78+
data
79+
);
80+
});
81+
82+
it('should notifiy because we dont know if it is REST or GraphQL', () => {
83+
const liveUpdates = new LiveUpdates();
84+
const data = { sys: { id: '1' }, title: 'Data 1' };
85+
const cb = vi.fn();
86+
87+
liveUpdates.subscribe(data, locale, cb);
88+
89+
expect(helpers.debug.error).toHaveBeenCalledWith(
90+
'For live updates as a basic requirement the provided data must include the "fields" property for REST or "__typename" for Graphql, otherwise the data will be treated as invalid and live updates will not work.',
91+
data
92+
);
93+
});
94+
});
95+
5496
it('no longer receives updates after unsubcribing', () => {
5597
const liveUpdates = new LiveUpdates();
5698
const data = { sys: { id: '1' }, title: 'Data 1', __typename: 'Demo' };
@@ -71,7 +113,7 @@ describe('LiveUpdates', () => {
71113

72114
it('ignores invalid messages', () => {
73115
const liveUpdates = new LiveUpdates();
74-
const data = { sys: { id: '1' }, title: 'Data 1' };
116+
const data = { sys: { id: '1' }, title: 'Data 1', __typename: 'Demo' };
75117
const cb = vi.fn();
76118
liveUpdates.subscribe(data, locale, cb);
77119

@@ -97,7 +139,7 @@ describe('LiveUpdates', () => {
97139
liveUpdates.subscribe(nestedDataFromPreviewApp, locale, cb);
98140
liveUpdates.receiveMessage({ entity: assetFromEntryEditor });
99141

100-
const expected = clone(nestedDataFromPreviewApp);
142+
const expected = helpers.clone(nestedDataFromPreviewApp);
101143
expected.featuredImage.title = assetFromEntryEditor.fields.title[locale];
102144
(expected.featuredImage.description as string | null) =
103145
assetFromEntryEditor.fields.description[locale];
@@ -114,12 +156,40 @@ describe('LiveUpdates', () => {
114156
contentType: landingPageContentType,
115157
});
116158

117-
const expected = clone(nestedCollectionFromPreviewApp);
159+
const expected = helpers.clone(nestedCollectionFromPreviewApp);
118160
expected.items[0].menuItemsCollection.items[2].featuredPagesCollection.items[1].pageName =
119161
pageInsideCollectionFromEntryEditor.fields.pageName[locale];
120162
expected.items[0].menuItemsCollection.items[2].featuredPagesCollection.items[1].slug =
121163
pageInsideCollectionFromEntryEditor.fields.slug[locale];
122164

123165
expect(cb).toHaveBeenCalledWith(expected);
124166
});
167+
168+
describe('sendMessageToEditor', () => {
169+
it('sends a message to the editor for a subscription with GQL data', () => {
170+
const liveUpdates = new LiveUpdates();
171+
const data = { sys: { id: '1' }, title: 'Data 1', __typename: 'Demo' };
172+
const cb = vi.fn();
173+
liveUpdates.subscribe(data, locale, cb);
174+
175+
expect(sendMessage).toHaveBeenCalledTimes(1);
176+
expect(sendMessage).toHaveBeenCalledWith({
177+
action: 'SUBSCRIBED',
178+
type: 'GQL',
179+
});
180+
});
181+
182+
it('sends a message to the editor for a subscription with REST data', () => {
183+
const liveUpdates = new LiveUpdates();
184+
const data = { sys: { id: '1' }, fields: { title: 'Data 1' } };
185+
const cb = vi.fn();
186+
liveUpdates.subscribe(data, locale, cb);
187+
188+
expect(sendMessage).toHaveBeenCalledTimes(1);
189+
expect(sendMessage).toHaveBeenCalledWith({
190+
action: 'SUBSCRIBED',
191+
type: 'REST',
192+
});
193+
});
194+
});
125195
});

src/liveUpdates.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { AssetProps, EntryProps } from 'contentful-management';
22

33
import * as gql from './graphql';
4-
import { clone, generateUID, StorageMap } from './helpers';
4+
import { clone, generateUID, sendMessageToEditor, StorageMap, debug } from './helpers';
55
import * as rest from './rest';
66
import {
77
Argument,
@@ -210,16 +210,66 @@ export class LiveUpdates {
210210
}
211211
}
212212

213+
/**
214+
* **Basic** validating of the subscribed data
215+
* Is it GraphQL or REST and does it contain the sys information
216+
* TODO: add more accurate checks
217+
*/
218+
private validateDataFromPreview(data: Argument) {
219+
const dataAsString = JSON.stringify(data);
220+
221+
const isGQL = dataAsString.includes('__typename');
222+
const isREST = dataAsString.includes('fields":{');
223+
const hasSys = dataAsString.includes('sys":{');
224+
225+
let isValid = true;
226+
227+
if (!hasSys) {
228+
isValid = false;
229+
debug.error('Live Updates requires the "sys.id" to be present on the provided data', data);
230+
}
231+
232+
if (!isGQL && !isREST) {
233+
isValid = false;
234+
debug.error(
235+
'For live updates as a basic requirement the provided data must include the "fields" property for REST or "__typename" for Graphql, otherwise the data will be treated as invalid and live updates will not work.',
236+
data
237+
);
238+
}
239+
240+
return {
241+
isGQL,
242+
isREST,
243+
hasSys,
244+
isValid,
245+
};
246+
}
247+
213248
/**
214249
* Subscribe to data changes from the Editor, returns a function to unsubscribe
215250
* Will be called once initially for the restored data
216251
*/
217252
public subscribe(data: Argument, locale: string, cb: SubscribeCallback): VoidFunction {
253+
const { isGQL, isValid } = this.validateDataFromPreview(data);
254+
255+
if (!isValid) {
256+
return () => {
257+
/* noop */
258+
};
259+
}
260+
218261
const id = generateUID();
219262
this.subscriptions.set(id, { data, locale, cb });
220263
// TODO: https://contentful.atlassian.net/browse/TOL-1080
221264
// this.restore(data, cb);
222265

266+
// Tell the editor that there is a subscription
267+
// It's possible that the `type` is not 100% accurate as we don't know how it will be merged in the future.
268+
sendMessageToEditor({
269+
action: 'SUBSCRIBED',
270+
type: isGQL ? 'GQL' : 'REST',
271+
});
272+
223273
return () => {
224274
this.subscriptions.delete(id);
225275
};

src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,17 @@ type UrlChangedMessage = {
6666
action: 'URL_CHANGED';
6767
};
6868

69+
type SubscribedMessage = {
70+
action: 'SUBSCRIBED';
71+
type: 'GQL' | 'REST';
72+
};
73+
6974
export type EditorMessage =
7075
| IframeConnectedMessage
7176
| TaggedFieldClickMessage
7277
| UnknownEntityMessage
73-
| UrlChangedMessage;
78+
| UrlChangedMessage
79+
| SubscribedMessage;
7480

7581
export type MessageFromSDK = EditorMessage & {
7682
from: 'live-preview';

0 commit comments

Comments
 (0)