@obm/apaas-embed-sdk 0.1.0 → 0.1.1
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/README.md +18 -5
- package/dist/index.cjs +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,10 @@ const params: Record<string, EmbedParamValue> = {
|
|
|
25
25
|
const instance = await sdk.embed({
|
|
26
26
|
appId: String(runtimeAppId), apiName: 'order_detail', token,
|
|
27
27
|
containerId: 'medipaas-container', params,
|
|
28
|
-
onError: error => console.error(error),
|
|
28
|
+
onError: error => console.error(error),
|
|
29
|
+
onEvent: ({ name, payload }) => {
|
|
30
|
+
if (name === 'order.saved') refreshOrder((payload as { orderId: string }).orderId)
|
|
31
|
+
},
|
|
29
32
|
})
|
|
30
33
|
// 需要关闭时:instance.close()
|
|
31
34
|
```
|
|
@@ -79,9 +82,11 @@ Content-Type: application/json
|
|
|
79
82
|
| `containerId` | 可选宿主 DOM ID;省略时创建遮罩弹窗 |
|
|
80
83
|
| `fullscreen` | 默认 false,仅控制默认弹窗尺寸 |
|
|
81
84
|
| `iframeAttrs` | iframe 属性字典,如 title、allow、sandbox;禁止 src/srcdoc(大小写均禁止) |
|
|
82
|
-
| `onReady` | iframe load 回调,不表示首次数据查询完成 |
|
|
85
|
+
| `onReady` | iframe load 回调,不表示首次数据查询完成 |
|
|
86
|
+
| `onBridgeReady` | 消息桥握手完成回调;此后可调用实例 `send()` |
|
|
83
87
|
| `onClose` | 实例 close() 回调;closeAll()、遮罩点击不会触发 |
|
|
84
|
-
| `onError` | 参数、容器或 iframe 错误回调;参数错误同时拒绝 embed Promise |
|
|
88
|
+
| `onError` | 参数、容器或 iframe 错误回调;参数错误同时拒绝 embed Promise |
|
|
89
|
+
| `onEvent` | 嵌入页业务事件回调,参数为 `{ name, payload }` |
|
|
85
90
|
|
|
86
91
|
`params` 的值允许字符串、有限数字、布尔值、null、undefined。字符串通过 URLSearchParams 编码,调用方传原始值,不预先编码。数字与布尔转为字符串;0、false、空字符串保留;null/undefined 属性忽略。对象、数组、函数、NaN、Infinity 均拒绝;params 本身不能为 null。
|
|
87
92
|
|
|
@@ -89,9 +94,17 @@ Content-Type: application/json
|
|
|
89
94
|
|
|
90
95
|
页面通过 `{{ urlParams.value.orderId }}` 读取。所有 SDK 参数在页面中均为字符串,布尔判断使用 `{{ urlParams.value.editable === 'true' }}`,ID 建议传字符串以保留前导零和大整数精度。URL 显式值优先于页面参数默认值,显式空字符串不会被默认值覆盖。目标页面需将参数绑定到数据源查询条件或初始化动作。
|
|
91
96
|
|
|
92
|
-
参数属于当前 iframe
|
|
97
|
+
参数属于当前 iframe 地址,刷新可恢复,各实例互不共享;页间导航需显式传参。切换记录可重新 embed,或在握手完成后调用 `instance.send('params.update', { orderId: '000124' })` 更新目标页面已声明的 URL 参数;返回 `false` 表示通道尚未就绪。`instance.send('page.refresh')` 可重新加载当前页面。URL 中的业务参数不能赋予权限,服务端仍验证 token 与记录权限;不要通过 URL 传敏感内容或大量数据。
|
|
98
|
+
|
|
99
|
+
## 双向消息
|
|
100
|
+
|
|
101
|
+
页面设计器的“通知第三方宿主”动作可以在按钮点击、数据请求成功等事件中发送业务事件。配置稳定的事件名(如 `order.saved`)和可选事件数据表达式(如 `{{ { orderId: variables.orderId } }}`);第三方通过 `embed({ onEvent })` 接收 `{ name, payload }`。页面未嵌入或通道未握手时不会发出事件。
|
|
102
|
+
|
|
103
|
+
SDK 与嵌入页使用 `medipaas.embed` v1 协议。SDK 核对消息来源窗口、A-PaaS origin 和实例通道 ID;嵌入页只接受父窗口的握手,回复与发送时使用该窗口的精确 origin。握手用于隔离 iframe 消息,不替代 embed token 和服务端数据权限。不要在事件载荷中传 token 或敏感业务数据。
|
|
104
|
+
|
|
105
|
+
`onReady` 仍只表示 iframe `load`,不表示业务数据完成或消息桥就绪。`onBridgeReady` 表示可以调用 `send()`;提前调用会返回 `false`。页面初始化期间发给宿主的少量事件会在握手后发送。页面内保存成功通知应配置在数据请求成功事件中。消息仅送达当前打开的 iframe,不提供离线投递。
|
|
93
106
|
|
|
94
|
-
实例返回 iframe、token、expiresAt(当前固定 0
|
|
107
|
+
实例返回 iframe、token、expiresAt(当前固定 0)、send() 和 close()。SDK 还提供 closeAll() 与 getInstanceCount()。
|
|
95
108
|
|
|
96
109
|
## 构建与示例
|
|
97
110
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var g=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var K=(r,e,t)=>e in r?g(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var L=(r,e)=>{for(var t in e)g(r,t,{get:e[t],enumerable:!0})},N=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of $(e))!k.call(r,s)&&s!==t&&g(r,s,{get:()=>e[s],enumerable:!(n=D(e,s))||n.enumerable});return r};var T=r=>N(g({},"__esModule",{value:!0}),r);var l=(r,e,t)=>K(r,typeof e!="symbol"?e+"":e,t);var z={};L(z,{ApassEmbedSDK:()=>h});module.exports=T(z);var _=new Set(["token","__proto__","constructor","prototype"]);function x(r){if(r===null||typeof r!="object")return!1;let e=Object.getPrototypeOf(r);return e===null?!0:Object.getPrototypeOf(e)===null&&Object.prototype.hasOwnProperty.call(e,"constructor")&&typeof e.constructor=="function"&&Function.prototype.toString.call(e.constructor)===Function.prototype.toString.call(Object)}function O(r={}){if(!x(r))throw new Error("iframeAttrs must be a plain object");let e=Object.entries(r);for(let[t]of e){let n=t.trim().toLowerCase();if(n==="src"||n==="srcdoc")throw new Error(`iframeAttrs.${t} cannot override the SDK page address`);if(!t||/[\s"'>/=\u0000-\u001f]/.test(t))throw new Error(`Invalid iframe attribute name: ${t}`)}return Object.fromEntries(e)}function I(r,e){if(!r||r==="."||r==="..")throw new Error(`${e} must be a non-empty path segment other than . or ..`);return encodeURIComponent(r)}function S(r,e){if(!e.token)throw new Error("Token is required");let t=String(e.apiName||e.pageCode||e.pageId||"").trim();if(!t)throw new Error("Either apiName, pageCode (deprecated), or pageId (deprecated) is required");let n=new URL(`/apps/${I(e.appId,"appId")}/user/${I(t,"apiName")}`,r);n.searchParams.set("token",e.token);let s=e.params;if(s!==void 0){if(!x(s))throw new Error("params must be a plain object");for(let[i,c]of Object.entries(s)){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)||_.has(i))throw new Error(`Invalid or reserved parameter name: ${i}`);if(c!=null){if(!["string","number","boolean"].includes(typeof c)||typeof c=="number"&&!Number.isFinite(c))throw new Error(`Parameter ${i} must be a string, finite number, or boolean`);n.searchParams.set(i,String(c))}}}return n.toString()}var h=class{constructor(e){l(this,"config");l(this,"instances",new Map);l(this,"cleanups",new Map);l(this,"instanceCounter",0);this.validateConfig(e),this.config={timeout:3e4,...e}}async embed(e){let{apiName:t,pageCode:n,pageId:s,token:i,containerId:c,onReady:A,onBridgeReady:R,onClose:U,onError:E,onEvent:j,iframeAttrs:P={},fullscreen:M=!1}=e;try{let d=S(this.config.baseUrl,e),u=O(P);n&&!t&&console.warn("[MediPaaS SDK] pageCode is deprecated. Please use apiName instead."),s&&!t&&!n&&console.warn("[MediPaaS SDK] pageId is deprecated. Please use apiName instead.");let p=this.createIframe(d,u),v=this.prepareContainer(c,M),m=`apass-${++this.instanceCounter}`,b=crypto.randomUUID(),w=new URL(d).origin,f=!1,C=o=>{if(o.source!==p.contentWindow||o.origin!==w)return;let a=o.data;!a||typeof a!="object"||a.namespace!=="medipaas.embed"||a.version!==1||a.channelId!==b||(a.type==="ready"&&!f&&(f=!0,R?.()),a.type==="event"&&f&&typeof a.name=="string"&&a.name&&j?.({name:a.name,payload:a.payload}))};window.addEventListener("message",C);let y={iframe:p,token:i,expiresAt:0,close:()=>{this.closeInstance(m),U?.()},send:(o,a)=>{if(!f||!this.instances.has(m)||o!=="page.refresh"&&o!=="params.update")return!1;try{return p.contentWindow?.postMessage({namespace:"medipaas.embed",version:1,channelId:b,type:"event",name:o,payload:a},w),!0}catch{return!1}}};if(!c){let o=v.parentElement;o.dataset.apassOverlay=m,o.addEventListener("click",a=>{a.target===o&&y.close()})}return p.onload=()=>{f=!1,p.contentWindow?.postMessage({namespace:"medipaas.embed",version:1,channelId:b,type:"hello"},w),A?.()},p.onerror=()=>{let o=new Error("Failed to load A-pass iframe");E?.(o)},v.appendChild(p),this.instances.set(m,y),this.cleanups.set(m,()=>window.removeEventListener("message",C)),y}catch(d){let u=d instanceof Error?d:new Error(String(d));throw E?.(u),u}}closeInstance(e){let t=this.instances.get(e);if(t){this.cleanups.get(e)?.(),this.cleanups.delete(e);let n=t.iframe.parentElement?.parentElement;t.iframe.remove(),n?.dataset.apassOverlay===e&&n.remove(),this.instances.delete(e)}}prepareContainer(e,t){if(e){let i=document.getElementById(e);if(!i)throw new Error(`Container with id "${e}" not found`);return i}let n=document.createElement("div");n.style.cssText=`
|
|
2
2
|
position: fixed;
|
|
3
3
|
top: 0;
|
|
4
4
|
left: 0;
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
display: flex;
|
|
10
10
|
align-items: center;
|
|
11
11
|
justify-content: center;
|
|
12
|
-
`;let
|
|
12
|
+
`;let s=document.createElement("div");return t?s.style.cssText=`
|
|
13
13
|
width: 100%;
|
|
14
14
|
height: 100%;
|
|
15
|
-
`:
|
|
15
|
+
`:s.style.cssText=`
|
|
16
16
|
width: 90%;
|
|
17
17
|
height: 90%;
|
|
18
18
|
max-width: 1200px;
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
border-radius: 8px;
|
|
21
21
|
overflow: hidden;
|
|
22
22
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
23
|
-
`,n.appendChild(
|
|
23
|
+
`,n.appendChild(s),document.body.appendChild(n),s}createIframe(e,t){let n=document.createElement("iframe");return n.src=e,n.style.cssText=`
|
|
24
24
|
width: 100%;
|
|
25
25
|
height: 100%;
|
|
26
26
|
border: none;
|
|
27
|
-
`,Object.entries(t).forEach(([
|
|
27
|
+
`,Object.entries(t).forEach(([s,i])=>{n.setAttribute(s,i)}),n}validateConfig(e){if(!e.baseUrl)throw new Error("SDKConfig.baseUrl is required");try{new URL(e.baseUrl)}catch{throw new Error(`SDKConfig.baseUrl must be a valid URL: ${e.baseUrl}`)}}closeAll(){this.instances.forEach((e,t)=>{this.closeInstance(t)})}getInstanceCount(){return this.instances.size}};0&&(module.exports={ApassEmbedSDK});
|
|
28
28
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/embedUrl.ts","../src/core/sdk.ts"],"sourcesContent":["export { ApassEmbedSDK } from './core/sdk';\r\nexport type {\r\n SDKConfig,\r\n EmbedOptions,\r\n EmbedParamValue,\r\n ApassInstanceRef,\r\n} from './types';\r\n","import type { EmbedOptions } from '../types';\r\n\r\nconst reservedNames = new Set(['token', '__proto__', 'constructor', 'prototype']);\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== 'object') return false;\r\n const prototype = Object.getPrototypeOf(value);\r\n if (prototype === null) return true;\r\n // Object.prototype differs between same-origin windows. Compare its native\r\n // constructor instead of requiring this window's exact prototype identity.\r\n return Object.getPrototypeOf(prototype) === null &&\r\n Object.prototype.hasOwnProperty.call(prototype, 'constructor') &&\r\n typeof prototype.constructor === 'function' &&\r\n Function.prototype.toString.call(prototype.constructor) === Function.prototype.toString.call(Object);\r\n}\r\n\r\n/** Snapshot once so parameter/attribute getters cannot later replace the address. */\r\nexport function prepareIframeAttributes(attrs: Record<string, string> = {}): Record<string, string> {\r\n if (!isPlainObject(attrs)) throw new Error('iframeAttrs must be a plain object');\r\n const entries = Object.entries(attrs);\r\n for (const [name] of entries) {\r\n const normalized = name.trim().toLowerCase();\r\n if (normalized === 'src' || normalized === 'srcdoc') {\r\n throw new Error(`iframeAttrs.${name} cannot override the SDK page address`);\r\n }\r\n // eslint-disable-next-line no-control-regex -- Intentionally reject ASCII control characters in attribute names.\r\n if (!name || /[\\s\"'>/=\\u0000-\\u001f]/.test(name)) {\r\n throw new Error(`Invalid iframe attribute name: ${name}`);\r\n }\r\n }\r\n return Object.fromEntries(entries);\r\n}\r\n\r\nfunction encodeSegment(value: string, name: string): string {\r\n if (!value || value === '.' || value === '..') {\r\n throw new Error(`${name} must be a non-empty path segment other than . or ..`);\r\n }\r\n return encodeURIComponent(value);\r\n}\r\n\r\n/** Build and validate the complete iframe address before any DOM is created. */\r\nexport function buildEmbedUrl(baseUrl: string, options: EmbedOptions): string {\r\n if (!options.token) throw new Error('Token is required');\r\n const apiName = String(options.apiName || options.pageCode || options.pageId || '').trim();\r\n if (!apiName) {\r\n throw new Error('Either apiName, pageCode (deprecated), or pageId (deprecated) is required');\r\n }\r\n const url = new URL(`/apps/${encodeSegment(options.appId, 'appId')}/user/${encodeSegment(apiName, 'apiName')}`, baseUrl);\r\n url.searchParams.set('token', options.token);\r\n const params = options.params;\r\n if (params !== undefined) {\r\n if (!isPlainObject(params)) {\r\n throw new Error('params must be a plain object');\r\n }\r\n for (const [name, value] of Object.entries(params)) {\r\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || reservedNames.has(name)) {\r\n throw new Error(`Invalid or reserved parameter name: ${name}`);\r\n }\r\n if (value === null || value === undefined) continue;\r\n if (!['string', 'number', 'boolean'].includes(typeof value) ||\r\n (typeof value === 'number' && !Number.isFinite(value))) {\r\n throw new Error(`Parameter ${name} must be a string, finite number, or boolean`);\r\n }\r\n url.searchParams.set(name, String(value));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n","import type {\r\n SDKConfig,\r\n EmbedOptions,\r\n ApassInstanceRef,\r\n} from '../types';\r\nimport { buildEmbedUrl, prepareIframeAttributes } from './embedUrl';\r\n\r\n/**\r\n * Main SDK class for embedding A-pass applications\r\n *\r\n * @example\r\n * ```ts\r\n * const sdk = new ApassEmbedSDK({\r\n * baseUrl: 'https://apass.example.com',\r\n * });\r\n *\r\n * // CRM 项目先调自己的后端获取 token\r\n * const tokenResponse = await fetch('/api/embed-token', {\r\n * method: 'POST',\r\n * body: JSON.stringify({\r\n * recordId: 416,\r\n * enterpriseId: 456,\r\n * }),\r\n * });\r\n * const { token } = await tokenResponse.json();\r\n *\r\n * // 然后把 token 传给 SDK\r\n * await sdk.embed({\r\n * appId: '180',\r\n * apiName: 'patient_list',\r\n * token: token,\r\n * containerId: 'my-container',\r\n * });\r\n * ```\r\n */\r\nexport class ApassEmbedSDK {\r\n private config: SDKConfig;\r\n private instances: Map<string, ApassInstanceRef> = new Map();\r\n private instanceCounter = 0;\r\n\r\n constructor(config: SDKConfig) {\r\n this.validateConfig(config);\r\n this.config = {\r\n timeout: 30000,\r\n ...config,\r\n };\r\n }\r\n\r\n /**\r\n * Embed an A-pass application in an iframe\r\n */\r\n async embed(options: EmbedOptions): Promise<ApassInstanceRef> {\r\n const {\r\n apiName,\r\n pageCode,\r\n pageId,\r\n token,\r\n containerId,\r\n onReady,\r\n onClose,\r\n onError,\r\n iframeAttrs = {},\r\n fullscreen = false,\r\n } = options;\r\n\r\n try {\r\n const url = buildEmbedUrl(this.config.baseUrl, options);\r\n const attrs = prepareIframeAttributes(iframeAttrs);\r\n // Warn if using deprecated parameters\r\n if (pageCode && !apiName) {\r\n console.warn('[MediPaaS SDK] pageCode is deprecated. Please use apiName instead.');\r\n }\r\n if (pageId && !apiName && !pageCode) {\r\n console.warn('[MediPaaS SDK] pageId is deprecated. Please use apiName instead.');\r\n }\r\n\r\n // Prepare the detached iframe first: DOM attribute validation must finish\r\n // before a default overlay is attached to the host page.\r\n const iframe = this.createIframe(url, attrs);\r\n const container = this.prepareContainer(containerId, fullscreen);\r\n\r\n // Step 5: Setup lifecycle handlers\r\n const instanceId = `apass-${++this.instanceCounter}`;\r\n\r\n const instance: ApassInstanceRef = {\r\n iframe,\r\n token,\r\n expiresAt: 0, // Token expiration not tracked in this mode\r\n close: () => {\r\n this.closeInstance(instanceId);\r\n onClose?.();\r\n },\r\n };\r\n\r\n // Step 6: Append iframe to container\r\n container.appendChild(iframe);\r\n\r\n // Step 7: Setup ready handler\r\n iframe.onload = () => {\r\n onReady?.();\r\n };\r\n\r\n // Step 8: Setup error handler\r\n iframe.onerror = () => {\r\n const error = new Error('Failed to load A-pass iframe');\r\n onError?.(error);\r\n };\r\n\r\n // Store instance reference\r\n this.instances.set(instanceId, instance);\r\n\r\n return instance;\r\n } catch (error) {\r\n const err =\r\n error instanceof Error ? error : new Error(String(error));\r\n onError?.(err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Close and cleanup an instance\r\n */\r\n private closeInstance(instanceId: string): void {\r\n const instance = this.instances.get(instanceId);\r\n if (instance) {\r\n // Remove iframe from DOM\r\n instance.iframe.remove();\r\n // Remove from tracking\r\n this.instances.delete(instanceId);\r\n }\r\n }\r\n\r\n\r\n /**\r\n * Prepare container for iframe\r\n */\r\n private prepareContainer(\r\n containerId: string | undefined,\r\n fullscreen: boolean,\r\n ): HTMLElement {\r\n if (containerId) {\r\n const container = document.getElementById(containerId);\r\n if (!container) {\r\n throw new Error(`Container with id \"${containerId}\" not found`);\r\n }\r\n return container;\r\n }\r\n\r\n // Create default container\r\n const container = document.createElement('div');\r\n container.style.cssText = `\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n z-index: 10000;\r\n background: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n `;\r\n\r\n const wrapper = document.createElement('div');\r\n if (fullscreen) {\r\n wrapper.style.cssText = `\r\n width: 100%;\r\n height: 100%;\r\n `;\r\n } else {\r\n wrapper.style.cssText = `\r\n width: 90%;\r\n height: 90%;\r\n max-width: 1200px;\r\n max-height: 800px;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\r\n `;\r\n }\r\n\r\n container.appendChild(wrapper);\r\n document.body.appendChild(container);\r\n\r\n // Auto-close on background click\r\n container.addEventListener('click', (e) => {\r\n if (e.target === container) {\r\n container.remove();\r\n }\r\n });\r\n\r\n return wrapper;\r\n }\r\n\r\n /**\r\n * Create iframe element\r\n */\r\n private createIframe(\r\n url: string,\r\n attrs: Record<string, string>,\r\n ): HTMLIFrameElement {\r\n const iframe = document.createElement('iframe');\r\n\r\n // Build A-pass URL with token parameter\r\n iframe.src = url;\r\n iframe.style.cssText = `\r\n width: 100%;\r\n height: 100%;\r\n border: none;\r\n `;\r\n\r\n // Apply custom attributes\r\n Object.entries(attrs).forEach(([key, value]) => {\r\n iframe.setAttribute(key, value);\r\n });\r\n\r\n return iframe;\r\n }\r\n\r\n /**\r\n * Validate SDK configuration\r\n */\r\n private validateConfig(config: SDKConfig): void {\r\n if (!config.baseUrl) {\r\n throw new Error('SDKConfig.baseUrl is required');\r\n }\r\n\r\n try {\r\n new URL(config.baseUrl);\r\n } catch {\r\n throw new Error(\r\n `SDKConfig.baseUrl must be a valid URL: ${config.baseUrl}`,\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Close all instances\r\n */\r\n closeAll(): void {\r\n this.instances.forEach((_, instanceId) => {\r\n this.closeInstance(instanceId);\r\n });\r\n }\r\n\r\n /**\r\n * Get active instances count\r\n */\r\n getInstanceCount(): number {\r\n return this.instances.size;\r\n }\r\n}\r\n"],"mappings":"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","names":["src_exports","__export","ApassEmbedSDK","__toCommonJS","reservedNames","isPlainObject","value","prototype","prepareIframeAttributes","attrs","entries","name","normalized","encodeSegment","buildEmbedUrl","baseUrl","options","apiName","url","params","ApassEmbedSDK","config","__publicField","options","apiName","pageCode","pageId","token","containerId","onReady","onClose","onError","iframeAttrs","fullscreen","url","buildEmbedUrl","attrs","prepareIframeAttributes","iframe","container","instanceId","instance","error","err","wrapper","e","key","value","_"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/embedUrl.ts","../src/core/sdk.ts"],"sourcesContent":["export { ApassEmbedSDK } from './core/sdk';\r\nexport type {\r\n SDKConfig,\r\n EmbedOptions,\r\n EmbedParamValue,\n EmbedBusinessEvent,\n ApassInstanceRef,\r\n} from './types';\r\n","import type { EmbedOptions } from '../types';\r\n\r\nconst reservedNames = new Set(['token', '__proto__', 'constructor', 'prototype']);\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== 'object') return false;\r\n const prototype = Object.getPrototypeOf(value);\r\n if (prototype === null) return true;\r\n // Object.prototype differs between same-origin windows. Compare its native\r\n // constructor instead of requiring this window's exact prototype identity.\r\n return Object.getPrototypeOf(prototype) === null &&\r\n Object.prototype.hasOwnProperty.call(prototype, 'constructor') &&\r\n typeof prototype.constructor === 'function' &&\r\n Function.prototype.toString.call(prototype.constructor) === Function.prototype.toString.call(Object);\r\n}\r\n\r\n/** Snapshot once so parameter/attribute getters cannot later replace the address. */\r\nexport function prepareIframeAttributes(attrs: Record<string, string> = {}): Record<string, string> {\r\n if (!isPlainObject(attrs)) throw new Error('iframeAttrs must be a plain object');\r\n const entries = Object.entries(attrs);\r\n for (const [name] of entries) {\r\n const normalized = name.trim().toLowerCase();\r\n if (normalized === 'src' || normalized === 'srcdoc') {\r\n throw new Error(`iframeAttrs.${name} cannot override the SDK page address`);\r\n }\r\n // eslint-disable-next-line no-control-regex -- Intentionally reject ASCII control characters in attribute names.\r\n if (!name || /[\\s\"'>/=\\u0000-\\u001f]/.test(name)) {\r\n throw new Error(`Invalid iframe attribute name: ${name}`);\r\n }\r\n }\r\n return Object.fromEntries(entries);\r\n}\r\n\r\nfunction encodeSegment(value: string, name: string): string {\r\n if (!value || value === '.' || value === '..') {\r\n throw new Error(`${name} must be a non-empty path segment other than . or ..`);\r\n }\r\n return encodeURIComponent(value);\r\n}\r\n\r\n/** Build and validate the complete iframe address before any DOM is created. */\r\nexport function buildEmbedUrl(baseUrl: string, options: EmbedOptions): string {\r\n if (!options.token) throw new Error('Token is required');\r\n const apiName = String(options.apiName || options.pageCode || options.pageId || '').trim();\r\n if (!apiName) {\r\n throw new Error('Either apiName, pageCode (deprecated), or pageId (deprecated) is required');\r\n }\r\n const url = new URL(`/apps/${encodeSegment(options.appId, 'appId')}/user/${encodeSegment(apiName, 'apiName')}`, baseUrl);\r\n url.searchParams.set('token', options.token);\r\n const params = options.params;\r\n if (params !== undefined) {\r\n if (!isPlainObject(params)) {\r\n throw new Error('params must be a plain object');\r\n }\r\n for (const [name, value] of Object.entries(params)) {\r\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || reservedNames.has(name)) {\r\n throw new Error(`Invalid or reserved parameter name: ${name}`);\r\n }\r\n if (value === null || value === undefined) continue;\r\n if (!['string', 'number', 'boolean'].includes(typeof value) ||\r\n (typeof value === 'number' && !Number.isFinite(value))) {\r\n throw new Error(`Parameter ${name} must be a string, finite number, or boolean`);\r\n }\r\n url.searchParams.set(name, String(value));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n","import type {\r\n SDKConfig,\r\n EmbedOptions,\r\n ApassInstanceRef,\r\n} from '../types';\r\nimport { buildEmbedUrl, prepareIframeAttributes } from './embedUrl';\r\n\r\n/**\r\n * Main SDK class for embedding A-pass applications\r\n *\r\n * @example\r\n * ```ts\r\n * const sdk = new ApassEmbedSDK({\r\n * baseUrl: 'https://apass.example.com',\r\n * });\r\n *\r\n * // CRM 项目先调自己的后端获取 token\r\n * const tokenResponse = await fetch('/api/embed-token', {\r\n * method: 'POST',\r\n * body: JSON.stringify({\r\n * recordId: 416,\r\n * enterpriseId: 456,\r\n * }),\r\n * });\r\n * const { token } = await tokenResponse.json();\r\n *\r\n * // 然后把 token 传给 SDK\r\n * await sdk.embed({\r\n * appId: '180',\r\n * apiName: 'patient_list',\r\n * token: token,\r\n * containerId: 'my-container',\r\n * });\r\n * ```\r\n */\r\nexport class ApassEmbedSDK {\r\n private config: SDKConfig;\r\n private instances: Map<string, ApassInstanceRef> = new Map();\n private cleanups: Map<string, () => void> = new Map();\n private instanceCounter = 0;\r\n\r\n constructor(config: SDKConfig) {\r\n this.validateConfig(config);\r\n this.config = {\r\n timeout: 30000,\r\n ...config,\r\n };\r\n }\r\n\r\n /**\r\n * Embed an A-pass application in an iframe\r\n */\r\n async embed(options: EmbedOptions): Promise<ApassInstanceRef> {\r\n const {\r\n apiName,\r\n pageCode,\r\n pageId,\r\n token,\r\n containerId,\r\n onReady,\n onBridgeReady,\n onClose,\r\n onError,\n onEvent,\n iframeAttrs = {},\r\n fullscreen = false,\r\n } = options;\r\n\r\n try {\r\n const url = buildEmbedUrl(this.config.baseUrl, options);\r\n const attrs = prepareIframeAttributes(iframeAttrs);\r\n // Warn if using deprecated parameters\r\n if (pageCode && !apiName) {\r\n console.warn('[MediPaaS SDK] pageCode is deprecated. Please use apiName instead.');\r\n }\r\n if (pageId && !apiName && !pageCode) {\r\n console.warn('[MediPaaS SDK] pageId is deprecated. Please use apiName instead.');\r\n }\r\n\r\n // Prepare the detached iframe first: DOM attribute validation must finish\r\n // before a default overlay is attached to the host page.\r\n const iframe = this.createIframe(url, attrs);\r\n const container = this.prepareContainer(containerId, fullscreen);\r\n\r\n // Step 5: Setup lifecycle handlers\r\n const instanceId = `apass-${++this.instanceCounter}`;\n const channelId = crypto.randomUUID();\n const targetOrigin = new URL(url).origin;\n let ready = false;\n const receiveMessage = (event: MessageEvent) => {\n if (event.source !== iframe.contentWindow || event.origin !== targetOrigin) return;\n const message = event.data;\n if (!message || typeof message !== 'object' || message.namespace !== 'medipaas.embed'\n || message.version !== 1 || message.channelId !== channelId) return;\n if (message.type === 'ready' && !ready) {\n ready = true;\n onBridgeReady?.();\n }\n if (message.type === 'event' && ready && typeof message.name === 'string' && message.name) {\n onEvent?.({ name: message.name, payload: message.payload });\n }\n };\n window.addEventListener('message', receiveMessage);\n\n const instance: ApassInstanceRef = {\n iframe,\r\n token,\r\n expiresAt: 0, // Token expiration not tracked in this mode\r\n close: () => {\n this.closeInstance(instanceId);\n onClose?.();\n },\n send: (name, payload) => {\n if (!ready || !this.instances.has(instanceId)) return false;\n if (name !== 'page.refresh' && name !== 'params.update') return false;\n try {\n iframe.contentWindow?.postMessage({ namespace: 'medipaas.embed', version: 1,\n channelId, type: 'event', name, payload }, targetOrigin);\n return true;\n } catch {\n return false;\n }\n },\n };\n\n if (!containerId) {\n const overlay = container.parentElement!;\n overlay.dataset.apassOverlay = instanceId;\n overlay.addEventListener('click', (event) => {\n if (event.target === overlay) instance.close();\n });\n }\n\r\n // Step 6: Setup ready handler before attaching the iframe\n iframe.onload = () => {\n ready = false;\n iframe.contentWindow?.postMessage({ namespace: 'medipaas.embed', version: 1,\n channelId, type: 'hello' }, targetOrigin);\n onReady?.();\n };\r\n\r\n // Step 8: Setup error handler\r\n iframe.onerror = () => {\n const error = new Error('Failed to load A-pass iframe');\r\n onError?.(error);\r\n };\n\n container.appendChild(iframe);\n\r\n // Store instance reference\r\n this.instances.set(instanceId, instance);\n this.cleanups.set(instanceId, () => window.removeEventListener('message', receiveMessage));\n\r\n return instance;\r\n } catch (error) {\r\n const err =\r\n error instanceof Error ? error : new Error(String(error));\r\n onError?.(err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Close and cleanup an instance\r\n */\r\n private closeInstance(instanceId: string): void {\n const instance = this.instances.get(instanceId);\r\n if (instance) {\n this.cleanups.get(instanceId)?.();\n this.cleanups.delete(instanceId);\n // Remove iframe from DOM\n const overlay = instance.iframe.parentElement?.parentElement;\n instance.iframe.remove();\n if (overlay?.dataset.apassOverlay === instanceId) overlay.remove();\n // Remove from tracking\r\n this.instances.delete(instanceId);\r\n }\r\n }\n\r\n\r\n /**\r\n * Prepare container for iframe\r\n */\r\n private prepareContainer(\r\n containerId: string | undefined,\r\n fullscreen: boolean,\r\n ): HTMLElement {\r\n if (containerId) {\r\n const container = document.getElementById(containerId);\r\n if (!container) {\r\n throw new Error(`Container with id \"${containerId}\" not found`);\r\n }\r\n return container;\r\n }\r\n\r\n // Create default container\r\n const container = document.createElement('div');\r\n container.style.cssText = `\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n z-index: 10000;\r\n background: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n `;\r\n\r\n const wrapper = document.createElement('div');\r\n if (fullscreen) {\r\n wrapper.style.cssText = `\r\n width: 100%;\r\n height: 100%;\r\n `;\r\n } else {\r\n wrapper.style.cssText = `\r\n width: 90%;\r\n height: 90%;\r\n max-width: 1200px;\r\n max-height: 800px;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\r\n `;\r\n }\r\n\r\n container.appendChild(wrapper);\r\n document.body.appendChild(container);\r\n\r\n return wrapper;\n }\r\n\r\n /**\r\n * Create iframe element\r\n */\r\n private createIframe(\r\n url: string,\r\n attrs: Record<string, string>,\r\n ): HTMLIFrameElement {\r\n const iframe = document.createElement('iframe');\r\n\r\n // Build A-pass URL with token parameter\r\n iframe.src = url;\r\n iframe.style.cssText = `\r\n width: 100%;\r\n height: 100%;\r\n border: none;\r\n `;\r\n\r\n // Apply custom attributes\r\n Object.entries(attrs).forEach(([key, value]) => {\r\n iframe.setAttribute(key, value);\r\n });\r\n\r\n return iframe;\r\n }\r\n\r\n /**\r\n * Validate SDK configuration\r\n */\r\n private validateConfig(config: SDKConfig): void {\r\n if (!config.baseUrl) {\r\n throw new Error('SDKConfig.baseUrl is required');\r\n }\r\n\r\n try {\r\n new URL(config.baseUrl);\r\n } catch {\r\n throw new Error(\r\n `SDKConfig.baseUrl must be a valid URL: ${config.baseUrl}`,\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Close all instances\r\n */\r\n closeAll(): void {\r\n this.instances.forEach((_, instanceId) => {\r\n this.closeInstance(instanceId);\r\n });\r\n }\r\n\r\n /**\r\n * Get active instances count\r\n */\r\n getInstanceCount(): number {\r\n return this.instances.size;\r\n }\r\n}\r\n"],"mappings":"ijBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GCEA,IAAMI,EAAgB,IAAI,IAAI,CAAC,QAAS,YAAa,cAAe,WAAW,CAAC,EAEhF,SAASC,EAAcC,EAAkD,CACvE,GAAIA,IAAU,MAAQ,OAAOA,GAAU,SAAU,MAAO,GACxD,IAAMC,EAAY,OAAO,eAAeD,CAAK,EAC7C,OAAIC,IAAc,KAAa,GAGxB,OAAO,eAAeA,CAAS,IAAM,MAC1C,OAAO,UAAU,eAAe,KAAKA,EAAW,aAAa,GAC7D,OAAOA,EAAU,aAAgB,YACjC,SAAS,UAAU,SAAS,KAAKA,EAAU,WAAW,IAAM,SAAS,UAAU,SAAS,KAAK,MAAM,CACvG,CAGO,SAASC,EAAwBC,EAAgC,CAAC,EAA2B,CAClG,GAAI,CAACJ,EAAcI,CAAK,EAAG,MAAM,IAAI,MAAM,oCAAoC,EAC/E,IAAMC,EAAU,OAAO,QAAQD,CAAK,EACpC,OAAW,CAACE,CAAI,IAAKD,EAAS,CAC5B,IAAME,EAAaD,EAAK,KAAK,EAAE,YAAY,EAC3C,GAAIC,IAAe,OAASA,IAAe,SACzC,MAAM,IAAI,MAAM,eAAeD,CAAI,uCAAuC,EAG5E,GAAI,CAACA,GAAQ,yBAAyB,KAAKA,CAAI,EAC7C,MAAM,IAAI,MAAM,kCAAkCA,CAAI,EAAE,CAE5D,CACA,OAAO,OAAO,YAAYD,CAAO,CACnC,CAEA,SAASG,EAAcP,EAAeK,EAAsB,CAC1D,GAAI,CAACL,GAASA,IAAU,KAAOA,IAAU,KACvC,MAAM,IAAI,MAAM,GAAGK,CAAI,sDAAsD,EAE/E,OAAO,mBAAmBL,CAAK,CACjC,CAGO,SAASQ,EAAcC,EAAiBC,EAA+B,CAC5E,GAAI,CAACA,EAAQ,MAAO,MAAM,IAAI,MAAM,mBAAmB,EACvD,IAAMC,EAAU,OAAOD,EAAQ,SAAWA,EAAQ,UAAYA,EAAQ,QAAU,EAAE,EAAE,KAAK,EACzF,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,2EAA2E,EAE7F,IAAMC,EAAM,IAAI,IAAI,SAASL,EAAcG,EAAQ,MAAO,OAAO,CAAC,SAASH,EAAcI,EAAS,SAAS,CAAC,GAAIF,CAAO,EACvHG,EAAI,aAAa,IAAI,QAASF,EAAQ,KAAK,EAC3C,IAAMG,EAASH,EAAQ,OACvB,GAAIG,IAAW,OAAW,CACxB,GAAI,CAACd,EAAcc,CAAM,EACvB,MAAM,IAAI,MAAM,+BAA+B,EAEjD,OAAW,CAACR,EAAML,CAAK,IAAK,OAAO,QAAQa,CAAM,EAAG,CAClD,GAAI,CAAC,2BAA2B,KAAKR,CAAI,GAAKP,EAAc,IAAIO,CAAI,EAClE,MAAM,IAAI,MAAM,uCAAuCA,CAAI,EAAE,EAE/D,GAAIL,GAAU,KACd,IAAI,CAAC,CAAC,SAAU,SAAU,SAAS,EAAE,SAAS,OAAOA,CAAK,GACvD,OAAOA,GAAU,UAAY,CAAC,OAAO,SAASA,CAAK,EACpD,MAAM,IAAI,MAAM,aAAaK,CAAI,8CAA8C,EAEjFO,EAAI,aAAa,IAAIP,EAAM,OAAOL,CAAK,CAAC,EAC1C,CACF,CACA,OAAOY,EAAI,SAAS,CACtB,CChCO,IAAME,EAAN,KAAoB,CAMzB,YAAYC,EAAmB,CAL/BC,EAAA,KAAQ,UACRA,EAAA,KAAQ,YAA2C,IAAI,KACvDA,EAAA,KAAQ,WAAoC,IAAI,KAChDA,EAAA,KAAQ,kBAAkB,GAGxB,KAAK,eAAeD,CAAM,EAC1B,KAAK,OAAS,CACZ,QAAS,IACT,GAAGA,CACL,CACF,CAKA,MAAM,MAAME,EAAkD,CAC5D,GAAM,CACJ,QAAAC,EACA,SAAAC,EACA,OAAAC,EACA,MAAAC,EACA,YAAAC,EACA,QAAAC,EACA,cAAAC,EACA,QAAAC,EACA,QAAAC,EACA,QAAAC,EACA,YAAAC,EAAc,CAAC,EACf,WAAAC,EAAa,EACf,EAAIZ,EAEJ,GAAI,CACF,IAAMa,EAAMC,EAAc,KAAK,OAAO,QAASd,CAAO,EAChDe,EAAQC,EAAwBL,CAAW,EAE7CT,GAAY,CAACD,GACf,QAAQ,KAAK,oEAAoE,EAE/EE,GAAU,CAACF,GAAW,CAACC,GACzB,QAAQ,KAAK,kEAAkE,EAKjF,IAAMe,EAAS,KAAK,aAAaJ,EAAKE,CAAK,EACrCG,EAAY,KAAK,iBAAiBb,EAAaO,CAAU,EAGzDO,EAAa,SAAS,EAAE,KAAK,eAAe,GAC5CC,EAAY,OAAO,WAAW,EAC9BC,EAAe,IAAI,IAAIR,CAAG,EAAE,OAC9BS,EAAQ,GACNC,EAAkBC,GAAwB,CAC9C,GAAIA,EAAM,SAAWP,EAAO,eAAiBO,EAAM,SAAWH,EAAc,OAC5E,IAAMI,EAAUD,EAAM,KAClB,CAACC,GAAW,OAAOA,GAAY,UAAYA,EAAQ,YAAc,kBAChEA,EAAQ,UAAY,GAAKA,EAAQ,YAAcL,IAChDK,EAAQ,OAAS,SAAW,CAACH,IAC/BA,EAAQ,GACRf,IAAgB,GAEdkB,EAAQ,OAAS,SAAWH,GAAS,OAAOG,EAAQ,MAAS,UAAYA,EAAQ,MACnFf,IAAU,CAAE,KAAMe,EAAQ,KAAM,QAASA,EAAQ,OAAQ,CAAC,EAE9D,EACA,OAAO,iBAAiB,UAAWF,CAAc,EAEjD,IAAMG,EAA6B,CACjC,OAAAT,EACA,MAAAb,EACA,UAAW,EACX,MAAO,IAAM,CACX,KAAK,cAAce,CAAU,EAC7BX,IAAU,CACZ,EACA,KAAM,CAACmB,EAAMC,IAAY,CAEvB,GADI,CAACN,GAAS,CAAC,KAAK,UAAU,IAAIH,CAAU,GACxCQ,IAAS,gBAAkBA,IAAS,gBAAiB,MAAO,GAChE,GAAI,CACF,OAAAV,EAAO,eAAe,YAAY,CAAE,UAAW,iBAAkB,QAAS,EACxE,UAAAG,EAAW,KAAM,QAAS,KAAAO,EAAM,QAAAC,CAAQ,EAAGP,CAAY,EAClD,EACT,MAAQ,CACN,MAAO,EACT,CACF,CACF,EAEA,GAAI,CAAChB,EAAa,CAChB,IAAMwB,EAAUX,EAAU,cAC1BW,EAAQ,QAAQ,aAAeV,EAC/BU,EAAQ,iBAAiB,QAAUL,GAAU,CACvCA,EAAM,SAAWK,GAASH,EAAS,MAAM,CAC/C,CAAC,CACH,CAGA,OAAAT,EAAO,OAAS,IAAM,CACpBK,EAAQ,GACRL,EAAO,eAAe,YAAY,CAAE,UAAW,iBAAkB,QAAS,EACxE,UAAAG,EAAW,KAAM,OAAQ,EAAGC,CAAY,EAC1Cf,IAAU,CACZ,EAGAW,EAAO,QAAU,IAAM,CACrB,IAAMa,EAAQ,IAAI,MAAM,8BAA8B,EACtDrB,IAAUqB,CAAK,CACjB,EAEAZ,EAAU,YAAYD,CAAM,EAG5B,KAAK,UAAU,IAAIE,EAAYO,CAAQ,EACvC,KAAK,SAAS,IAAIP,EAAY,IAAM,OAAO,oBAAoB,UAAWI,CAAc,CAAC,EAElFG,CACT,OAASI,EAAO,CACd,IAAMC,EACJD,aAAiB,MAAQA,EAAQ,IAAI,MAAM,OAAOA,CAAK,CAAC,EAC1D,MAAArB,IAAUsB,CAAG,EACPA,CACR,CACF,CAKQ,cAAcZ,EAA0B,CAC9C,IAAMO,EAAW,KAAK,UAAU,IAAIP,CAAU,EAC9C,GAAIO,EAAU,CACZ,KAAK,SAAS,IAAIP,CAAU,IAAI,EAChC,KAAK,SAAS,OAAOA,CAAU,EAE/B,IAAMU,EAAUH,EAAS,OAAO,eAAe,cAC/CA,EAAS,OAAO,OAAO,EACnBG,GAAS,QAAQ,eAAiBV,GAAYU,EAAQ,OAAO,EAEjE,KAAK,UAAU,OAAOV,CAAU,CAClC,CACF,CAMQ,iBACNd,EACAO,EACa,CACb,GAAIP,EAAa,CACf,IAAMa,EAAY,SAAS,eAAeb,CAAW,EACrD,GAAI,CAACa,EACH,MAAM,IAAI,MAAM,sBAAsBb,CAAW,aAAa,EAEhE,OAAOa,CACT,CAGA,IAAMA,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAa1B,IAAMc,EAAU,SAAS,cAAc,KAAK,EAC5C,OAAIpB,EACFoB,EAAQ,MAAM,QAAU;AAAA;AAAA;AAAA,QAKxBA,EAAQ,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAW1Bd,EAAU,YAAYc,CAAO,EAC7B,SAAS,KAAK,YAAYd,CAAS,EAE5Bc,CACT,CAKQ,aACNnB,EACAE,EACmB,CACnB,IAAME,EAAS,SAAS,cAAc,QAAQ,EAG9C,OAAAA,EAAO,IAAMJ,EACbI,EAAO,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA,MAOvB,OAAO,QAAQF,CAAK,EAAE,QAAQ,CAAC,CAACkB,EAAKC,CAAK,IAAM,CAC9CjB,EAAO,aAAagB,EAAKC,CAAK,CAChC,CAAC,EAEMjB,CACT,CAKQ,eAAenB,EAAyB,CAC9C,GAAI,CAACA,EAAO,QACV,MAAM,IAAI,MAAM,+BAA+B,EAGjD,GAAI,CACF,IAAI,IAAIA,EAAO,OAAO,CACxB,MAAQ,CACN,MAAM,IAAI,MACR,0CAA0CA,EAAO,OAAO,EAC1D,CACF,CACF,CAKA,UAAiB,CACf,KAAK,UAAU,QAAQ,CAACqC,EAAGhB,IAAe,CACxC,KAAK,cAAcA,CAAU,CAC/B,CAAC,CACH,CAKA,kBAA2B,CACzB,OAAO,KAAK,UAAU,IACxB,CACF","names":["src_exports","__export","ApassEmbedSDK","__toCommonJS","reservedNames","isPlainObject","value","prototype","prepareIframeAttributes","attrs","entries","name","normalized","encodeSegment","buildEmbedUrl","baseUrl","options","apiName","url","params","ApassEmbedSDK","config","__publicField","options","apiName","pageCode","pageId","token","containerId","onReady","onBridgeReady","onClose","onError","onEvent","iframeAttrs","fullscreen","url","buildEmbedUrl","attrs","prepareIframeAttributes","iframe","container","instanceId","channelId","targetOrigin","ready","receiveMessage","event","message","instance","name","payload","overlay","error","err","wrapper","key","value","_"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -11,6 +11,10 @@ interface SDKConfig {
|
|
|
11
11
|
* Options for embedding an A-pass application
|
|
12
12
|
*/
|
|
13
13
|
type EmbedParamValue = string | number | boolean | null | undefined;
|
|
14
|
+
interface EmbedBusinessEvent {
|
|
15
|
+
name: string;
|
|
16
|
+
payload: unknown;
|
|
17
|
+
}
|
|
14
18
|
interface EmbedOptions {
|
|
15
19
|
/** Application ID in A-pass */
|
|
16
20
|
appId: string;
|
|
@@ -28,10 +32,14 @@ interface EmbedOptions {
|
|
|
28
32
|
containerId?: string;
|
|
29
33
|
/** Optional callback when iframe is loaded */
|
|
30
34
|
onReady?: () => void;
|
|
35
|
+
/** Called after the embedded page acknowledges the message channel. */
|
|
36
|
+
onBridgeReady?: () => void;
|
|
31
37
|
/** Optional callback when iframe is closed/destroyed */
|
|
32
38
|
onClose?: () => void;
|
|
33
39
|
/** Optional callback on error */
|
|
34
40
|
onError?: (error: Error) => void;
|
|
41
|
+
/** Called for business events emitted by the embedded page. */
|
|
42
|
+
onEvent?: (event: EmbedBusinessEvent) => void;
|
|
35
43
|
/** Optional custom iframe attributes. src and srcdoc are prohibited. */
|
|
36
44
|
iframeAttrs?: Record<string, string>;
|
|
37
45
|
/** Whether to fullscreen the iframe (default: false) */
|
|
@@ -45,6 +53,8 @@ interface ApassInstanceRef {
|
|
|
45
53
|
iframe: HTMLIFrameElement;
|
|
46
54
|
/** Close/destroy the instance */
|
|
47
55
|
close: () => void;
|
|
56
|
+
/** Send a supported event to the running page. Returns false until the bridge is ready. */
|
|
57
|
+
send: (name: 'page.refresh' | 'params.update', payload?: unknown) => boolean;
|
|
48
58
|
/** Token expiration time */
|
|
49
59
|
expiresAt: number;
|
|
50
60
|
/** Token value */
|
|
@@ -82,6 +92,7 @@ interface ApassInstanceRef {
|
|
|
82
92
|
declare class ApassEmbedSDK {
|
|
83
93
|
private config;
|
|
84
94
|
private instances;
|
|
95
|
+
private cleanups;
|
|
85
96
|
private instanceCounter;
|
|
86
97
|
constructor(config: SDKConfig);
|
|
87
98
|
/**
|
|
@@ -114,4 +125,4 @@ declare class ApassEmbedSDK {
|
|
|
114
125
|
getInstanceCount(): number;
|
|
115
126
|
}
|
|
116
127
|
|
|
117
|
-
export { ApassEmbedSDK, type ApassInstanceRef, type EmbedOptions, type EmbedParamValue, type SDKConfig };
|
|
128
|
+
export { ApassEmbedSDK, type ApassInstanceRef, type EmbedBusinessEvent, type EmbedOptions, type EmbedParamValue, type SDKConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ interface SDKConfig {
|
|
|
11
11
|
* Options for embedding an A-pass application
|
|
12
12
|
*/
|
|
13
13
|
type EmbedParamValue = string | number | boolean | null | undefined;
|
|
14
|
+
interface EmbedBusinessEvent {
|
|
15
|
+
name: string;
|
|
16
|
+
payload: unknown;
|
|
17
|
+
}
|
|
14
18
|
interface EmbedOptions {
|
|
15
19
|
/** Application ID in A-pass */
|
|
16
20
|
appId: string;
|
|
@@ -28,10 +32,14 @@ interface EmbedOptions {
|
|
|
28
32
|
containerId?: string;
|
|
29
33
|
/** Optional callback when iframe is loaded */
|
|
30
34
|
onReady?: () => void;
|
|
35
|
+
/** Called after the embedded page acknowledges the message channel. */
|
|
36
|
+
onBridgeReady?: () => void;
|
|
31
37
|
/** Optional callback when iframe is closed/destroyed */
|
|
32
38
|
onClose?: () => void;
|
|
33
39
|
/** Optional callback on error */
|
|
34
40
|
onError?: (error: Error) => void;
|
|
41
|
+
/** Called for business events emitted by the embedded page. */
|
|
42
|
+
onEvent?: (event: EmbedBusinessEvent) => void;
|
|
35
43
|
/** Optional custom iframe attributes. src and srcdoc are prohibited. */
|
|
36
44
|
iframeAttrs?: Record<string, string>;
|
|
37
45
|
/** Whether to fullscreen the iframe (default: false) */
|
|
@@ -45,6 +53,8 @@ interface ApassInstanceRef {
|
|
|
45
53
|
iframe: HTMLIFrameElement;
|
|
46
54
|
/** Close/destroy the instance */
|
|
47
55
|
close: () => void;
|
|
56
|
+
/** Send a supported event to the running page. Returns false until the bridge is ready. */
|
|
57
|
+
send: (name: 'page.refresh' | 'params.update', payload?: unknown) => boolean;
|
|
48
58
|
/** Token expiration time */
|
|
49
59
|
expiresAt: number;
|
|
50
60
|
/** Token value */
|
|
@@ -82,6 +92,7 @@ interface ApassInstanceRef {
|
|
|
82
92
|
declare class ApassEmbedSDK {
|
|
83
93
|
private config;
|
|
84
94
|
private instances;
|
|
95
|
+
private cleanups;
|
|
85
96
|
private instanceCounter;
|
|
86
97
|
constructor(config: SDKConfig);
|
|
87
98
|
/**
|
|
@@ -114,4 +125,4 @@ declare class ApassEmbedSDK {
|
|
|
114
125
|
getInstanceCount(): number;
|
|
115
126
|
}
|
|
116
127
|
|
|
117
|
-
export { ApassEmbedSDK, type ApassInstanceRef, type EmbedOptions, type EmbedParamValue, type SDKConfig };
|
|
128
|
+
export { ApassEmbedSDK, type ApassInstanceRef, type EmbedBusinessEvent, type EmbedOptions, type EmbedParamValue, type SDKConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var M=Object.defineProperty;var D=(n,e,t)=>e in n?M(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var l=(n,e,t)=>D(n,typeof e!="symbol"?e+"":e,t);var $=new Set(["token","__proto__","constructor","prototype"]);function I(n){if(n===null||typeof n!="object")return!1;let e=Object.getPrototypeOf(n);return e===null?!0:Object.getPrototypeOf(e)===null&&Object.prototype.hasOwnProperty.call(e,"constructor")&&typeof e.constructor=="function"&&Function.prototype.toString.call(e.constructor)===Function.prototype.toString.call(Object)}function x(n={}){if(!I(n))throw new Error("iframeAttrs must be a plain object");let e=Object.entries(n);for(let[t]of e){let r=t.trim().toLowerCase();if(r==="src"||r==="srcdoc")throw new Error(`iframeAttrs.${t} cannot override the SDK page address`);if(!t||/[\s"'>/=\u0000-\u001f]/.test(t))throw new Error(`Invalid iframe attribute name: ${t}`)}return Object.fromEntries(e)}function C(n,e){if(!n||n==="."||n==="..")throw new Error(`${e} must be a non-empty path segment other than . or ..`);return encodeURIComponent(n)}function O(n,e){if(!e.token)throw new Error("Token is required");let t=String(e.apiName||e.pageCode||e.pageId||"").trim();if(!t)throw new Error("Either apiName, pageCode (deprecated), or pageId (deprecated) is required");let r=new URL(`/apps/${C(e.appId,"appId")}/user/${C(t,"apiName")}`,n);r.searchParams.set("token",e.token);let o=e.params;if(o!==void 0){if(!I(o))throw new Error("params must be a plain object");for(let[i,c]of Object.entries(o)){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)||$.has(i))throw new Error(`Invalid or reserved parameter name: ${i}`);if(c!=null){if(!["string","number","boolean"].includes(typeof c)||typeof c=="number"&&!Number.isFinite(c))throw new Error(`Parameter ${i} must be a string, finite number, or boolean`);r.searchParams.set(i,String(c))}}}return r.toString()}var w=class{constructor(e){l(this,"config");l(this,"instances",new Map);l(this,"cleanups",new Map);l(this,"instanceCounter",0);this.validateConfig(e),this.config={timeout:3e4,...e}}async embed(e){let{apiName:t,pageCode:r,pageId:o,token:i,containerId:c,onReady:S,onBridgeReady:A,onClose:R,onError:y,onEvent:U,iframeAttrs:j={},fullscreen:P=!1}=e;try{let d=O(this.config.baseUrl,e),u=x(j);r&&!t&&console.warn("[MediPaaS SDK] pageCode is deprecated. Please use apiName instead."),o&&!t&&!r&&console.warn("[MediPaaS SDK] pageId is deprecated. Please use apiName instead.");let p=this.createIframe(d,u),E=this.prepareContainer(c,P),m=`apass-${++this.instanceCounter}`,g=crypto.randomUUID(),h=new URL(d).origin,f=!1,v=a=>{if(a.source!==p.contentWindow||a.origin!==h)return;let s=a.data;!s||typeof s!="object"||s.namespace!=="medipaas.embed"||s.version!==1||s.channelId!==g||(s.type==="ready"&&!f&&(f=!0,A?.()),s.type==="event"&&f&&typeof s.name=="string"&&s.name&&U?.({name:s.name,payload:s.payload}))};window.addEventListener("message",v);let b={iframe:p,token:i,expiresAt:0,close:()=>{this.closeInstance(m),R?.()},send:(a,s)=>{if(!f||!this.instances.has(m)||a!=="page.refresh"&&a!=="params.update")return!1;try{return p.contentWindow?.postMessage({namespace:"medipaas.embed",version:1,channelId:g,type:"event",name:a,payload:s},h),!0}catch{return!1}}};if(!c){let a=E.parentElement;a.dataset.apassOverlay=m,a.addEventListener("click",s=>{s.target===a&&b.close()})}return p.onload=()=>{f=!1,p.contentWindow?.postMessage({namespace:"medipaas.embed",version:1,channelId:g,type:"hello"},h),S?.()},p.onerror=()=>{let a=new Error("Failed to load A-pass iframe");y?.(a)},E.appendChild(p),this.instances.set(m,b),this.cleanups.set(m,()=>window.removeEventListener("message",v)),b}catch(d){let u=d instanceof Error?d:new Error(String(d));throw y?.(u),u}}closeInstance(e){let t=this.instances.get(e);if(t){this.cleanups.get(e)?.(),this.cleanups.delete(e);let r=t.iframe.parentElement?.parentElement;t.iframe.remove(),r?.dataset.apassOverlay===e&&r.remove(),this.instances.delete(e)}}prepareContainer(e,t){if(e){let i=document.getElementById(e);if(!i)throw new Error(`Container with id "${e}" not found`);return i}let r=document.createElement("div");r.style.cssText=`
|
|
2
2
|
position: fixed;
|
|
3
3
|
top: 0;
|
|
4
4
|
left: 0;
|
|
@@ -9,10 +9,10 @@ var S=Object.defineProperty;var O=(n,e,t)=>e in n?S(n,e,{enumerable:!0,configura
|
|
|
9
9
|
display: flex;
|
|
10
10
|
align-items: center;
|
|
11
11
|
justify-content: center;
|
|
12
|
-
`;let
|
|
12
|
+
`;let o=document.createElement("div");return t?o.style.cssText=`
|
|
13
13
|
width: 100%;
|
|
14
14
|
height: 100%;
|
|
15
|
-
`:
|
|
15
|
+
`:o.style.cssText=`
|
|
16
16
|
width: 90%;
|
|
17
17
|
height: 90%;
|
|
18
18
|
max-width: 1200px;
|
|
@@ -20,9 +20,9 @@ var S=Object.defineProperty;var O=(n,e,t)=>e in n?S(n,e,{enumerable:!0,configura
|
|
|
20
20
|
border-radius: 8px;
|
|
21
21
|
overflow: hidden;
|
|
22
22
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
23
|
-
`,r.appendChild(
|
|
23
|
+
`,r.appendChild(o),document.body.appendChild(r),o}createIframe(e,t){let r=document.createElement("iframe");return r.src=e,r.style.cssText=`
|
|
24
24
|
width: 100%;
|
|
25
25
|
height: 100%;
|
|
26
26
|
border: none;
|
|
27
|
-
`,Object.entries(t).forEach(([
|
|
27
|
+
`,Object.entries(t).forEach(([o,i])=>{r.setAttribute(o,i)}),r}validateConfig(e){if(!e.baseUrl)throw new Error("SDKConfig.baseUrl is required");try{new URL(e.baseUrl)}catch{throw new Error(`SDKConfig.baseUrl must be a valid URL: ${e.baseUrl}`)}}closeAll(){this.instances.forEach((e,t)=>{this.closeInstance(t)})}getInstanceCount(){return this.instances.size}};export{w as ApassEmbedSDK};
|
|
28
28
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/embedUrl.ts","../src/core/sdk.ts"],"sourcesContent":["import type { EmbedOptions } from '../types';\r\n\r\nconst reservedNames = new Set(['token', '__proto__', 'constructor', 'prototype']);\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== 'object') return false;\r\n const prototype = Object.getPrototypeOf(value);\r\n if (prototype === null) return true;\r\n // Object.prototype differs between same-origin windows. Compare its native\r\n // constructor instead of requiring this window's exact prototype identity.\r\n return Object.getPrototypeOf(prototype) === null &&\r\n Object.prototype.hasOwnProperty.call(prototype, 'constructor') &&\r\n typeof prototype.constructor === 'function' &&\r\n Function.prototype.toString.call(prototype.constructor) === Function.prototype.toString.call(Object);\r\n}\r\n\r\n/** Snapshot once so parameter/attribute getters cannot later replace the address. */\r\nexport function prepareIframeAttributes(attrs: Record<string, string> = {}): Record<string, string> {\r\n if (!isPlainObject(attrs)) throw new Error('iframeAttrs must be a plain object');\r\n const entries = Object.entries(attrs);\r\n for (const [name] of entries) {\r\n const normalized = name.trim().toLowerCase();\r\n if (normalized === 'src' || normalized === 'srcdoc') {\r\n throw new Error(`iframeAttrs.${name} cannot override the SDK page address`);\r\n }\r\n // eslint-disable-next-line no-control-regex -- Intentionally reject ASCII control characters in attribute names.\r\n if (!name || /[\\s\"'>/=\\u0000-\\u001f]/.test(name)) {\r\n throw new Error(`Invalid iframe attribute name: ${name}`);\r\n }\r\n }\r\n return Object.fromEntries(entries);\r\n}\r\n\r\nfunction encodeSegment(value: string, name: string): string {\r\n if (!value || value === '.' || value === '..') {\r\n throw new Error(`${name} must be a non-empty path segment other than . or ..`);\r\n }\r\n return encodeURIComponent(value);\r\n}\r\n\r\n/** Build and validate the complete iframe address before any DOM is created. */\r\nexport function buildEmbedUrl(baseUrl: string, options: EmbedOptions): string {\r\n if (!options.token) throw new Error('Token is required');\r\n const apiName = String(options.apiName || options.pageCode || options.pageId || '').trim();\r\n if (!apiName) {\r\n throw new Error('Either apiName, pageCode (deprecated), or pageId (deprecated) is required');\r\n }\r\n const url = new URL(`/apps/${encodeSegment(options.appId, 'appId')}/user/${encodeSegment(apiName, 'apiName')}`, baseUrl);\r\n url.searchParams.set('token', options.token);\r\n const params = options.params;\r\n if (params !== undefined) {\r\n if (!isPlainObject(params)) {\r\n throw new Error('params must be a plain object');\r\n }\r\n for (const [name, value] of Object.entries(params)) {\r\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || reservedNames.has(name)) {\r\n throw new Error(`Invalid or reserved parameter name: ${name}`);\r\n }\r\n if (value === null || value === undefined) continue;\r\n if (!['string', 'number', 'boolean'].includes(typeof value) ||\r\n (typeof value === 'number' && !Number.isFinite(value))) {\r\n throw new Error(`Parameter ${name} must be a string, finite number, or boolean`);\r\n }\r\n url.searchParams.set(name, String(value));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n","import type {\r\n SDKConfig,\r\n EmbedOptions,\r\n ApassInstanceRef,\r\n} from '../types';\r\nimport { buildEmbedUrl, prepareIframeAttributes } from './embedUrl';\r\n\r\n/**\r\n * Main SDK class for embedding A-pass applications\r\n *\r\n * @example\r\n * ```ts\r\n * const sdk = new ApassEmbedSDK({\r\n * baseUrl: 'https://apass.example.com',\r\n * });\r\n *\r\n * // CRM 项目先调自己的后端获取 token\r\n * const tokenResponse = await fetch('/api/embed-token', {\r\n * method: 'POST',\r\n * body: JSON.stringify({\r\n * recordId: 416,\r\n * enterpriseId: 456,\r\n * }),\r\n * });\r\n * const { token } = await tokenResponse.json();\r\n *\r\n * // 然后把 token 传给 SDK\r\n * await sdk.embed({\r\n * appId: '180',\r\n * apiName: 'patient_list',\r\n * token: token,\r\n * containerId: 'my-container',\r\n * });\r\n * ```\r\n */\r\nexport class ApassEmbedSDK {\r\n private config: SDKConfig;\r\n private instances: Map<string, ApassInstanceRef> = new Map();\r\n private instanceCounter = 0;\r\n\r\n constructor(config: SDKConfig) {\r\n this.validateConfig(config);\r\n this.config = {\r\n timeout: 30000,\r\n ...config,\r\n };\r\n }\r\n\r\n /**\r\n * Embed an A-pass application in an iframe\r\n */\r\n async embed(options: EmbedOptions): Promise<ApassInstanceRef> {\r\n const {\r\n apiName,\r\n pageCode,\r\n pageId,\r\n token,\r\n containerId,\r\n onReady,\r\n onClose,\r\n onError,\r\n iframeAttrs = {},\r\n fullscreen = false,\r\n } = options;\r\n\r\n try {\r\n const url = buildEmbedUrl(this.config.baseUrl, options);\r\n const attrs = prepareIframeAttributes(iframeAttrs);\r\n // Warn if using deprecated parameters\r\n if (pageCode && !apiName) {\r\n console.warn('[MediPaaS SDK] pageCode is deprecated. Please use apiName instead.');\r\n }\r\n if (pageId && !apiName && !pageCode) {\r\n console.warn('[MediPaaS SDK] pageId is deprecated. Please use apiName instead.');\r\n }\r\n\r\n // Prepare the detached iframe first: DOM attribute validation must finish\r\n // before a default overlay is attached to the host page.\r\n const iframe = this.createIframe(url, attrs);\r\n const container = this.prepareContainer(containerId, fullscreen);\r\n\r\n // Step 5: Setup lifecycle handlers\r\n const instanceId = `apass-${++this.instanceCounter}`;\r\n\r\n const instance: ApassInstanceRef = {\r\n iframe,\r\n token,\r\n expiresAt: 0, // Token expiration not tracked in this mode\r\n close: () => {\r\n this.closeInstance(instanceId);\r\n onClose?.();\r\n },\r\n };\r\n\r\n // Step 6: Append iframe to container\r\n container.appendChild(iframe);\r\n\r\n // Step 7: Setup ready handler\r\n iframe.onload = () => {\r\n onReady?.();\r\n };\r\n\r\n // Step 8: Setup error handler\r\n iframe.onerror = () => {\r\n const error = new Error('Failed to load A-pass iframe');\r\n onError?.(error);\r\n };\r\n\r\n // Store instance reference\r\n this.instances.set(instanceId, instance);\r\n\r\n return instance;\r\n } catch (error) {\r\n const err =\r\n error instanceof Error ? error : new Error(String(error));\r\n onError?.(err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Close and cleanup an instance\r\n */\r\n private closeInstance(instanceId: string): void {\r\n const instance = this.instances.get(instanceId);\r\n if (instance) {\r\n // Remove iframe from DOM\r\n instance.iframe.remove();\r\n // Remove from tracking\r\n this.instances.delete(instanceId);\r\n }\r\n }\r\n\r\n\r\n /**\r\n * Prepare container for iframe\r\n */\r\n private prepareContainer(\r\n containerId: string | undefined,\r\n fullscreen: boolean,\r\n ): HTMLElement {\r\n if (containerId) {\r\n const container = document.getElementById(containerId);\r\n if (!container) {\r\n throw new Error(`Container with id \"${containerId}\" not found`);\r\n }\r\n return container;\r\n }\r\n\r\n // Create default container\r\n const container = document.createElement('div');\r\n container.style.cssText = `\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n z-index: 10000;\r\n background: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n `;\r\n\r\n const wrapper = document.createElement('div');\r\n if (fullscreen) {\r\n wrapper.style.cssText = `\r\n width: 100%;\r\n height: 100%;\r\n `;\r\n } else {\r\n wrapper.style.cssText = `\r\n width: 90%;\r\n height: 90%;\r\n max-width: 1200px;\r\n max-height: 800px;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\r\n `;\r\n }\r\n\r\n container.appendChild(wrapper);\r\n document.body.appendChild(container);\r\n\r\n // Auto-close on background click\r\n container.addEventListener('click', (e) => {\r\n if (e.target === container) {\r\n container.remove();\r\n }\r\n });\r\n\r\n return wrapper;\r\n }\r\n\r\n /**\r\n * Create iframe element\r\n */\r\n private createIframe(\r\n url: string,\r\n attrs: Record<string, string>,\r\n ): HTMLIFrameElement {\r\n const iframe = document.createElement('iframe');\r\n\r\n // Build A-pass URL with token parameter\r\n iframe.src = url;\r\n iframe.style.cssText = `\r\n width: 100%;\r\n height: 100%;\r\n border: none;\r\n `;\r\n\r\n // Apply custom attributes\r\n Object.entries(attrs).forEach(([key, value]) => {\r\n iframe.setAttribute(key, value);\r\n });\r\n\r\n return iframe;\r\n }\r\n\r\n /**\r\n * Validate SDK configuration\r\n */\r\n private validateConfig(config: SDKConfig): void {\r\n if (!config.baseUrl) {\r\n throw new Error('SDKConfig.baseUrl is required');\r\n }\r\n\r\n try {\r\n new URL(config.baseUrl);\r\n } catch {\r\n throw new Error(\r\n `SDKConfig.baseUrl must be a valid URL: ${config.baseUrl}`,\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Close all instances\r\n */\r\n closeAll(): void {\r\n this.instances.forEach((_, instanceId) => {\r\n this.closeInstance(instanceId);\r\n });\r\n }\r\n\r\n /**\r\n * Get active instances count\r\n */\r\n getInstanceCount(): number {\r\n return this.instances.size;\r\n }\r\n}\r\n"],"mappings":"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","names":["reservedNames","isPlainObject","value","prototype","prepareIframeAttributes","attrs","entries","name","normalized","encodeSegment","buildEmbedUrl","baseUrl","options","apiName","url","params","ApassEmbedSDK","config","__publicField","options","apiName","pageCode","pageId","token","containerId","onReady","onClose","onError","iframeAttrs","fullscreen","url","buildEmbedUrl","attrs","prepareIframeAttributes","iframe","container","instanceId","instance","error","err","wrapper","e","key","value","_"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/embedUrl.ts","../src/core/sdk.ts"],"sourcesContent":["import type { EmbedOptions } from '../types';\r\n\r\nconst reservedNames = new Set(['token', '__proto__', 'constructor', 'prototype']);\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== 'object') return false;\r\n const prototype = Object.getPrototypeOf(value);\r\n if (prototype === null) return true;\r\n // Object.prototype differs between same-origin windows. Compare its native\r\n // constructor instead of requiring this window's exact prototype identity.\r\n return Object.getPrototypeOf(prototype) === null &&\r\n Object.prototype.hasOwnProperty.call(prototype, 'constructor') &&\r\n typeof prototype.constructor === 'function' &&\r\n Function.prototype.toString.call(prototype.constructor) === Function.prototype.toString.call(Object);\r\n}\r\n\r\n/** Snapshot once so parameter/attribute getters cannot later replace the address. */\r\nexport function prepareIframeAttributes(attrs: Record<string, string> = {}): Record<string, string> {\r\n if (!isPlainObject(attrs)) throw new Error('iframeAttrs must be a plain object');\r\n const entries = Object.entries(attrs);\r\n for (const [name] of entries) {\r\n const normalized = name.trim().toLowerCase();\r\n if (normalized === 'src' || normalized === 'srcdoc') {\r\n throw new Error(`iframeAttrs.${name} cannot override the SDK page address`);\r\n }\r\n // eslint-disable-next-line no-control-regex -- Intentionally reject ASCII control characters in attribute names.\r\n if (!name || /[\\s\"'>/=\\u0000-\\u001f]/.test(name)) {\r\n throw new Error(`Invalid iframe attribute name: ${name}`);\r\n }\r\n }\r\n return Object.fromEntries(entries);\r\n}\r\n\r\nfunction encodeSegment(value: string, name: string): string {\r\n if (!value || value === '.' || value === '..') {\r\n throw new Error(`${name} must be a non-empty path segment other than . or ..`);\r\n }\r\n return encodeURIComponent(value);\r\n}\r\n\r\n/** Build and validate the complete iframe address before any DOM is created. */\r\nexport function buildEmbedUrl(baseUrl: string, options: EmbedOptions): string {\r\n if (!options.token) throw new Error('Token is required');\r\n const apiName = String(options.apiName || options.pageCode || options.pageId || '').trim();\r\n if (!apiName) {\r\n throw new Error('Either apiName, pageCode (deprecated), or pageId (deprecated) is required');\r\n }\r\n const url = new URL(`/apps/${encodeSegment(options.appId, 'appId')}/user/${encodeSegment(apiName, 'apiName')}`, baseUrl);\r\n url.searchParams.set('token', options.token);\r\n const params = options.params;\r\n if (params !== undefined) {\r\n if (!isPlainObject(params)) {\r\n throw new Error('params must be a plain object');\r\n }\r\n for (const [name, value] of Object.entries(params)) {\r\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || reservedNames.has(name)) {\r\n throw new Error(`Invalid or reserved parameter name: ${name}`);\r\n }\r\n if (value === null || value === undefined) continue;\r\n if (!['string', 'number', 'boolean'].includes(typeof value) ||\r\n (typeof value === 'number' && !Number.isFinite(value))) {\r\n throw new Error(`Parameter ${name} must be a string, finite number, or boolean`);\r\n }\r\n url.searchParams.set(name, String(value));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n","import type {\r\n SDKConfig,\r\n EmbedOptions,\r\n ApassInstanceRef,\r\n} from '../types';\r\nimport { buildEmbedUrl, prepareIframeAttributes } from './embedUrl';\r\n\r\n/**\r\n * Main SDK class for embedding A-pass applications\r\n *\r\n * @example\r\n * ```ts\r\n * const sdk = new ApassEmbedSDK({\r\n * baseUrl: 'https://apass.example.com',\r\n * });\r\n *\r\n * // CRM 项目先调自己的后端获取 token\r\n * const tokenResponse = await fetch('/api/embed-token', {\r\n * method: 'POST',\r\n * body: JSON.stringify({\r\n * recordId: 416,\r\n * enterpriseId: 456,\r\n * }),\r\n * });\r\n * const { token } = await tokenResponse.json();\r\n *\r\n * // 然后把 token 传给 SDK\r\n * await sdk.embed({\r\n * appId: '180',\r\n * apiName: 'patient_list',\r\n * token: token,\r\n * containerId: 'my-container',\r\n * });\r\n * ```\r\n */\r\nexport class ApassEmbedSDK {\r\n private config: SDKConfig;\r\n private instances: Map<string, ApassInstanceRef> = new Map();\n private cleanups: Map<string, () => void> = new Map();\n private instanceCounter = 0;\r\n\r\n constructor(config: SDKConfig) {\r\n this.validateConfig(config);\r\n this.config = {\r\n timeout: 30000,\r\n ...config,\r\n };\r\n }\r\n\r\n /**\r\n * Embed an A-pass application in an iframe\r\n */\r\n async embed(options: EmbedOptions): Promise<ApassInstanceRef> {\r\n const {\r\n apiName,\r\n pageCode,\r\n pageId,\r\n token,\r\n containerId,\r\n onReady,\n onBridgeReady,\n onClose,\r\n onError,\n onEvent,\n iframeAttrs = {},\r\n fullscreen = false,\r\n } = options;\r\n\r\n try {\r\n const url = buildEmbedUrl(this.config.baseUrl, options);\r\n const attrs = prepareIframeAttributes(iframeAttrs);\r\n // Warn if using deprecated parameters\r\n if (pageCode && !apiName) {\r\n console.warn('[MediPaaS SDK] pageCode is deprecated. Please use apiName instead.');\r\n }\r\n if (pageId && !apiName && !pageCode) {\r\n console.warn('[MediPaaS SDK] pageId is deprecated. Please use apiName instead.');\r\n }\r\n\r\n // Prepare the detached iframe first: DOM attribute validation must finish\r\n // before a default overlay is attached to the host page.\r\n const iframe = this.createIframe(url, attrs);\r\n const container = this.prepareContainer(containerId, fullscreen);\r\n\r\n // Step 5: Setup lifecycle handlers\r\n const instanceId = `apass-${++this.instanceCounter}`;\n const channelId = crypto.randomUUID();\n const targetOrigin = new URL(url).origin;\n let ready = false;\n const receiveMessage = (event: MessageEvent) => {\n if (event.source !== iframe.contentWindow || event.origin !== targetOrigin) return;\n const message = event.data;\n if (!message || typeof message !== 'object' || message.namespace !== 'medipaas.embed'\n || message.version !== 1 || message.channelId !== channelId) return;\n if (message.type === 'ready' && !ready) {\n ready = true;\n onBridgeReady?.();\n }\n if (message.type === 'event' && ready && typeof message.name === 'string' && message.name) {\n onEvent?.({ name: message.name, payload: message.payload });\n }\n };\n window.addEventListener('message', receiveMessage);\n\n const instance: ApassInstanceRef = {\n iframe,\r\n token,\r\n expiresAt: 0, // Token expiration not tracked in this mode\r\n close: () => {\n this.closeInstance(instanceId);\n onClose?.();\n },\n send: (name, payload) => {\n if (!ready || !this.instances.has(instanceId)) return false;\n if (name !== 'page.refresh' && name !== 'params.update') return false;\n try {\n iframe.contentWindow?.postMessage({ namespace: 'medipaas.embed', version: 1,\n channelId, type: 'event', name, payload }, targetOrigin);\n return true;\n } catch {\n return false;\n }\n },\n };\n\n if (!containerId) {\n const overlay = container.parentElement!;\n overlay.dataset.apassOverlay = instanceId;\n overlay.addEventListener('click', (event) => {\n if (event.target === overlay) instance.close();\n });\n }\n\r\n // Step 6: Setup ready handler before attaching the iframe\n iframe.onload = () => {\n ready = false;\n iframe.contentWindow?.postMessage({ namespace: 'medipaas.embed', version: 1,\n channelId, type: 'hello' }, targetOrigin);\n onReady?.();\n };\r\n\r\n // Step 8: Setup error handler\r\n iframe.onerror = () => {\n const error = new Error('Failed to load A-pass iframe');\r\n onError?.(error);\r\n };\n\n container.appendChild(iframe);\n\r\n // Store instance reference\r\n this.instances.set(instanceId, instance);\n this.cleanups.set(instanceId, () => window.removeEventListener('message', receiveMessage));\n\r\n return instance;\r\n } catch (error) {\r\n const err =\r\n error instanceof Error ? error : new Error(String(error));\r\n onError?.(err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Close and cleanup an instance\r\n */\r\n private closeInstance(instanceId: string): void {\n const instance = this.instances.get(instanceId);\r\n if (instance) {\n this.cleanups.get(instanceId)?.();\n this.cleanups.delete(instanceId);\n // Remove iframe from DOM\n const overlay = instance.iframe.parentElement?.parentElement;\n instance.iframe.remove();\n if (overlay?.dataset.apassOverlay === instanceId) overlay.remove();\n // Remove from tracking\r\n this.instances.delete(instanceId);\r\n }\r\n }\n\r\n\r\n /**\r\n * Prepare container for iframe\r\n */\r\n private prepareContainer(\r\n containerId: string | undefined,\r\n fullscreen: boolean,\r\n ): HTMLElement {\r\n if (containerId) {\r\n const container = document.getElementById(containerId);\r\n if (!container) {\r\n throw new Error(`Container with id \"${containerId}\" not found`);\r\n }\r\n return container;\r\n }\r\n\r\n // Create default container\r\n const container = document.createElement('div');\r\n container.style.cssText = `\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n z-index: 10000;\r\n background: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n `;\r\n\r\n const wrapper = document.createElement('div');\r\n if (fullscreen) {\r\n wrapper.style.cssText = `\r\n width: 100%;\r\n height: 100%;\r\n `;\r\n } else {\r\n wrapper.style.cssText = `\r\n width: 90%;\r\n height: 90%;\r\n max-width: 1200px;\r\n max-height: 800px;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\r\n `;\r\n }\r\n\r\n container.appendChild(wrapper);\r\n document.body.appendChild(container);\r\n\r\n return wrapper;\n }\r\n\r\n /**\r\n * Create iframe element\r\n */\r\n private createIframe(\r\n url: string,\r\n attrs: Record<string, string>,\r\n ): HTMLIFrameElement {\r\n const iframe = document.createElement('iframe');\r\n\r\n // Build A-pass URL with token parameter\r\n iframe.src = url;\r\n iframe.style.cssText = `\r\n width: 100%;\r\n height: 100%;\r\n border: none;\r\n `;\r\n\r\n // Apply custom attributes\r\n Object.entries(attrs).forEach(([key, value]) => {\r\n iframe.setAttribute(key, value);\r\n });\r\n\r\n return iframe;\r\n }\r\n\r\n /**\r\n * Validate SDK configuration\r\n */\r\n private validateConfig(config: SDKConfig): void {\r\n if (!config.baseUrl) {\r\n throw new Error('SDKConfig.baseUrl is required');\r\n }\r\n\r\n try {\r\n new URL(config.baseUrl);\r\n } catch {\r\n throw new Error(\r\n `SDKConfig.baseUrl must be a valid URL: ${config.baseUrl}`,\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Close all instances\r\n */\r\n closeAll(): void {\r\n this.instances.forEach((_, instanceId) => {\r\n this.closeInstance(instanceId);\r\n });\r\n }\r\n\r\n /**\r\n * Get active instances count\r\n */\r\n getInstanceCount(): number {\r\n return this.instances.size;\r\n }\r\n}\r\n"],"mappings":"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","names":["reservedNames","isPlainObject","value","prototype","prepareIframeAttributes","attrs","entries","name","normalized","encodeSegment","buildEmbedUrl","baseUrl","options","apiName","url","params","ApassEmbedSDK","config","__publicField","options","apiName","pageCode","pageId","token","containerId","onReady","onBridgeReady","onClose","onError","onEvent","iframeAttrs","fullscreen","url","buildEmbedUrl","attrs","prepareIframeAttributes","iframe","container","instanceId","channelId","targetOrigin","ready","receiveMessage","event","message","instance","name","payload","overlay","error","err","wrapper","key","value","_"]}
|