cozy-external-bridge 0.8.0 → 0.10.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@ This library allows communication between a container app and an app embedded in
4
4
 
5
5
  ## For container app
6
6
 
7
- Just add the `useExternalBridge` hook and it will :
7
+ Add the `useExternalBridge` hook and it will :
8
8
 
9
9
  - allow history syncing
10
10
  - expose a `getContacts` method
@@ -18,6 +18,20 @@ const App = () => {
18
18
  }
19
19
  ```
20
20
 
21
+ You also need to manage routing if embedded app use history syncing :
22
+
23
+ ```
24
+ <HashRouter>
25
+ <Routes>
26
+ <Route element={<AppLayout />}>
27
+ <Route path="/" element={<OutletWrapper Component={App} />}>
28
+ <Route path="bridge/*" />
29
+ </Route>
30
+ </Route>
31
+ </Routes>
32
+ </HashRouter>
33
+ ```
34
+
21
35
  ## For embedded app
22
36
 
23
37
  Import `dist/embedded/bundle.js` script. It exposes method in `window._cozyBridge`.
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BRIDGE_ROUTE_PREFIX = void 0;
7
+ var BRIDGE_ROUTE_PREFIX = '/bridge';
8
+ exports.BRIDGE_ROUTE_PREFIX = BRIDGE_ROUTE_PREFIX;
@@ -3,7 +3,19 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.handleRequestParentOrigin = exports.extractUrl = void 0;
6
+ exports.handleParentOriginRequest = exports.getIframe = exports.extractUrl = void 0;
7
+
8
+ var getIframe = function getIframe() {
9
+ var iframe = document.getElementById('embeddedApp');
10
+
11
+ if (iframe === null) {
12
+ throw new Error('No iframe found');
13
+ }
14
+
15
+ return iframe;
16
+ };
17
+
18
+ exports.getIframe = getIframe;
7
19
 
8
20
  var extractUrl = function extractUrl(url) {
9
21
  if (url.startsWith('http')) {
@@ -16,7 +28,7 @@ var extractUrl = function extractUrl(url) {
16
28
 
17
29
  exports.extractUrl = extractUrl;
18
30
 
19
- var handleRequestParentOrigin = function handleRequestParentOrigin(event, origin) {
31
+ var handleParentOriginRequest = function handleParentOriginRequest(event, origin) {
20
32
  // We do not care about message from other origin that our iframe
21
33
  if (event.origin !== origin) {
22
34
  return;
@@ -28,4 +40,4 @@ var handleRequestParentOrigin = function handleRequestParentOrigin(event, origin
28
40
  }
29
41
  };
30
42
 
31
- exports.handleRequestParentOrigin = handleRequestParentOrigin;
43
+ exports.handleParentOriginRequest = handleParentOriginRequest;
@@ -0,0 +1,64 @@
1
+ import { renderHook } from '@testing-library/react-hooks'
2
+ import { useLocation } from 'react-router-dom'
3
+
4
+ import { getIframe } from './helpers'
5
+ import { useRedirectOnLoad } from './useRedirectOnLoad'
6
+
7
+ jest.mock('react-router-dom', () => ({
8
+ useLocation: jest.fn()
9
+ }))
10
+
11
+ jest.mock('./helpers', () => ({
12
+ getIframe: jest.fn()
13
+ }))
14
+
15
+ const mockedUseLocation = useLocation as jest.Mock
16
+ const mockedGetIframe = getIframe as jest.Mock
17
+
18
+ describe('useInitialRedirection', () => {
19
+ afterEach(() => {
20
+ jest.clearAllMocks()
21
+ })
22
+
23
+ it('should not modify iframe.src when pathname does not start with bridge prefix', () => {
24
+ const mockLocation = { pathname: 'assistant', hash: '', search: '' }
25
+ mockedUseLocation.mockReturnValue(mockLocation)
26
+
27
+ const mockIframe = { src: 'https://alice-chat.mycozy.cloud' }
28
+ mockedGetIframe.mockReturnValue(mockIframe)
29
+
30
+ renderHook(() => useRedirectOnLoad())
31
+
32
+ expect(mockIframe.src).toBe('https://alice-chat.mycozy.cloud')
33
+ })
34
+
35
+ it('should modify iframe.src when pathname start with bridge prefix', () => {
36
+ const mockLocation = { pathname: '/bridge/welcome', hash: '', search: '' }
37
+ mockedUseLocation.mockReturnValue(mockLocation)
38
+
39
+ const mockIframe = { src: 'https://alice-chat.mycozy.cloud' }
40
+ mockedGetIframe.mockReturnValue(mockIframe)
41
+
42
+ renderHook(() => useRedirectOnLoad())
43
+
44
+ expect(mockIframe.src).toBe('https://alice-chat.mycozy.cloud/welcome')
45
+ })
46
+
47
+ it('should modify iframe.src when pathname start with bridge prefix and allow bridge keyword in iframe url', () => {
48
+ const mockLocation = {
49
+ pathname: '/bridge/welcome/bridge',
50
+ hash: '',
51
+ search: ''
52
+ }
53
+ mockedUseLocation.mockReturnValue(mockLocation)
54
+
55
+ const mockIframe = { src: 'https://alice-chat.mycozy.cloud' }
56
+ mockedGetIframe.mockReturnValue(mockIframe)
57
+
58
+ renderHook(() => useRedirectOnLoad())
59
+
60
+ expect(mockIframe.src).toBe(
61
+ 'https://alice-chat.mycozy.cloud/welcome/bridge'
62
+ )
63
+ })
64
+ })
@@ -1,136 +1,29 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
-
5
3
  Object.defineProperty(exports, "__esModule", {
6
4
  value: true
7
5
  });
8
6
  exports.useExternalBridge = void 0;
9
7
 
10
- var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
-
12
- var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
13
-
14
- var Comlink = _interopRequireWildcard(require("comlink"));
15
-
16
- var _react = require("react");
17
-
18
- var _reactRouterDom = require("react-router-dom");
19
-
20
- var _cozyClient = require("cozy-client");
21
-
22
- var _cozyFlags = _interopRequireDefault(require("cozy-flags"));
23
-
24
- var _helpers = require("./helpers");
25
-
26
- function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
27
-
28
- function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
29
-
30
- /* eslint-disable no-console */
31
- var getIframe = function getIframe() {
32
- var iframe = document.getElementById('embeddedApp');
8
+ var _useListenBridgeRequests = require("./useListenBridgeRequests");
33
9
 
34
- if (iframe === null) {
35
- throw new Error('No iframe found');
36
- }
10
+ var _useListenParentOriginRequest = require("./useListenParentOriginRequest");
37
11
 
38
- return iframe;
39
- }; // Initial redirection is necessary when we load a page from a direct link
40
- // to load the iframe to the direct link
41
-
42
-
43
- var useInitialRedirection = function useInitialRedirection() {
44
- var location = (0, _reactRouterDom.useLocation)();
45
- (0, _react.useEffect)(function () {
46
- // If current url is root url, do nothing
47
- if (location.pathname === '/' && location.hash === '' && location.search === '') {
48
- return;
49
- }
50
-
51
- var iframe = getIframe();
52
- var destUrl = new URL(iframe.src);
53
- destUrl.pathname = location.pathname;
54
- destUrl.hash = location.hash;
55
- destUrl.search = location.search;
56
- var currentIframeUrl = new URL(iframe.src);
57
-
58
- if (destUrl.toString() !== currentIframeUrl.toString()) {
59
- iframe.src = destUrl.toString();
60
- }
61
- }, []);
62
- }; // Allow the iframe to request the origin of the parent window
63
-
64
-
65
- var useParentOrigin = function useParentOrigin(origin) {
66
- var client = (0, _cozyClient.useClient)();
67
- (0, _react.useEffect)(function () {
68
- if (!client) return;
69
-
70
- var requestParentOriginHandler = function requestParentOriginHandler(event) {
71
- return (0, _helpers.handleRequestParentOrigin)(event, origin);
72
- };
73
-
74
- window.addEventListener('message', requestParentOriginHandler);
75
- return function () {
76
- window.removeEventListener('message', requestParentOriginHandler);
77
- };
78
- }, [client, origin]);
79
- };
12
+ var _useRedirectOnLoad = require("./useRedirectOnLoad");
80
13
 
81
14
  var useExternalBridge = function useExternalBridge(origin) {
82
- var client = (0, _cozyClient.useClient)();
83
- var navigate = (0, _reactRouterDom.useNavigate)();
84
- useInitialRedirection();
85
- useParentOrigin(origin);
86
- (0, _react.useEffect)(function () {
87
- if (!client) return;
88
- var exposedMethods = {
89
- // Proof of concepts of Twake <-> Cozy communication
90
- getContacts: function () {
91
- var _getContacts = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
92
- var _ref, data;
93
-
94
- return _regenerator.default.wrap(function _callee$(_context) {
95
- while (1) {
96
- switch (_context.prev = _context.next) {
97
- case 0:
98
- _context.next = 2;
99
- return client.query((0, _cozyClient.Q)('io.cozy.contacts'));
100
-
101
- case 2:
102
- _ref = _context.sent;
103
- data = _ref.data;
104
- return _context.abrupt("return", data);
15
+ (0, _useRedirectOnLoad.useRedirectOnLoad)();
105
16
 
106
- case 5:
107
- case "end":
108
- return _context.stop();
109
- }
110
- }
111
- }, _callee);
112
- }));
17
+ var _useListenParentOrigi = (0, _useListenParentOriginRequest.useListenParentOriginRequest)(origin),
18
+ isParentOriginRequestListenerReady = _useListenParentOrigi.isReady;
113
19
 
114
- function getContacts() {
115
- return _getContacts.apply(this, arguments);
116
- }
20
+ var _useListenBridgeReque = (0, _useListenBridgeRequests.useListenBridgeRequests)(origin),
21
+ isBridgeListenerReady = _useListenBridgeReque.isReady;
117
22
 
118
- return getContacts;
119
- }(),
120
- getFlag: function getFlag(key) {
121
- return (0, _cozyFlags.default)(key);
122
- },
123
- // Proof of concepts of Twake <-> Cozy URL synchronization
124
- updateHistory: function updateHistory(newUrl) {
125
- var url = (0, _helpers.extractUrl)(newUrl);
126
- console.log('🟢 Replacing route:', url);
127
- navigate(url, {
128
- replace: true
129
- });
130
- }
131
- };
132
- Comlink.expose(exposedMethods, Comlink.windowEndpoint(getIframe().contentWindow, self, origin));
133
- }, [navigate, client, origin]);
23
+ var isReady = isParentOriginRequestListenerReady && isBridgeListenerReady;
24
+ return {
25
+ isReady: isReady
26
+ };
134
27
  };
135
28
 
136
29
  exports.useExternalBridge = useExternalBridge;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.useListenBridgeRequests = void 0;
9
+
10
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
+
12
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
13
+
14
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
15
+
16
+ var Comlink = _interopRequireWildcard(require("comlink"));
17
+
18
+ var _react = require("react");
19
+
20
+ var _reactRouterDom = require("react-router-dom");
21
+
22
+ var _cozyClient = require("cozy-client");
23
+
24
+ var _cozyFlags = _interopRequireDefault(require("cozy-flags"));
25
+
26
+ var _cozyMinilog = _interopRequireDefault(require("cozy-minilog"));
27
+
28
+ var _constants = require("./constants");
29
+
30
+ var _helpers = require("./helpers");
31
+
32
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
33
+
34
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
35
+
36
+ var log = (0, _cozyMinilog.default)('🌉 [Container bridge]');
37
+
38
+ var useListenBridgeRequests = function useListenBridgeRequests(origin) {
39
+ var client = (0, _cozyClient.useClient)();
40
+ var navigate = (0, _reactRouterDom.useNavigate)();
41
+
42
+ var _useState = (0, _react.useState)(false),
43
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
44
+ isReady = _useState2[0],
45
+ setIsReady = _useState2[1];
46
+
47
+ (0, _react.useEffect)(function () {
48
+ if (!client) return;
49
+ var exposedMethods = {
50
+ // Proof of concepts of Twake <-> Cozy communication
51
+ getContacts: function () {
52
+ var _getContacts = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
53
+ var _ref, data;
54
+
55
+ return _regenerator.default.wrap(function _callee$(_context) {
56
+ while (1) {
57
+ switch (_context.prev = _context.next) {
58
+ case 0:
59
+ _context.next = 2;
60
+ return client.query((0, _cozyClient.Q)('io.cozy.contacts'));
61
+
62
+ case 2:
63
+ _ref = _context.sent;
64
+ data = _ref.data;
65
+ return _context.abrupt("return", data);
66
+
67
+ case 5:
68
+ case "end":
69
+ return _context.stop();
70
+ }
71
+ }
72
+ }, _callee);
73
+ }));
74
+
75
+ function getContacts() {
76
+ return _getContacts.apply(this, arguments);
77
+ }
78
+
79
+ return getContacts;
80
+ }(),
81
+ getFlag: function getFlag(key) {
82
+ return (0, _cozyFlags.default)(key);
83
+ },
84
+ // Proof of concepts of Twake <-> Cozy URL synchronization
85
+ updateHistory: function updateHistory(newUrl) {
86
+ var url = (0, _helpers.extractUrl)(newUrl);
87
+ log.debug("Navigating to ".concat(url, " because received ").concat(newUrl, " from embedded app"));
88
+ navigate(_constants.BRIDGE_ROUTE_PREFIX + url, {
89
+ replace: true
90
+ });
91
+ }
92
+ };
93
+ Comlink.expose(exposedMethods, Comlink.windowEndpoint((0, _helpers.getIframe)().contentWindow, self, origin));
94
+ log.debug('Listening to bridge requests');
95
+ setIsReady(true);
96
+ }, [navigate, client, origin]);
97
+ return {
98
+ isReady: isReady
99
+ };
100
+ };
101
+
102
+ exports.useListenBridgeRequests = useListenBridgeRequests;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.useListenParentOriginRequest = void 0;
9
+
10
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
+
12
+ var _react = require("react");
13
+
14
+ var _cozyClient = require("cozy-client");
15
+
16
+ var _cozyMinilog = _interopRequireDefault(require("cozy-minilog"));
17
+
18
+ var _helpers = require("./helpers");
19
+
20
+ var log = (0, _cozyMinilog.default)('🌉 [Container bridge]');
21
+
22
+ // Allow the iframe to request the origin of the parent window
23
+ var useListenParentOriginRequest = function useListenParentOriginRequest(origin) {
24
+ var client = (0, _cozyClient.useClient)();
25
+
26
+ var _useState = (0, _react.useState)(false),
27
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
28
+ isReady = _useState2[0],
29
+ setIsReady = _useState2[1];
30
+
31
+ (0, _react.useEffect)(function () {
32
+ if (!client) return;
33
+
34
+ var parentOriginRequestHandler = function parentOriginRequestHandler(event) {
35
+ return (0, _helpers.handleParentOriginRequest)(event, origin);
36
+ };
37
+
38
+ window.addEventListener('message', parentOriginRequestHandler);
39
+ log.debug('Listening to parent origin request');
40
+ setIsReady(true);
41
+ return function () {
42
+ window.removeEventListener('message', parentOriginRequestHandler);
43
+ };
44
+ }, [client, origin]);
45
+ return {
46
+ isReady: isReady
47
+ };
48
+ };
49
+
50
+ exports.useListenParentOriginRequest = useListenParentOriginRequest;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.useRedirectOnLoad = void 0;
9
+
10
+ var _react = require("react");
11
+
12
+ var _reactRouterDom = require("react-router-dom");
13
+
14
+ var _cozyMinilog = _interopRequireDefault(require("cozy-minilog"));
15
+
16
+ var _constants = require("./constants");
17
+
18
+ var _helpers = require("./helpers");
19
+
20
+ var log = (0, _cozyMinilog.default)('🌉 [Container bridge]'); // When we load the container app, we want to forward
21
+ // the relevant part of the URL to the iframe
22
+
23
+ var useRedirectOnLoad = function useRedirectOnLoad() {
24
+ var location = (0, _reactRouterDom.useLocation)();
25
+ (0, _react.useEffect)(function () {
26
+ if (location.pathname.startsWith(_constants.BRIDGE_ROUTE_PREFIX)) {
27
+ var iframe = (0, _helpers.getIframe)();
28
+ var destUrl = new URL(iframe.src);
29
+ destUrl.pathname = location.pathname.replace(_constants.BRIDGE_ROUTE_PREFIX, '');
30
+ destUrl.hash = location.hash;
31
+ destUrl.search = location.search;
32
+ iframe.src = destUrl.toString();
33
+ log.debug('Redirecting iframe to', destUrl.toString());
34
+ }
35
+ }, []);
36
+ };
37
+
38
+ exports.useRedirectOnLoad = useRedirectOnLoad;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cozy-external-bridge",
3
3
  "description": "Library allows communication between a container app and an app embedded in an iframe",
4
- "version": "0.8.0",
4
+ "version": "0.10.0",
5
5
  "author": {
6
6
  "name": "Cozy Cloud",
7
7
  "url": "https://github.com/cozy"
@@ -15,6 +15,7 @@
15
15
  "peerDependencies": {
16
16
  "cozy-client": ">=54.0.0",
17
17
  "cozy-flags": ">=4.7.0",
18
+ "cozy-minilog": ">=3.10.0",
18
19
  "react": ">=16.12.0",
19
20
  "react-dom": ">=16.12.0",
20
21
  "react-router-dom": ">=6.14.2"
@@ -31,6 +32,7 @@
31
32
  "babel-preset-cozy-app": "^2.8.1",
32
33
  "cozy-client": "^54.0.0",
33
34
  "cozy-flags": "^4.7.0",
35
+ "cozy-minilog": "^3.10.0",
34
36
  "jest": "26.6.3",
35
37
  "react": "16.12.0",
36
38
  "react-dom": "16.12.0",
@@ -66,5 +68,5 @@
66
68
  "lint": "cd ../.. && yarn eslint --ext js,jsx,ts packages/cozy-external-bridge"
67
69
  },
68
70
  "types": "dist/index.d.ts",
69
- "gitHead": "40a33c2ba35543add056495d9348501e24909a70"
71
+ "gitHead": "7f514d6c45cce3e980d239c421e370c9f8b49498"
70
72
  }