@ptengine/inline-editor 0.0.0-stage → 0.1.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.
Files changed (169) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +286 -2
  3. package/dist/InlineEditor.d.ts +342 -0
  4. package/dist/InlineEditor.js +867 -0
  5. package/dist/PagePreview.d.ts +83 -0
  6. package/dist/PagePreview.js +111 -0
  7. package/dist/assets/AssetsPanel.d.ts +19 -0
  8. package/dist/assets/AssetsPanel.js +23 -0
  9. package/dist/assets/componentLibrary.d.ts +23 -0
  10. package/dist/assets/componentLibrary.js +12 -0
  11. package/dist/canvas-ui/index.d.ts +17 -0
  12. package/dist/canvas-ui/index.js +7 -0
  13. package/dist/canvas-ui/scope.d.ts +13 -0
  14. package/dist/canvas-ui/scope.js +1 -0
  15. package/dist/canvas-ui/selection/classNames.d.ts +30 -0
  16. package/dist/canvas-ui/selection/classNames.js +15 -0
  17. package/dist/canvas-ui/selection/tokens.css +204 -0
  18. package/dist/canvas-ui/tree/TreePanel.d.ts +47 -0
  19. package/dist/canvas-ui/tree/TreePanel.js +132 -0
  20. package/dist/canvas-ui/tree/classNames.d.ts +21 -0
  21. package/dist/canvas-ui/tree/classNames.js +8 -0
  22. package/dist/canvas-ui/tree/order.d.ts +38 -0
  23. package/dist/canvas-ui/tree/order.js +38 -0
  24. package/dist/canvas-ui/tree/tree.css +131 -0
  25. package/dist/chrome/EditorChrome.d.ts +83 -0
  26. package/dist/chrome/EditorChrome.js +10 -0
  27. package/dist/chrome/VersionMenu.d.ts +26 -0
  28. package/dist/chrome/VersionMenu.js +86 -0
  29. package/dist/chrome/topbarPrimitives.d.ts +69 -0
  30. package/dist/chrome/topbarPrimitives.js +39 -0
  31. package/dist/chrome/useDismissOnOutside.d.ts +23 -0
  32. package/dist/chrome/useDismissOnOutside.js +27 -0
  33. package/dist/dialogs/ClickActionDialog.d.ts +24 -0
  34. package/dist/dialogs/ClickActionDialog.js +35 -0
  35. package/dist/dialogs/HtmlEditDialog.d.ts +23 -0
  36. package/dist/dialogs/HtmlEditDialog.js +23 -0
  37. package/dist/dialogs/ImageEditDialog.d.ts +30 -0
  38. package/dist/dialogs/ImageEditDialog.js +57 -0
  39. package/dist/dialogs/ImageSettingDialog.d.ts +16 -0
  40. package/dist/dialogs/ImageSettingDialog.js +17 -0
  41. package/dist/dialogs/InsertElementDialog.d.ts +23 -0
  42. package/dist/dialogs/InsertElementDialog.js +28 -0
  43. package/dist/dialogs/ReplaceVideoDialog.d.ts +18 -0
  44. package/dist/dialogs/ReplaceVideoDialog.js +16 -0
  45. package/dist/dialogs/actionTypes.d.ts +53 -0
  46. package/dist/dialogs/actionTypes.js +74 -0
  47. package/dist/dialogs/htmlValidation.d.ts +24 -0
  48. package/dist/dialogs/htmlValidation.js +37 -0
  49. package/dist/dialogs/imageEdit.d.ts +49 -0
  50. package/dist/dialogs/imageEdit.js +30 -0
  51. package/dist/index.d.ts +101 -0
  52. package/dist/index.js +53 -0
  53. package/dist/kernel/CanvasContextBar.d.ts +23 -0
  54. package/dist/kernel/CanvasContextBar.js +12 -0
  55. package/dist/kernel/CanvasFrame.d.ts +56 -0
  56. package/dist/kernel/CanvasFrame.js +53 -0
  57. package/dist/kernel/ai/aiInlineApply.d.ts +326 -0
  58. package/dist/kernel/ai/aiInlineApply.js +461 -0
  59. package/dist/kernel/ai/imageSource.d.ts +24 -0
  60. package/dist/kernel/ai/imageSource.js +19 -0
  61. package/dist/kernel/ai/insertHtmlGate.d.ts +71 -0
  62. package/dist/kernel/ai/insertHtmlGate.js +123 -0
  63. package/dist/kernel/ai/postApplyCheck.d.ts +56 -0
  64. package/dist/kernel/ai/postApplyCheck.js +74 -0
  65. package/dist/kernel/ai/replaceHtmlGate.d.ts +71 -0
  66. package/dist/kernel/ai/replaceHtmlGate.js +147 -0
  67. package/dist/kernel/blocks/BlockOverlay.d.ts +16 -0
  68. package/dist/kernel/blocks/BlockOverlay.js +66 -0
  69. package/dist/kernel/blocks/annotateAndTile.d.ts +106 -0
  70. package/dist/kernel/blocks/annotateAndTile.js +122 -0
  71. package/dist/kernel/blocks/resolveBlocks.d.ts +153 -0
  72. package/dist/kernel/blocks/resolveBlocks.js +233 -0
  73. package/dist/kernel/canvasCapabilities.d.ts +31 -0
  74. package/dist/kernel/canvasCapabilities.js +39 -0
  75. package/dist/kernel/canvasCommands.d.ts +126 -0
  76. package/dist/kernel/canvasCommands.js +146 -0
  77. package/dist/kernel/canvasErrors.d.ts +1 -0
  78. package/dist/kernel/canvasErrors.js +14 -0
  79. package/dist/kernel/captureGeometry.d.ts +35 -0
  80. package/dist/kernel/captureGeometry.js +25 -0
  81. package/dist/kernel/captureOrchestration.d.ts +48 -0
  82. package/dist/kernel/captureOrchestration.js +43 -0
  83. package/dist/kernel/captureSlices.d.ts +36 -0
  84. package/dist/kernel/captureSlices.js +20 -0
  85. package/dist/kernel/changelist/ChangeListPanel.d.ts +13 -0
  86. package/dist/kernel/changelist/ChangeListPanel.js +47 -0
  87. package/dist/kernel/changelist/ChangeListPopover.d.ts +20 -0
  88. package/dist/kernel/changelist/ChangeListPopover.js +10 -0
  89. package/dist/kernel/changelist/builders.d.ts +89 -0
  90. package/dist/kernel/changelist/builders.js +81 -0
  91. package/dist/kernel/changelist/changeSummary.d.ts +19 -0
  92. package/dist/kernel/changelist/changeSummary.js +85 -0
  93. package/dist/kernel/changelist/clipboard.d.ts +16 -0
  94. package/dist/kernel/changelist/clipboard.js +4 -0
  95. package/dist/kernel/changelist/exportForSave.d.ts +30 -0
  96. package/dist/kernel/changelist/exportForSave.js +11 -0
  97. package/dist/kernel/changelist/mergeForSave.d.ts +79 -0
  98. package/dist/kernel/changelist/mergeForSave.js +40 -0
  99. package/dist/kernel/changelist/moveObserverSafety.d.ts +70 -0
  100. package/dist/kernel/changelist/moveObserverSafety.js +39 -0
  101. package/dist/kernel/changelist/overrides.d.ts +22 -0
  102. package/dist/kernel/changelist/overrides.js +63 -0
  103. package/dist/kernel/changelist/useChangeList.d.ts +185 -0
  104. package/dist/kernel/changelist/useChangeList.js +259 -0
  105. package/dist/kernel/config.d.ts +29 -0
  106. package/dist/kernel/config.js +10 -0
  107. package/dist/kernel/dataUrlToBlob.d.ts +8 -0
  108. package/dist/kernel/dataUrlToBlob.js +17 -0
  109. package/dist/kernel/linkHref.d.ts +25 -0
  110. package/dist/kernel/linkHref.js +16 -0
  111. package/dist/kernel/mobileFit.d.ts +46 -0
  112. package/dist/kernel/mobileFit.js +44 -0
  113. package/dist/kernel/moveState.d.ts +35 -0
  114. package/dist/kernel/moveState.js +14 -0
  115. package/dist/kernel/selection/SelectionOverlay.d.ts +10 -0
  116. package/dist/kernel/selection/SelectionOverlay.js +25 -0
  117. package/dist/kernel/selection/SelectionToolbar.d.ts +30 -0
  118. package/dist/kernel/selection/SelectionToolbar.js +82 -0
  119. package/dist/kernel/selection/SelectorPathBar.d.ts +27 -0
  120. package/dist/kernel/selection/SelectorPathBar.js +55 -0
  121. package/dist/kernel/selection/buildDescendantShelf.d.ts +40 -0
  122. package/dist/kernel/selection/buildDescendantShelf.js +50 -0
  123. package/dist/kernel/selection/buildRootShelf.d.ts +26 -0
  124. package/dist/kernel/selection/buildRootShelf.js +149 -0
  125. package/dist/kernel/selection/elementAiContext.d.ts +313 -0
  126. package/dist/kernel/selection/elementAiContext.js +122 -0
  127. package/dist/kernel/selection/elementGeometryForAi.d.ts +9 -0
  128. package/dist/kernel/selection/elementGeometryForAi.js +15 -0
  129. package/dist/kernel/selection/geometry.d.ts +55 -0
  130. package/dist/kernel/selection/geometry.js +18 -0
  131. package/dist/kernel/selection/resolveAnchors.d.ts +116 -0
  132. package/dist/kernel/selection/resolveAnchors.js +173 -0
  133. package/dist/kernel/selection/shelfRow.d.ts +33 -0
  134. package/dist/kernel/selection/shelfRow.js +19 -0
  135. package/dist/kernel/selection/tagLabels.d.ts +9 -0
  136. package/dist/kernel/selection/tagLabels.js +46 -0
  137. package/dist/kernel/selection/toolbarButtons.d.ts +60 -0
  138. package/dist/kernel/selection/toolbarButtons.js +105 -0
  139. package/dist/kernel/selection/useAiSelectionContext.d.ts +26 -0
  140. package/dist/kernel/selection/useAiSelectionContext.js +26 -0
  141. package/dist/kernel/selection/useCanvasGeometry.d.ts +32 -0
  142. package/dist/kernel/selection/useCanvasGeometry.js +47 -0
  143. package/dist/kernel/selection/useSelectionRestore.d.ts +18 -0
  144. package/dist/kernel/selection/useSelectionRestore.js +25 -0
  145. package/dist/kernel/shrinkImageForUpload.d.ts +45 -0
  146. package/dist/kernel/shrinkImageForUpload.js +61 -0
  147. package/dist/kernel/useCanvasBridge.d.ts +36 -0
  148. package/dist/kernel/useCanvasBridge.js +41 -0
  149. package/dist/kernel/useCanvasGate.d.ts +22 -0
  150. package/dist/kernel/useCanvasGate.js +35 -0
  151. package/dist/kernel/useCanvasSession.d.ts +44 -0
  152. package/dist/kernel/useCanvasSession.js +182 -0
  153. package/dist/kernel/useCanvasStream.d.ts +39 -0
  154. package/dist/kernel/useCanvasStream.js +45 -0
  155. package/dist/kernel/useMobileFit.d.ts +40 -0
  156. package/dist/kernel/useMobileFit.js +139 -0
  157. package/dist/locales.d.ts +1094 -0
  158. package/dist/locales.js +545 -0
  159. package/dist/panels/StylePanel.d.ts +24 -0
  160. package/dist/panels/StylePanel.js +40 -0
  161. package/dist/panels/styleFields.d.ts +39 -0
  162. package/dist/panels/styleFields.js +91 -0
  163. package/dist/tree/LayersPanel.d.ts +30 -0
  164. package/dist/tree/LayersPanel.js +15 -0
  165. package/dist/tree/domTreeAdapter.d.ts +63 -0
  166. package/dist/tree/domTreeAdapter.js +90 -0
  167. package/dist/useInlineEditor.d.ts +158 -0
  168. package/dist/useInlineEditor.js +343 -0
  169. package/package.json +58 -3
@@ -0,0 +1,61 @@
1
+ export const AI_UPLOAD_LIMITS = {
2
+ maxBytes: 5 * 1024 * 1024,
3
+ maxWidth: 1024,
4
+ maxHeight: 8000
5
+ };
6
+ export const JPEG_QUALITY_LADDER = [0.85, 0.7, 0.55];
7
+ export function planShrink(size, limits) {
8
+ const scale = Math.min(1, limits.maxWidth / size.width, limits.maxHeight / size.height);
9
+ if (scale >= 1)
10
+ return { scale: 1, width: size.width, height: size.height };
11
+ return {
12
+ scale,
13
+ width: Math.max(1, Math.round(size.width * scale)),
14
+ height: Math.max(1, Math.round(size.height * scale))
15
+ };
16
+ }
17
+ const browserDeps = {
18
+ async decode(blob) {
19
+ const bitmap = await createImageBitmap(blob);
20
+ return { width: bitmap.width, height: bitmap.height, source: bitmap };
21
+ },
22
+ async encode(image, plan, quality) {
23
+ const canvas = document.createElement('canvas');
24
+ canvas.width = plan.width;
25
+ canvas.height = plan.height;
26
+ const ctx = canvas.getContext('2d');
27
+ if (!ctx)
28
+ throw new Error('canvas 2d context unavailable');
29
+ ctx.drawImage(image.source, 0, 0, plan.width, plan.height);
30
+ return new Promise((resolve, reject) => {
31
+ canvas.toBlob(b => (b ? resolve(b) : reject(new Error('toBlob returned null'))), 'image/jpeg', quality);
32
+ });
33
+ }
34
+ };
35
+ export async function shrinkImageForUpload(blob, limits = AI_UPLOAD_LIMITS, deps = browserDeps) {
36
+ let image;
37
+ try {
38
+ image = await deps.decode(blob);
39
+ }
40
+ catch {
41
+ return blob;
42
+ }
43
+ const plan = planShrink(image, limits);
44
+ if (plan.scale === 1 && blob.size <= limits.maxBytes && blob.type === 'image/jpeg')
45
+ return blob;
46
+ let best = null;
47
+ for (const quality of JPEG_QUALITY_LADDER) {
48
+ let encoded;
49
+ try {
50
+ encoded = await deps.encode(image, plan, quality);
51
+ }
52
+ catch {
53
+ break;
54
+ }
55
+ if (!best || encoded.size < best.size)
56
+ best = encoded;
57
+ if (encoded.size <= limits.maxBytes)
58
+ return encoded;
59
+ }
60
+ return best ?? blob;
61
+ }
@@ -0,0 +1,36 @@
1
+ import type { HandshakeResult } from '@ptengine/inline-core/protocol';
2
+ /**
3
+ * 插件上报 id 用的根节点属性。⚠️ 与 ptengine-kit `extensionIdBeacon.ts` 逐字一致,
4
+ * 改一边必须改另一边。插件只在 externally_connectable 白名单的 origin 上写它。
5
+ */
6
+ export declare const EXTENSION_ID_ATTR = "data-ptengine-kit-id";
7
+ /**
8
+ * 本次要通信的扩展 id:配了就用配置(`configureInlineEditor`,严格模式),没配就读插件上报的(内测模式)。
9
+ * 读不到返回空串 —— 调用方据此判 NO_EXTENSION,绝不把空 id 传给 chrome.runtime。
10
+ *
11
+ * 扩展 id 由消费方经 `configureInlineEditor({ extensionId })` 配置。**非空 = 严格模式**:
12
+ * 只认这一个 id(正式上线填商店 id)。不配 = 内测模式:id 改由插件在产品页根节点上报
13
+ * (`EXTENSION_ID_ATTR`)。unpacked 构建没有 manifest `key`,id 由加载路径派生、每个人都不一样,
14
+ * 构建时烤一个 id 进产物只对一个人成立。
15
+ *
16
+ * 只换了「id 从哪来」,通信方式不变:照旧 externally_connectable + sendMessage / connect,
17
+ * 扩展侧照旧按 origin 白名单放行。
18
+ */
19
+ export declare function getExtensionId(): string;
20
+ /**
21
+ * 「忘配 extensionId」与「用户没装插件」原本是两种故障(EXTENSION_ID_MISSING /
22
+ * NO_EXTENSION)。内测模式下不配 id 是**合法配置**(id 由插件上报),所以这里恒为真;
23
+ * 读不到上报的 id 只说明插件没装 / 没在本页生效,落 NO_EXTENSION、走安装引导。
24
+ *
25
+ * 保留这个函数与 EXTENSION_ID_MISSING:外部调用方与失败原因枚举不用跟着改。
26
+ */
27
+ export declare function isExtensionIdConfigured(): boolean;
28
+ export declare function hasExtensionChannel(): boolean;
29
+ export declare function sendCanvasCommand<T = unknown>(msg: Record<string, unknown>): Promise<T>;
30
+ /**
31
+ * 未装插件 / 握手失败一律返回 null,交由门禁(P9)决定 UI。
32
+ *
33
+ * `origins` 是要让 SW 检查 host 访问权的 pattern 列表 —— 网页上下文没有
34
+ * `chrome.permissions`,检测只能由 SW 做(见 HandshakeParams 的注释)。
35
+ */
36
+ export declare function probeExtension(origins: string[]): Promise<HandshakeResult | null>;
@@ -0,0 +1,41 @@
1
+ import { getInlineEditorConfig } from './config.js';
2
+ export const EXTENSION_ID_ATTR = 'data-ptengine-kit-id';
3
+ export function getExtensionId() {
4
+ const configured = getInlineEditorConfig().extensionId;
5
+ if (configured)
6
+ return configured;
7
+ if (typeof document === 'undefined')
8
+ return '';
9
+ return document.documentElement?.getAttribute(EXTENSION_ID_ATTR) ?? '';
10
+ }
11
+ export function isExtensionIdConfigured() {
12
+ return true;
13
+ }
14
+ export function hasExtensionChannel() {
15
+ return typeof chrome !== 'undefined' && !!chrome?.runtime?.sendMessage;
16
+ }
17
+ export function sendCanvasCommand(msg) {
18
+ const extensionId = getExtensionId();
19
+ if (!extensionId || !hasExtensionChannel()) {
20
+ return Promise.reject(new Error('NO_EXTENSION'));
21
+ }
22
+ return new Promise((resolve, reject) => {
23
+ chrome.runtime.sendMessage(extensionId, msg, (res) => {
24
+ const lastError = chrome.runtime.lastError;
25
+ if (lastError || !res)
26
+ return reject(new Error('NO_EXTENSION'));
27
+ if (!res.ok)
28
+ return reject(new Error(res.error ?? 'canvas command failed'));
29
+ resolve(res.result);
30
+ });
31
+ });
32
+ }
33
+ export async function probeExtension(origins) {
34
+ try {
35
+ const params = { origins };
36
+ return await sendCanvasCommand({ command: 'handshake', ...params });
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
@@ -0,0 +1,22 @@
1
+ import { type HandshakeResult, type LoadFailedReason } from '@ptengine/inline-core/protocol';
2
+ export type GateResult = {
3
+ ok: true;
4
+ handshake: HandshakeResult;
5
+ /** 本会话可用的能力集(握手推导 + 产品页开关),消费方据此逐项启用 / 降级。 */
6
+ capabilities: Set<string>;
7
+ } | {
8
+ ok: false;
9
+ reason: LoadFailedReason;
10
+ };
11
+ /**
12
+ * 门禁四道:① 构建时配了扩展 id 没有 ② 插件装没装 ③ 契约是否兼容(见下)
13
+ * ④ 目标域 + 产品页自身域的 Site access 有没有被用户收回。
14
+ *
15
+ * ③④ 都只能靠扩展回答:`chrome.permissions` **只存在于扩展上下文**,
16
+ * externally_connectable 放行的普通网页拿到的 `chrome` 只有 loadTimes / csi / app /
17
+ * runtime(runtime 又只有 connect / sendMessage / id)—— 产品页里读
18
+ * `chrome.permissions.contains` 恒为 undefined。所以这里把两个 origin pattern 交给 SW,
19
+ * 由 SW 判定后随 handshake 回传 `hasHostAccess`。
20
+ * (`<all_urls>` 只在 manifest 里声明,用户可在扩展详情页改成 "On click" 把它收回。)
21
+ */
22
+ export declare function checkCanvasGate(targetUrl: string): Promise<GateResult>;
@@ -0,0 +1,35 @@
1
+ import { isExtensionIdConfigured, probeExtension } from './useCanvasBridge.js';
2
+ import { capabilitiesFromHandshake, missingCoreCapabilities } from './canvasCapabilities.js';
3
+ import { CONTRACT_VERSION } from '@ptengine/inline-core/protocol';
4
+ function productOriginPattern() {
5
+ return `${location.origin}/*`;
6
+ }
7
+ function originPattern(url) {
8
+ try {
9
+ return `${new URL(url).origin}/*`;
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ export async function checkCanvasGate(targetUrl) {
16
+ if (!isExtensionIdConfigured())
17
+ return { ok: false, reason: 'EXTENSION_ID_MISSING' };
18
+ const pattern = originPattern(targetUrl);
19
+ if (!pattern)
20
+ return { ok: false, reason: 'NAV_AWAY' };
21
+ const handshake = await probeExtension([pattern, productOriginPattern()]);
22
+ if (!handshake)
23
+ return { ok: false, reason: 'NO_EXTENSION' };
24
+ const minSupported = handshake.minSupportedContractVersion ?? handshake.contractVersion;
25
+ if (CONTRACT_VERSION < minSupported) {
26
+ return { ok: false, reason: 'CONTRACT_MISMATCH' };
27
+ }
28
+ const capabilities = capabilitiesFromHandshake(handshake);
29
+ if (missingCoreCapabilities(capabilities).length > 0) {
30
+ return { ok: false, reason: 'CONTRACT_MISMATCH' };
31
+ }
32
+ if (!handshake.hasHostAccess)
33
+ return { ok: false, reason: 'PERMISSION_DENIED' };
34
+ return { ok: true, handshake, capabilities };
35
+ }
@@ -0,0 +1,44 @@
1
+ import { type CanvasDevice, type HandshakeResult, type LoadFailedReason, type ReadyPayload } from '@ptengine/inline-core/protocol';
2
+ import type { CanvasStreamMessage } from './useCanvasStream.js';
3
+ type State = 'gating' | 'opening' | 'loading' | 'ready' | 'failed';
4
+ interface FrameProps {
5
+ sessionId: string;
6
+ url: string;
7
+ device: CanvasDevice;
8
+ }
9
+ /**
10
+ * Port 心跳间隔。
11
+ *
12
+ * ⚠️ 这条 Port 是 SW 往产品页推事件(geometry / lifecycle / elementPicked / textEditCommit)
13
+ * 的**唯一**通道,而 SW 的 Port 表只在内存里。Chrome 回收空闲 SW 时 Port 随之断开,SW 被下
14
+ * 一条消息唤醒后 Port 表是空的 —— agent 的每一条事件都被静默丢掉,命令(走 sendMessage)
15
+ * 却照常能用。真机表现:分析画布放着等 agent 想几分钟,之后区块框再也不跟随滚动、编辑器
16
+ * 点元素没反应,零报错。
17
+ *
18
+ * 真机实测(Chrome 153,2026-09-24):
19
+ * - 画布空闲约 6 分钟 Port 断开;
20
+ * - 期间有流量(agent 推事件、或产品页经 Port 发消息)就不会断 —— 所以心跳走 Port 本身;
21
+ * - 只靠断开后重连不够:新起的 SW 空闲 30 秒又被回收,会陷入每 30 秒断一次的循环。
22
+ *
23
+ * 取 20s:落在 30s 空闲回收阈值之内,留出定时器在后台标签页被节流的余量。
24
+ */
25
+ export declare const PORT_KEEPALIVE_MS = 20000;
26
+ /**
27
+ * 画布会话编排:门禁 → open(装 DNR 规则)→ 挂 iframe → 等扩展回报 ready。
28
+ * 顺序是硬要求:规则必须在 iframe 发起导航之前就位,否则第一次加载会被 XFO 挡住。
29
+ */
30
+ export declare function useCanvasSession(input: {
31
+ url: string;
32
+ device: CanvasDevice;
33
+ /** agent 主动发来的流(lifecycle / geometry / elementPicked / …)转出去给 useCanvasStream 消费。 */
34
+ onStreamEvent?: (msg: CanvasStreamMessage) => void;
35
+ }): {
36
+ state: State;
37
+ reason: LoadFailedReason | undefined;
38
+ portError: string | null;
39
+ frameProps: FrameProps | null;
40
+ handshake: HandshakeResult | undefined;
41
+ readyPayload: ReadyPayload | undefined;
42
+ capabilities: Set<string> | undefined;
43
+ };
44
+ export {};
@@ -0,0 +1,182 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { CANVAS_MSG_NAMESPACE } from '@ptengine/inline-core/protocol';
3
+ import { getExtensionId, hasExtensionChannel, sendCanvasCommand } from './useCanvasBridge.js';
4
+ import { checkCanvasGate } from './useCanvasGate.js';
5
+ import { narrowByReady } from './canvasCapabilities.js';
6
+ const READY_TIMEOUT_MS = 20000;
7
+ const GATE_TIMEOUT_MS = 3000;
8
+ export const PORT_KEEPALIVE_MS = 20000;
9
+ const QUICK_DROP_MS = 1000;
10
+ const MAX_QUICK_DROPS = 3;
11
+ export function useCanvasSession(input) {
12
+ const [state, setState] = useState('gating');
13
+ const [reason, setReason] = useState();
14
+ const [portError, setPortError] = useState(null);
15
+ const [frameProps, setFrameProps] = useState(null);
16
+ const [handshake, setHandshake] = useState();
17
+ const [readyPayload, setReadyPayload] = useState();
18
+ const [capabilities, setCapabilities] = useState();
19
+ const sessionIdRef = useRef('');
20
+ const onStreamEventRef = useRef(input.onStreamEvent);
21
+ onStreamEventRef.current = input.onStreamEvent;
22
+ useEffect(() => {
23
+ let cancelled = false;
24
+ const sessionId = crypto.randomUUID();
25
+ sessionIdRef.current = sessionId;
26
+ setHandshake(undefined);
27
+ setReadyPayload(undefined);
28
+ setCapabilities(undefined);
29
+ let readyTimer;
30
+ const clearReadyTimer = () => {
31
+ if (readyTimer !== undefined) {
32
+ clearTimeout(readyTimer);
33
+ readyTimer = undefined;
34
+ }
35
+ };
36
+ let port = null;
37
+ let readyReached = false;
38
+ let quickDrops = 0;
39
+ let keepaliveTimer;
40
+ const stopKeepalive = () => {
41
+ if (keepaliveTimer !== undefined) {
42
+ clearInterval(keepaliveTimer);
43
+ keepaliveTimer = undefined;
44
+ }
45
+ };
46
+ const onPortMessage = (msg) => {
47
+ if (msg?.namespace !== CANVAS_MSG_NAMESPACE)
48
+ return;
49
+ if (msg.sessionId !== sessionIdRef.current)
50
+ return;
51
+ if (msg.event === 'ready') {
52
+ clearReadyTimer();
53
+ readyReached = true;
54
+ if (msg.payload) {
55
+ const payload = msg.payload;
56
+ setReadyPayload(payload);
57
+ setCapabilities(prev => (prev ? narrowByReady(prev, payload) : prev));
58
+ }
59
+ setState('ready');
60
+ return;
61
+ }
62
+ onStreamEventRef.current?.(msg);
63
+ };
64
+ const connectPort = () => {
65
+ let next;
66
+ try {
67
+ next = chrome.runtime.connect(getExtensionId(), { name: CANVAS_MSG_NAMESPACE });
68
+ }
69
+ catch {
70
+ port = null;
71
+ return;
72
+ }
73
+ port = next;
74
+ const connectedAt = Date.now();
75
+ next.onMessage.addListener(onPortMessage);
76
+ next.onDisconnect.addListener(() => {
77
+ const connectError = chrome.runtime?.lastError?.message;
78
+ if (connectError) {
79
+ console.warn('[inline-editor] canvas port disconnected', connectError);
80
+ }
81
+ if (next !== port)
82
+ return;
83
+ stopKeepalive();
84
+ port = null;
85
+ if (cancelled)
86
+ return;
87
+ if (readyReached) {
88
+ if (connectError)
89
+ return;
90
+ quickDrops = Date.now() - connectedAt < QUICK_DROP_MS ? quickDrops + 1 : 0;
91
+ if (quickDrops >= MAX_QUICK_DROPS)
92
+ return;
93
+ connectPort();
94
+ return;
95
+ }
96
+ clearReadyTimer();
97
+ setState(prev => {
98
+ if (prev === 'ready')
99
+ return prev;
100
+ setReason('NO_EXTENSION');
101
+ setPortError(connectError ?? null);
102
+ return 'failed';
103
+ });
104
+ });
105
+ stopKeepalive();
106
+ keepaliveTimer = setInterval(() => {
107
+ try {
108
+ next.postMessage({ namespace: CANVAS_MSG_NAMESPACE, event: 'keepalive' });
109
+ }
110
+ catch {
111
+ }
112
+ }, PORT_KEEPALIVE_MS);
113
+ };
114
+ if (getExtensionId() && hasExtensionChannel())
115
+ connectPort();
116
+ let gateTimer;
117
+ const clearGateTimer = () => {
118
+ if (gateTimer !== undefined) {
119
+ clearTimeout(gateTimer);
120
+ gateTimer = undefined;
121
+ }
122
+ };
123
+ (async () => {
124
+ const gateTimeout = new Promise(resolve => {
125
+ gateTimer = setTimeout(() => {
126
+ resolve({ ok: false, reason: 'TIMEOUT' });
127
+ }, GATE_TIMEOUT_MS);
128
+ });
129
+ const gate = await Promise.race([checkCanvasGate(input.url), gateTimeout]);
130
+ clearGateTimer();
131
+ if (cancelled)
132
+ return;
133
+ if (gate.ok === false) {
134
+ setReason(gate.reason);
135
+ setState('failed');
136
+ return;
137
+ }
138
+ setHandshake(gate.handshake);
139
+ setCapabilities(gate.capabilities);
140
+ setState('opening');
141
+ readyTimer = setTimeout(() => {
142
+ if (cancelled)
143
+ return;
144
+ setState(prev => {
145
+ if (prev === 'ready' || prev === 'failed')
146
+ return prev;
147
+ setReason('TIMEOUT');
148
+ return 'failed';
149
+ });
150
+ }, READY_TIMEOUT_MS);
151
+ try {
152
+ await sendCanvasCommand({
153
+ command: 'open',
154
+ sessionId,
155
+ url: input.url,
156
+ device: input.device
157
+ });
158
+ }
159
+ catch {
160
+ if (!cancelled) {
161
+ clearReadyTimer();
162
+ setReason('NO_EXTENSION');
163
+ setState('failed');
164
+ }
165
+ return;
166
+ }
167
+ if (cancelled)
168
+ return;
169
+ setFrameProps({ sessionId, url: input.url, device: input.device });
170
+ setState('loading');
171
+ })();
172
+ return () => {
173
+ cancelled = true;
174
+ clearGateTimer();
175
+ clearReadyTimer();
176
+ stopKeepalive();
177
+ void sendCanvasCommand({ command: 'destroy', sessionId }).catch(() => { });
178
+ port?.disconnect();
179
+ };
180
+ }, [input.url, input.device]);
181
+ return { state, reason, portError, frameProps, handshake, readyPayload, capabilities };
182
+ }
@@ -0,0 +1,39 @@
1
+ import { type CanvasDocumentSizePayload, type CanvasGeometryPayload, type CanvasLifecyclePayload, type CanvasPickedPayload, type CanvasTextEditPayload } from '@ptengine/inline-core/protocol';
2
+ export interface CanvasStreamMessage {
3
+ namespace?: string;
4
+ event?: string;
5
+ sessionId?: string;
6
+ payload?: unknown;
7
+ }
8
+ /**
9
+ * 消费 Port 上 agent 主动发来的流:生命周期事件(时间线)、选中、几何帧、文字编辑回程、
10
+ * 文档尺寸变化。
11
+ *
12
+ * 与短命令(`canvasCommands.ts`)是**两套机制**,不要混(D2):短命令走 sendMessage
13
+ * 回调链、天然请求-响应配对;这里是单向流。
14
+ *
15
+ * v11 起不再有整页 DOM 分片(`domChunk` / `getDom`):agent 曾在每次加载后推两次整页 HTML
16
+ * (上限 8MB),而产品页拼好的快照没有任何消费者 —— 白白序列化、传输,还多一份数据暴露。
17
+ * 契约 v11 把它删了(inline-core `docs/contract-v11-design.md` §6)。
18
+ *
19
+ * **契约:调用方必须在切换会话(`sessionId` 变化)时调用 `reset()`**——传入
20
+ * `sessionId` 参数即可自动做到这一点(见下方 effect);如果不传,调用方要自己在
21
+ * `useCanvasSession` 的 `sessionId` 变化时手动调 `reset()`,否则 `timeline` 会把两次会话的
22
+ * 生命周期事件混在一条列表里,出现两组 `injected/domReady/loaded` 且 `seq` 重复。
23
+ */
24
+ export declare function useCanvasStream(sessionId?: string, onGeometry?: (payload: CanvasGeometryPayload) => void, onTextEditCommit?: (payload: CanvasTextEditPayload) => void,
25
+ /**
26
+ * 生命周期事件(带**它属于哪个会话**)。
27
+ *
28
+ * ⚠️ 存在的理由:`timeline` 是跨会话累积的,从它 `some()` 出来的 domReady 会让
29
+ * 新会话一诞生就"已经就绪"。消费方要的是「**这个** sessionId 就绪了没有」,
30
+ * 那个信息只有事件信封上有,timeline 里的 payload 不带。
31
+ */
32
+ onLifecycle?: (payload: CanvasLifecyclePayload, sessionId: string | undefined) => void,
33
+ /** v11:`observeDocumentSize` 打开后的尺寸变化帧。与 geometry 同理不进 state,直接交给回调。 */
34
+ onDocumentSize?: (payload: CanvasDocumentSizePayload) => void): {
35
+ timeline: CanvasLifecyclePayload[];
36
+ picked: CanvasPickedPayload | null;
37
+ onStreamEvent: (msg: CanvasStreamMessage) => void;
38
+ reset: () => void;
39
+ };
@@ -0,0 +1,45 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { CANVAS_MSG_NAMESPACE } from '@ptengine/inline-core/protocol';
3
+ export function useCanvasStream(sessionId, onGeometry, onTextEditCommit, onLifecycle, onDocumentSize) {
4
+ const [timeline, setTimeline] = useState([]);
5
+ const [picked, setPicked] = useState(null);
6
+ const onStreamEvent = useCallback((msg) => {
7
+ if (msg?.namespace !== CANVAS_MSG_NAMESPACE)
8
+ return;
9
+ if (msg.event === 'lifecycle') {
10
+ const payload = msg.payload;
11
+ setTimeline(prev => [...prev, payload]);
12
+ onLifecycle?.(payload, msg.sessionId);
13
+ return;
14
+ }
15
+ if (msg.event === 'elementPicked') {
16
+ setPicked(msg.payload);
17
+ return;
18
+ }
19
+ if (msg.event === 'textEditCommit') {
20
+ onTextEditCommit?.(msg.payload);
21
+ return;
22
+ }
23
+ if (msg.event === 'geometry') {
24
+ onGeometry?.(msg.payload);
25
+ return;
26
+ }
27
+ if (msg.event === 'documentSize') {
28
+ onDocumentSize?.(msg.payload);
29
+ }
30
+ }, [onGeometry, onTextEditCommit, onLifecycle, onDocumentSize]);
31
+ const reset = useCallback(() => {
32
+ setTimeline([]);
33
+ setPicked(null);
34
+ }, []);
35
+ const sessionIdRef = useRef(sessionId);
36
+ useEffect(() => {
37
+ if (sessionId === undefined)
38
+ return;
39
+ if (sessionIdRef.current === sessionId)
40
+ return;
41
+ sessionIdRef.current = sessionId;
42
+ reset();
43
+ }, [sessionId, reset]);
44
+ return { timeline, picked, onStreamEvent, reset };
45
+ }
@@ -0,0 +1,40 @@
1
+ import type { CanvasDevice, CanvasDocumentSizePayload } from '@ptengine/inline-core/protocol';
2
+ /**
3
+ * 移动端超宽缩小的**编排**(算法见 `mobileFit.ts`)。
4
+ *
5
+ * 触发条件全部满足才动:`device === 'mobile'`、当前会话 domReady、插件回报了
6
+ * `measureDocument` + `setStyleLayer`(老插件 / `disabledCapabilities` 配置关掉其一 →
7
+ * 原样不缩,和 v11 之前一致)。`observeDocumentSize` 是锦上添花:没有它只在 domReady 时缩一次。
8
+ *
9
+ * 流程(每个会话一次):量(摘掉 `mobile-fit` 层)→ 按需写 `render-fixes` 层并重量 → 写 `mobile-fit`
10
+ * 层 → 打开尺寸观察。之后每帧 `documentSize`:**宽度变了才重跑整套**(懒加载、轮播初始化会把
11
+ * scrollWidth 撑大),只有高度变就只改 `height`。宽度不变就什么都不做 —— 这是不和自己的样式层
12
+ * 互相触发的关键(engage-editor 用 `isApplyingScale` 锁干的是同一件事)。
13
+ *
14
+ * 所有命令失败都静默:缩放是增强,失败的后果只是「和以前一样横向溢出」,不值得让编辑器进 failed。
15
+ */
16
+ export interface MobileFitState {
17
+ /** 当前施加在 `<html>` 上的比例;1 = 没缩。热图坐标对齐、文档坐标换算要除以它(设计稿 §8.5)。 */
18
+ scale: number;
19
+ /** 自然宽高;`null` = 还没量过(非移动端 / 能力缺失 / 还没 domReady)。 */
20
+ naturalWidth: number | null;
21
+ naturalHeight: number | null;
22
+ /**
23
+ * 当前会话的首轮缩放还没落地。会缩的页面在这段窗口里 `scale` 还是 1 —— 宿主拿比例去对
24
+ * 热图坐标(`resolveBlocks` 的 band、区块框、截图标注)必须等它变 false,否则对的是缩放前
25
+ * 的页面。不会缩(PC / 能力缺失)时恒为 false,与之前的时序完全一致。
26
+ */
27
+ pending: boolean;
28
+ }
29
+ export interface UseMobileFitInput {
30
+ sessionId: string | undefined;
31
+ device: CanvasDevice;
32
+ domReady: boolean;
33
+ capabilities: Set<string> | undefined;
34
+ }
35
+ export interface UseMobileFitResult {
36
+ state: MobileFitState;
37
+ /** 接到 `useCanvasStream` 的 `onDocumentSize`。引用恒定。 */
38
+ onDocumentSize: (payload: CanvasDocumentSizePayload) => void;
39
+ }
40
+ export declare function useMobileFit(input: UseMobileFitInput): UseMobileFitResult;