@elliemae/ssf-host 2.23.6 → 2.25.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 (104) hide show
  1. package/dist/cjs/callchain-host.html +262 -0
  2. package/dist/cjs/callchain-intermediate.html +92 -0
  3. package/dist/cjs/e2e-host.html +603 -0
  4. package/dist/cjs/e2e-index.html +234 -0
  5. package/dist/cjs/guest.js +15 -16
  6. package/dist/cjs/host.js +91 -52
  7. package/dist/cjs/index.html +304 -151
  8. package/dist/cjs/performanceTracker.js +111 -0
  9. package/dist/cjs/popup-focus-host.html +318 -0
  10. package/dist/cjs/utils.js +14 -1
  11. package/dist/cjs/v2-host-v1-guest.html +3 -0
  12. package/dist/esm/callchain-host.html +262 -0
  13. package/dist/esm/callchain-intermediate.html +92 -0
  14. package/dist/esm/e2e-host.html +603 -0
  15. package/dist/esm/e2e-index.html +234 -0
  16. package/dist/esm/guest.js +15 -16
  17. package/dist/esm/host.js +92 -53
  18. package/dist/esm/index.html +304 -151
  19. package/dist/esm/performanceTracker.js +91 -0
  20. package/dist/esm/popup-focus-host.html +318 -0
  21. package/dist/esm/utils.js +14 -1
  22. package/dist/esm/v2-host-v1-guest.html +3 -0
  23. package/dist/public/callchain-host.html +34 -0
  24. package/dist/public/callchain-host.js +3 -0
  25. package/dist/public/callchain-host.js.br +0 -0
  26. package/dist/public/callchain-host.js.gz +0 -0
  27. package/dist/public/callchain-host.js.map +1 -0
  28. package/dist/public/callchain-intermediate.html +1 -0
  29. package/dist/public/e2e-host.html +5 -0
  30. package/dist/public/e2e-host.js +8 -0
  31. package/dist/public/e2e-host.js.br +0 -0
  32. package/dist/public/e2e-host.js.gz +0 -0
  33. package/dist/public/e2e-host.js.map +1 -0
  34. package/dist/public/e2e-index.html +1 -0
  35. package/dist/public/index.html +1 -1
  36. package/dist/public/init.js +1 -1
  37. package/dist/public/init.js.br +0 -0
  38. package/dist/public/init.js.gz +0 -0
  39. package/dist/public/init.js.map +1 -1
  40. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js +3 -0
  41. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.br +0 -0
  42. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.gz +0 -0
  43. package/dist/public/js/emuiSsfHost.5bb7139d7e86c74f0b6d.js.map +1 -0
  44. package/dist/public/loan-object.js +1 -1
  45. package/dist/public/loan-object.js.br +0 -0
  46. package/dist/public/loan-object.js.gz +0 -0
  47. package/dist/public/loan-object.js.map +1 -1
  48. package/dist/public/popup-focus-host.html +1 -0
  49. package/dist/public/popup-focus-host.js +6 -0
  50. package/dist/public/popup-focus-host.js.br +0 -0
  51. package/dist/public/popup-focus-host.js.gz +0 -0
  52. package/dist/public/popup-focus-host.js.map +1 -0
  53. package/dist/public/utils.js +1 -1
  54. package/dist/public/utils.js.br +0 -0
  55. package/dist/public/utils.js.gz +0 -0
  56. package/dist/public/utils.js.map +1 -1
  57. package/dist/public/v1-guest-v2-host.html +1 -1
  58. package/dist/public/v2-host-v1-guest.html +1 -1
  59. package/dist/types/lib/guest.d.ts +4 -3
  60. package/dist/types/lib/ihost.d.ts +32 -0
  61. package/dist/types/lib/performanceTracker.d.ts +46 -0
  62. package/dist/types/lib/tests/timingDedup.test.d.ts +1 -0
  63. package/dist/types/lib/utils.d.ts +1 -0
  64. package/dist/types/tsconfig.tsbuildinfo +1 -1
  65. package/dist/umd/callchain-host.html +34 -0
  66. package/dist/umd/callchain-host.js +3 -0
  67. package/dist/umd/callchain-host.js.br +0 -0
  68. package/dist/umd/callchain-host.js.gz +0 -0
  69. package/dist/umd/callchain-host.js.map +1 -0
  70. package/dist/umd/callchain-intermediate.html +1 -0
  71. package/dist/umd/e2e-host.html +5 -0
  72. package/dist/umd/e2e-host.js +8 -0
  73. package/dist/umd/e2e-host.js.br +0 -0
  74. package/dist/umd/e2e-host.js.gz +0 -0
  75. package/dist/umd/e2e-host.js.map +1 -0
  76. package/dist/umd/e2e-index.html +1 -0
  77. package/dist/umd/index.html +1 -1
  78. package/dist/umd/index.js +1 -1
  79. package/dist/umd/index.js.br +0 -0
  80. package/dist/umd/index.js.gz +0 -0
  81. package/dist/umd/index.js.map +1 -1
  82. package/dist/umd/init.js +1 -1
  83. package/dist/umd/init.js.br +0 -0
  84. package/dist/umd/init.js.gz +0 -0
  85. package/dist/umd/init.js.map +1 -1
  86. package/dist/umd/loan-object.js +1 -1
  87. package/dist/umd/loan-object.js.br +0 -0
  88. package/dist/umd/loan-object.js.gz +0 -0
  89. package/dist/umd/loan-object.js.map +1 -1
  90. package/dist/umd/popup-focus-host.html +1 -0
  91. package/dist/umd/popup-focus-host.js +6 -0
  92. package/dist/umd/popup-focus-host.js.br +0 -0
  93. package/dist/umd/popup-focus-host.js.gz +0 -0
  94. package/dist/umd/popup-focus-host.js.map +1 -0
  95. package/dist/umd/utils.js +1 -1
  96. package/dist/umd/utils.js.br +0 -0
  97. package/dist/umd/utils.js.gz +0 -0
  98. package/dist/umd/utils.js.map +1 -1
  99. package/dist/umd/v2-host-v1-guest.html +1 -1
  100. package/package.json +6 -6
  101. package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js +0 -3
  102. package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js.br +0 -0
  103. package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js.gz +0 -0
  104. package/dist/public/js/emuiSsfHost.5855ec3cd0fa60013d84.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["webpack://ice.host/loan-object.js"],"sourcesContent":["import { getHost } from './utils.js';\nimport { Analytics } from './analytics-object-v2.js';\n\nconst getLoanField = (name) => document.getElementById(name);\n\nconst host = getHost(new Analytics());\n\nclass Loan extends ice.host.ScriptingObject {\n constructor() {\n super('Loan');\n this.loanId = 'ABC123';\n this.creditScore = 0;\n this.serviceOrderStatus = {\n title: false,\n credit: false,\n };\n this.onSaved = new ice.host.Event({\n name: 'onSaved',\n objectId: this.constructor.name,\n });\n this.onPreSave = new ice.host.Event({\n name: 'onPreSave',\n objectId: this.constructor.name,\n });\n this.onLoanAmountChanged = new ice.host.Event({\n name: 'onLoanAmountChanged',\n objectId: this.constructor.name,\n });\n this.onLoanTermChanged = new ice.host.Event({\n name: 'onLoanTermChanged',\n objectId: this.constructor.name,\n });\n this.onDownPaymentChanged = new ice.host.Event({\n name: 'onDownPaymentChanged',\n objectId: this.constructor.name,\n });\n\n getLoanField('amount')?.addEventListener?.('blur', (e) => {\n host.dispatchEvent({\n event: this.onLoanAmountChanged,\n eventParams: {\n amount: e.target.value,\n },\n });\n });\n\n getLoanField('term')?.addEventListener?.('blur', (e) => {\n host.dispatchEvent({\n event: this.onLoanTermChanged,\n eventParams: { term: e.target.value },\n });\n });\n\n getLoanField('downPayment')?.addEventListener?.('blur', (e) => {\n host.dispatchEvent({\n event: this.onDownPaymentChanged,\n eventParams: {\n downPayment: e.target.value,\n },\n });\n });\n }\n\n getLoanDetails = () => ({\n id: this.loanId,\n firstName: getLoanField('firstName')?.value,\n lastName: getLoanField('lastName')?.value,\n ssn: getLoanField('ssn')?.value,\n amount: getLoanField('amount')?.value,\n term: getLoanField('term')?.value,\n downPayment: getLoanField('downPayment')?.value,\n creditScore: this.creditScore,\n });\n\n setCreditScore = (creditScore) => {\n this.serviceOrderStatus.credit = true;\n this.creditScore = creditScore;\n const creditBtn = document.getElementById('credit');\n if (creditBtn) {\n creditBtn.disabled = true;\n creditBtn.textContent = `${creditBtn.textContent} : ${creditScore}`;\n }\n };\n\n setServiceOrderStatus = (service, completed) => {\n this.serviceOrderStatus[service] = completed;\n const btn = document.getElementById(service);\n if (btn) {\n btn.disabled = completed;\n btn.textContent = `${btn.textContent} : Done`;\n }\n };\n\n saveLoan = async () => {\n host.dispatchEvent({\n event: this.onSaved,\n eventParams: { id: this.loanId },\n });\n };\n}\n\nexport { Loan };\n"],"mappings":"AAAA,OAAS,WAAAA,MAAe,aACxB,OAAS,aAAAC,MAAiB,2BAE1B,MAAMC,EAAgBC,GAAS,SAAS,eAAeA,CAAI,EAErDC,EAAOJ,EAAQ,IAAIC,CAAW,EAEpC,MAAMI,UAAa,IAAI,KAAK,eAAgB,CAC1C,aAAc,CACZ,MAAM,MAAM,EACZ,KAAK,OAAS,SACd,KAAK,YAAc,EACnB,KAAK,mBAAqB,CACxB,MAAO,GACP,OAAQ,EACV,EACA,KAAK,QAAU,IAAI,IAAI,KAAK,MAAM,CAChC,KAAM,UACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EACD,KAAK,UAAY,IAAI,IAAI,KAAK,MAAM,CAClC,KAAM,YACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EACD,KAAK,oBAAsB,IAAI,IAAI,KAAK,MAAM,CAC5C,KAAM,sBACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EACD,KAAK,kBAAoB,IAAI,IAAI,KAAK,MAAM,CAC1C,KAAM,oBACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EACD,KAAK,qBAAuB,IAAI,IAAI,KAAK,MAAM,CAC7C,KAAM,uBACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EAEDH,EAAa,QAAQ,GAAG,mBAAmB,OAAS,GAAM,CACxDE,EAAK,cAAc,CACjB,MAAO,KAAK,oBACZ,YAAa,CACX,OAAQ,EAAE,OAAO,KACnB,CACF,CAAC,CACH,CAAC,EAEDF,EAAa,MAAM,GAAG,mBAAmB,OAAS,GAAM,CACtDE,EAAK,cAAc,CACjB,MAAO,KAAK,kBACZ,YAAa,CAAE,KAAM,EAAE,OAAO,KAAM,CACtC,CAAC,CACH,CAAC,EAEDF,EAAa,aAAa,GAAG,mBAAmB,OAAS,GAAM,CAC7DE,EAAK,cAAc,CACjB,MAAO,KAAK,qBACZ,YAAa,CACX,YAAa,EAAE,OAAO,KACxB,CACF,CAAC,CACH,CAAC,CACH,CAEA,eAAiB,KAAO,CACtB,GAAI,KAAK,OACT,UAAWF,EAAa,WAAW,GAAG,MACtC,SAAUA,EAAa,UAAU,GAAG,MACpC,IAAKA,EAAa,KAAK,GAAG,MAC1B,OAAQA,EAAa,QAAQ,GAAG,MAChC,KAAMA,EAAa,MAAM,GAAG,MAC5B,YAAaA,EAAa,aAAa,GAAG,MAC1C,YAAa,KAAK,WACpB,GAEA,eAAkBI,GAAgB,CAChC,KAAK,mBAAmB,OAAS,GACjC,KAAK,YAAcA,EACnB,MAAMC,EAAY,SAAS,eAAe,QAAQ,EAC9CA,IACFA,EAAU,SAAW,GACrBA,EAAU,YAAc,GAAGA,EAAU,WAAW,MAAMD,CAAW,GAErE,EAEA,sBAAwB,CAACE,EAASC,IAAc,CAC9C,KAAK,mBAAmBD,CAAO,EAAIC,EACnC,MAAMC,EAAM,SAAS,eAAeF,CAAO,EACvCE,IACFA,EAAI,SAAWD,EACfC,EAAI,YAAc,GAAGA,EAAI,WAAW,UAExC,EAEA,SAAW,SAAY,CACrBN,EAAK,cAAc,CACjB,MAAO,KAAK,QACZ,YAAa,CAAE,GAAI,KAAK,MAAO,CACjC,CAAC,CACH,CACF,CAEA,OAASC,KAAA","names":["getHost","Analytics","getLoanField","name","host","Loan","creditScore","creditBtn","service","completed","btn"],"sourceRoot":"","file":"loan-object.js"}
1
+ {"version":3,"sources":["webpack://ice.host/loan-object.js"],"sourcesContent":["import { getHost } from './utils.js';\nimport { Analytics } from './analytics-object-v2.js';\n\nconst getLoanField = (name) => document.getElementById(name);\n\nconst host = getHost(new Analytics());\n\nclass Loan extends ice.host.ScriptingObject {\n constructor() {\n super('Loan');\n this.loanId = 'ABC123';\n this.creditScore = 0;\n this.serviceOrderStatus = {\n title: false,\n credit: false,\n };\n this.onSaved = new ice.host.Event({\n name: 'onSaved',\n objectId: this.constructor.name,\n });\n this.onPreSave = new ice.host.Event({\n name: 'onPreSave',\n objectId: this.constructor.name,\n });\n this.onLoanAmountChanged = new ice.host.Event({\n name: 'onLoanAmountChanged',\n objectId: this.constructor.name,\n });\n this.onLoanTermChanged = new ice.host.Event({\n name: 'onLoanTermChanged',\n objectId: this.constructor.name,\n });\n this.onDownPaymentChanged = new ice.host.Event({\n name: 'onDownPaymentChanged',\n objectId: this.constructor.name,\n });\n\n getLoanField('amount')?.addEventListener?.('blur', (e) => {\n host.dispatchEvent({\n event: this.onLoanAmountChanged,\n eventParams: {\n amount: e.target.value,\n },\n });\n });\n\n getLoanField('term')?.addEventListener?.('blur', (e) => {\n host.dispatchEvent({\n event: this.onLoanTermChanged,\n eventParams: { term: e.target.value },\n });\n });\n\n getLoanField('downPayment')?.addEventListener?.('blur', (e) => {\n host.dispatchEvent({\n event: this.onDownPaymentChanged,\n eventParams: {\n downPayment: e.target.value,\n },\n });\n });\n }\n\n getLoanDetails = () => {\n const ctx = this.getLoanDetails.callContext;\n if (ctx?.callChain?.length) {\n console.log(\n '[Loan.getLoanDetails] callChain:',\n JSON.stringify(ctx.callChain),\n );\n }\n return {\n id: this.loanId,\n firstName: getLoanField('firstName')?.value,\n lastName: getLoanField('lastName')?.value,\n ssn: getLoanField('ssn')?.value,\n amount: getLoanField('amount')?.value,\n term: getLoanField('term')?.value,\n downPayment: getLoanField('downPayment')?.value,\n creditScore: this.creditScore,\n };\n };\n\n setCreditScore = (creditScore) => {\n const ctx = this.setCreditScore.callContext;\n if (ctx?.callChain?.length) {\n console.log(\n '[Loan.setCreditScore] callChain:',\n JSON.stringify(ctx.callChain),\n );\n }\n this.serviceOrderStatus.credit = true;\n this.creditScore = creditScore;\n const creditBtn = document.getElementById('credit');\n if (creditBtn) {\n creditBtn.disabled = true;\n creditBtn.textContent = `${creditBtn.textContent} : ${creditScore}`;\n }\n };\n\n setServiceOrderStatus = (service, completed) => {\n const ctx = this.setServiceOrderStatus.callContext;\n if (ctx?.callChain?.length) {\n console.log(\n '[Loan.setServiceOrderStatus] callChain:',\n JSON.stringify(ctx.callChain),\n );\n }\n this.serviceOrderStatus[service] = completed;\n const btn = document.getElementById(service);\n if (btn) {\n btn.disabled = completed;\n btn.textContent = `${btn.textContent} : Done`;\n }\n };\n\n saveLoan = async () => {\n host.dispatchEvent({\n event: this.onSaved,\n eventParams: { id: this.loanId },\n });\n };\n}\n\nexport { Loan };\n"],"mappings":"AAAA,OAAS,WAAAA,MAAe,aACxB,OAAS,aAAAC,MAAiB,2BAE1B,MAAMC,EAAgBC,GAAS,SAAS,eAAeA,CAAI,EAErDC,EAAOJ,EAAQ,IAAIC,CAAW,EAEpC,MAAMI,UAAa,IAAI,KAAK,eAAgB,CAC1C,aAAc,CACZ,MAAM,MAAM,EACZ,KAAK,OAAS,SACd,KAAK,YAAc,EACnB,KAAK,mBAAqB,CACxB,MAAO,GACP,OAAQ,EACV,EACA,KAAK,QAAU,IAAI,IAAI,KAAK,MAAM,CAChC,KAAM,UACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EACD,KAAK,UAAY,IAAI,IAAI,KAAK,MAAM,CAClC,KAAM,YACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EACD,KAAK,oBAAsB,IAAI,IAAI,KAAK,MAAM,CAC5C,KAAM,sBACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EACD,KAAK,kBAAoB,IAAI,IAAI,KAAK,MAAM,CAC1C,KAAM,oBACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EACD,KAAK,qBAAuB,IAAI,IAAI,KAAK,MAAM,CAC7C,KAAM,uBACN,SAAU,KAAK,YAAY,IAC7B,CAAC,EAEDH,EAAa,QAAQ,GAAG,mBAAmB,OAASI,GAAM,CACxDF,EAAK,cAAc,CACjB,MAAO,KAAK,oBACZ,YAAa,CACX,OAAQE,EAAE,OAAO,KACnB,CACF,CAAC,CACH,CAAC,EAEDJ,EAAa,MAAM,GAAG,mBAAmB,OAASI,GAAM,CACtDF,EAAK,cAAc,CACjB,MAAO,KAAK,kBACZ,YAAa,CAAE,KAAME,EAAE,OAAO,KAAM,CACtC,CAAC,CACH,CAAC,EAEDJ,EAAa,aAAa,GAAG,mBAAmB,OAASI,GAAM,CAC7DF,EAAK,cAAc,CACjB,MAAO,KAAK,qBACZ,YAAa,CACX,YAAaE,EAAE,OAAO,KACxB,CACF,CAAC,CACH,CAAC,CACH,CAEA,eAAiB,IAAM,CACrB,MAAMC,EAAM,KAAK,eAAe,YAChC,OAAIA,GAAK,WAAW,QAGhB,KAAK,UAAUA,EAAI,SAAS,EAGzB,CACL,GAAI,KAAK,OACT,UAAWL,EAAa,WAAW,GAAG,MACtC,SAAUA,EAAa,UAAU,GAAG,MACpC,IAAKA,EAAa,KAAK,GAAG,MAC1B,OAAQA,EAAa,QAAQ,GAAG,MAChC,KAAMA,EAAa,MAAM,GAAG,MAC5B,YAAaA,EAAa,aAAa,GAAG,MAC1C,YAAa,KAAK,WACpB,CACF,EAEA,eAAkBM,GAAgB,CAChC,MAAMD,EAAM,KAAK,eAAe,YAC5BA,GAAK,WAAW,QAGhB,KAAK,UAAUA,EAAI,SAAS,EAGhC,KAAK,mBAAmB,OAAS,GACjC,KAAK,YAAcC,EACnB,MAAMC,EAAY,SAAS,eAAe,QAAQ,EAC9CA,IACFA,EAAU,SAAW,GACrBA,EAAU,YAAc,GAAGA,EAAU,WAAW,MAAMD,CAAW,GAErE,EAEA,sBAAwB,CAACE,EAASC,IAAc,CAC9C,MAAMJ,EAAM,KAAK,sBAAsB,YACnCA,GAAK,WAAW,QAGhB,KAAK,UAAUA,EAAI,SAAS,EAGhC,KAAK,mBAAmBG,CAAO,EAAIC,EACnC,MAAMC,EAAM,SAAS,eAAeF,CAAO,EACvCE,IACFA,EAAI,SAAWD,EACfC,EAAI,YAAc,GAAGA,EAAI,WAAW,UAExC,EAEA,SAAW,SAAY,CACrBR,EAAK,cAAc,CACjB,MAAO,KAAK,QACZ,YAAa,CAAE,GAAI,KAAK,MAAO,CACjC,CAAC,CACH,CACF,CAEA,OAASC,KAAA","names":["getHost","Analytics","getLoanField","name","host","Loan","e","ctx","creditScore","creditBtn","service","completed","btn"],"sourceRoot":"","file":"loan-object.js"}
@@ -0,0 +1 @@
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Popup Focus E2E Test</title><script src="https://cdn.tailwindcss.com?plugins=forms"></script><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiSsfHost.5bb7139d7e86c74f0b6d.js"></script></head><body class="bg-gray-50"><header class="bg-purple-600 text-white px-4 py-3 flex items-center justify-between"><h1 class="text-lg font-semibold">Popup Focus E2E Test</h1><a href="./index.html" class="text-purple-200 hover:text-white text-sm">&larr; Back to main</a></header><main class="mx-auto max-w-5xl px-4 py-4"><div class="grid grid-cols-2 gap-4 mb-4"><div class="bg-purple-50 border border-purple-200 rounded-md p-3"><h2 class="text-sm font-bold text-purple-900 mb-2">Behavior Under Test</h2><ul class="text-xs text-purple-800 space-y-1 list-disc list-inside"><li><strong>Trusted domains</strong> (.ice.com, .elliemae.com, .ellielabs.com, localhost): <code>window.opener</code> is kept intact. Re-opening uses <code>window.open('', name)</code> to bring the popup to front.</li><li><strong>Untrusted domains</strong> (everything else): <code>window.opener = null</code> is set for security. Re-opening falls back to <code>guest.window.focus()</code>.</li><li>The host tracks popup windows and detects closure via a periodic monitor.</li></ul></div><div class="bg-green-50 border border-green-200 rounded-md p-3"><h2 class="text-sm font-bold text-green-900 mb-2">Test Steps</h2><ol class="text-xs text-green-800 space-y-1.5 list-decimal list-inside"><li>Wait for <code>"Popup Focus Host ready"</code> in the Event Log.</li><li><strong>Trusted popup test:</strong> Click "Open Title Service" → popup opens. Click "Re-open Title Service" → the <em>existing</em> popup should come to the foreground (no new window).</li><li><strong>Second trusted popup:</strong> Click "Open Credit Service" → second popup opens. Click "Re-open Credit" → same foreground behavior.</li><li><strong>Untrusted popup test:</strong> Click "Open Third-Party" → popup opens to <code>example.com</code>. Click "Re-open Third-Party" → focus may or may not work (opener was nulled). Observe the behavior.</li><li>Verify the Guest Status panel shows the open/closed state of each popup.</li><li>Manually close a popup window, then check Guest Status updates to "closed".</li></ol></div></div><div class="bg-amber-50 border border-amber-200 rounded-md p-3 mb-4"><h2 class="text-sm font-bold text-amber-900 mb-2">Expected Behavior</h2><div class="grid grid-cols-3 gap-3 text-xs"><div><h3 class="font-semibold text-amber-800 mb-1">Title / Credit Service (Trusted)</h3><ul class="text-amber-900 list-disc list-inside space-y-0.5"><li>"Open" → new popup window appears</li><li>"Re-open" → existing popup comes to front, <strong>no new window</strong></li><li>Guest Status shows <span class="text-green-600 font-semibold">open</span></li><li>Embedded Pricing Service renders normally</li></ul></div><div><h3 class="font-semibold text-amber-800 mb-1">Third-Party (Untrusted)</h3><ul class="text-amber-900 list-disc list-inside space-y-0.5"><li>"Open" → popup opens to <code>example.com</code></li><li>"Re-open" → <code>focus()</code> is called on stored reference (browser may or may not honor it)</li><li><code>opener</code> is <code>null</code> inside the popup</li></ul></div><div><h3 class="font-semibold text-amber-800 mb-1">Popup Closure</h3><ul class="text-amber-900 list-disc list-inside space-y-0.5"><li>Close a popup manually (X button)</li><li>Guest Status updates to <span class="text-red-500 font-semibold">closed</span> within 2 seconds</li><li>Re-opening a closed popup should open a new window</li></ul></div></div></div><div class="grid grid-cols-2 gap-4 mb-4"><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-3">Popup Actions</h2><div class="flex flex-col gap-2"><p class="text-xs text-gray-400 font-medium">Trusted (localhost — opener preserved)</p><button data-testid="btn-open-title-popup" id="btnOpenTitle" class="rounded bg-purple-600 px-4 py-2 text-sm text-white hover:bg-purple-700">Open Title Service (Popup)</button> <button data-testid="btn-reopen-title-popup" id="btnReopenTitle" class="rounded bg-purple-500 px-4 py-2 text-sm text-white hover:bg-purple-600">Re-open Title Service (should focus existing)</button> <button data-testid="btn-open-credit-popup" id="btnOpenCredit" class="rounded bg-indigo-600 px-4 py-2 text-sm text-white hover:bg-indigo-700">Open Credit Service (Popup)</button> <button data-testid="btn-reopen-credit-popup" id="btnReopenCredit" class="rounded bg-indigo-500 px-4 py-2 text-sm text-white hover:bg-indigo-600">Re-open Credit Service (should focus existing)</button><hr class="my-2 border-gray-300"/><p class="text-xs text-gray-400 font-medium">Untrusted (opener nulled)</p><button data-testid="btn-open-thirdparty-popup" id="btnOpenThirdParty" class="rounded bg-red-600 px-4 py-2 text-sm text-white hover:bg-red-700">Open Third-Party (Popup) — untrusted</button> <button data-testid="btn-reopen-thirdparty-popup" id="btnReopenThirdParty" class="rounded bg-red-500 px-4 py-2 text-sm text-white hover:bg-red-600">Re-open Third-Party (focus test — opener nulled)</button></div></div><div class="bg-white rounded-lg shadow p-4"><h2 class="text-sm font-semibold text-gray-700 mb-3">Guest Status</h2><div data-testid="popup-guest-list" id="guestList" class="text-xs space-y-1 min-h-[100px]"><p class="text-gray-400">No popup guests loaded yet</p></div></div></div><div class="bg-white rounded-lg shadow p-4 mb-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">Event Log</h2><div data-testid="popup-event-log" id="eventLog" class="text-xs bg-gray-100 rounded p-2 min-h-[100px] max-h-[250px] overflow-y-auto"></div></div><div class="mb-4"><h2 class="text-sm font-semibold text-gray-700 mb-2">Embedded Guest (for comparison)</h2><div data-testid="embedded-container" id="embedded-container" class="border-2 border-dashed border-purple-300 rounded-lg min-h-[150px]"></div></div><div class="bg-gray-100 border border-gray-300 rounded-md p-3"><h2 class="text-sm font-bold text-gray-800 mb-2">Verification Checklist</h2><div class="grid grid-cols-2 gap-2 text-xs"><label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-01:</strong> Host initializes, embedded Pricing Service guest loads without error</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-02:</strong> "Open Title Service" opens a new popup window</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-03:</strong> "Re-open Title Service" brings the existing popup to front — <strong>no new window or about:blank tab</strong></span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-04:</strong> "Open Credit Service" opens a second popup (both coexist)</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-05:</strong> "Re-open Credit Service" brings credit popup to front</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-06:</strong> Guest Status shows each popup as <span class="text-green-600 font-semibold">open</span></span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-07:</strong> Manually close a popup → Guest Status updates to <span class="text-red-500 font-semibold">closed</span> within ~2s</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-08:</strong> Re-opening a closed popup opens a new window (not just focus)</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-09:</strong> "Open Third-Party" opens <code>example.com</code> popup</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-10:</strong> "Re-open Third-Party" attempts <code>focus()</code> (may open about:blank — expected for untrusted, opener was nulled)</span></label> <label class="flex items-start gap-2"><input type="checkbox" class="mt-0.5"/> <span><strong>TC-POP-11:</strong> No console errors during trusted popup operations</span></label></div></div></main><script src="./popup-focus-host.js" type="module"></script></body></html>
@@ -0,0 +1,6 @@
1
+ import{Analytics as v}from"./analytics-object-v2.js";import{getGuestBaseUrl as L,getHost as S}from"./utils.js";const p=document.getElementById("eventLog"),m=document.getElementById("guestList"),e=i=>{const t=new Date().toLocaleTimeString();p.innerHTML+=`<div>[${t}] ${i}</div>`,p.scrollTop=p.scrollHeight},l=i=>{const t=i.getGuests();if(!t.length){m.innerHTML='<p class="text-gray-400">No popup guests loaded</p>';return}m.innerHTML=t.map(n=>`<div class="flex justify-between items-center p-1 bg-gray-50 rounded">
2
+ <span><strong>${n.id}</strong> \u2014 ${n.title??"untitled"}</span>
3
+ <span class="${n.window?.closed?"text-red-500":"text-green-500"}">${n.window?.closed?"closed":"open"}</span>
4
+ </div>`).join("")},y=async()=>{e("Initializing Popup Focus Host...");const i=new v,t=S(i);if(!t){e("ERROR: Failed to create SSFHost");return}class n extends ice.host.ScriptingObject{constructor(){super("Loan"),this.onLoanAmountChanged=new ice.host.Event({name:"onLoanAmountChanged",objectId:this.constructor.name}),this.onLoanTermChanged=new ice.host.Event({name:"onLoanTermChanged",objectId:this.constructor.name}),this.onDownPaymentChanged=new ice.host.Event({name:"onDownPaymentChanged",objectId:this.constructor.name})}getLoanDetails=()=>({id:"POPUP-LOAN",amount:25e4,term:30,downPayment:5e4,creditScore:720});setCreditScore=d=>{e(`setCreditScore(${d}) called by popup guest`)};setServiceOrderStatus=(d,s)=>{e(`setServiceOrderStatus("${d}", ${s}) called by popup guest`)}}const g=new n;t.addScriptingObject(g),e("Loan scripting object registered");const r=await L();t.loadGuest({id:"pricingService",url:new URL("./pricingService.html",r).href,title:"Pricing Service (Embedded)",targetElement:document.getElementById("embedded-container"),options:{fitToContent:!0},onLoad:()=>e("Pricing Service (embedded) loaded")});const o=(c,d,s)=>{e(`loadGuest("${c}") as popup...`);const h=t.loadGuest({id:c,url:d,title:s,targetElement:document.getElementById("embedded-container"),options:{openMode:ice.host.OpenMode.Popup},onLoad:a=>{e(`Popup guest "${a}" loaded`),l(t)},onError:a=>e(`ERROR: Popup guest "${a}" failed`)});return l(t),h};document.getElementById("btnOpenTitle").addEventListener("click",()=>{o("titleService",new URL("./titleService.html",r).href,"Title Service Corp")}),document.getElementById("btnReopenTitle").addEventListener("click",()=>{e("Re-opening Title Service popup (should bring to front)..."),o("titleService",new URL("./titleService.html",r).href,"Title Service Corp"),e("Check if Title Service popup came to front")}),document.getElementById("btnOpenCredit").addEventListener("click",()=>{o("creditService",new URL("./creditService.html",r).href,"Credit Service Corp")}),document.getElementById("btnReopenCredit").addEventListener("click",()=>{e("Re-opening Credit Service popup (should bring to front)..."),o("creditService",new URL("./creditService.html",r).href,"Credit Service Corp"),e("Check if Credit Service popup came to front")});const u="https://example.com";document.getElementById("btnOpenThirdParty").addEventListener("click",()=>{o("thirdPartyGuest",u,"Third Party App")}),document.getElementById("btnReopenThirdParty").addEventListener("click",()=>{e("Re-opening Third Party popup (opener was nulled \u2014 focus may not work)..."),o("thirdPartyGuest",u,"Third Party App"),e("Check if Third Party popup came to front or a new about:blank opened")}),setInterval(()=>l(t),2e3),e("Popup Focus Host ready")};window.addEventListener("DOMContentLoaded",y);
5
+
6
+ //# sourceMappingURL=popup-focus-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["webpack://ice.host/popup-focus-host.js"],"sourcesContent":["import { Analytics } from './analytics-object-v2.js';\nimport { getGuestBaseUrl, getHost } from './utils.js';\n\nconst logEl = document.getElementById('eventLog');\nconst guestListEl = document.getElementById('guestList');\n\nconst log = (msg) => {\n const ts = new Date().toLocaleTimeString();\n logEl.innerHTML += `<div>[${ts}] ${msg}</div>`;\n logEl.scrollTop = logEl.scrollHeight;\n};\n\nconst refreshGuestList = (host) => {\n const guests = host.getGuests();\n if (!guests.length) {\n guestListEl.innerHTML =\n '<p class=\"text-gray-400\">No popup guests loaded</p>';\n return;\n }\n guestListEl.innerHTML = guests\n .map(\n (g) =>\n `<div class=\"flex justify-between items-center p-1 bg-gray-50 rounded\">\n <span><strong>${g.id}</strong> — ${g.title ?? 'untitled'}</span>\n <span class=\"${\n g.window?.closed ? 'text-red-500' : 'text-green-500'\n }\">${g.window?.closed ? 'closed' : 'open'}</span>\n </div>`,\n )\n .join('');\n};\n\nconst init = async () => {\n log('Initializing Popup Focus Host...');\n const analyticsObj = new Analytics();\n const host = getHost(analyticsObj);\n if (!host) {\n log('ERROR: Failed to create SSFHost');\n return;\n }\n\n // Scripting object compatible with the shared pricingService guest\n class Loan extends ice.host.ScriptingObject {\n constructor() {\n super('Loan');\n this.onLoanAmountChanged = new ice.host.Event({\n name: 'onLoanAmountChanged',\n objectId: this.constructor.name,\n });\n this.onLoanTermChanged = new ice.host.Event({\n name: 'onLoanTermChanged',\n objectId: this.constructor.name,\n });\n this.onDownPaymentChanged = new ice.host.Event({\n name: 'onDownPaymentChanged',\n objectId: this.constructor.name,\n });\n }\n getLoanDetails = () => ({\n id: 'POPUP-LOAN',\n amount: 250000,\n term: 30,\n downPayment: 50000,\n creditScore: 720,\n });\n setCreditScore = (score) => {\n log(`setCreditScore(${score}) called by popup guest`);\n };\n setServiceOrderStatus = (service, done) => {\n log(`setServiceOrderStatus(\"${service}\", ${done}) called by popup guest`);\n };\n }\n\n const loanObj = new Loan();\n host.addScriptingObject(loanObj);\n log('Loan scripting object registered');\n\n const guestBaseUrl = await getGuestBaseUrl();\n\n // Load an embedded guest for comparison\n host.loadGuest({\n id: 'pricingService',\n url: new URL('./pricingService.html', guestBaseUrl).href,\n title: 'Pricing Service (Embedded)',\n targetElement: document.getElementById('embedded-container'),\n options: { fitToContent: true },\n onLoad: () => log('Pricing Service (embedded) loaded'),\n });\n\n const loadPopup = (id, url, title) => {\n log(`loadGuest(\"${id}\") as popup...`);\n const guest = host.loadGuest({\n id,\n url,\n title,\n targetElement: document.getElementById('embedded-container'),\n options: { openMode: ice.host.OpenMode.Popup },\n onLoad: (guestId) => {\n log(`Popup guest \"${guestId}\" loaded`);\n refreshGuestList(host);\n },\n onError: (guestId) => log(`ERROR: Popup guest \"${guestId}\" failed`),\n });\n refreshGuestList(host);\n return guest;\n };\n\n // Title Service popup\n document.getElementById('btnOpenTitle').addEventListener('click', () => {\n loadPopup(\n 'titleService',\n new URL('./titleService.html', guestBaseUrl).href,\n 'Title Service Corp',\n );\n });\n\n document.getElementById('btnReopenTitle').addEventListener('click', () => {\n log('Re-opening Title Service popup (should bring to front)...');\n loadPopup(\n 'titleService',\n new URL('./titleService.html', guestBaseUrl).href,\n 'Title Service Corp',\n );\n log('Check if Title Service popup came to front');\n });\n\n // Credit Service popup\n document.getElementById('btnOpenCredit').addEventListener('click', () => {\n loadPopup(\n 'creditService',\n new URL('./creditService.html', guestBaseUrl).href,\n 'Credit Service Corp',\n );\n });\n\n document.getElementById('btnReopenCredit').addEventListener('click', () => {\n log('Re-opening Credit Service popup (should bring to front)...');\n loadPopup(\n 'creditService',\n new URL('./creditService.html', guestBaseUrl).href,\n 'Credit Service Corp',\n );\n log('Check if Credit Service popup came to front');\n });\n\n // Third-party (untrusted) popup — opener will be nulled\n const THIRD_PARTY_URL = 'https://example.com';\n document.getElementById('btnOpenThirdParty').addEventListener('click', () => {\n loadPopup('thirdPartyGuest', THIRD_PARTY_URL, 'Third Party App');\n });\n\n document\n .getElementById('btnReopenThirdParty')\n .addEventListener('click', () => {\n log(\n 'Re-opening Third Party popup (opener was nulled — focus may not work)...',\n );\n loadPopup('thirdPartyGuest', THIRD_PARTY_URL, 'Third Party App');\n log(\n 'Check if Third Party popup came to front or a new about:blank opened',\n );\n });\n\n // Periodic guest list refresh\n setInterval(() => refreshGuestList(host), 2000);\n\n log('Popup Focus Host ready');\n};\n\nwindow.addEventListener('DOMContentLoaded', init);\n"],"mappings":"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","names":["Analytics","getGuestBaseUrl","getHost","logEl","guestListEl","log","msg","ts","refreshGuestList","host","guests","g","init","analyticsObj","Loan","score","service","done","loanObj","guestBaseUrl","loadPopup","id","url","title","guest","guestId","THIRD_PARTY_URL"],"sourceRoot":"","file":"popup-focus-host.js"}
@@ -1,3 +1,3 @@
1
- let n=null,s=null;const a=()=>{try{new PerformanceObserver(o=>{for(const e of o.getEntries())switch(e.entryType){case"measure":{const r=e.detail??{},i={name:e.name,duration:e.duration,startTime:new Date(performance.timeOrigin+e.startTime).toISOString(),...r};break}default:break}}).observe({type:"measure",buffered:!0})}catch{}},c=()=>{const{logger:t,http:o,webvitals:e,logUnhandledErrors:r,Console:i}=window.emuiDiagnostics||{};t&&(window.logger=t({transport:o("https://int.api.ellielabs.com/diagnostics/v2/logging"),index:"ssfhost",team:"ui platform",appName:"SSF Host Demo"}),e(window.logger),r(window.logger),window.logger.info("SSF Host launched"))};export const getHost=t=>{if(s)return s;c(),a();const o={logger:window.logger,readyStateCallback:e=>{},analyticsObj:t,onGuestEventSubscribe:e=>{},onGuestEventUnSubscribe:e=>{}};return s=ice?.host?.SSFHost?new ice.host.SSFHost("IMTProduct",o):null,s},getGuestBaseUrl=async()=>(n||(n=await(await fetch("./app.config.json")).json()),{...n?.options,...n[n.activeEnv]?.options}?.guestUrl);
1
+ let n=null,s=null;const a=()=>{try{new PerformanceObserver(o=>{for(const e of o.getEntries())switch(e.entryType){case"measure":{const r=e.detail??{},i={name:e.name,duration:e.duration,startTime:new Date(performance.timeOrigin+e.startTime).toISOString(),...r};break}default:break}}).observe({type:"measure",buffered:!0})}catch{}},c=()=>{const{logger:t,http:o,webvitals:e,logUnhandledErrors:r,Console:i}=window.emuiDiagnostics||{};t&&(window.logger=t({transport:o("https://int.api.ellielabs.com/diagnostics/v2/logging"),index:"ssfhost",team:"ui platform",appName:"SSF Host Demo"}),e(window.logger),r(window.logger),window.logger.info("SSF Host launched"))};export const getHost=t=>{if(s)return s;c(),a();const o={logger:window.logger,readyStateCallback:e=>{},analyticsObj:t,measurePerformance:!0,onGuestEventSubscribe:e=>{},onGuestEventUnSubscribe:e=>{}};return s=ice?.host?.SSFHost?new ice.host.SSFHost("IMTProduct",o):null,s},getGuestBaseUrl=async()=>(n||(n=await(await fetch("./app.config.json")).json()),{...n?.options,...n[n.activeEnv]?.options}?.guestUrl);
2
2
 
3
3
  //# sourceMappingURL=utils.js.map
Binary file
Binary file
@@ -1 +1 @@
1
- {"version":3,"sources":["webpack://ice.host/utils.js"],"sourcesContent":["let appConfig = null;\nlet host = null;\n\nconst monitorPerformance = () => {\n try {\n const po = new PerformanceObserver((list) => {\n // eslint-disable-next-line no-restricted-syntax\n for (const entry of list.getEntries()) {\n switch (entry.entryType) {\n case 'measure': {\n const detail = entry.detail ?? {};\n const data = {\n name: entry.name,\n duration: entry.duration,\n startTime: new Date(\n performance.timeOrigin + entry.startTime,\n ).toISOString(),\n ...detail,\n };\n console.log('timing', data);\n break;\n }\n default:\n break;\n }\n }\n });\n po.observe({ type: 'measure', buffered: true });\n } catch (e) {}\n};\n\nconst createLogger = () => {\n const { logger, http, webvitals, logUnhandledErrors, Console } =\n window.emuiDiagnostics || {};\n if (logger) {\n window.logger = logger({\n transport: http('https://int.api.ellielabs.com/diagnostics/v2/logging'),\n // transport: Console(),\n index: 'ssfhost',\n team: 'ui platform',\n appName: 'SSF Host Demo',\n });\n\n webvitals(window.logger);\n logUnhandledErrors(window.logger);\n window.logger.info('SSF Host launched');\n } else console.warn('ui logger missing'); // eslint-disable-line no-console\n};\n\nexport const getHost = (analyticsObj) => {\n if (host) return host;\n createLogger();\n monitorPerformance();\n const hostOptions = {\n logger: window.logger,\n readyStateCallback: (guest) => {},\n analyticsObj,\n onGuestEventSubscribe: (options) => {\n console.log('Guest Event Subscribe:', options);\n },\n onGuestEventUnSubscribe: (options) => {\n console.log('Guest Event UnSubscribe:', options);\n },\n };\n host = ice?.host?.SSFHost\n ? new ice.host.SSFHost('IMTProduct', hostOptions)\n : null;\n // debug loglevel\n // host.setLogLevel(10);\n return host;\n};\n\nexport const getGuestBaseUrl = async () => {\n if (!appConfig) {\n const response = await fetch('./app.config.json');\n appConfig = await response.json();\n }\n const activeEnvConfig = {\n ...appConfig?.options,\n ...appConfig[appConfig.activeEnv]?.options,\n };\n return activeEnvConfig?.guestUrl;\n};\n"],"mappings":"AAAA,IAAIA,EAAY,KACZC,EAAO,KAEX,MAAMC,EAAqB,IAAM,CAC/B,GAAI,CACS,IAAI,oBAAqBC,GAAS,CAE3C,UAAWC,KAASD,EAAK,WAAW,EAClC,OAAQC,EAAM,UAAW,CACvB,IAAK,UAAW,CACd,MAAMC,EAASD,EAAM,QAAU,CAAC,EAC1BE,EAAO,CACX,KAAMF,EAAM,KACZ,SAAUA,EAAM,SAChB,UAAW,IAAI,KACb,YAAY,WAAaA,EAAM,SACjC,EAAE,YAAY,EACd,GAAGC,CACL,EAEA,KACF,CACA,QACE,KACJ,CAEJ,CAAC,EACE,QAAQ,CAAE,KAAM,UAAW,SAAU,EAAK,CAAC,CAChD,MAAY,CAAC,CACf,EAEME,EAAe,IAAM,CACzB,KAAM,CAAE,OAAAC,EAAQ,KAAAC,EAAM,UAAAC,EAAW,mBAAAC,EAAoB,QAAAC,CAAQ,EAC3D,OAAO,iBAAmB,CAAC,EACzBJ,IACF,OAAO,OAASA,EAAO,CACrB,UAAWC,EAAK,sDAAsD,EAEtE,MAAO,UACP,KAAM,cACN,QAAS,eACX,CAAC,EAEDC,EAAU,OAAO,MAAM,EACvBC,EAAmB,OAAO,MAAM,EAChC,OAAO,OAAO,KAAK,mBAAmB,EAE1C,EAEO,aAAM,QAAWE,GAAiB,CACvC,GAAIZ,EAAM,OAAOA,EACjBM,EAAa,EACbL,EAAmB,EACnB,MAAMY,EAAc,CAClB,OAAQ,OAAO,OACf,mBAAqBC,GAAU,CAAC,EAChC,aAAAF,EACA,sBAAwBG,GAAY,CAEpC,EACA,wBAA0BA,GAAY,CAEtC,CACF,EACA,OAAAf,EAAO,KAAK,MAAM,QACd,IAAI,IAAI,KAAK,QAAQ,aAAca,CAAW,EAC9C,KAGGb,CACT,EAEa,gBAAkB,UACxBD,IAEHA,EAAY,MADK,MAAM,MAAM,mBAAmB,GACrB,KAAK,GAEV,CACtB,GAAGA,GAAW,QACd,GAAGA,EAAUA,EAAU,SAAS,GAAG,OACrC,GACwB","names":["appConfig","host","monitorPerformance","list","entry","detail","data","createLogger","logger","http","webvitals","logUnhandledErrors","Console","analyticsObj","hostOptions","guest","options"],"sourceRoot":"","file":"utils.js"}
1
+ {"version":3,"sources":["webpack://ice.host/utils.js"],"sourcesContent":["let appConfig = null;\nlet host = null;\n\nconst monitorPerformance = () => {\n try {\n const po = new PerformanceObserver((list) => {\n // eslint-disable-next-line no-restricted-syntax\n for (const entry of list.getEntries()) {\n switch (entry.entryType) {\n case 'measure': {\n const detail = entry.detail ?? {};\n const data = {\n name: entry.name,\n duration: entry.duration,\n startTime: new Date(\n performance.timeOrigin + entry.startTime,\n ).toISOString(),\n ...detail,\n };\n console.log('timing', data);\n break;\n }\n default:\n break;\n }\n }\n });\n po.observe({ type: 'measure', buffered: true });\n } catch (e) {}\n};\n\nconst createLogger = () => {\n const { logger, http, webvitals, logUnhandledErrors, Console } =\n window.emuiDiagnostics || {};\n if (logger) {\n window.logger = logger({\n transport: http('https://int.api.ellielabs.com/diagnostics/v2/logging'),\n // transport: Console(),\n index: 'ssfhost',\n team: 'ui platform',\n appName: 'SSF Host Demo',\n });\n\n webvitals(window.logger);\n logUnhandledErrors(window.logger);\n window.logger.info('SSF Host launched');\n } else console.warn('ui logger missing'); // eslint-disable-line no-console\n};\n\nexport const getHost = (analyticsObj) => {\n if (host) return host;\n createLogger();\n monitorPerformance();\n const hostOptions = {\n logger: window.logger,\n readyStateCallback: (guest) => {},\n analyticsObj,\n measurePerformance: true,\n onGuestEventSubscribe: (options) => {\n console.log('Guest Event Subscribe:', options);\n },\n onGuestEventUnSubscribe: (options) => {\n console.log('Guest Event UnSubscribe:', options);\n },\n };\n host = ice?.host?.SSFHost\n ? new ice.host.SSFHost('IMTProduct', hostOptions)\n : null;\n // debug loglevel\n // host.setLogLevel(10);\n return host;\n};\n\nexport const getGuestBaseUrl = async () => {\n if (!appConfig) {\n const response = await fetch('./app.config.json');\n appConfig = await response.json();\n }\n const activeEnvConfig = {\n ...appConfig?.options,\n ...appConfig[appConfig.activeEnv]?.options,\n };\n return activeEnvConfig?.guestUrl;\n};\n"],"mappings":"AAAA,IAAIA,EAAY,KACZC,EAAO,KAEX,MAAMC,EAAqB,IAAM,CAC/B,GAAI,CACS,IAAI,oBAAqBC,GAAS,CAE3C,UAAWC,KAASD,EAAK,WAAW,EAClC,OAAQC,EAAM,UAAW,CACvB,IAAK,UAAW,CACd,MAAMC,EAASD,EAAM,QAAU,CAAC,EAC1BE,EAAO,CACX,KAAMF,EAAM,KACZ,SAAUA,EAAM,SAChB,UAAW,IAAI,KACb,YAAY,WAAaA,EAAM,SACjC,EAAE,YAAY,EACd,GAAGC,CACL,EAEA,KACF,CACA,QACE,KACJ,CAEJ,CAAC,EACE,QAAQ,CAAE,KAAM,UAAW,SAAU,EAAK,CAAC,CAChD,MAAY,CAAC,CACf,EAEME,EAAe,IAAM,CACzB,KAAM,CAAE,OAAAC,EAAQ,KAAAC,EAAM,UAAAC,EAAW,mBAAAC,EAAoB,QAAAC,CAAQ,EAC3D,OAAO,iBAAmB,CAAC,EACzBJ,IACF,OAAO,OAASA,EAAO,CACrB,UAAWC,EAAK,sDAAsD,EAEtE,MAAO,UACP,KAAM,cACN,QAAS,eACX,CAAC,EAEDC,EAAU,OAAO,MAAM,EACvBC,EAAmB,OAAO,MAAM,EAChC,OAAO,OAAO,KAAK,mBAAmB,EAE1C,EAEO,aAAM,QAAWE,GAAiB,CACvC,GAAIZ,EAAM,OAAOA,EACjBM,EAAa,EACbL,EAAmB,EACnB,MAAMY,EAAc,CAClB,OAAQ,OAAO,OACf,mBAAqBC,GAAU,CAAC,EAChC,aAAAF,EACA,mBAAoB,GACpB,sBAAwBG,GAAY,CAEpC,EACA,wBAA0BA,GAAY,CAEtC,CACF,EACA,OAAAf,EAAO,KAAK,MAAM,QACd,IAAI,IAAI,KAAK,QAAQ,aAAca,CAAW,EAC9C,KAGGb,CACT,EAEa,gBAAkB,UACxBD,IAEHA,EAAY,MADK,MAAM,MAAM,mBAAmB,GACrB,KAAK,GAEV,CACtB,GAAGA,GAAW,QACd,GAAGA,EAAUA,EAAU,SAAS,GAAG,OACrC,GACwB","names":["appConfig","host","monitorPerformance","list","entry","detail","data","createLogger","logger","http","webvitals","logUnhandledErrors","Console","analyticsObj","hostOptions","guest","options"],"sourceRoot":"","file":"utils.js"}
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Guest V1 -> V2 Host</title><style>body,html,main{height:100%;margin:2px;padding:0}iframe{width:100%;height:100%;border:none}#guest-container{height:100%}</style><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script src="https://cdn.elliemae.io/elliemae/core/ssf/1.0/elli.ssf.guest.js"></script><script defer="defer" src="js/emuiSsfHost.5855ec3cd0fa60013d84.js"></script></head><body><h2>Guest V1 nesting Host V2</h2><div id="result"><p id="loan-details"></p><p id="loan-pre-save-event-details"></p></div><div id="guest-container"></div><script type="module">import{getGuestBaseUrl,getHost}from"./utils.js";import{Analytics}from"./analytics-object-v2.js";const analyticsObj=new Analytics,hostV2=getHost(analyticsObj),loadV2Guest=async()=>{const t=await getGuestBaseUrl(),{id:e}=hostV2.loadGuest({id:"guestV2",url:new URL("./v2-guest.html",t).href,title:"Guest V2",targetElement:document.getElementById("guest-container")})};window.addEventListener("load",async()=>{await elli.script.connect();const t=await elli.script.getObject("Application"),e=await elli.script.getObject("Loan"),a=await e.getLoanDetails();document.getElementById("loan-details").innerText=`Loan Data: ${JSON.stringify(a)}`,elli.script.subscribe("loan","onPreSave",(t,e)=>(document.getElementById("loan-pre-save-event-details").innerText=`Presave event data: ${JSON.stringify(e)}`,!1)),hostV2.addScriptingObject(hostV2.cloneScriptingObject(t,{})),hostV2.addScriptingObject(hostV2.cloneScriptingObject(e,{})),await loadV2Guest()})</script></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Guest V1 -> V2 Host</title><style>body,html,main{height:100%;margin:2px;padding:0}iframe{width:100%;height:100%;border:none}#guest-container{height:100%}</style><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script src="https://cdn.elliemae.io/elliemae/core/ssf/1.0/elli.ssf.guest.js"></script><script defer="defer" src="js/emuiSsfHost.5bb7139d7e86c74f0b6d.js"></script></head><body><h2>Guest V1 nesting Host V2</h2><div id="result"><p id="loan-details"></p><p id="loan-pre-save-event-details"></p></div><div id="guest-container"></div><script type="module">import{getGuestBaseUrl,getHost}from"./utils.js";import{Analytics}from"./analytics-object-v2.js";const analyticsObj=new Analytics,hostV2=getHost(analyticsObj),loadV2Guest=async()=>{const t=await getGuestBaseUrl(),{id:e}=hostV2.loadGuest({id:"guestV2",url:new URL("./v2-guest.html",t).href,title:"Guest V2",targetElement:document.getElementById("guest-container")})};window.addEventListener("load",async()=>{await elli.script.connect();const t=await elli.script.getObject("Application"),e=await elli.script.getObject("Loan"),a=await e.getLoanDetails();document.getElementById("loan-details").innerText=`Loan Data: ${JSON.stringify(a)}`,elli.script.subscribe("loan","onPreSave",(t,e)=>(document.getElementById("loan-pre-save-event-details").innerText=`Presave event data: ${JSON.stringify(e)}`,!1)),hostV2.addScriptingObject(hostV2.cloneScriptingObject(t,{})),hostV2.addScriptingObject(hostV2.cloneScriptingObject(e,{})),await loadV2Guest()})</script></body></html>
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Host V2 -> V1 Guest</title><style>body,html,main{height:100%;margin:4px;padding:0}iframe{width:100%;height:100%;border:none}#guest-container{height:100%}</style><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiSsfHost.5855ec3cd0fa60013d84.js"></script></head><body><main><h1>Host V2 nesting V1 Guest</h1><button id="save-btn" type="button" onclick="saveLoan()">Save Loan</button><div id="results"><p id="pre-save-feedback-result"></p></div><div id="guest-container"></div></main><script type="module">import{Loan}from"./loan-object.js";import{getGuestBaseUrl,getHost}from"./utils.js";import{Analytics}from"./analytics-object-v2.js";const analyticsObj=new Analytics,hostV2=getHost(analyticsObj),loanObj=new Loan;hostV2.addScriptingObject(loanObj);const params=new URLSearchParams(document.location.search),{id:id}=hostV2.loadGuest({id:"guestV1",url:new URL("true"!==params.get("nestV1GuestV2Host")?"./v1-guest.html":"./v1-guest-v2-host.html",window.location.href).href,searchParams:{nestHostV1:!0},title:"Guest V1",targetElement:document.getElementById("guest-container")});window.saveLoan=async()=>{const e=loanObj.getLoanDetails();(await hostV2.dispatchEvent({event:loanObj.onPreSave,eventParams:e,eventOptions:{timeout:1e3}})).some(e=>!1===e)?document.getElementById("pre-save-feedback-result").innerText="Pre save failed":document.getElementById("pre-save-feedback-result").innerText="Pre save succeeded"}</script></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Host V2 -> V1 Guest</title><style>body,html,main{height:100%;margin:4px;padding:0}iframe{width:100%;height:100%;border:none}#guest-container{height:100%}</style><script src="https://cdn.mortgagetech.q1.ice.com/pui-diagnostics@3"></script><script defer="defer" src="js/emuiSsfHost.5bb7139d7e86c74f0b6d.js"></script></head><body><main><h1>Host V2 nesting V1 Guest</h1><button id="save-btn" type="button" onclick="saveLoan()">Save Loan</button><div id="results"><p id="pre-save-feedback-result"></p></div><div id="guest-container"></div></main><script type="module">import{Loan}from"./loan-object.js";import{Application}from"./application-object-v2.js";import{getGuestBaseUrl,getHost}from"./utils.js";import{Analytics}from"./analytics-object-v2.js";const analyticsObj=new Analytics,hostV2=getHost(analyticsObj),loanObj=new Loan,appObj=new Application;hostV2.addScriptingObject(loanObj),hostV2.addScriptingObject(appObj);const params=new URLSearchParams(document.location.search),{id:id}=hostV2.loadGuest({id:"guestV1",url:new URL("true"!==params.get("nestV1GuestV2Host")?"./v1-guest.html":"./v1-guest-v2-host.html",window.location.href).href,searchParams:{nestHostV1:!0},title:"Guest V1",targetElement:document.getElementById("guest-container")});window.saveLoan=async()=>{const e=loanObj.getLoanDetails();(await hostV2.dispatchEvent({event:loanObj.onPreSave,eventParams:e,eventOptions:{timeout:1e3}})).some(e=>!1===e)?document.getElementById("pre-save-feedback-result").innerText="Pre save failed":document.getElementById("pre-save-feedback-result").innerText="Pre save succeeded"}</script></body></html>
@@ -1,6 +1,6 @@
1
- import { IAnalytics } from '@elliemae/pui-scripting-object';
2
1
  import { Remoting } from '@elliemae/microfe-common';
3
2
  import { EventObject, OpenMode } from './types.js';
3
+ import type { PerformanceTracker } from './performanceTracker.js';
4
4
  /**
5
5
  * options for creating a guest application
6
6
  */
@@ -38,9 +38,9 @@ type GuestOption = {
38
38
  */
39
39
  remoting: Remoting;
40
40
  /**
41
- * analytics object
41
+ * performance tracker for timing measurements
42
42
  */
43
- analyticsObj: IAnalytics;
43
+ perfTracker: PerformanceTracker;
44
44
  };
45
45
  /**
46
46
  * message to be sent
@@ -135,6 +135,7 @@ export declare class Guest {
135
135
  * invokes event callback on the guest application
136
136
  * @param {EventObject} event - event object
137
137
  * @param {number} timeout - timeout in milliseconds
138
+ * @returns {Promise} resolves when the guest handles the event
138
139
  */
139
140
  dispatchEvent: (event: EventObject, timeout: number) => Promise<unknown>;
140
141
  /**
@@ -61,6 +61,32 @@ export type HostOption = {
61
61
  * callback to process guest event unsubscription
62
62
  */
63
63
  onGuestEventUnsubscribe?: GuestEventUnsubscribeCallback;
64
+ /**
65
+ * Enable performance timing for SSF operations (guest load, scripting
66
+ * object method calls, and event dispatches). Disabled by default to
67
+ * avoid excessive analytics volume.
68
+ * @default false
69
+ */
70
+ measurePerformance?: boolean;
71
+ /**
72
+ * Minimum interval (ms) between timing measurements for the same metric name.
73
+ * When a timing for a given name is recorded, subsequent timings for the same
74
+ * name are skipped until this window elapses. Avoids redundant cross-window
75
+ * analytics calls for high-frequency APIs and events.
76
+ * Set to 0 to disable deduplication (time every call).
77
+ * Only effective when {@link measurePerformance} is true.
78
+ * @default 10000
79
+ */
80
+ performanceDedupWindowMs?: number;
81
+ /**
82
+ * Sampling ratio (0–1) for high-frequency timing events such as
83
+ * scripting object method calls (`ScriptingObject.API.*`) and event
84
+ * dispatches (`ScriptingObject.Event.*`).
85
+ * `SSF.Guest.Load` is low-frequency and uses the analytics-so default.
86
+ * Only effective when {@link measurePerformance} is true.
87
+ * @default 0.05
88
+ */
89
+ performanceSamplingRatio?: number;
64
90
  };
65
91
  /**
66
92
  * parameters for rendering a guest
@@ -101,6 +127,12 @@ export type LoadGuestParam<SearchParams> = {
101
127
  * @param guestId - unique id of the guest application
102
128
  */
103
129
  onError?: (guestId: string) => void;
130
+ /**
131
+ * arbitrary metadata to associate with this guest.
132
+ * Automatically included in callContext.callChain when
133
+ * the guest's scripting object calls are forwarded to a parent host.
134
+ */
135
+ metadata?: Record<string, unknown>;
104
136
  };
105
137
  export type GuestCloseParam = {
106
138
  /**
@@ -0,0 +1,46 @@
1
+ import type { IAnalytics, TimingOptions } from '@elliemae/pui-scripting-object';
2
+ export type PerformanceTrackerOptions = {
3
+ analyticsObj: IAnalytics;
4
+ enabled?: boolean;
5
+ samplingRatio?: number;
6
+ dedupWindowMs?: number;
7
+ onError?: (message: string) => void;
8
+ };
9
+ /**
10
+ * Encapsulates SSF performance timing — enabled guard, per-name sampling
11
+ * ratio configuration, deduplication window, and start/end lifecycle.
12
+ *
13
+ * Low-frequency timing names (e.g. `SSF.Guest.Load`) bypass both the
14
+ * dedup window and the custom sampling ratio, using the analytics-so default.
15
+ */
16
+ export declare class PerformanceTracker {
17
+ #private;
18
+ static readonly DEFAULT_SAMPLING_RATIO = 0.05;
19
+ static readonly DEFAULT_DEDUP_WINDOW_MS = 10000;
20
+ constructor(options: PerformanceTrackerOptions);
21
+ /**
22
+ * Whether performance tracking is active. Callers can use this to
23
+ * skip expensive work (e.g. building timing name strings) when
24
+ * tracking is disabled.
25
+ * @returns {boolean} true when performance tracking is enabled
26
+ */
27
+ get enabled(): boolean;
28
+ /**
29
+ * Begin a timing measurement. Returns the timing name as a token when
30
+ * timing was started, or `undefined` when timing is disabled, deduped,
31
+ * or skipped. Pass the returned token to {@link end}.
32
+ * Never throws — failures are reported via the `onError` callback.
33
+ * @param {string} name - timing event name
34
+ * @param {TimingOptions} options - context passed to the analytics object
35
+ * @returns {string | undefined} the timing token, or undefined if skipped
36
+ */
37
+ start(name: string, options: TimingOptions): string | undefined;
38
+ /**
39
+ * End a timing measurement previously started via {@link start}.
40
+ * No-op when token is `undefined` (timing was not started).
41
+ * Never throws — failures are reported via the `onError` callback.
42
+ * @param {string | undefined} token - token returned by {@link start}
43
+ * @param {TimingOptions} options - context passed to the analytics object
44
+ */
45
+ end(token: string | undefined, options: TimingOptions): void;
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -2,3 +2,4 @@ import { GenericFunction } from './types.js';
2
2
  export declare const getOrigin: (url: string) => string;
3
3
  export declare const flatten: (source: unknown[]) => unknown[];
4
4
  export declare function isFunction(value: any): value is GenericFunction;
5
+ export declare const isTrustedDomain: (url: string) => boolean;