diff --git a/.github/workflows/common-release.yml b/.github/workflows/common-release.yml index 1516c5ec..c7e8852c 100644 --- a/.github/workflows/common-release.yml +++ b/.github/workflows/common-release.yml @@ -95,13 +95,13 @@ jobs: run: npm run build-browser-sdk env: IFRAME_SECURE_ORIGIN: ${{ secrets.IFRAME_SECURE_ORIGIN }} - IFRAME_SECURE_SITE: "v${{ env.RELEASE_VERSION }}/${{ secrets.IFRAME_SECURE_SITE }}" + IFRAME_SECURE_SITE: "v${{ env.RELEASE_VERSION }}/elements" - name: Build Iframe - run: npm run build-iframe + run: npm run build-iframe-split env: IFRAME_SECURE_ORIGIN: ${{ secrets.IFRAME_SECURE_ORIGIN }} - IFRAME_SECURE_SITE: "v${{ env.RELEASE_VERSION }}/${{ secrets.IFRAME_SECURE_SITE }}" + IFRAME_SECURE_SITE: "v${{ env.RELEASE_VERSION }}/elements" - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 @@ -131,7 +131,7 @@ jobs: npm run build-node-sdk env: IFRAME_SECURE_ORIGIN: ${{ secrets.IFRAME_SECURE_ORIGIN }} - IFRAME_SECURE_SITE: "v${{ env.RELEASE_VERSION }}/${{ secrets.IFRAME_SECURE_SITE }}" + IFRAME_SECURE_SITE: "v${{ env.RELEASE_VERSION }}/elements" - name: Publish SDK run: | diff --git a/package.json b/package.json index ae9414ce..dee50ae6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.6.0", + "version": "2.6.0-dev.9975810", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", @@ -23,9 +23,11 @@ "type-check:watch": "npm run type-check -- --watch", "build:types": "tsc --emitDeclarationOnly", "dev": "webpack serve --config=webpack.dev.js --open --hot ", + "dev:split": "SPLIT_BUNDLES=true webpack serve --config=webpack.dev.js --open --hot", "build-browser-sdk": "webpack --config=webpack.skyflow-browser.js", "build-node-sdk": "webpack --config=webpack.skyflow-node.js", "build-iframe": "webpack --config=webpack.iframe.js", + "build-iframe-split": "webpack --config=webpack.iframe-split.js", "test": "jest --config=jest.config.json" }, "repository": { @@ -44,7 +46,8 @@ "lodash": "^4.17.21", "mime": "3.0.0", "regex-parser": "^2.2.11", - "set-value": "^4.0.1" + "set-value": "^4.0.1", + "jszip": "^3.10.1" }, "engines": { "node": ">=12.0", diff --git a/src/core/external/collect/collect-container.ts b/src/core/external/collect/collect-container.ts index 2642d236..9d56ef64 100644 --- a/src/core/external/collect/collect-container.ts +++ b/src/core/external/collect/collect-container.ts @@ -2,7 +2,7 @@ Copyright (c) 2022 Skyflow, Inc. */ import bus from 'framebus'; -import iframer, { setAttributes, getIframeSrc, setStyles } from '../../../iframe-libs/iframer'; +import iframer, { setAttributes, getIframeSrcByType, setStyles } from '../../../iframe-libs/iframer'; import deepClone from '../../../libs/deep-clone'; import { formatValidations, formatOptions, validateElementOptions, @@ -120,7 +120,7 @@ class CollectContainer extends Container { referrer: clientDomain, }); setAttributes(iframe, { - src: getIframeSrc(), + src: getIframeSrcByType(COLLECT_FRAME_CONTROLLER), }); setStyles(iframe, { ...CONTROLLER_STYLES }); printLog(parameterizedString(logs.infoLogs.CREATE_COLLECT_CONTAINER, CLASS_NAME), diff --git a/src/core/external/collect/compose-collect-container.ts b/src/core/external/collect/compose-collect-container.ts index d8915d0f..a0fddab9 100644 --- a/src/core/external/collect/compose-collect-container.ts +++ b/src/core/external/collect/compose-collect-container.ts @@ -6,7 +6,9 @@ Copyright (c) 2023 Skyflow, Inc. import bus from 'framebus'; import sum from 'lodash/sum'; import EventEmitter from '../../../event-emitter'; -import iframer, { setAttributes, getIframeSrc, setStyles } from '../../../iframe-libs/iframer'; +import iframer, { + setAttributes, getIframeSrc, setStyles, getIframeSrcByType, +} from '../../../iframe-libs/iframer'; import deepClone from '../../../libs/deep-clone'; import { formatValidations, formatOptions, validateElementOptions, getElements, @@ -125,7 +127,7 @@ class ComposableContainer extends Container { referrer: this.#clientDomain, }); setAttributes(iframe, { - src: getIframeSrc(), + src: getIframeSrcByType(COLLECT_FRAME_CONTROLLER), }); setStyles(iframe, { ...CONTROLLER_STYLES }); printLog(parameterizedString(logs.infoLogs.CREATE_COLLECT_CONTAINER, CLASS_NAME), diff --git a/src/core/external/common/iframe.ts b/src/core/external/common/iframe.ts index 96304fbf..3a17d9c5 100644 --- a/src/core/external/common/iframe.ts +++ b/src/core/external/common/iframe.ts @@ -3,12 +3,14 @@ Copyright (c) 2022 Skyflow, Inc. */ import iframer, { setAttributes, - getIframeSrc, + getIframeSrcByType, } from '../../../iframe-libs/iframer'; import SkyflowError from '../../../libs/skyflow-error'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import { updateMetricObjectValue } from '../../../metrics/index'; -import { METRIC_TYPES } from '../../constants'; +import { + METRIC_TYPES, FRAME_ELEMENT, FRAME_REVEAL, COMPOSABLE_REVEAL, +} from '../../constants'; import { LogLevel } from '../../../index-node'; import { Metadata } from '../../internal/internal-types'; @@ -21,15 +23,31 @@ export default class IFrame { container?: Element; + #frameType: string; + constructor(name: string, metadata: Metadata, containerId: string, logLevel: LogLevel) { const clientDomain = metadata.clientDomain || ''; this.name = `${name}:${containerId}:${logLevel}:${btoa(clientDomain)}`; this.metadata = metadata; + + // Determine frame type from name + const namePrefix = name.split(':')[0]; + console.log('namePrefix', namePrefix); + if (namePrefix === FRAME_ELEMENT || namePrefix === 'element') { + this.#frameType = FRAME_ELEMENT; + } else if (namePrefix === FRAME_REVEAL || namePrefix === 'reveal') { + this.#frameType = FRAME_REVEAL; + } else if (namePrefix === COMPOSABLE_REVEAL || namePrefix === 'reveal-composable') { + this.#frameType = COMPOSABLE_REVEAL; + } else { + this.#frameType = FRAME_ELEMENT; // Default fallback + } this.iframe = iframer({ name: this.name, referrer: clientDomain, title: name.match(/^element:([^:]+):/)?.[1] ?? name, }); + console.log('src', this.iframe.src); } mount = (domElement, elementId?: string, data?: any) => { @@ -52,7 +70,8 @@ export default class IFrame { // throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ELEMENT_SELECTOR, [], true); } - setAttributes(this.iframe, { src: `${getIframeSrc()}${data ? `?${btoa(data?.record)}` : ''}` }); + setAttributes(this.iframe, { src: `${getIframeSrcByType(this.#frameType)}${data ? `?${btoa(data?.record)}` : ''}` }); + console.log('iframe src before set', getIframeSrcByType(this.#frameType)); this.container?.appendChild(this.iframe); }; diff --git a/src/core/external/reveal/composable-reveal-container.ts b/src/core/external/reveal/composable-reveal-container.ts index 91a41a20..91756a6e 100644 --- a/src/core/external/reveal/composable-reveal-container.ts +++ b/src/core/external/reveal/composable-reveal-container.ts @@ -6,7 +6,7 @@ Copyright (c) 2023 Skyflow, Inc. import bus from 'framebus'; import sum from 'lodash/sum'; import EventEmitter from '../../../event-emitter'; -import iframer, { setAttributes, getIframeSrc, setStyles } from '../../../iframe-libs/iframer'; +import iframer, { setAttributes, getIframeSrcByType, setStyles } from '../../../iframe-libs/iframer'; import deepClone from '../../../libs/deep-clone'; import SkyflowError from '../../../libs/skyflow-error'; import uuid from '../../../libs/uuid'; @@ -115,7 +115,7 @@ class ComposableRevealContainer extends Container { referrer: this.#clientDomain, }); setAttributes(iframe, { - src: getIframeSrc(), + src: getIframeSrcByType(COLLECT_FRAME_CONTROLLER), }); setStyles(iframe, { ...CONTROLLER_STYLES }); printLog(parameterizedString(logs.infoLogs.CREATE_COLLECT_CONTAINER, CLASS_NAME), diff --git a/src/core/external/reveal/reveal-container.ts b/src/core/external/reveal/reveal-container.ts index f4e1b80b..20a61299 100644 --- a/src/core/external/reveal/reveal-container.ts +++ b/src/core/external/reveal/reveal-container.ts @@ -3,7 +3,7 @@ Copyright (c) 2022 Skyflow, Inc. */ import bus from 'framebus'; import EventEmitter from '../../../event-emitter'; -import iframer, { getIframeSrc, setAttributes, setStyles } from '../../../iframe-libs/iframer'; +import iframer, { getIframeSrcByType, setAttributes, setStyles } from '../../../iframe-libs/iframer'; import SkyflowError from '../../../libs/skyflow-error'; import uuid from '../../../libs/uuid'; import { ContainerType } from '../../../skyflow'; @@ -103,7 +103,7 @@ class RevealContainer extends Container { referrer: clientDomain, }); setAttributes(iframe, { - src: getIframeSrc(), + src: getIframeSrcByType(REVEAL_FRAME_CONTROLLER), }); setStyles(iframe, { ...CONTROLLER_STYLES }); printLog(parameterizedString(logs.infoLogs.CREATE_REVEAL_CONTAINER, CLASS_NAME), diff --git a/src/core/external/skyflow-container.ts b/src/core/external/skyflow-container.ts index b7b9f33d..d6e872da 100644 --- a/src/core/external/skyflow-container.ts +++ b/src/core/external/skyflow-container.ts @@ -4,7 +4,7 @@ Copyright (c) 2022 Skyflow, Inc. import bus from 'framebus'; import Client from '../../client'; import iframer, { - getIframeSrc, + getIframeSrcByType, setAttributes, setStyles, } from '../../iframe-libs/iframer'; @@ -71,7 +71,7 @@ class SkyflowContainer { referrer: clientDomain, }); setAttributes(iframe, { - src: getIframeSrc(), + src: getIframeSrcByType(SKYFLOW_FRAME_CONTROLLER), }); setStyles(iframe, { ...CONTROLLER_STYLES }); document.body.append(iframe); diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 973e0a2a..060f37ba 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -37,6 +37,9 @@ import properties from '../../../properties'; const { getType } = require('mime'); +// eslint-disable-next-line import/no-extraneous-dependencies +const { loadAsync } = require('jszip'); + const CLASS_NAME = 'RevealFrame'; class RevealFrame { static revealFrame: RevealFrame; @@ -421,11 +424,17 @@ class RevealFrame { return new Promise((resolve, reject) => { try { getFileURLFromVaultBySkyflowIDComposable(data, this.#client, clientConfig.authToken) - .then((resolvedResult) => { + .then(async (resolvedResult) => { let url = ''; if (resolvedResult.fields && data.column) { url = resolvedResult.fields[data.column]; } + // fetch file from url + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + console.log('array', arrayBuffer); + const zip = await loadAsync(arrayBuffer); + console.log('zip files', zip); this.sub2({ url, iframeName: this.#name, diff --git a/src/iframe-libs/iframer.ts b/src/iframe-libs/iframer.ts index 1761e6f6..3acd2255 100644 --- a/src/iframe-libs/iframer.ts +++ b/src/iframe-libs/iframer.ts @@ -44,6 +44,32 @@ export default (options = {}) => { return iframe; }; +/** + * Get iframe source URL - now returns single iframe.html for all types + * The iframe.html will dynamically load the appropriate bundle based on frame name + * @param frameType - Type of frame (not used anymore, kept for backwards compatibility) + * @returns URL to iframe.html (single entry point for all frame types) + */ +export const getIframeSrcByType = (_frameType: string): string => { + const secureSite = properties.IFRAME_SECURE_SITE; + // keep param for backwards compatibility + if (_frameType) { /* intentionally unused */ } + + // debug: secureSite and frameType + + // If IFRAME_SECURE_SITE ends with .html, return as-is (monolithic or dynamic loader) + if (secureSite.endsWith('.html')) { + return secureSite; + } + + // Append iframe.html to base URL + const baseUrl = secureSite.replace(/\/$/, ''); // Remove trailing slash + return `${baseUrl}/iframe.html`; + + // Note: iframe.html will read window.name to determine which bundle to load + // This enables 1-to-many mapping: single HTML → multiple bundles +}; + export const getIframeSrc = () => properties.IFRAME_SECURE_SITE; export const setStyles = (element: HTMLElement, styles) => { diff --git a/src/index-internal-collect.ts b/src/index-internal-collect.ts new file mode 100644 index 00000000..43cb80db --- /dev/null +++ b/src/index-internal-collect.ts @@ -0,0 +1,40 @@ +/* +Copyright (c) 2022 Skyflow, Inc. +*/ +import 'core-js/stable'; +import { FRAME_ELEMENT } from './core/constants'; +import logs from './utils/logs'; +import { MessageType, LogLevel } from './utils/common'; +import { + printLog, + parameterizedString, + getElementName, +} from './utils/logs-helper'; +import { getValueFromName } from './utils/helpers'; +import FrameElementInit from './core/internal/frame-element-init'; + +(function init(root: any) { + try { + const frameName = root.name; + const frameType = getValueFromName(frameName, 0); + console.log('frameType 2', frameType); + if (frameType === FRAME_ELEMENT) { + const logLevel = getValueFromName(frameName, 4) || LogLevel.ERROR; + printLog( + parameterizedString( + logs.infoLogs.COLLECT_ELEMET_START, + 'index-internal-collect', + getElementName(frameName), + ), + MessageType.LOG, + LogLevel[logLevel], + ); + root.Skyflow = FrameElementInit; + FrameElementInit.startFrameElement(); + } else { + throw new Error('Invalid frame type for collect bundle'); + } + } catch (e) { + throw new Error(parameterizedString(logs.errorLogs.INVALID_IFRAME)); + } +}(window)); diff --git a/src/index-internal-composable-reveal.ts b/src/index-internal-composable-reveal.ts new file mode 100644 index 00000000..1d6e5360 --- /dev/null +++ b/src/index-internal-composable-reveal.ts @@ -0,0 +1,39 @@ +/* +Copyright (c) 2022 Skyflow, Inc. +*/ +import 'core-js/stable'; +import { COMPOSABLE_REVEAL } from './core/constants'; +import logs from './utils/logs'; +import { MessageType, LogLevel } from './utils/common'; +import { + printLog, + parameterizedString, +} from './utils/logs-helper'; +import { getValueFromName } from './utils/helpers'; +import RevealComposableFrameElementInit from './core/internal/composable-frame-element-init'; + +(function init(root: any) { + try { + const frameName = root.name; + const frameType = getValueFromName(frameName, 0); + console.log('frameType 3', frameType); + if (frameType === COMPOSABLE_REVEAL) { + const logLevel = getValueFromName(frameName, 4) || LogLevel.ERROR; + printLog( + parameterizedString( + logs.infoLogs.COLLECT_ELEMET_START, + 'index-internal-composable-reveal', + frameName, + ), + MessageType.LOG, + LogLevel[logLevel], + ); + root.Skyflow = RevealComposableFrameElementInit; + RevealComposableFrameElementInit.startFrameElement(); + } else { + throw new Error('Invalid frame type for composable reveal bundle'); + } + } catch (e) { + throw new Error(parameterizedString(logs.errorLogs.INVALID_IFRAME)); + } +}(window)); diff --git a/src/index-internal-controller.ts b/src/index-internal-controller.ts new file mode 100644 index 00000000..8c9abee1 --- /dev/null +++ b/src/index-internal-controller.ts @@ -0,0 +1,26 @@ +/* +Copyright (c) 2022 Skyflow, Inc. +*/ +import 'core-js/stable'; +import { SKYFLOW_FRAME_CONTROLLER } from './core/constants'; +import SkyflowFrameController from './core/internal/skyflow-frame/skyflow-frame-controller'; +import logs from './utils/logs'; +import { parameterizedString } from './utils/logs-helper'; +import { getValueFromName } from './utils/helpers'; + +(function init(root: any) { + try { + const frameName = root.name; + const frameType = getValueFromName(frameName, 0); + const frameId = getValueFromName(frameName, 1); + console.log('frameType 5', frameType); + + if (frameType === SKYFLOW_FRAME_CONTROLLER) { + SkyflowFrameController.init(frameId); + } else { + throw new Error('Invalid frame type for controller bundle'); + } + } catch (e) { + throw new Error(parameterizedString(logs.errorLogs.INVALID_IFRAME)); + } +}(window)); diff --git a/src/index-internal-reveal.ts b/src/index-internal-reveal.ts new file mode 100644 index 00000000..c91ddd4f --- /dev/null +++ b/src/index-internal-reveal.ts @@ -0,0 +1,40 @@ +/* +Copyright (c) 2022 Skyflow, Inc. +*/ +import 'core-js/stable'; +import RevealFrame from './core/internal/reveal/reveal-frame'; +import { FRAME_REVEAL } from './core/constants'; +import logs from './utils/logs'; +import { MessageType, LogLevel } from './utils/common'; +import { + printLog, + parameterizedString, +} from './utils/logs-helper'; +import { getAtobValue, getValueFromName } from './utils/helpers'; + +(function init(root: any) { + try { + const frameName = root.name; + const frameType = getValueFromName(frameName, 0); + const frameId = getValueFromName(frameName, 1); + console.log('frameType 6', frameType); + + if (frameType === FRAME_REVEAL) { + const logLevel = getValueFromName(frameName, 3) || LogLevel.ERROR; + printLog( + parameterizedString( + logs.infoLogs.REVEAL_ELEMENT_START, + 'index-internal-reveal', + getAtobValue(frameId), + ), + MessageType.LOG, + LogLevel[logLevel], + ); + RevealFrame.init(); + } else { + throw new Error('Invalid frame type for reveal bundle'); + } + } catch (e) { + throw new Error(parameterizedString(logs.errorLogs.INVALID_IFRAME)); + } +}(window)); diff --git a/webpack-plugins/dynamic-bundle-loader.js b/webpack-plugins/dynamic-bundle-loader.js new file mode 100644 index 00000000..19975798 --- /dev/null +++ b/webpack-plugins/dynamic-bundle-loader.js @@ -0,0 +1,71 @@ +class DynamicBundleLoaderPlugin { + constructor(options = {}) { + this.bundleMap = options.bundleMap || { + 'element': 'collect.js', + 'reveal': 'reveal.js', + 'reveal-composable': 'composable-reveal.js', + 'skyflow_controller': 'controller.js' + }; + } + + apply(compiler) { + compiler.hooks.compilation.tap('DynamicBundleLoaderPlugin', (compilation) => { + // Hook into HtmlWebpackPlugin to modify HTML before emit + const HtmlWebpackPlugin = compiler.options.plugins + .map(plugin => plugin.constructor) + .find(constructor => constructor && constructor.name === 'HtmlWebpackPlugin'); + + if (HtmlWebpackPlugin) { + HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync( + 'DynamicBundleLoaderPlugin', + (data, cb) => { + // Only inject into iframe.html + if (data.outputName === 'iframe.html') { + const loaderScript = this.generateLoaderScript(); + // Inject script before closing tag + data.html = data.html.replace('', `${loaderScript}`); + } + cb(null, data); + } + ); + } + }); + } + + generateLoaderScript() { + const bundleMapJson = JSON.stringify(this.bundleMap); + + return ``; + } +} + +module.exports = DynamicBundleLoaderPlugin; diff --git a/webpack.iframe-split.js b/webpack.iframe-split.js new file mode 100644 index 00000000..5e6d36a3 --- /dev/null +++ b/webpack.iframe-split.js @@ -0,0 +1,84 @@ +/* +Copyright (c) 2022 Skyflow, Inc. +*/ +const { merge } = require('webpack-merge'); +const path = require('path'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); +const terserWebpackPlugin = require('terser-webpack-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); +const HtmlWebPackPlugin = require('html-webpack-plugin'); +const DynamicBundleLoaderPlugin = require('./webpack-plugins/dynamic-bundle-loader'); +const common = require('./webpack.common.js'); + +const minify = { + collapseWhitespace: true, + removeComments: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true, + useShortDoctype: true, + minifyCSS: true, + minifyJS: true, +}; + +module.exports = () => merge(common, { + mode: 'production', + + entry: { + 'collect': [ + path.resolve(__dirname, 'src/index-internal-collect.ts'), + ], + 'reveal': [ + path.resolve(__dirname, 'src/index-internal-reveal.ts'), + ], + 'composable-reveal': [ + path.resolve(__dirname, 'src/index-internal-composable-reveal.ts'), + ], + 'controller': [ + path.resolve(__dirname, 'src/index-internal-controller.ts'), + ], + }, + + output: { + filename: '[name].js', + path: path.resolve(__dirname, 'dist/v1/elements'), + }, + + optimization: { + runtimeChunk: false, + minimizer: [new terserWebpackPlugin()], + // splitChunks disabled - all dependencies bundled into each file + }, + + module: { + rules: [], + }, + + plugins: [ + // Single HTML file that dynamically loads the correct JS bundle + // based on container type from window.name + new HtmlWebPackPlugin({ + filename: 'iframe.html', + template: 'assets/iframe.html', + chunks: [], // No chunks - we'll load JS dynamically via plugin + inject: false, // Don't auto-inject scripts + minify, + }), + // Inject dynamic bundle loader script at build time + new DynamicBundleLoaderPlugin({ + bundleMap: { + 'element': 'collect.js', + 'reveal': 'reveal.js', + 'reveal-composable': 'composable-reveal.js', + 'skyflow_controller': 'controller.js' + } + }), + new CleanWebpackPlugin({ + verbose: true, + dry: false, + }), + new WebpackManifestPlugin(), + new CompressionPlugin(), + ], +});