@no-mess/client 0.2.0 → 0.3.0

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.
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@ export { NoMessClient } from "./client.js";
2
2
  export { createLiveEditHandler } from "./live-edit.js";
3
3
  export { DEFAULT_ADMIN_ORIGIN, DEFAULT_API_URL, isPublishableKey, isSecretKey, NoMessError, } from "./types.js";
4
4
  import { NoMessClient } from "./client.js";
5
+ import { createNoMessProtocolError, normalizeNoMessError, } from "./error-utils.js";
6
+ import { createSdkLogger } from "./logging.js";
5
7
  /**
6
8
  * Create a no-mess client instance.
7
9
  */
@@ -25,48 +27,172 @@ export function createNoMessClient(config) {
25
27
  * ```
26
28
  */
27
29
  export function createPreviewHandler(config) {
30
+ const logger = createSdkLogger(config.logger);
28
31
  let sessionAuth = null;
32
+ let disposed = false;
33
+ let inFlightRequestId = 0;
34
+ let latestCompletedRequestId = 0;
35
+ const logEvent = (level, code, message, context, error) => {
36
+ logger({
37
+ level,
38
+ code,
39
+ message,
40
+ scope: "preview",
41
+ operation: "createPreviewHandler",
42
+ error: error instanceof Error ? error : undefined,
43
+ timestamp: new Date().toISOString(),
44
+ context,
45
+ });
46
+ };
47
+ const invokeOnError = (error) => {
48
+ try {
49
+ config.onError?.(error);
50
+ }
51
+ catch (callbackError) {
52
+ logEvent("error", "preview_exchange_failed", "Preview onError callback threw unexpectedly", {}, normalizeNoMessError(callbackError, {
53
+ kind: "runtime",
54
+ code: "preview_exchange_failed",
55
+ operation: "preview.onError",
56
+ }));
57
+ }
58
+ };
59
+ const postToParent = (message, origin, context) => {
60
+ try {
61
+ window.parent.postMessage(message, origin);
62
+ return true;
63
+ }
64
+ catch (error) {
65
+ const normalized = normalizeNoMessError(error, {
66
+ kind: "runtime",
67
+ code: "preview_postmessage_failed",
68
+ operation: "preview.postMessage",
69
+ details: context,
70
+ });
71
+ logEvent("error", normalized.code, "Failed to post preview message to parent window", {
72
+ ...context,
73
+ targetOrigin: origin,
74
+ }, normalized);
75
+ return false;
76
+ }
77
+ };
78
+ const emitPreviewError = (error, origin, context) => {
79
+ invokeOnError(error);
80
+ const normalized = normalizeNoMessError(error, {
81
+ kind: "runtime",
82
+ code: "preview_exchange_failed",
83
+ operation: "preview.emitError",
84
+ details: context,
85
+ });
86
+ logEvent("error", normalized.code, normalized.message, context, normalized);
87
+ postToParent({
88
+ type: "no-mess:preview-error",
89
+ error: normalized.message,
90
+ code: normalized.code,
91
+ status: normalized.status,
92
+ retryable: normalized.retryable,
93
+ }, origin, context);
94
+ };
95
+ const runExchange = async (reason, origin) => {
96
+ if (!sessionAuth) {
97
+ logEvent("debug", "preview_message_invalid", "Preview refresh ignored because no session is available", {
98
+ reason,
99
+ });
100
+ return;
101
+ }
102
+ const requestId = ++inFlightRequestId;
103
+ try {
104
+ const result = await config.client.exchangePreviewSession(sessionAuth);
105
+ if (disposed ||
106
+ requestId < inFlightRequestId ||
107
+ requestId <= latestCompletedRequestId) {
108
+ logEvent("debug", "preview_exchange_failed", "Discarded stale preview exchange result", {
109
+ reason,
110
+ requestId,
111
+ inFlightRequestId,
112
+ latestCompletedRequestId,
113
+ });
114
+ return;
115
+ }
116
+ latestCompletedRequestId = requestId;
117
+ try {
118
+ config.onEntry(result.entry);
119
+ }
120
+ catch (error) {
121
+ emitPreviewError(normalizeNoMessError(error, {
122
+ kind: "runtime",
123
+ code: "preview_exchange_failed",
124
+ operation: "preview.onEntry",
125
+ details: { reason },
126
+ }), origin, { reason, requestId, phase: "onEntry" });
127
+ return;
128
+ }
129
+ postToParent({ type: "no-mess:preview-loaded" }, origin, { reason, requestId, phase: "loaded" });
130
+ }
131
+ catch (error) {
132
+ if (disposed ||
133
+ requestId < inFlightRequestId ||
134
+ requestId <= latestCompletedRequestId) {
135
+ logEvent("debug", "preview_exchange_failed", "Discarded stale preview exchange error", {
136
+ reason,
137
+ requestId,
138
+ inFlightRequestId,
139
+ latestCompletedRequestId,
140
+ });
141
+ return;
142
+ }
143
+ latestCompletedRequestId = requestId;
144
+ emitPreviewError(normalizeNoMessError(error, {
145
+ kind: "runtime",
146
+ code: "preview_exchange_failed",
147
+ operation: "preview.exchange",
148
+ details: { reason },
149
+ }), origin, { reason, requestId, phase: "exchange" });
150
+ }
151
+ };
29
152
  const handleMessage = async (event) => {
30
- if (event.origin !== config.adminOrigin)
153
+ if (disposed || event.origin !== config.adminOrigin)
31
154
  return;
32
155
  const data = event.data;
33
156
  if (!data || typeof data.type !== "string")
34
157
  return;
35
158
  if (data.type === "no-mess:session-auth") {
159
+ if (typeof data.sessionId !== "string" ||
160
+ typeof data.sessionSecret !== "string") {
161
+ emitPreviewError(createNoMessProtocolError("Received invalid preview session credentials", {
162
+ kind: "protocol",
163
+ code: "preview_message_invalid",
164
+ operation: "preview.session-auth",
165
+ details: {
166
+ receivedKeys: data && typeof data === "object" ? Object.keys(data) : [],
167
+ },
168
+ }), event.origin, { type: data.type });
169
+ return;
170
+ }
36
171
  sessionAuth = {
37
172
  sessionId: data.sessionId,
38
173
  sessionSecret: data.sessionSecret,
39
174
  };
40
- try {
41
- const result = await config.client.exchangePreviewSession(sessionAuth);
42
- config.onEntry(result.entry);
43
- window.parent.postMessage({ type: "no-mess:preview-loaded" }, event.origin);
44
- }
45
- catch (err) {
46
- const error = err instanceof Error ? err : new Error(String(err));
47
- config.onError?.(error);
48
- window.parent.postMessage({ type: "no-mess:preview-error", error: error.message }, event.origin);
49
- }
175
+ await runExchange("session-auth", event.origin);
176
+ return;
50
177
  }
51
- if (data.type === "no-mess:refresh" && sessionAuth) {
52
- try {
53
- const result = await config.client.exchangePreviewSession(sessionAuth);
54
- config.onEntry(result.entry);
55
- window.parent.postMessage({ type: "no-mess:preview-loaded" }, event.origin);
56
- }
57
- catch (err) {
58
- const error = err instanceof Error ? err : new Error(String(err));
59
- config.onError?.(error);
60
- window.parent.postMessage({ type: "no-mess:preview-error", error: error.message }, event.origin);
178
+ if (data.type === "no-mess:refresh") {
179
+ if (!sessionAuth) {
180
+ logEvent("debug", "preview_message_invalid", "Ignored preview refresh before session authentication", {
181
+ type: data.type,
182
+ });
183
+ return;
61
184
  }
185
+ await runExchange("refresh", event.origin);
62
186
  }
63
187
  };
64
188
  return {
65
189
  start: () => {
190
+ disposed = false;
66
191
  window.addEventListener("message", handleMessage);
67
- window.parent.postMessage({ type: "no-mess:preview-ready" }, "*");
192
+ postToParent({ type: "no-mess:preview-ready" }, "*", { phase: "ready" });
68
193
  },
69
194
  cleanup: () => {
195
+ disposed = true;
70
196
  window.removeEventListener("message", handleMessage);
71
197
  },
72
198
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"live-edit.d.ts","sourceRoot":"","sources":["../src/live-edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAyDjE;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CA0Q5E"}
1
+ {"version":3,"file":"live-edit.d.ts","sourceRoot":"","sources":["../src/live-edit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAyFjE;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CA+Y5E"}
package/dist/live-edit.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { normalizeNoMessError } from "./error-utils.js";
2
+ import { createSdkLogger } from "./logging.js";
1
3
  const OVERLAY_CONTAINER_ID = "no-mess-live-edit-overlays";
2
4
  const OVERLAY_ATTR = "data-no-mess-overlay-for";
3
5
  const FIELD_ATTR = "data-no-mess-field";
@@ -45,6 +47,22 @@ const OVERLAY_CSS = `
45
47
  background: oklch(0.546 0.245 262.9);
46
48
  }
47
49
  `;
50
+ function serializeRect(rect) {
51
+ const rectWithJson = rect;
52
+ if (typeof rectWithJson.toJSON === "function") {
53
+ return rectWithJson.toJSON();
54
+ }
55
+ return {
56
+ x: rect.x,
57
+ y: rect.y,
58
+ width: rect.width,
59
+ height: rect.height,
60
+ top: rect.top,
61
+ right: rect.right,
62
+ bottom: rect.bottom,
63
+ left: rect.left,
64
+ };
65
+ }
48
66
  /**
49
67
  * Create a live edit handler that manages overlay highlights on annotated DOM
50
68
  * elements and communicates with the admin dashboard via postMessage.
@@ -58,6 +76,7 @@ const OVERLAY_CSS = `
58
76
  * ```
59
77
  */
60
78
  export function createLiveEditHandler(config) {
79
+ const logger = createSdkLogger(config.logger);
61
80
  let active = false;
62
81
  let container = null;
63
82
  let styleEl = null;
@@ -66,6 +85,55 @@ export function createLiveEditHandler(config) {
66
85
  let mutationObserver = null;
67
86
  let scrollListener = null;
68
87
  let rafId = null;
88
+ const emitLog = (level, message, context, error) => {
89
+ logger({
90
+ level,
91
+ code: "live_edit_runtime_failed",
92
+ message,
93
+ scope: "live-edit",
94
+ operation: "createLiveEditHandler",
95
+ error: error instanceof Error ? error : undefined,
96
+ timestamp: new Date().toISOString(),
97
+ context,
98
+ });
99
+ };
100
+ const reportRuntimeError = (error, context, message = "Live edit runtime failure") => {
101
+ const normalized = normalizeNoMessError(error, {
102
+ kind: "runtime",
103
+ code: "live_edit_runtime_failed",
104
+ operation: "live-edit",
105
+ details: context,
106
+ });
107
+ try {
108
+ config.onError?.(normalized);
109
+ }
110
+ catch (callbackError) {
111
+ emitLog("error", "Live edit onError callback threw unexpectedly", context, normalizeNoMessError(callbackError, {
112
+ kind: "runtime",
113
+ code: "live_edit_runtime_failed",
114
+ operation: "live-edit.onError",
115
+ }));
116
+ }
117
+ emitLog("error", message, context, normalized);
118
+ };
119
+ const safeInvoke = (callback, context, message) => {
120
+ if (!callback)
121
+ return;
122
+ try {
123
+ callback();
124
+ }
125
+ catch (error) {
126
+ reportRuntimeError(error, context, message);
127
+ }
128
+ };
129
+ const safePostMessage = (message, context) => {
130
+ try {
131
+ window.parent.postMessage(message, config.adminOrigin);
132
+ }
133
+ catch (error) {
134
+ reportRuntimeError(error, context, "Failed to post live edit message");
135
+ }
136
+ };
69
137
  function injectStyles() {
70
138
  if (document.getElementById("no-mess-live-edit-styles"))
71
139
  return;
@@ -113,105 +181,151 @@ export function createLiveEditHandler(config) {
113
181
  return;
114
182
  rafId = requestAnimationFrame(() => {
115
183
  rafId = null;
116
- updateAllPositions();
184
+ try {
185
+ updateAllPositions();
186
+ }
187
+ catch (error) {
188
+ reportRuntimeError(error, { phase: "updateAllPositions" });
189
+ }
117
190
  });
118
191
  }
119
192
  function buildOverlays() {
120
- // Clear existing overlays
121
- for (const entry of overlays) {
122
- entry.overlay.remove();
123
- }
124
- overlays = [];
125
- if (!container)
126
- return;
127
- const fieldElements = getFieldElements();
128
- const fieldRects = [];
129
- for (const [fieldName, elements] of fieldElements) {
130
- for (const element of elements) {
131
- const overlay = document.createElement("div");
132
- overlay.className = "no-mess-overlay";
133
- overlay.setAttribute(OVERLAY_ATTR, fieldName);
134
- const label = document.createElement("span");
135
- label.className = "no-mess-overlay-label";
136
- label.textContent = fieldName;
137
- overlay.appendChild(label);
138
- positionOverlay(overlay, element);
139
- overlay.addEventListener("click", () => {
140
- window.parent.postMessage({ type: "no-mess:field-clicked", fieldName }, config.adminOrigin);
141
- config.onFieldClicked?.(fieldName);
142
- });
143
- container.appendChild(overlay);
144
- overlays.push({ element, overlay, fieldName });
145
- const rect = element.getBoundingClientRect();
146
- fieldRects.push({ fieldName, rect: rect.toJSON() });
147
- // Observe element for size changes
148
- resizeObserver?.observe(element);
193
+ try {
194
+ for (const entry of overlays) {
195
+ entry.overlay.remove();
196
+ }
197
+ overlays = [];
198
+ if (!container)
199
+ return;
200
+ const fieldElements = getFieldElements();
201
+ const fieldRects = [];
202
+ for (const [fieldName, elements] of fieldElements) {
203
+ for (const element of elements) {
204
+ const overlay = document.createElement("div");
205
+ overlay.className = "no-mess-overlay";
206
+ overlay.setAttribute(OVERLAY_ATTR, fieldName);
207
+ const label = document.createElement("span");
208
+ label.className = "no-mess-overlay-label";
209
+ label.textContent = fieldName;
210
+ overlay.appendChild(label);
211
+ positionOverlay(overlay, element);
212
+ overlay.addEventListener("click", () => {
213
+ safePostMessage({ type: "no-mess:field-clicked", fieldName }, { phase: "field-clicked", fieldName });
214
+ try {
215
+ config.onFieldClicked?.(fieldName);
216
+ }
217
+ catch (error) {
218
+ reportRuntimeError(error, { phase: "field-clicked", fieldName });
219
+ }
220
+ });
221
+ container.appendChild(overlay);
222
+ overlays.push({ element, overlay, fieldName });
223
+ const rect = element.getBoundingClientRect();
224
+ fieldRects.push({ fieldName, rect: serializeRect(rect) });
225
+ resizeObserver?.observe(element);
226
+ }
149
227
  }
228
+ const uniqueFields = [...new Set(fieldRects.map((field) => field.fieldName))];
229
+ safePostMessage({
230
+ type: "no-mess:field-map",
231
+ fields: uniqueFields.map((fieldName) => {
232
+ const match = fieldRects.find((field) => field.fieldName === fieldName);
233
+ return { fieldName, rect: match?.rect };
234
+ }),
235
+ }, { phase: "field-map", count: uniqueFields.length });
236
+ }
237
+ catch (error) {
238
+ reportRuntimeError(error, { phase: "buildOverlays" }, "Failed to build live edit overlays");
150
239
  }
151
- // Send field map to admin
152
- const uniqueFields = [...new Set(fieldRects.map((f) => f.fieldName))];
153
- window.parent.postMessage({
154
- type: "no-mess:field-map",
155
- fields: uniqueFields.map((fieldName) => {
156
- const match = fieldRects.find((f) => f.fieldName === fieldName);
157
- return { fieldName, rect: match?.rect };
158
- }),
159
- }, config.adminOrigin);
160
240
  }
161
241
  function enterLiveEdit() {
162
242
  if (active)
163
243
  return;
164
244
  active = true;
165
- injectStyles();
166
- createContainer();
167
- resizeObserver = new ResizeObserver(() => {
168
- schedulePositionUpdate();
169
- });
170
- buildOverlays();
171
- // Track scroll for repositioning
172
- scrollListener = () => schedulePositionUpdate();
173
- window.addEventListener("scroll", scrollListener, { passive: true });
174
- window.addEventListener("resize", scrollListener, { passive: true });
175
- // Track DOM mutations for SPA navigation
176
- mutationObserver = new MutationObserver(() => {
177
- // Debounce rescan slightly for batch mutations
178
- setTimeout(() => {
179
- if (active)
180
- buildOverlays();
181
- }, 100);
182
- });
183
- mutationObserver.observe(document.body, {
184
- childList: true,
185
- subtree: true,
186
- });
187
- config.onEnter?.();
245
+ try {
246
+ injectStyles();
247
+ createContainer();
248
+ if (typeof ResizeObserver !== "undefined") {
249
+ resizeObserver = new ResizeObserver(() => {
250
+ try {
251
+ schedulePositionUpdate();
252
+ }
253
+ catch (error) {
254
+ reportRuntimeError(error, { phase: "resizeObserver" });
255
+ }
256
+ });
257
+ }
258
+ else {
259
+ emitLog("warn", "ResizeObserver is not available; live edit overlays will fall back to scroll/resize updates only", {
260
+ feature: "ResizeObserver",
261
+ });
262
+ }
263
+ buildOverlays();
264
+ scrollListener = () => {
265
+ try {
266
+ schedulePositionUpdate();
267
+ }
268
+ catch (error) {
269
+ reportRuntimeError(error, { phase: "scrollListener" });
270
+ }
271
+ };
272
+ window.addEventListener("scroll", scrollListener, { passive: true });
273
+ window.addEventListener("resize", scrollListener, { passive: true });
274
+ if (typeof MutationObserver !== "undefined") {
275
+ mutationObserver = new MutationObserver(() => {
276
+ setTimeout(() => {
277
+ if (!active)
278
+ return;
279
+ buildOverlays();
280
+ }, 100);
281
+ });
282
+ mutationObserver.observe(document.body, {
283
+ childList: true,
284
+ subtree: true,
285
+ });
286
+ }
287
+ else {
288
+ emitLog("warn", "MutationObserver is not available; live edit overlays will not auto-refresh for DOM changes", {
289
+ feature: "MutationObserver",
290
+ });
291
+ }
292
+ safeInvoke(config.onEnter, { phase: "enter" }, "Live edit onEnter callback threw unexpectedly");
293
+ }
294
+ catch (error) {
295
+ reportRuntimeError(error, { phase: "enterLiveEdit" });
296
+ }
188
297
  }
189
298
  function exitLiveEdit() {
190
299
  if (!active)
191
300
  return;
192
301
  active = false;
193
- if (rafId !== null) {
194
- cancelAnimationFrame(rafId);
195
- rafId = null;
302
+ try {
303
+ if (rafId !== null) {
304
+ cancelAnimationFrame(rafId);
305
+ rafId = null;
306
+ }
307
+ resizeObserver?.disconnect();
308
+ resizeObserver = null;
309
+ mutationObserver?.disconnect();
310
+ mutationObserver = null;
311
+ if (scrollListener) {
312
+ window.removeEventListener("scroll", scrollListener);
313
+ window.removeEventListener("resize", scrollListener);
314
+ scrollListener = null;
315
+ }
316
+ for (const entry of overlays) {
317
+ entry.overlay.remove();
318
+ }
319
+ overlays = [];
320
+ container?.remove();
321
+ container = null;
322
+ styleEl?.remove();
323
+ styleEl = null;
324
+ safeInvoke(config.onExit, { phase: "exit" }, "Live edit onExit callback threw unexpectedly");
196
325
  }
197
- resizeObserver?.disconnect();
198
- resizeObserver = null;
199
- mutationObserver?.disconnect();
200
- mutationObserver = null;
201
- if (scrollListener) {
202
- window.removeEventListener("scroll", scrollListener);
203
- window.removeEventListener("resize", scrollListener);
204
- scrollListener = null;
326
+ catch (error) {
327
+ reportRuntimeError(error, { phase: "exitLiveEdit" });
205
328
  }
206
- for (const entry of overlays) {
207
- entry.overlay.remove();
208
- }
209
- overlays = [];
210
- container?.remove();
211
- container = null;
212
- styleEl?.remove();
213
- styleEl = null;
214
- config.onExit?.();
215
329
  }
216
330
  function handleFieldUpdate(fieldName, value) {
217
331
  const elements = document.querySelectorAll(`[${FIELD_ATTR}="${fieldName}"]`);
@@ -252,31 +366,56 @@ export function createLiveEditHandler(config) {
252
366
  const data = event.data;
253
367
  if (!data || typeof data.type !== "string")
254
368
  return;
255
- switch (data.type) {
256
- case "no-mess:live-edit-enter":
257
- enterLiveEdit();
258
- break;
259
- case "no-mess:live-edit-exit":
260
- exitLiveEdit();
261
- break;
262
- case "no-mess:field-updated":
263
- if (data.fieldName && active) {
369
+ try {
370
+ switch (data.type) {
371
+ case "no-mess:live-edit-enter":
372
+ enterLiveEdit();
373
+ break;
374
+ case "no-mess:live-edit-exit":
375
+ exitLiveEdit();
376
+ break;
377
+ case "no-mess:field-updated":
378
+ if (!active)
379
+ break;
380
+ if (typeof data.fieldName !== "string") {
381
+ emitLog("debug", "Ignored malformed live edit field update message", {
382
+ type: data.type,
383
+ });
384
+ break;
385
+ }
264
386
  handleFieldUpdate(data.fieldName, data.value);
265
- }
266
- break;
267
- case "no-mess:field-focus":
268
- if (data.fieldName && active) {
387
+ break;
388
+ case "no-mess:field-focus":
389
+ if (!active)
390
+ break;
391
+ if (typeof data.fieldName !== "string") {
392
+ emitLog("debug", "Ignored malformed live edit focus message", {
393
+ type: data.type,
394
+ });
395
+ break;
396
+ }
269
397
  handleFieldFocus(data.fieldName);
270
- }
271
- break;
272
- case "no-mess:field-blur":
273
- if (data.fieldName && active) {
398
+ break;
399
+ case "no-mess:field-blur":
400
+ if (!active)
401
+ break;
402
+ if (typeof data.fieldName !== "string") {
403
+ emitLog("debug", "Ignored malformed live edit blur message", {
404
+ type: data.type,
405
+ });
406
+ break;
407
+ }
274
408
  handleFieldBlur(data.fieldName);
275
- }
276
- break;
409
+ break;
410
+ }
411
+ }
412
+ catch (error) {
413
+ reportRuntimeError(error, {
414
+ phase: "handleMessage",
415
+ type: data.type,
416
+ });
277
417
  }
278
418
  }
279
- // Start listening immediately
280
419
  window.addEventListener("message", handleMessage);
281
420
  return {
282
421
  cleanup: () => {