ncblock 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,9 +10,20 @@ import type { InitMessage } from "./messages/init";
10
10
  * protocol. Increment this number any time a breaking change is made to the bridge protocol.
11
11
  */
12
12
  export declare const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 1;
13
+ /**
14
+ * A single entry in the bridge message log. Kept intentionally plain so the log
15
+ * is copy-pasteable to a local coding agent without needing extra context.
16
+ */
17
+ export type MessageLogEntry = {
18
+ timestamp: string;
19
+ direction: "sent" | "received";
20
+ data: unknown;
21
+ };
13
22
  export declare class SandboxBridge {
14
23
  private hostState;
15
24
  private listeners;
25
+ private messageLog;
26
+ private messageLogListeners;
16
27
  private nextRequestId;
17
28
  private readonly pendingCreatePage;
18
29
  private readonly pendingGetPage;
@@ -23,8 +34,13 @@ export declare class SandboxBridge {
23
34
  private readonly initMessage;
24
35
  private manifest;
25
36
  constructor();
37
+ private static MAX_LOG_ENTRIES;
38
+ private logMessage;
39
+ getMessageLog(): readonly MessageLogEntry[];
40
+ subscribeToMessageLog(listener: () => void): () => boolean;
26
41
  awaitInit(signal?: AbortSignal): Promise<InitMessage>;
27
42
  sendReady(manifest: CustomBlockManifest | null): void;
43
+ private postToHost;
28
44
  private notify;
29
45
  private handleMessage;
30
46
  subscribe(listener: () => void): () => boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"SandboxBridge.d.ts","sourceRoot":"","sources":["../../src/bridge/SandboxBridge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,MAAM,UAAU,CAAA;AAGjB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAChE,OAAO,KAAK,EACX,+BAA+B,EAC/B,gCAAgC,EAChC,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EACN,KAAK,oBAAoB,EAGzB,MAAM,aAAa,CAAA;AAEpB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAOrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAUlD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,IAAI,CAAA;AAErD,qBAAa,aAAa;IACzB,OAAO,CAAC,SAAS,CAGhB;IACD,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAEjC;IACD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE9B;IACD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE9B;IACD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAEhC;IACD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAEjC;IACD,OAAO,CAAC,WAAW,CAA8C;IACjE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAE1B;IACF,OAAO,CAAC,QAAQ,CAAmC;;IAWnD,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAwBrD,SAAS,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAc9C,OAAO,CAAC,MAAM,CAIb;IAED,OAAO,CAAC,aAAa,CAwMpB;IAED,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI;IAK9B,YAAY,IAAI,oBAAoB;IAIpC;;;;;OAKG;IACH,YAAY,CAAC,OAAO,EAAE,WAAW;IAIjC,OAAO,CAAC,SAAS;IAsBjB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAuC1C,UAAU,CAAC,MAAM,EAAE,MAAM;IAczB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqC7D,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAarD,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAarD,SAAS,CAAC,KAAK,GAAE,cAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAc/D,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwC7D;;;;OAIG;IACH,oBAAoB,CAAC,IAAI,EAAE;QAC1B,UAAU,EAAE,gBAAgB,CAAA;QAC5B,MAAM,EAAE,YAAY,CAAA;QACpB,KAAK,EAAE,+BAA+B,CAAA;KACtC,GAAG,OAAO,CAAC,gCAAgC,CAAC;IAuB7C;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;CAsD/B"}
1
+ {"version":3,"file":"SandboxBridge.d.ts","sourceRoot":"","sources":["../../src/bridge/SandboxBridge.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,8 @@ export class SandboxBridge {
19
19
  theme: "light",
20
20
  };
21
21
  this.listeners = new Set();
22
+ this.messageLog = [];
23
+ this.messageLogListeners = new Set();
22
24
  this.nextRequestId = 1;
23
25
  this.pendingCreatePage = new PendingRequests("custom-block-create-page");
24
26
  this.pendingGetPage = new PendingRequests("custom-block-get-page");
@@ -42,6 +44,7 @@ export class SandboxBridge {
42
44
  if (event.source !== window.parent) {
43
45
  return;
44
46
  }
47
+ this.logMessage("received", event.data);
45
48
  const parsed = v.safeParse(hostToSandboxMessageSchema, event.data);
46
49
  if (!parsed.success) {
47
50
  console.warn("[notion-custom-sdk] ignoring malformed host message", parsed.issues);
@@ -56,8 +59,7 @@ export class SandboxBridge {
56
59
  type: "invalidHostMessage",
57
60
  reason: formatInvalidHostReason(incomingType, parsed.issues),
58
61
  };
59
- console.debug("[notion-custom-sdk] outbound postMessage", nack);
60
- window.parent.postMessage(nack, "*");
62
+ this.postToHost(nack);
61
63
  }
62
64
  return;
63
65
  }
@@ -202,6 +204,26 @@ export class SandboxBridge {
202
204
  window.addEventListener("message", this.handleMessage);
203
205
  }
204
206
  }
207
+ logMessage(direction, data) {
208
+ if (this.messageLog.length >= SandboxBridge.MAX_LOG_ENTRIES) {
209
+ this.messageLog.shift();
210
+ }
211
+ this.messageLog.push({
212
+ timestamp: new Date().toISOString(),
213
+ direction,
214
+ data,
215
+ });
216
+ for (const listener of this.messageLogListeners) {
217
+ listener();
218
+ }
219
+ }
220
+ getMessageLog() {
221
+ return this.messageLog;
222
+ }
223
+ subscribeToMessageLog(listener) {
224
+ this.messageLogListeners.add(listener);
225
+ return () => this.messageLogListeners.delete(listener);
226
+ }
205
227
  awaitInit(signal) {
206
228
  if (!signal) {
207
229
  return this.initMessage;
@@ -232,8 +254,12 @@ export class SandboxBridge {
232
254
  bridgeProtocolVersion: CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
233
255
  manifest,
234
256
  };
235
- console.debug("[notion-custom-sdk] outbound postMessage", readyMessage);
236
- window.parent.postMessage(readyMessage, "*");
257
+ this.postToHost(readyMessage);
258
+ }
259
+ postToHost(message) {
260
+ console.debug("[notion-custom-sdk] outbound postMessage", message);
261
+ this.logMessage("sent", message);
262
+ window.parent.postMessage(message, "*");
237
263
  }
238
264
  subscribe(listener) {
239
265
  this.listeners.add(listener);
@@ -301,8 +327,7 @@ export class SandboxBridge {
301
327
  key,
302
328
  limit,
303
329
  };
304
- console.debug("[notion-custom-sdk] outbound postMessage", outbound);
305
- window.parent.postMessage(outbound, "*");
330
+ this.postToHost(outbound);
306
331
  }
307
332
  postResize(height) {
308
333
  if (typeof window === "undefined") {
@@ -313,8 +338,7 @@ export class SandboxBridge {
313
338
  type: "resize",
314
339
  height: safeHeight,
315
340
  };
316
- console.debug("[notion-custom-sdk] outbound postMessage", outbound);
317
- window.parent.postMessage(outbound, "*");
341
+ this.postToHost(outbound);
318
342
  }
319
343
  createPage(input) {
320
344
  return new Promise(resolve => {
@@ -348,8 +372,7 @@ export class SandboxBridge {
348
372
  if (input.position !== undefined) {
349
373
  outbound.position = input.position;
350
374
  }
351
- console.debug("[notion-custom-sdk] outbound postMessage", outbound);
352
- window.parent.postMessage(outbound, "*");
375
+ this.postToHost(outbound);
353
376
  });
354
377
  }
355
378
  getPage(pageId) {
@@ -360,8 +383,7 @@ export class SandboxBridge {
360
383
  requestId,
361
384
  pageId,
362
385
  };
363
- console.debug("[notion-custom-sdk] outbound postMessage", outbound);
364
- window.parent.postMessage(outbound, "*");
386
+ this.postToHost(outbound);
365
387
  });
366
388
  }
367
389
  getUser(userId) {
@@ -372,8 +394,7 @@ export class SandboxBridge {
372
394
  requestId,
373
395
  userId,
374
396
  };
375
- console.debug("[notion-custom-sdk] outbound postMessage", outbound);
376
- window.parent.postMessage(outbound, "*");
397
+ this.postToHost(outbound);
377
398
  });
378
399
  }
379
400
  listUsers(input = {}) {
@@ -385,8 +406,7 @@ export class SandboxBridge {
385
406
  startCursor: input.startCursor,
386
407
  pageSize: input.pageSize,
387
408
  };
388
- console.debug("[notion-custom-sdk] outbound postMessage", outbound);
389
- window.parent.postMessage(outbound, "*");
409
+ this.postToHost(outbound);
390
410
  });
391
411
  }
392
412
  updatePage(input) {
@@ -420,8 +440,7 @@ export class SandboxBridge {
420
440
  if (input.archived !== undefined) {
421
441
  outbound.archived = input.archived;
422
442
  }
423
- console.debug("[notion-custom-sdk] outbound postMessage", outbound);
424
- window.parent.postMessage(outbound, "*");
443
+ this.postToHost(outbound);
425
444
  });
426
445
  }
427
446
  /**
@@ -498,6 +517,7 @@ export class SandboxBridge {
498
517
  }
499
518
  }
500
519
  }
520
+ SandboxBridge.MAX_LOG_ENTRIES = 100;
501
521
  function formatInvalidHostReason(incomingType, issues) {
502
522
  const labelled = incomingType
503
523
  ? `host message of type "${incomingType}"`
@@ -2,6 +2,8 @@ import type { CreatePageInput, CreatePageResult, GetPageResult, GetUserResult, L
2
2
  import { type CustomBlockHostState, type DataSourceQueryView } from "./hostState";
3
3
  import type { CustomBlockManifest } from "./manifest";
4
4
  import type { InitMessage } from "./messages/init";
5
+ import { type MessageLogEntry } from "./SandboxBridge";
6
+ export type { MessageLogEntry };
5
7
  export declare function sendCustomBlockReady(manifest: CustomBlockManifest | null): void;
6
8
  export declare function awaitCustomBlockInit(signal?: AbortSignal): Promise<InitMessage>;
7
9
  export declare function subscribeToCustomBlockHost(listener: () => void): () => boolean;
@@ -15,6 +17,8 @@ export declare function queryCustomBlockDataSource(key: string, limit: number):
15
17
  */
16
18
  export declare function setMockCustomBlockState(message: InitMessage): void;
17
19
  export declare function postCustomBlockResize(height: number): void;
20
+ export declare function getMessageLog(): readonly MessageLogEntry[];
21
+ export declare function subscribeToMessageLog(listener: () => void): () => boolean;
18
22
  export declare function getUser(userId: NotionUserId): Promise<GetUserResult>;
19
23
  export declare function listUsers(input?: ListUsersInput): Promise<ListUsersResult>;
20
24
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sandboxClient.d.ts","sourceRoot":"","sources":["../../src/bridge/sandboxClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,MAAM,UAAU,CAAA;AACjB,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAExB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAKlD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,QAExE;AAED,wBAAgB,oBAAoB,CACnC,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,WAAW,CAAC,CAEtB;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,IAAI,iBAE9D;AAED,wBAAgB,uBAAuB,IAAI,oBAAoB,CAE9D;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAEpE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,QAE3D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,QAEnD;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAEpE;AAED,wBAAgB,SAAS,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAE1E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,SAAS,EAAE,oBAAoB,EAC/B,GAAG,EAAE,MAAM,GACT,mBAAmB,CAIrB;AAED;;GAEG;AACH,eAAO,MAAM,KAAK;IACjB;;OAEG;kBACW,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIzD;;OAEG;gBACS,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAIjD;;OAEG;kBACW,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIzD;;OAEG;mBACY,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAGvD,CAAA"}
1
+ {"version":3,"file":"sandboxClient.d.ts","sourceRoot":"","sources":["../../src/bridge/sandboxClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,MAAM,UAAU,CAAA;AACjB,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAExB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,KAAK,eAAe,EAAiB,MAAM,iBAAiB,CAAA;AAErE,YAAY,EAAE,eAAe,EAAE,CAAA;AAI/B,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,QAExE;AAED,wBAAgB,oBAAoB,CACnC,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,WAAW,CAAC,CAEtB;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,IAAI,iBAE9D;AAED,wBAAgB,uBAAuB,IAAI,oBAAoB,CAE9D;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAEpE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,QAE3D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,QAEnD;AAED,wBAAgB,aAAa,IAAI,SAAS,eAAe,EAAE,CAE1D;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,IAAI,iBAEzD;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAEpE;AAED,wBAAgB,SAAS,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAE1E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,SAAS,EAAE,oBAAoB,EAC/B,GAAG,EAAE,MAAM,GACT,mBAAmB,CAIrB;AAED;;GAEG;AACH,eAAO,MAAM,KAAK;IACjB;;OAEG;kBACW,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIzD;;OAEG;gBACS,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAIjD;;OAEG;kBACW,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIzD;;OAEG;mBACY,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAGvD,CAAA"}
@@ -28,6 +28,12 @@ export function setMockCustomBlockState(message) {
28
28
  export function postCustomBlockResize(height) {
29
29
  bridge.postResize(height);
30
30
  }
31
+ export function getMessageLog() {
32
+ return bridge.getMessageLog();
33
+ }
34
+ export function subscribeToMessageLog(listener) {
35
+ return bridge.subscribeToMessageLog(listener);
36
+ }
31
37
  export function getUser(userId) {
32
38
  return bridge.getUser(userId);
33
39
  }
package/dist/index.d.ts CHANGED
@@ -19,7 +19,8 @@ export type { NotionDataSourceId, NotionSpaceId } from "./bridge/ids";
19
19
  export type { CustomBlockManifest, ManifestDataSource, ManifestIcon, ManifestProperty, } from "./bridge/manifest";
20
20
  export type { NotionCreatePagePosition } from "./bridge/messages/createPage";
21
21
  export type { NotionPage, NotionPageCover, NotionPageIcon, NotionPageId, NotionPageParent, NotionPagePropertyInputMap, NotionPagePropertyInputValue, NotionPagePropertyValue, NotionPagePropertyWriteMap, } from "./bridge/pages/page";
22
- export { pages } from "./bridge/sandboxClient";
22
+ export type { MessageLogEntry } from "./bridge/sandboxClient";
23
+ export { getMessageLog, pages, subscribeToMessageLog, } from "./bridge/sandboxClient";
23
24
  export type { NotionTheme } from "./bridge/theme";
24
25
  export { type CustomBlockInitial, type InitCustomBlockOptions, initCustomBlock, NotInIframeError, } from "./init";
25
26
  export { NotionCustomBlock, type NotionCustomBlockProps, type UseCustomBlockInitResult, useCustomBlockAutoResize, useCustomBlockContext, useCustomBlockInit, useDataSource, useDataSourceDefinitions, useTheme, } from "./react";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAChE,YAAY,EACX,sBAAsB,EACtB,gBAAgB,GAChB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EACX,oBAAoB,EACpB,+BAA+B,EAC/B,gCAAgC,GAChC,MAAM,qCAAqC,CAAA;AAC5C,YAAY,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AACjF,YAAY,EACX,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,gCAAgC,CAAA;AACvC,YAAY,EACX,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GACjB,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EACN,2BAA2B,EAC3B,qBAAqB,GACrB,MAAM,qCAAqC,CAAA;AAC5C,YAAY,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AAC7E,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACrE,YAAY,EACX,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,GAChB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAA;AAC5E,YAAY,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,0BAA0B,EAC1B,4BAA4B,EAC5B,uBAAuB,EACvB,0BAA0B,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,eAAe,EACf,gBAAgB,GAChB,MAAM,QAAQ,CAAA;AACf,OAAO,EACN,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,wBAAwB,EACxB,QAAQ,GACR,MAAM,SAAS,CAAA;AAChB,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAChE,YAAY,EACX,sBAAsB,EACtB,gBAAgB,GAChB,MAAM,iCAAiC,CAAA;AACxC,YAAY,EACX,oBAAoB,EACpB,+BAA+B,EAC/B,gCAAgC,GAChC,MAAM,qCAAqC,CAAA;AAC5C,YAAY,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AACjF,YAAY,EACX,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,gCAAgC,CAAA;AACvC,YAAY,EACX,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GACjB,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EACN,2BAA2B,EAC3B,qBAAqB,GACrB,MAAM,qCAAqC,CAAA;AAC5C,YAAY,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AAC7E,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACrE,YAAY,EACX,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,GAChB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAA;AAC5E,YAAY,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,0BAA0B,EAC1B,4BAA4B,EAC5B,uBAAuB,EACvB,0BAA0B,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EACN,aAAa,EACb,KAAK,EACL,qBAAqB,GACrB,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,eAAe,EACf,gBAAgB,GAChB,MAAM,QAAQ,CAAA;AACf,OAAO,EACN,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,wBAAwB,EACxB,QAAQ,GACR,MAAM,SAAS,CAAA;AAChB,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA"}
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * downstream code.
9
9
  */
10
10
  export { NOTION_BUILTIN_PROPERTY_IDS, NOTION_PROPERTY_TYPES, } from "./bridge/dataSources/propertySchema";
11
- export { pages } from "./bridge/sandboxClient";
11
+ export { getMessageLog, pages, subscribeToMessageLog, } from "./bridge/sandboxClient";
12
12
  export { initCustomBlock, NotInIframeError, } from "./init";
13
13
  export { NotionCustomBlock, useCustomBlockAutoResize, useCustomBlockContext, useCustomBlockInit, useDataSource, useDataSourceDefinitions, useTheme, } from "./react";
14
14
  export * from "./types";
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EAKd,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAUvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAG3B,MAAM,QAAQ,CAAA;AACf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAsBlD;;;;;;;;;GASG;AACH,MAAM,MAAM,wBAAwB,GACjC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GACrC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GACjC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAAA;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,CAAC,EAAE,sBAAsB,GAC3B,wBAAwB,CA8B1B;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG;IAC7D,QAAQ,EAAE,SAAS,CAAA;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAA;IACzD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,EACjC,QAAQ,EACR,SAAS,EACT,QAAe,EACf,aAAa,EACb,UAAiB,GACjB,EAAE,sBAAsB,2CAyDxB;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,IAAI,wBAAwB,CAIhE;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,IAAI,WAAW,CAItC;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,gBAAgB,EAAE,CAI7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,MAAwC,GACpD,mBAAmB,CAiDrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,GAAE;IACL;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CACZ,GACJ,IAAI,CAmCN"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EAKd,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAYvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAG3B,MAAM,QAAQ,CAAA;AACf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAsBlD;;;;;;;;;GASG;AACH,MAAM,MAAM,wBAAwB,GACjC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GACrC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GACjC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAAA;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,CAAC,EAAE,sBAAsB,GAC3B,wBAAwB,CA8B1B;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG;IAC7D,QAAQ,EAAE,SAAS,CAAA;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAA;IACzD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,EACjC,QAAQ,EACR,SAAS,EACT,QAAe,EACf,aAAa,EACb,UAAiB,GACjB,EAAE,sBAAsB,2CAoExB;AAwCD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,IAAI,wBAAwB,CAIhE;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,IAAI,WAAW,CAItC;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,gBAAgB,EAAE,CAI7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,MAAwC,GACpD,mBAAmB,CAiDrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,GAAE;IACL;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CACZ,GACJ,IAAI,CAmCN"}
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useState, useSyncExternalStore, } from "react";
3
- import { getCustomBlockHostState, getDataSourceQueryView, postCustomBlockResize, queryCustomBlockDataSource, setMockCustomBlockState, subscribeToCustomBlockHost, } from "./bridge/sandboxClient";
3
+ import { getCustomBlockHostState, getDataSourceQueryView, getMessageLog, postCustomBlockResize, queryCustomBlockDataSource, setMockCustomBlockState, subscribeToCustomBlockHost, subscribeToMessageLog, } from "./bridge/sandboxClient";
4
4
  import { initCustomBlock, NotInIframeError, } from "./init";
5
5
  const DEFAULT_DATA_SOURCE_QUERY_LIMIT = 20;
6
6
  function useCustomBlockHost() {
@@ -80,6 +80,16 @@ export function NotionCustomBlock({ children, timeoutMs, fallback = null, errorF
80
80
  useCustomBlockAutoResize({ enabled: autoResize });
81
81
  const isStandalone = init.error instanceof NotInIframeError;
82
82
  const host = useCustomBlockHost();
83
+ const [debugOpen, setDebugOpen] = useState(false);
84
+ useEffect(() => {
85
+ const onKeyDown = (e) => {
86
+ if (e.key === "\\") {
87
+ setDebugOpen(prev => !prev);
88
+ }
89
+ };
90
+ window.addEventListener("keydown", onKeyDown);
91
+ return () => window.removeEventListener("keydown", onKeyDown);
92
+ }, []);
83
93
  useEffect(() => {
84
94
  if (!isStandalone) {
85
95
  return;
@@ -110,13 +120,32 @@ export function NotionCustomBlock({ children, timeoutMs, fallback = null, errorF
110
120
  if (host.status !== "initialized") {
111
121
  return _jsx(_Fragment, { children: fallback });
112
122
  }
113
- return (_jsxs(_Fragment, { children: [_jsx("div", { role: "status", style: STANDALONE_BANNER_STYLE, children: "Notion host not detected \u2014 running in standalone preview. SDK hooks return placeholder values until embedded in Notion." }), children] }));
123
+ return (_jsxs(_Fragment, { children: [_jsx("div", { role: "status", style: STANDALONE_BANNER_STYLE, children: "Notion host not detected \u2014 running in standalone preview. SDK hooks return placeholder values until embedded in Notion." }), debugOpen ? _jsx(DebugMessageLog, {}) : children] }));
114
124
  }
115
125
  if (!init.isLoaded) {
116
126
  return _jsx(_Fragment, { children: fallback });
117
127
  }
118
- return _jsx(_Fragment, { children: children });
128
+ return _jsx(_Fragment, { children: debugOpen ? _jsx(DebugMessageLog, {}) : children });
119
129
  }
130
+ function DebugMessageLog() {
131
+ const log = useSyncExternalStore(subscribeToMessageLog, getMessageLog);
132
+ const text = log
133
+ .map(entry => `[${entry.timestamp}] ${entry.direction}: ${JSON.stringify(entry.data)}`)
134
+ .join("\n");
135
+ return (_jsx("pre", { "data-testid": "debug-message-log", style: DEBUG_PRE_STYLE, children: text || "(no messages yet)" }));
136
+ }
137
+ const DEBUG_PRE_STYLE = {
138
+ margin: 0,
139
+ padding: "8px 12px",
140
+ fontSize: 12,
141
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
142
+ lineHeight: 1.5,
143
+ whiteSpace: "pre-wrap",
144
+ wordBreak: "break-all",
145
+ userSelect: "text",
146
+ background: "#1e1e1e",
147
+ color: "#d4d4d4",
148
+ };
120
149
  const STANDALONE_BANNER_STYLE = {
121
150
  padding: "8px 12px",
122
151
  background: "#fff8e1",
package/docs/lifecycle.md CHANGED
@@ -90,3 +90,35 @@ function App() {
90
90
  return <div>…</div>;
91
91
  }
92
92
  ```
93
+
94
+ ## Debug console
95
+
96
+ Press `\` while focused in a custom block to toggle a debug overlay that replaces the block's children with a `<pre>` log of every `postMessage` sent and received over the bridge. Each line is formatted as:
97
+
98
+ ```
99
+ [ISO timestamp] sent/received: {"type":"ready", …}
100
+ ```
101
+
102
+ The log is intentionally plain — no filtering or decoration — so it can be copied and pasted directly to a local coding agent for debugging.
103
+
104
+ ### `getMessageLog()`
105
+
106
+ ```ts
107
+ function getMessageLog(): readonly MessageLogEntry[];
108
+
109
+ type MessageLogEntry = {
110
+ timestamp: string;
111
+ direction: "sent" | "received";
112
+ data: unknown;
113
+ };
114
+ ```
115
+
116
+ Returns the full in-memory log. Entries accumulate for the lifetime of the page.
117
+
118
+ ### `subscribeToMessageLog(listener)`
119
+
120
+ ```ts
121
+ function subscribeToMessageLog(listener: () => void): () => void;
122
+ ```
123
+
124
+ Subscribe to new log entries. Compatible with `useSyncExternalStore` for custom debug UIs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ncblock",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -50,12 +50,24 @@ import type { NotionUser } from "./users/user"
50
50
  */
51
51
  export const CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION = 1
52
52
 
53
+ /**
54
+ * A single entry in the bridge message log. Kept intentionally plain so the log
55
+ * is copy-pasteable to a local coding agent without needing extra context.
56
+ */
57
+ export type MessageLogEntry = {
58
+ timestamp: string
59
+ direction: "sent" | "received"
60
+ data: unknown
61
+ }
62
+
53
63
  export class SandboxBridge {
54
64
  private hostState: CustomBlockHostState = {
55
65
  status: "uninitialized",
56
66
  theme: "light",
57
67
  }
58
68
  private listeners = new Set<() => void>()
69
+ private messageLog: MessageLogEntry[] = []
70
+ private messageLogListeners = new Set<() => void>()
59
71
  private nextRequestId = 1
60
72
  private readonly pendingCreatePage = new PendingRequests<CreatePageResult>(
61
73
  "custom-block-create-page",
@@ -87,6 +99,31 @@ export class SandboxBridge {
87
99
  }
88
100
  }
89
101
 
102
+ private static MAX_LOG_ENTRIES = 100
103
+
104
+ private logMessage(direction: "sent" | "received", data: unknown) {
105
+ if (this.messageLog.length >= SandboxBridge.MAX_LOG_ENTRIES) {
106
+ this.messageLog.shift()
107
+ }
108
+ this.messageLog.push({
109
+ timestamp: new Date().toISOString(),
110
+ direction,
111
+ data,
112
+ })
113
+ for (const listener of this.messageLogListeners) {
114
+ listener()
115
+ }
116
+ }
117
+
118
+ getMessageLog(): readonly MessageLogEntry[] {
119
+ return this.messageLog
120
+ }
121
+
122
+ subscribeToMessageLog(listener: () => void) {
123
+ this.messageLogListeners.add(listener)
124
+ return () => this.messageLogListeners.delete(listener)
125
+ }
126
+
90
127
  awaitInit(signal?: AbortSignal): Promise<InitMessage> {
91
128
  if (!signal) {
92
129
  return this.initMessage
@@ -121,8 +158,13 @@ export class SandboxBridge {
121
158
  bridgeProtocolVersion: CUSTOM_BLOCK_BRIDGE_PROTOCOL_VERSION,
122
159
  manifest,
123
160
  }
124
- console.debug("[notion-custom-sdk] outbound postMessage", readyMessage)
125
- window.parent.postMessage(readyMessage, "*")
161
+ this.postToHost(readyMessage)
162
+ }
163
+
164
+ private postToHost(message: unknown) {
165
+ console.debug("[notion-custom-sdk] outbound postMessage", message)
166
+ this.logMessage("sent", message)
167
+ window.parent.postMessage(message, "*")
126
168
  }
127
169
 
128
170
  private notify = () => {
@@ -139,6 +181,7 @@ export class SandboxBridge {
139
181
  if (event.source !== window.parent) {
140
182
  return
141
183
  }
184
+ this.logMessage("received", event.data)
142
185
 
143
186
  const parsed = v.safeParse(hostToSandboxMessageSchema, event.data)
144
187
  if (!parsed.success) {
@@ -159,8 +202,7 @@ export class SandboxBridge {
159
202
  type: "invalidHostMessage",
160
203
  reason: formatInvalidHostReason(incomingType, parsed.issues),
161
204
  }
162
- console.debug("[notion-custom-sdk] outbound postMessage", nack)
163
- window.parent.postMessage(nack, "*")
205
+ this.postToHost(nack)
164
206
  }
165
207
  return
166
208
  }
@@ -409,8 +451,7 @@ export class SandboxBridge {
409
451
  key,
410
452
  limit,
411
453
  }
412
- console.debug("[notion-custom-sdk] outbound postMessage", outbound)
413
- window.parent.postMessage(outbound, "*")
454
+ this.postToHost(outbound)
414
455
  }
415
456
 
416
457
  postResize(height: number) {
@@ -423,8 +464,7 @@ export class SandboxBridge {
423
464
  type: "resize",
424
465
  height: safeHeight,
425
466
  }
426
- console.debug("[notion-custom-sdk] outbound postMessage", outbound)
427
- window.parent.postMessage(outbound, "*")
467
+ this.postToHost(outbound)
428
468
  }
429
469
 
430
470
  createPage(input: CreatePageInput): Promise<CreatePageResult> {
@@ -459,8 +499,7 @@ export class SandboxBridge {
459
499
  if (input.position !== undefined) {
460
500
  outbound.position = input.position
461
501
  }
462
- console.debug("[notion-custom-sdk] outbound postMessage", outbound)
463
- window.parent.postMessage(outbound, "*")
502
+ this.postToHost(outbound)
464
503
  })
465
504
  }
466
505
 
@@ -472,8 +511,7 @@ export class SandboxBridge {
472
511
  requestId,
473
512
  pageId,
474
513
  }
475
- console.debug("[notion-custom-sdk] outbound postMessage", outbound)
476
- window.parent.postMessage(outbound, "*")
514
+ this.postToHost(outbound)
477
515
  })
478
516
  }
479
517
 
@@ -485,8 +523,7 @@ export class SandboxBridge {
485
523
  requestId,
486
524
  userId,
487
525
  }
488
- console.debug("[notion-custom-sdk] outbound postMessage", outbound)
489
- window.parent.postMessage(outbound, "*")
526
+ this.postToHost(outbound)
490
527
  })
491
528
  }
492
529
 
@@ -499,8 +536,7 @@ export class SandboxBridge {
499
536
  startCursor: input.startCursor,
500
537
  pageSize: input.pageSize,
501
538
  }
502
- console.debug("[notion-custom-sdk] outbound postMessage", outbound)
503
- window.parent.postMessage(outbound, "*")
539
+ this.postToHost(outbound)
504
540
  })
505
541
  }
506
542
 
@@ -539,8 +575,7 @@ export class SandboxBridge {
539
575
  if (input.archived !== undefined) {
540
576
  outbound.archived = input.archived
541
577
  }
542
- console.debug("[notion-custom-sdk] outbound postMessage", outbound)
543
- window.parent.postMessage(outbound, "*")
578
+ this.postToHost(outbound)
544
579
  })
545
580
  }
546
581
 
@@ -17,7 +17,9 @@ import {
17
17
  } from "./hostState"
18
18
  import type { CustomBlockManifest } from "./manifest"
19
19
  import type { InitMessage } from "./messages/init"
20
- import { SandboxBridge } from "./SandboxBridge"
20
+ import { type MessageLogEntry, SandboxBridge } from "./SandboxBridge"
21
+
22
+ export type { MessageLogEntry }
21
23
 
22
24
  const bridge = new SandboxBridge()
23
25
 
@@ -57,6 +59,14 @@ export function postCustomBlockResize(height: number) {
57
59
  bridge.postResize(height)
58
60
  }
59
61
 
62
+ export function getMessageLog(): readonly MessageLogEntry[] {
63
+ return bridge.getMessageLog()
64
+ }
65
+
66
+ export function subscribeToMessageLog(listener: () => void) {
67
+ return bridge.subscribeToMessageLog(listener)
68
+ }
69
+
60
70
  export function getUser(userId: NotionUserId): Promise<GetUserResult> {
61
71
  return bridge.getUser(userId)
62
72
  }
package/src/index.ts CHANGED
@@ -63,7 +63,12 @@ export type {
63
63
  NotionPagePropertyValue,
64
64
  NotionPagePropertyWriteMap,
65
65
  } from "./bridge/pages/page"
66
- export { pages } from "./bridge/sandboxClient"
66
+ export type { MessageLogEntry } from "./bridge/sandboxClient"
67
+ export {
68
+ getMessageLog,
69
+ pages,
70
+ subscribeToMessageLog,
71
+ } from "./bridge/sandboxClient"
67
72
  export type { NotionTheme } from "./bridge/theme"
68
73
  export {
69
74
  type CustomBlockInitial,
package/src/react.tsx CHANGED
@@ -11,10 +11,12 @@ import type { CustomBlockHostState } from "./bridge/hostState"
11
11
  import {
12
12
  getCustomBlockHostState,
13
13
  getDataSourceQueryView,
14
+ getMessageLog,
14
15
  postCustomBlockResize,
15
16
  queryCustomBlockDataSource,
16
17
  setMockCustomBlockState,
17
18
  subscribeToCustomBlockHost,
19
+ subscribeToMessageLog,
18
20
  } from "./bridge/sandboxClient"
19
21
  import type { NotionTheme } from "./bridge/theme"
20
22
  import {
@@ -168,6 +170,17 @@ export function NotionCustomBlock({
168
170
  useCustomBlockAutoResize({ enabled: autoResize })
169
171
  const isStandalone = init.error instanceof NotInIframeError
170
172
  const host = useCustomBlockHost()
173
+ const [debugOpen, setDebugOpen] = useState(false)
174
+
175
+ useEffect(() => {
176
+ const onKeyDown = (e: KeyboardEvent) => {
177
+ if (e.key === "\\") {
178
+ setDebugOpen(prev => !prev)
179
+ }
180
+ }
181
+ window.addEventListener("keydown", onKeyDown)
182
+ return () => window.removeEventListener("keydown", onKeyDown)
183
+ }, [])
171
184
 
172
185
  useEffect(() => {
173
186
  if (!isStandalone) {
@@ -212,14 +225,42 @@ export function NotionCustomBlock({
212
225
  Notion host not detected — running in standalone preview. SDK hooks
213
226
  return placeholder values until embedded in Notion.
214
227
  </div>
215
- {children}
228
+ {debugOpen ? <DebugMessageLog /> : children}
216
229
  </>
217
230
  )
218
231
  }
219
232
  if (!init.isLoaded) {
220
233
  return <>{fallback}</>
221
234
  }
222
- return <>{children}</>
235
+ return <>{debugOpen ? <DebugMessageLog /> : children}</>
236
+ }
237
+
238
+ function DebugMessageLog() {
239
+ const log = useSyncExternalStore(subscribeToMessageLog, getMessageLog)
240
+ const text = log
241
+ .map(
242
+ entry =>
243
+ `[${entry.timestamp}] ${entry.direction}: ${JSON.stringify(entry.data)}`,
244
+ )
245
+ .join("\n")
246
+ return (
247
+ <pre data-testid="debug-message-log" style={DEBUG_PRE_STYLE}>
248
+ {text || "(no messages yet)"}
249
+ </pre>
250
+ )
251
+ }
252
+
253
+ const DEBUG_PRE_STYLE = {
254
+ margin: 0,
255
+ padding: "8px 12px",
256
+ fontSize: 12,
257
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
258
+ lineHeight: 1.5,
259
+ whiteSpace: "pre-wrap" as const,
260
+ wordBreak: "break-all" as const,
261
+ userSelect: "text" as const,
262
+ background: "#1e1e1e",
263
+ color: "#d4d4d4",
223
264
  }
224
265
 
225
266
  const STANDALONE_BANNER_STYLE = {