diff --git a/README.md b/README.md index 34a784f..58dd990 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ If you are familiar with `socket.io`, you already know how to use this package. `msgio` is based on postMessage. +Occasionally, we need to build web apps with iframes agross different domains. Communication between these iframes always make us uncomfortable +.Using this library less pain you will suffer. + +## Examples +All examples are in the 'examples' directory in this repository. ## Install diff --git a/examples/create-react-app/.editorconfig b/examples/create-react-app/.editorconfig new file mode 100644 index 0000000..fdda3f6 --- /dev/null +++ b/examples/create-react-app/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# General settings for whole project +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_size = 2 diff --git a/examples/create-react-app/README.md b/examples/create-react-app/README.md new file mode 100644 index 0000000..2cee08d --- /dev/null +++ b/examples/create-react-app/README.md @@ -0,0 +1,5 @@ +# create-react-app +1. Open two terminal window. +2. Run `PORT=3456 npm start` in the first terminal. +3. Run `PORT=4567 npm start` in the second terminal. +4. Open `http://localhost:3456/host` in the browser and see the console. diff --git a/examples/create-react-app/package.json b/examples/create-react-app/package.json new file mode 100644 index 0000000..6433554 --- /dev/null +++ b/examples/create-react-app/package.json @@ -0,0 +1,45 @@ +{ + "name": "create-react-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.18", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "msgio": "^1.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.9.0", + "react-scripts": "5.0.1", + "typescript": "^4.9.5", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/examples/create-react-app/public/favicon.ico b/examples/create-react-app/public/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/examples/create-react-app/public/favicon.ico differ diff --git a/examples/create-react-app/public/index.html b/examples/create-react-app/public/index.html new file mode 100644 index 0000000..aa069f2 --- /dev/null +++ b/examples/create-react-app/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/examples/create-react-app/src/App.tsx b/examples/create-react-app/src/App.tsx new file mode 100644 index 0000000..4996491 --- /dev/null +++ b/examples/create-react-app/src/App.tsx @@ -0,0 +1,51 @@ +import {useCallback, SyntheticEvent} from 'react'; +import {Iframe} from 'msgio'; + +const summarize = (...args: any) => args.reduce((acc: any, cur: any) => (acc + cur), 0); + +const multiply = (a: any, b: any) => Promise.resolve(a * b); + +const throw1 = () => { + throw new Error('Host throws Error. Error1'); +}; + +const throw2 = () => { + return Promise.reject(new Error('Host throws Error: Error2')); +}; + +const App = () => { + const handleLoad = useCallback( + (e: SyntheticEvent) => { + const host = new Iframe(e.target as HTMLIFrameElement); + + host.on('connect', socket => { + console.log('host:connected'); + + socket.on('guest_event_1', (...args: any) => { + console.log('host:main:guest_event_1', ...args); + }); + + socket.emit('host_event_1', {name: 'host_event_1', a: 'a', x: 1}); + + socket.func('summarize', summarize); + + socket.func('multiply', multiply); + + socket.func('throw1', throw1); + + socket.func('throw2', throw2); + + socket.call('concat', 'hello', ' ', 'world', '!').then((result: any) => { + console.log('host:return:', result); + }); + }); + }, + [], + ); + + return ( + + + + + + diff --git a/examples/umd/msgio.min.js b/examples/umd/msgio.min.js new file mode 100644 index 0000000..214be95 --- /dev/null +++ b/examples/umd/msgio.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).msgio={})}(this,(function(e){"use strict";function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function n(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n({events:{},emit(e,...t){let n=this.events[e]||[];for(let e=0,r=n.length;e{this.events[e]=this.events[e]?.filter((e=>t!==e))}}});var v="application/msgio+json",h=r((function e(n){var r=this;t(this,e),o(this,"emitter",p()),o(this,"origin",window.location.origin),o(this,"socketId","none"),o(this,"sendPacket",(function(e,t){r.receiver.target.postMessage({mime:v,type:e,socket:"CONNECT"===e?r.socketId:void 0,body:t},r.receiver.origin)})),o(this,"isValidEvent",(function(e){var t=e.origin,n=e.data,o=n.mime,i=n.type,a=n.socket;return t===r.receiver.origin&&(o===v&&("CONNECT"===i||a===r.socketId))})),o(this,"on",(function(e,t){r.emitter.on(e,t)})),this.receiver=n}));var m=function(e){var t=document.createElement("a");t.href=e;var n=t.protocol.length>4?t.protocol:window.location.protocol,r=t.host.length>0?"80"===t.port||"443"===t.port?t.hostname:t.host:window.location.host;return t.origin||"".concat(n,"//").concat(r)},y=function(){return"".concat(((e=21)=>crypto.getRandomValues(new Uint8Array(e)).reduce(((e,t)=>e+((t&=63)<36?t.toString(36):t<62?(t-26).toString(36).toUpperCase():t>62?"-":"_")),""))(),"")},g=function(e){return e.data.type},E=function(e){return e.data.body},w=r((function e(n){var r=this;t(this,e),o(this,"emitter",p()),o(this,"isValidEvent",(function(e){var t=e.origin,n=e.data,o=n.mime,i=n.socket;return t===r.connection.receiver.origin&&(o===v&&i===r.id)})),o(this,"sendPacket",(function(e,t){var n=r.connection.receiver,o=n.target,i=n.origin;o.postMessage({mime:v,type:e,socket:r.id,body:t},i)})),o(this,"onEvent",(function(e){if(r.isValidEvent(e)&&"SOCKET_EVENT"===g(e)){var t=E(e),n=t.event,o=t.payload;r.emitter.emit(n,o)}})),o(this,"on",(function(e,t){r.emitter.on(e,t)})),o(this,"emit",(function(e,t){r.sendPacket("SOCKET_EVENT",{event:e,payload:t})})),o(this,"func",(function(e,t){if("function"!=typeof t)throw new Error("The object supplied to 'function' must be a function!");r.functionRegistry.set(e,t)})),o(this,"onCallRequest",(function(e){if(r.isValidEvent(e)&&"SOCKET_CALL_REQUEST"===g(e)){var t=E(e),n=t.callId,o=t.fname,i=t.args,a=function(e){r.sendPacket("SOCKET_CALL_RESPONSE",{callId:n,fname:o,payload:e})},s=function(e){return a({error:!0,result:(t=e,{name:t.name,message:t.message})});var t};if(r.functionRegistry.has(o))try{var c=r.functionRegistry.get(o).apply(void 0,f(i));Promise.resolve(c).then((function(e){return a({error:!1,result:e})})).catch(s)}catch(e){s(e)}else s(new ReferenceError("".concat(o," is not defined!")))}})),o(this,"onCallResponse",(function(e){if(r.isValidEvent(e)&&"SOCKET_CALL_RESPONSE"===g(e)){var t=E(e),n=t.callId,o=t.payload,i=o.error,a=o.result;if(r.callRegistry.has(n)){var s,c,u,f,l=r.callRegistry.get(n),d=l.resolve,p=l.reject;i?p((c=(s=a).name,u=s.message,f={Error:Error,EvalError:EvalError,RangeError:RangeError,ReferenceError:ReferenceError,SyntaxError:SyntaxError,TypeError:TypeError,URIError:URIError},c in f?new f[c](u):new Error("Unknown Error type!"))):d(a),r.callRegistry.delete(n)}}})),o(this,"call",(function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o");return o(c(r=n.call(this,{target:e.contentWindow,origin:m(e.src)})),"desiredSocketId",y()),o(c(r),"onConnect",(function(e){r.isValidEvent(e)&&("CONNECT"===g(e)&&e.data.body===r.desiredSocketId&&(r.socketId=r.desiredSocketId,r.desiredSocketId="verified",r.emitter.emit("connect",new w(c(r))),window.removeEventListener("message",r.onConnect)))})),o(c(r),"onResize",(function(e){if(r.isValidEvent(e)&&"RESIZE"===g(e)){var t=e.data.body,n=t.width,o=t.height;r.dom.width=n,r.dom.height=o}})),r.dom=e,window.addEventListener("message",r.onConnect),window.addEventListener("message",r.onResize),r.sendPacket("CONNECT",r.desiredSocketId),r}return r(a)}(h),R=function(e){i(a,e);var n=u(a);function a(e){var r;t(this,a);var i=e.host;return o(c(r=n.call(this,{target:window.parent,origin:m(i)})),"onConnect",(function(e){if(r.isValidEvent(e)&&"CONNECT"===g(e)){var t=E(e);t&&(r.socketId=t,r.sendPacket("CONNECT",r.socketId),r.emitter.emit("connect",new w(c(r))),window.removeEventListener("message",r.onConnect))}})),o(c(r),"resize",(function(e){r.sendPacket("RESIZE",e)})),r.option=e,window.addEventListener("message",r.onConnect),r}return r(a)}(h);e.Guest=R,e.Iframe=b})); +//# sourceMappingURL=msgio.min.js.map diff --git a/examples/umd/msgio.min.js.map b/examples/umd/msgio.min.js.map new file mode 100644 index 0000000..ddab909 --- /dev/null +++ b/examples/umd/msgio.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"msgio.min.js","sources":["../node_modules/nanoevents/index.js","../src/constants.ts","../src/endpoint.ts","../src/utils.ts","../node_modules/nanoid/index.browser.js","../src/socket.ts","../src/iframe.ts","../src/guest.ts"],"sourcesContent":["export let createNanoEvents = () => ({\n events: {},\n emit(event, ...args) {\n let callbacks = this.events[event] || []\n for (let i = 0, length = callbacks.length; i < length; i++) {\n callbacks[i](...args)\n }\n },\n on(event, cb) {\n this.events[event]?.push(cb) || (this.events[event] = [cb])\n return () => {\n this.events[event] = this.events[event]?.filter(i => cb !== i)\n }\n }\n})\n","/* eslint import/prefer-default-export: 0 */\nexport const MIME_TYPE = 'application/msgio+json';\n","import {createNanoEvents} from 'nanoevents';\nimport {EndpointEvent, EndpointEventType, Receiver} from './types';\nimport type Socket from './socket';\nimport {MIME_TYPE} from './constants';\n\nclass Endpoint {\n emitter = createNanoEvents();\n\n origin = window.location.origin;\n\n socketId = 'none';\n\n receiver: Receiver;\n\n constructor(receiver: Receiver) {\n this.receiver = receiver;\n }\n\n sendPacket = (type: EndpointEventType, body: any) => {\n this.receiver.target.postMessage({\n mime: MIME_TYPE,\n type,\n socket: type === 'CONNECT' ? this.socketId : undefined,\n body,\n }, this.receiver.origin);\n };\n\n isValidEvent = (e: EndpointEvent) => {\n const {origin, data: {mime, type, socket}} = e;\n\n if (origin !== this.receiver.origin) {\n return false;\n }\n\n if (mime !== MIME_TYPE) {\n return false;\n }\n\n if (type !== 'CONNECT' && socket !== this.socketId) {\n return false;\n }\n\n return true;\n };\n\n on = (event: 'connect', fn: (socket: Socket) => void) => {\n this.emitter.on(event, fn);\n };\n}\n\nexport default Endpoint;\n","import {nanoid} from 'nanoid';\nimport {SocketError, ErrorNameConstructorMap} from './types';\n\nexport const resolveOrigin = (url: string) => {\n const a = document.createElement('a');\n\n a.href = url;\n\n const protocol = a.protocol.length > 4 ? a.protocol : window.location.protocol;\n\n const host = (() => {\n if (a.host.length > 0) {\n return (a.port === '80' || a.port === '443') ? a.hostname : a.host;\n }\n return window.location.host;\n })();\n\n return a.origin || `${protocol}//${host}`;\n};\n\nexport const generateId = () => `${nanoid()}`;\n\nexport const typeOf = (e: MessageEvent) => e.data.type;\n\nexport const bodyOf = (e: MessageEvent) => e.data.body;\n\nexport const encodeError = ({name, message}: Error) => ({name, message});\n\nexport const decodeError = ({name, message}: SocketError) => {\n const ErrorConstructor: ErrorNameConstructorMap = {\n Error,\n EvalError,\n RangeError,\n ReferenceError,\n SyntaxError,\n TypeError,\n URIError,\n };\n\n if (name in ErrorConstructor) {\n return new ErrorConstructor[name](message);\n }\n\n return new Error('Unknown Error type!');\n};\n","export { urlAlphabet } from './url-alphabet/index.js'\nexport let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))\nexport let customRandom = (alphabet, defaultSize, getRandom) => {\n let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1\n let step = -~((1.6 * mask * defaultSize) / alphabet.length)\n return (size = defaultSize) => {\n let id = ''\n while (true) {\n let bytes = getRandom(step)\n let j = step\n while (j--) {\n id += alphabet[bytes[j] & mask] || ''\n if (id.length === size) return id\n }\n }\n }\n}\nexport let customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size, random)\nexport let nanoid = (size = 21) =>\n crypto.getRandomValues(new Uint8Array(size)).reduce((id, byte) => {\n byte &= 63\n if (byte < 36) {\n id += byte.toString(36)\n } else if (byte < 62) {\n id += (byte - 26).toString(36).toUpperCase()\n } else if (byte > 62) {\n id += '-'\n } else {\n id += '_'\n }\n return id\n }, '')\n","import {createNanoEvents} from 'nanoevents';\nimport {SocketEvent, SocketEventType, SocketCallResponseEventPayload, SocketCallRegistryValue} from './types';\nimport type Endpoint from './endpoint';\nimport {MIME_TYPE} from './constants';\nimport {generateId, typeOf, bodyOf, encodeError, decodeError} from './utils';\n\nclass Socket {\n emitter = createNanoEvents();\n\n id: string;\n\n connection: Endpoint;\n\n functionRegistry: Map any>;\n\n callRegistry: Map;\n\n constructor(connection: Endpoint) {\n this.connection = connection;\n\n this.id = this.connection.socketId;\n\n this.functionRegistry = new Map();\n\n this.callRegistry = new Map();\n\n window.addEventListener('message', this.onEvent);\n window.addEventListener('message', this.onCallRequest);\n window.addEventListener('message', this.onCallResponse);\n }\n\n isValidEvent = (e: SocketEvent) => {\n const {origin, data: {mime, socket}} = e;\n\n if (origin !== this.connection.receiver.origin) {\n return false;\n }\n\n if (mime !== MIME_TYPE) {\n return false;\n }\n\n if (socket !== this.id) {\n return false;\n }\n\n return true;\n };\n\n sendPacket = (type: SocketEventType, body: any) => {\n const {target, origin} = this.connection.receiver;\n\n target.postMessage({\n mime: MIME_TYPE,\n type,\n socket: this.id,\n body,\n }, origin);\n };\n\n onEvent = (e: SocketEvent) => {\n if (!this.isValidEvent(e)) {\n return;\n }\n\n if (typeOf(e) !== 'SOCKET_EVENT') {\n return;\n }\n\n const {event, payload} = bodyOf(e);\n\n this.emitter.emit(event, payload);\n };\n\n on = (event: string, fn: (...args: any) => void) => {\n this.emitter.on(event, fn);\n };\n\n emit = (event: string, payload: any) => {\n this.sendPacket('SOCKET_EVENT', {event, payload});\n };\n\n func = (fname: string, fn: (...args: any) => void) => {\n if (typeof fn !== 'function') {\n throw new Error('The object supplied to \\'function\\' must be a function!');\n }\n\n this.functionRegistry.set(fname, fn);\n };\n\n onCallRequest = (e: SocketEvent) => {\n if (!this.isValidEvent(e)) {\n return;\n }\n\n if (typeOf(e) !== 'SOCKET_CALL_REQUEST') {\n return;\n }\n\n const {callId, fname, args} = bodyOf(e);\n\n const send = (payload: SocketCallResponseEventPayload) => {\n this.sendPacket('SOCKET_CALL_RESPONSE', {callId, fname, payload});\n };\n\n const sendError = (result: Error) => send({error: true, result: encodeError(result)});\n\n const sendResult = (result: unknown) => send({error: false, result});\n\n if (!this.functionRegistry.has(fname)) {\n sendError(new ReferenceError(`${fname} is not defined!`));\n return;\n }\n\n try {\n // NOTE: need typescirpt's non-null assertion operator ! below\n // see Line 186 above and https://github.com/microsoft/TypeScript/issues/41045\n const result = this.functionRegistry.get(fname)!(...args);\n\n Promise.resolve(result)\n .then(sendResult)\n .catch(sendError);\n } catch (error) {\n sendError(error as Error);\n }\n };\n\n onCallResponse = (e: SocketEvent) => {\n if (!this.isValidEvent(e)) {\n return;\n }\n\n if (typeOf(e) !== 'SOCKET_CALL_RESPONSE') {\n return;\n }\n\n const {callId, payload: {error, result}} = bodyOf(e);\n\n if (!this.callRegistry.has(callId)) {\n return;\n }\n\n // NOTE: need typescirpt's non-null assertion operator ! below\n // see Line 186 above and https://github.com/microsoft/TypeScript/issues/41045\n const {resolve, reject} = this.callRegistry.get(callId)!;\n\n if (error) {\n reject(decodeError(result));\n } else {\n resolve(result);\n }\n\n this.callRegistry.delete(callId);\n };\n\n call = (fname: string, ...args: any[]) => {\n const callId = generateId();\n\n this.sendPacket('SOCKET_CALL_REQUEST', {callId, fname, args});\n\n return new Promise((resolve, reject) => {\n this.callRegistry.set(callId, {callId, fname, args, resolve, reject});\n });\n };\n}\n\nexport default Socket;\n","import {EndpointEvent} from './types';\nimport Endpoint from './endpoint';\nimport Socket from './socket';\nimport {resolveOrigin, generateId, typeOf} from './utils';\n\nclass Iframe extends Endpoint {\n dom: HTMLIFrameElement;\n\n desiredSocketId = generateId();\n\n constructor(dom: HTMLIFrameElement) {\n if (dom.tagName !== 'IFRAME' || !dom.contentWindow) {\n throw new Error('The DOM Node supplied is not a valid iframe node!');\n }\n\n super({\n target: dom.contentWindow,\n origin: resolveOrigin(dom.src),\n });\n\n this.dom = dom;\n\n window.addEventListener('message', this.onConnect);\n window.addEventListener('message', this.onResize);\n\n this.sendPacket('CONNECT', this.desiredSocketId);\n }\n\n onConnect = (e: EndpointEvent) => {\n if (!this.isValidEvent(e)) {\n return;\n }\n\n if (typeOf(e) !== 'CONNECT') {\n return;\n }\n\n const repliedSocketId = e.data.body;\n\n if (repliedSocketId !== this.desiredSocketId) {\n return;\n }\n\n this.socketId = this.desiredSocketId;\n\n this.desiredSocketId = 'verified';\n\n this.emitter.emit('connect', new Socket(this));\n\n window.removeEventListener('message', this.onConnect);\n };\n\n onResize = (e: EndpointEvent) => {\n if (!this.isValidEvent(e)) {\n return;\n }\n\n if (typeOf(e) !== 'RESIZE') {\n return;\n }\n\n const {width, height} = e.data.body;\n\n this.dom.width = width;\n this.dom.height = height;\n };\n}\n\nexport default Iframe;\n","import {GuestOption, EndpointEvent, FrameSize} from './types';\nimport Endpoint from './endpoint';\nimport Socket from './socket';\nimport {resolveOrigin, typeOf, bodyOf} from './utils';\n\nclass Guest extends Endpoint {\n option: GuestOption; // only for debug\n\n constructor(option: GuestOption) {\n const {host} = option;\n\n super({\n target: window.parent,\n origin: resolveOrigin(host),\n });\n\n this.option = option;\n\n window.addEventListener('message', this.onConnect);\n }\n\n onConnect = (e: EndpointEvent) => {\n if (!this.isValidEvent(e)) {\n return;\n }\n\n if (typeOf(e) !== 'CONNECT') {\n return;\n }\n\n const desiredSocketId = bodyOf(e);\n\n if (!desiredSocketId) {\n return;\n }\n\n this.socketId = desiredSocketId;\n\n this.sendPacket('CONNECT', this.socketId);\n\n this.emitter.emit('connect', new Socket(this));\n\n window.removeEventListener('message', this.onConnect);\n };\n\n resize = (size: FrameSize) => {\n this.sendPacket('RESIZE', size);\n };\n}\n\nexport default Guest;\n"],"names":["createNanoEvents","events","emit","event","args","callbacks","this","i","length","on","cb","push","filter","MIME_TYPE","Endpoint","_createClass","receiver","_this","_classCallCheck","_defineProperty","window","location","origin","type","body","target","postMessage","mime","socket","socketId","undefined","e","_e$data","data","fn","emitter","resolveOrigin","url","a","document","createElement","href","protocol","host","port","hostname","concat","generateId","size","crypto","getRandomValues","Uint8Array","reduce","id","byte","toString","toUpperCase","nanoid","typeOf","bodyOf","Socket","connection","_this$connection$rece","isValidEvent","_bodyOf","payload","sendPacket","fname","Error","functionRegistry","set","_bodyOf2","callId","send","sendError","result","error","_ref","name","message","has","get","apply","_toConsumableArray","Promise","resolve","then","catch","ReferenceError","_bodyOf3","_bodyOf3$payload","callRegistry","_ref2","ErrorConstructor","reject","EvalError","RangeError","SyntaxError","TypeError","URIError","delete","_len","arguments","Array","_key","Map","addEventListener","onEvent","onCallRequest","onCallResponse","Iframe","_Endpoint","_inherits","_super","_createSuper","dom","tagName","contentWindow","_assertThisInitialized","call","src","desiredSocketId","removeEventListener","onConnect","_e$data$body","width","height","onResize","Guest","option","parent"],"mappings":"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","x_google_ignoreList":[0,4]} \ No newline at end of file diff --git a/examples/webpack/.browserslistrc b/examples/webpack/.browserslistrc new file mode 100644 index 0000000..0223254 --- /dev/null +++ b/examples/webpack/.browserslistrc @@ -0,0 +1,3 @@ +>0.2% +not dead +not op_mini all diff --git a/examples/webpack/.editorconfig b/examples/webpack/.editorconfig new file mode 100644 index 0000000..4432917 --- /dev/null +++ b/examples/webpack/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# General settings for whole project +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_size = 2 \ No newline at end of file diff --git a/examples/webpack/.eslintrc.js b/examples/webpack/.eslintrc.js new file mode 100644 index 0000000..d5481b1 --- /dev/null +++ b/examples/webpack/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + extends: [ + 'airbnb-base', + 'airbnb-typescript/base', + ], + parserOptions: { + project: './tsconfig.json', + }, + rules: { + 'object-curly-newline': ['error', {'multiline': true }], + '@typescript-eslint/indent': ['error', 4], + '@typescript-eslint/object-curly-spacing': ['error', 'never'], + }, +}; diff --git a/examples/webpack/.npmrc b/examples/webpack/.npmrc new file mode 100644 index 0000000..7549542 --- /dev/null +++ b/examples/webpack/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmmirror.com diff --git a/examples/webpack/README.md b/examples/webpack/README.md new file mode 100644 index 0000000..5a3628b --- /dev/null +++ b/examples/webpack/README.md @@ -0,0 +1,5 @@ +# webpack +1. Open two terminal window. +2. Run `npx webpack-dev-server --port 3456` in the first terminal. +3. Run `npx webpack-dev-server --port 4567` in the second terminal. +4. Open `http://localhost:3456/host.html` in the browser and see the console. diff --git a/examples/webpack/package.json b/examples/webpack/package.json new file mode 100644 index 0000000..dd0e477 --- /dev/null +++ b/examples/webpack/package.json @@ -0,0 +1,32 @@ +{ + "name": "msgio-example-webpack", + "version": "1.0.1", + "description": "", + "dependencies": { + "msgio": "^1.0.1", + "nanoevents": "^7.0.1", + "nanoid": "^4.0.1" + }, + "devDependencies": { + "@babel/cli": "^7.21.0", + "@babel/core": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-typescript": "^7.21.0", + "@tsconfig/recommended": "^1.0.2", + "@typescript-eslint/eslint-plugin": "^5.55.0", + "@typescript-eslint/parser": "^5.55.0", + "eslint": "^8.36.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-plugin-import": "^2.27.5", + "html-loader": "^4.2.0", + "html-webpack-plugin": "^5.5.0", + "ts-loader": "^9.4.2", + "typescript": "^5.0.2", + "webpack": "^5.76.2", + "webpack-cli": "^5.0.1", + "webpack-dev-server": "^4.12.0" + }, + "author": "Zhixing Han ", + "license": "MIT" +} diff --git a/examples/webpack/public/favicon.ico b/examples/webpack/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/examples/webpack/public/guest.html b/examples/webpack/public/guest.html new file mode 100644 index 0000000..b8bb6fe --- /dev/null +++ b/examples/webpack/public/guest.html @@ -0,0 +1,12 @@ + + + + + + + msgio:guest + + +
msgio:guest
+ + diff --git a/examples/webpack/public/host.html b/examples/webpack/public/host.html new file mode 100644 index 0000000..ae8781f --- /dev/null +++ b/examples/webpack/public/host.html @@ -0,0 +1,15 @@ + + + + + + + msgio:host + + +
msgio:host
+
+ +
+ + diff --git a/examples/webpack/src/guest.ts b/examples/webpack/src/guest.ts new file mode 100644 index 0000000..cfddb7e --- /dev/null +++ b/examples/webpack/src/guest.ts @@ -0,0 +1,48 @@ +import {Guest} from 'msgio'; + +const guest = new Guest({host: '//localhost:3456'}); + +const concat = (...args: any) => (args.join('')); + +guest.on('connect', socket => { + console.log('guest:connected'); + + guest.resize({width: 400, height: 600}); + + socket.on('host_event_1', (...args: any) => { + console.log('guest:main:host_event_1', ...args); + }); + + socket.emit('guest_event_1', {name: 'guest_event_1', b: 'b', y: 2}); + + socket.func('concat', concat); + + socket.call('summarize', 1, 2).then((result: any) => { + console.log('guest:return:', result); + }); + + socket.call('summarize', 3, 4).then((result: any) => { + console.log('guest:return:', result); + }); + + socket.call('multiply', 5, 6).then((result: any) => { + console.log('guest:return:', result); + }); + + socket.call('throw1').catch((e: Error) => { + console.log('guest:return:', e); + }); + + socket.call('throw2') + .then((data: any) => { + console.log(data); + return data; + }) + .catch((e: Error) => { + console.log('guest:return:', e); + }); + + socket.call('foo').catch((e: Error) => { + console.log('guest:return:', e); + }); +}) diff --git a/examples/webpack/src/host.ts b/examples/webpack/src/host.ts new file mode 100644 index 0000000..2a0055e --- /dev/null +++ b/examples/webpack/src/host.ts @@ -0,0 +1,45 @@ +import {Iframe} from 'msgio'; + +const iframe = document.getElementById('iframe'); + +let sum = 0; + +const summarize = (...args: any) => args.reduce((acc: any, cur: any) => (acc + cur), 0); + +const multiply = (a: any, b: any) => Promise.resolve(a * b); + +const throw1 = () => { + throw new Error('Host throws Error. Error1'); +}; + +const throw2 = () => { + return Promise.reject(new Error('Host throws Error: Error2')); +}; + +const onLoad = (e: Event) => { + const host = new Iframe(e.target as HTMLIFrameElement); + + host.on('connect', socket => { + console.log('host:connected'); + + socket.on('guest_event_1', (...args: any) => { + console.log('host:main:guest_event_1', ...args); + }); + + socket.emit('host_event_1', {name: 'host_event_1', a: 'a', x: 1}); + + socket.func('summarize', summarize); + + socket.func('multiply', multiply); + + socket.func('throw1', throw1); + + socket.func('throw2', throw2); + + socket.call('concat', 'hello', ' ', 'world', '!').then((result: any) => { + console.log('host:return:', result); + }); + }); +}; + +iframe!.addEventListener('load', onLoad); diff --git a/examples/webpack/tsconfig.json b/examples/webpack/tsconfig.json new file mode 100644 index 0000000..282a7be --- /dev/null +++ b/examples/webpack/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "outDir": "./dist/", + "noImplicitAny": true, + "module": "es6", + "target": "es5", + "allowJs": true, + "moduleResolution": "node" + } +} diff --git a/examples/webpack/webpack.config.js b/examples/webpack/webpack.config.js new file mode 100755 index 0000000..bf9b5bb --- /dev/null +++ b/examples/webpack/webpack.config.js @@ -0,0 +1,40 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + mode: 'development', + entry: { + host: path.resolve(__dirname, 'src/host'), + guest: path.resolve(__dirname, 'src/guest'), + }, + output: { + filename: 'assets/js/[name].bundle.js', + chunkFilename: 'assets/js/[name].chunk.js', + }, + resolve: { + extensions: ['.js', '.ts'], + }, + module: { + rules: [ + { + test: /\.ts$/, + loader: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'public/host.html'), + filename: 'host.html', + inject: 'body', + chunks: ['host'], + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'public/guest.html'), + filename: 'guest.html', + inject: 'body', + chunks: ['guest'], + }), + ], +}; diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index abc18a7..0000000 --- a/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - clearMocks: true, - collectCoverage: true, - coverageDirectory: 'coverage', - testEnvironment: 'jsdom', -}; diff --git a/package.json b/package.json index b030e3f..d96f9c7 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { "name": "msgio", - "version": "0.1.0", - "description": "'postMessage' based communication library.", + "version": "1.0.1", + "description": "This library is built on 'postMessage' API to reduce painful communication codes.", + "keywords": ["msgio", "message", "iframe", "postMessage", "socket", "sandbox"], "license": "MIT", "author": "Zhixing Han ", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist", "src" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "build": "npm run clean && npm run build:cjs && npm run build:umd", "build:cjs": "tsc", @@ -32,12 +33,10 @@ "@tsconfig/recommended": "^1.0.2", "@typescript-eslint/eslint-plugin": "^5.56.0", "@typescript-eslint/parser": "^5.56.0", - "babel-jest": "^29.5.0", "eslint": "^8.36.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-plugin-import": "^2.27.5", - "jest": "^29.5.0", "rimraf": "^4.4.0", "rollup": "^3.19.1", "typescript": "^5.0.2"