}\n \n \n \n)\n\nexport default withTranslation('settings')(AnalyticsPage)\n","import React from 'react'\nimport Loadable from '@loadable/component'\nimport ComponentLoader from '../loader/ComponentLoader.js'\n\nconst LoadableWelcomePage = Loadable(() => import('./WelcomePage'),\n { fallback: }\n)\n\nexport default LoadableWelcomePage\n","import React from 'react'\n\nconst BlankPage = () => (\n
\n)\n\nexport default BlankPage\n","import { createRouteBundle } from 'redux-bundler'\nimport StatusPage from '../status/LoadableStatusPage'\nimport FilesPage from '../files/LoadableFilesPage'\nimport StartExploringPage from '../explore/LoadableStartExploringPage'\nimport ExplorePage from '../explore/LoadableExplorePage'\nimport PeersPage from '../peers/LoadablePeersPage'\nimport SettingsPage from '../settings/LoadableSettingsPage'\nimport AnalyticsPage from '../settings/AnalyticsPage'\nimport WelcomePage from '../welcome/LoadableWelcomePage'\nimport BlankPage from '../blank/BlankPage'\n\nexport default createRouteBundle({\n '/explore': StartExploringPage,\n '/explore*': ExplorePage,\n '/files*': FilesPage,\n '/ipfs*': FilesPage,\n '/ipns*': FilesPage,\n '/pins*': FilesPage,\n '/peers': PeersPage,\n '/settings/analytics': AnalyticsPage,\n '/settings*': SettingsPage,\n '/welcome': WelcomePage,\n '/blank': BlankPage,\n '/status*': StatusPage,\n '/': StatusPage,\n '': StatusPage\n}, { routeInfoSelector: 'selectHash' })\n","import { createSelector } from 'redux-bundler'\n\nconst redirectsBundle = {\n name: 'redirects',\n\n reactToEmptyHash: createSelector(\n 'selectHash',\n (hash) => {\n if (hash === '') {\n return { actionCreator: 'doUpdateHash', args: ['#/'] }\n }\n }\n ),\n\n reactToIpfsConnectionFail: createSelector(\n 'selectIpfsInitFailed',\n 'selectHash',\n (failed, hash) => {\n if (failed && hash !== '/welcome' && !hash.startsWith('/settings')) {\n return { actionCreator: 'doUpdateHash', args: ['#/welcome'] }\n }\n }\n )\n}\nexport default redirectsBundle\n","// @ts-check\n\nimport * as Enum from './enum'\nimport { perform } from './task'\n\n/**\n * @typedef {import('./task').Perform<'CONFIG_SAVE', Error, void, void>} ConfigSave\n *\n * @typedef {ConfigSave} Message\n *\n * @typedef {Object} Model\n * @property {boolean} isSaving\n * @property {number} [lastSuccess]\n * @property {number} [lastError]\n * @property {string} [errorMessage]\n *\n * @typedef {Object} State\n * @property {Model} config_save\n */\n\n/**\n * @typedef {import('redux-bundler').Selectors} Selectors\n */\n\nconst selectors = {\n /**\n * @param {State} state\n */\n selectConfigIsSaving: state => state.config_save.isSaving,\n /**\n * @param {State} state\n */\n selectConfigSaveLastSuccess: state => state.config_save.lastSuccess,\n /**\n * @param {State} state\n */\n selectConfigSaveLastError: state => state.config_save.lastError\n}\n\n/**\n * @typedef {import('redux-bundler').Actions} Actions\n * @typedef {import('./ipfs-provider').Extra} Extra\n * @typedef {Selectors & Actions} Ext\n * @typedef {import('redux-bundler').Context} Context\n */\n\nconst actions = {\n /**\n * @param {string} configStr\n * @returns {function(Context):Promise}\n */\n doSaveConfig: (configStr) => async ({ store }) => {\n if (store.selectConfigIsSaving()) {\n console.log('doSaveConfig skipped, config save already in progress')\n } else {\n await store.doPerformSaveConfig(configStr)\n }\n },\n\n /**\n * @param {string} configStr\n * @returns {function(Context):Promise}\n */\n doPerformSaveConfig: (configStr) => perform('CONFIG_SAVE', async (context) => {\n const result = await attempt(async () => {\n const obj = JSON.parse(configStr)\n const ipfs = context.getIpfs()\n if (ipfs == null) {\n throw Error('IPFS node is not found')\n }\n\n await ipfs.config.replace(obj)\n })\n\n if (!result.ok) {\n throw result.error\n }\n\n // @ts-ignore - Nor TS nor @gozala can tell where below function is defined\n // but it does appear to exist at runtime 🤷♂️\n await context.store.doMarkConfigAsOutdated()\n })\n}\n\n/**\n * @template T\n * @param {() => Promise} fn\n * @returns {Promise<{ok:true, value:T}|{ok:false, error:Error}>}\n */\nconst attempt = async (fn) => {\n try {\n const value = await fn()\n return { ok: true, value }\n } catch (error) {\n return { ok: false, error }\n }\n}\n\nexport const ACTIONS = Enum.from(['CONFIG_SAVE'])\n\nconst bundle = {\n name: 'config_save',\n\n /**\n * @param {Model} state\n * @param {Message} action\n * @returns {Model}\n */\n reducer: (state = { isSaving: false }, action) => {\n switch (action.type) {\n case ACTIONS.CONFIG_SAVE: {\n const { task } = action\n switch (task.status) {\n case 'Init': {\n return { ...state, isSaving: true }\n }\n case 'Exit': {\n const { result } = task\n if (result.ok) {\n return { ...state, isSaving: false, lastSuccess: Date.now() }\n } else {\n const { error } = result\n const errorMessage = (error && error.message) || String(error)\n return { ...state, isSaving: false, lastError: Date.now(), errorMessage }\n }\n }\n default: {\n return state\n }\n }\n }\n default: {\n return state\n }\n }\n },\n\n ...selectors,\n ...actions\n}\n\nexport default bundle\n","import root from 'window-or-global'\n\nconst toursBundle = {\n name: 'tours',\n\n init: (store) => {\n const tourTooltip = root.localStorage.getItem('tourTooltip')\n\n if (tourTooltip) {\n store.doDisableTooltip()\n }\n },\n\n reducer: (state = { enabled: false, tooltip: true }, action) => {\n if (action.type === 'TOURS_ENABLE') {\n return { ...state, enabled: true }\n }\n\n if (action.type === 'TOURS_DISABLE') {\n return { ...state, enabled: false }\n }\n\n if (action.type === 'TOURS_TOOLTIP_DISABLE') {\n return { ...state, tooltip: false }\n }\n\n return state\n },\n\n doDisableTooltip: () => ({ dispatch }) => {\n root.localStorage.setItem('tourTooltip', false)\n dispatch({ type: 'TOURS_TOOLTIP_DISABLE' })\n },\n\n doEnableTours: () => ({ dispatch }) => {\n dispatch({ type: 'TOURS_ENABLE' })\n },\n\n doDisableTours: () => ({ dispatch }) => {\n dispatch({ type: 'TOURS_DISABLE' })\n },\n\n selectTours: state => state.tours,\n\n selectToursEnabled: state => state.tours.enabled,\n\n selectShowTooltip: state => state.tours.tooltip\n}\nexport default toursBundle\n","import { createSelector } from 'redux-bundler'\nimport * as Enum from './enum'\nexport const ACTIONS = Enum.from([\n 'EXPERIMENTS_TOGGLE',\n 'EXPERIMENTS_UPDATE_STATE'\n])\n\n/**\n * @typedef {import('./task').Perform<'EXPERIMENTS_TOGGLE', Fail, Succeed, Init>} Toggle\n * @typedef {Object} Succeed\n * @property {string} key\n * @property {boolean} value\n *\n * @typedef {Object} Fail\n * @property {string} key\n * @property {string} message\n *\n * @typedef {Object} Init\n * @property {string} key\n *\n * @typedef {Object} UpdateState\n * @property {'EXPERIMENTS_UPDATE_STATE'} type\n * @property {Model} payload\n *\n * @typedef {Toggle|UpdateState} Message\n *\n * @typedef {Record} Model\n *\n * @typedef {Object} State\n * @property {Model} experiments\n */\n\n/**\n * @type {Array<{key:string}>}\n */\nconst EXPERIMENTS = []\n\n/**\n *\n * @param {Model} state\n * @param {Model} payload\n * @returns {Model}\n */\nconst mergeState = (state, payload) =>\n Object.keys(payload).reduce(\n (all, key) => ({\n ...all,\n [key]: {\n ...state[key],\n ...payload[key]\n }\n }),\n state\n )\n\n/**\n * @param {Model} state\n * @param {string} key\n * @returns {Model}\n */\nconst toggleEnabled = (state, key) => {\n return unblock(\n {\n ...state,\n [key]: {\n ...state[key],\n enabled: !(state && state[key] && state[key].enabled)\n }\n },\n key\n )\n}\n\n/**\n * @param {Model} state\n * @param {string} key\n * @returns {Model}\n */\nconst unblock = (state, key) => {\n return {\n ...state,\n [key]: {\n ...state[key],\n blocked: false\n }\n }\n}\n\n/**\n * @param {Model} state\n * @param {string} key\n * @returns {Model}\n */\nconst block = (state, key) => {\n return {\n ...state,\n [key]: {\n ...state[key],\n blocked: true\n }\n }\n}\n\n/**\n * @typedef {import('redux-bundler').Selectors} Selectors\n */\n\nconst selectors = {\n /**\n * @param {State} state\n */\n selectExperimentsState: state => state.experiments,\n\n selectExperiments: createSelector(\n 'selectExperimentsState',\n /**\n * @param {Model} state\n */\n (state) =>\n EXPERIMENTS.map(e => ({\n ...e,\n ...state[e.key]\n }))\n )\n}\n\n/**\n * @typedef {import('redux-bundler').Actions} Actions\n * @typedef {Selectors & Actions} Ext\n * @typedef {import('redux-bundler').Context} Context\n */\n\nconst actions = {\n /**\n * @param {string} key\n * @returns {function(Context): void}\n */\n doExpToggleAction: key => ({ dispatch }) => {\n if (!key) return\n\n dispatch({\n type: ACTIONS.EXPERIMENTS_TOGGLE,\n task: {\n status: 'Init',\n id: Symbol(ACTIONS.EXPERIMENTS_TOGGLE),\n init: { key }\n }\n })\n }\n}\n\nconst experimentsBundle = {\n name: 'experiments',\n\n persistActions: [\n ACTIONS.EXPERIMENTS_TOGGLE,\n ACTIONS.EXPERIMENTS_UPDATE_STATE\n ],\n\n /**\n * @param {Model} state\n * @param {Message} action\n * @returns {Model}\n */\n reducer: (state = {}, action) => {\n switch (action.type) {\n case ACTIONS.EXPERIMENTS_TOGGLE: {\n const { task } = action\n switch (task.status) {\n case 'Init': {\n return block(state, task.init.key)\n }\n case 'Exit': {\n const { result } = task\n if (result.ok) {\n return toggleEnabled(state, result.value.key)\n } else {\n return unblock(state, result.error.key)\n }\n }\n default: {\n return state\n }\n }\n }\n case ACTIONS.EXPERIMENTS_UPDATE_STATE: {\n return mergeState(state, action.payload)\n }\n\n default:\n return state\n }\n },\n\n ...selectors,\n ...actions\n}\nexport default experimentsBundle\n","import { createSelector } from 'redux-bundler'\nimport { ACTIONS as EXP_ACTIONS } from './experiments'\nimport { ACTIONS as FILES_ACTIONS } from './files'\n\n/*\n# Notify\n- show error when ipfs goes away.\n- show ok when it comes back.\n- dismiss the ok after 3s\n*/\n\nconst defaultState = {\n show: false,\n error: false,\n eventId: null,\n code: null\n}\n\nconst notify = {\n name: 'notify',\n\n reducer: (state = defaultState, action) => {\n if (action.type === 'NOTIFY_DISMISSED') {\n return { ...state, show: false }\n }\n\n if (action.type === 'STATS_FETCH_FAILED') {\n return {\n ...state,\n show: true,\n error: true,\n eventId: action.type\n }\n }\n\n if (action.type.match(/^FILES_\\w+_FAILED$/)) {\n return {\n ...state,\n show: true,\n error: true,\n eventId: 'FILES_EVENT_FAILED',\n code: action.payload.error.code\n }\n }\n\n if (action.type === 'STATS_FETCH_FINISHED' && state.eventId === 'STATS_FETCH_FAILED') {\n return {\n ...state,\n error: false,\n eventId: 'STATS_FETCH_FINISHED',\n lastSuccess: Date.now()\n }\n }\n\n if (action.type === 'SWARM_CONNECT_FAILED' || action.type === 'SWARM_CONNECT_FINISHED') {\n return {\n ...state,\n show: true,\n error: action.type === 'SWARM_CONNECT_FAILED',\n eventId: action.type\n }\n }\n\n if (action.type === EXP_ACTIONS.EXP_TOGGLE_FAILED) {\n return {\n ...state,\n show: true,\n error: true,\n eventId: `experimentsErrors.${action.payload.key}`\n }\n }\n\n if (action.type === 'IPFS_CONNECT_FAILED' || action.type === 'IPFS_PIN_FAILED') {\n return {\n ...state,\n show: true,\n error: true,\n eventId: action.type\n }\n }\n if (action.type === 'IPFS_CONNECT_SUCCEED') {\n return {\n ...state,\n show: true,\n error: false,\n eventId: action.type\n }\n }\n if (action.type === 'IPFS_API_ADDRESS_INVALID') {\n return {\n ...state,\n show: true,\n error: true,\n eventId: action.type\n }\n }\n\n return state\n },\n\n selectNotify: state => state.notify,\n\n selectNotifyI18nKey: createSelector(\n 'selectNotify',\n 'selectIpfsProvider',\n (notify, provider) => {\n const { eventId, code } = notify\n\n if (eventId === 'STATS_FETCH_FAILED') {\n return provider === 'window.ipfs' ? 'windowIpfsRequestFailed' : 'ipfsApiRequestFailed'\n }\n if (eventId === 'IPFS_CONNECT_FAILED') {\n return 'ipfsConnectFail'\n }\n if (eventId === 'IPFS_CONNECT_SUCCEED') {\n return 'ipfsConnectSuccess'\n }\n if (eventId === 'IPFS_API_ADDRESS_INVALID') {\n return 'ipfsInvalidApiAddress'\n }\n if (eventId === 'IPFS_PIN_FAILED') {\n return 'ipfsPinFail'\n }\n\n if (eventId === 'FILES_EVENT_FAILED') {\n const type = code ? code.replace(/^(ERR_)/, '') : ''\n\n switch (type) {\n case 'FOLDER_EXISTS':\n return 'folderExists'\n case FILES_ACTIONS.WRITE:\n case FILES_ACTIONS.ADD_BY_PATH:\n case 'API_RESPONSE':\n return 'filesAddFailed'\n case FILES_ACTIONS.FETCH:\n return 'filesFetchFailed'\n case FILES_ACTIONS.MOVE:\n return 'filesRenameFailed'\n case FILES_ACTIONS.MAKE_DIR:\n return 'filesMakeDirFailed'\n case FILES_ACTIONS.COPY:\n return 'filesCopyFailed'\n case FILES_ACTIONS.DELETE:\n return 'filesRemoveFailed'\n default:\n return 'filesEventFailed'\n }\n }\n\n if (eventId === 'STATS_FETCH_FINISHED') {\n return 'ipfsIsBack'\n }\n\n if (eventId === 'SWARM_CONNECT_FAILED') {\n return 'couldntConnectToPeer'\n }\n\n if (eventId === 'SWARM_CONNECT_FINISHED') {\n return 'connectedToPeer'\n }\n\n return eventId\n }\n ),\n\n doNotifyDismiss: () => ({ dispatch }) => dispatch({ type: 'NOTIFY_DISMISSED' }),\n\n // Dismiss the \"all ok\" message after 3 seconds\n reactNotifyOkDismiss: createSelector(\n 'selectAppTime',\n 'selectNotify',\n (appTime, notify) => {\n if (notify.eventId === 'STATS_FETCH_FINISHED' && notify.show && appTime - notify.lastSuccess > 3000) {\n return { type: 'NOTIFY_DISMISSED' }\n }\n }\n )\n}\n\nexport default notify\n","import { createSelector } from 'redux-bundler'\n\n/**\n * @typedef {Object} Model\n * @property {number} lastError\n * @property {boolean} isNodeInfoOpen\n *\n * @typedef {FetchFailed|InfoOpen} Message\n *\n * @typedef {Object} FetchFailed\n * @property {'STATS_FETCH_FAILED'} type\n *\n * @typedef {Object} InfoOpen\n * @property {'NODE_INFO_OPEN'} type\n * @property {boolean} payload\n *\n * @typedef {Object} State\n * @property {Model} connected\n *\n * @typedef {import('redux-bundler').Selectors} Selectors\n */\n\nconst selectors = {\n /**\n * @param {State} state\n */\n selectConnectedLastError: state => state.connected.lastError,\n\n selectIpfsConnected: createSelector(\n 'selectIpfsReady',\n 'selectNodeBandwidthLastSuccess',\n 'selectNodeBandwidthLastError',\n /**\n * @param {boolean} ipfsReady\n * @param {number|void} lastSuccess\n * @param {number} lastError\n * @returns {boolean}\n */\n (ipfsReady, lastSuccess, lastError) =>\n ipfsReady && lastSuccess != null && lastSuccess > lastError\n ),\n\n /**\n * @param {State} state\n */\n selectIsNodeInfoOpen: state => state.connected.isNodeInfoOpen\n}\n\n/**\n * @typedef {import('redux-bundler').Actions} Actions\n * @typedef {Selectors & Actions} Ext\n * @typedef {import('redux-bundler').Context} Context\n */\nconst actions = {\n /**\n * @param {boolean} value\n * @returns {function(Context): void}\n */\n doSetIsNodeInfoOpen: value => ({ dispatch }) => dispatch({ type: 'NODE_INFO_OPEN', payload: value })\n}\n\n// We ask for the stats every few seconds, so that gives a good indication\n// that ipfs things are working (or not), without additional polling of the api.\nconst connected = {\n name: 'connected',\n\n /**\n * @param {Model|void} state\n * @param {Message} action\n * @returns {Model}\n */\n reducer: (state, action) => {\n state = state || { lastError: 0, isNodeInfoOpen: false }\n switch (action.type) {\n case 'STATS_FETCH_FAILED':\n return { ...state, lastError: Date.now() }\n case 'NODE_INFO_OPEN':\n return { ...state, isNodeInfoOpen: action.payload }\n default:\n return state\n }\n },\n ...actions,\n ...selectors\n}\n\nexport default connected\n","import { createSelector } from 'redux-bundler'\nimport { ACTIONS } from './ipfs-provider'\n\n/**\n * @typedef {import('./ipfs-provider').Message} Message\n * @typedef {Object} Model\n * @property {number} [startedAt]\n * @property {number} [failedAt]\n *\n * @typedef {Object} State\n * @property {Model} retryInit\n */\n\n// We ask for the stats every few seconds, so that gives a good indication\n// that ipfs things are working (or not), without additional polling of the api.\nconst retryInit = {\n name: 'retryInit',\n\n /**\n * @param {Model} state\n * @param {Message} action\n * @returns {Model}\n */\n reducer: (state = {}, action) => {\n switch (action.type) {\n case ACTIONS.IPFS_INIT: {\n const { task } = action\n switch (task.status) {\n case 'Init': {\n return { ...state, startedAt: Date.now() }\n }\n case 'Exit': {\n if (task.result.ok) {\n return state\n } else {\n return { ...state, failedAt: Date.now() }\n }\n }\n default: {\n return state\n }\n }\n }\n default: {\n return state\n }\n }\n },\n\n /**\n * @param {State} state\n */\n selectInitStartedAt: state => state.retryInit.startedAt,\n\n /**\n * @param {State} state\n */\n selectInitFailedAt: state => state.retryInit.failedAt,\n\n reactConnectionInitRetry: createSelector(\n 'selectAppTime',\n 'selectInitStartedAt',\n 'selectInitFailedAt',\n /**\n * @param {number} appTime\n * @param {number|void} startedAt\n * @param {number|void} failedAt\n */\n (appTime, startedAt, failedAt) => {\n if (!failedAt || failedAt < startedAt) return false\n if (appTime - failedAt < 3000) return false\n return { actionCreator: 'doTryInitIpfs' }\n }\n )\n}\n\nexport default retryInit\n","import { createAsyncResourceBundle, createSelector } from 'redux-bundler'\n\nconst bundle = createAsyncResourceBundle({\n name: 'identity',\n actionBaseType: 'IDENTITY',\n getPromise: ({ getIpfs }) => getIpfs().id(),\n staleAfter: Infinity,\n persist: false,\n checkIfOnline: false\n})\n\nbundle.selectIdentityLastSuccess = state => state.identity.lastSuccess\n\n// Update identity after we (re)connect with ipfs\nbundle.reactIdentityFetch = createSelector(\n 'selectIpfsConnected',\n 'selectIdentityIsLoading',\n 'selectIdentityLastSuccess',\n 'selectConnectedLastError',\n (connected, isLoading, idLastSuccess, connLastError) => {\n if (connected && !isLoading) {\n if (!idLastSuccess || connLastError > idLastSuccess) {\n return { actionCreator: 'doFetchIdentity' }\n }\n }\n }\n)\n\nexport default bundle\n","import { getConfiguredCache } from 'money-clip'\n\nconst bundleCache = getConfiguredCache({\n name: 'bundle-cache',\n version: 1,\n maxAge: Infinity\n})\n\nexport default bundleCache\n","/**\n * @typedef {Object} IPFSDesktop\n * @property {string} version\n * @property {string} countlyDeviceId\n * @property {string[]} countlyActions\n * @property {() => Promise}>>} selectDirectory\n * @property {(consent:string[]) => void} removeConsent\n * @property {(consent:string[]) => void} addConsent\n * @property {(language:string) => void} updateLanguage\n */\n// @ts-ignore\n/** @type {{ ipfsDesktop: IPFSDesktop }} */\nconst root = (window)\n\n/**\n * @typedef {import('redux-bundler').Selectors} BaseSelectors\n */\n\nconst baseSelectors = {\n /**\n * @returns {boolean}\n */\n selectIsIpfsDesktop: () => !!root.ipfsDesktop,\n /**\n * @returns {string[]}\n */\n selectDesktopCountlyActions: () => ([])\n}\n\nconst desktopSelectors = {\n ...baseSelectors,\n selectDesktopVersion: () => root.ipfsDesktop.version,\n\n selectDesktopCountlyDeviceId: () => root.ipfsDesktop.countlyDeviceId,\n\n selectDesktopCountlyActions: () => root.ipfsDesktop.countlyActions\n}\n\n/**\n * @typedef {import('redux-bundler').Selectors} Selectors\n */\nconst selectors = root.ipfsDesktop\n ? desktopSelectors\n : baseSelectors\n\nconst desktopActions = {\n /**\n * @param {string[]} consent\n * @returns {() => void}\n */\n doDesktopAddConsent: consent => () => {\n return root.ipfsDesktop.addConsent(consent)\n },\n\n /**\n * @param {string[]} consent\n * @returns {() => void}\n */\n doDesktopRemoveConsent: consent => () => {\n return root.ipfsDesktop.removeConsent(consent)\n },\n\n /**\n * @param {string} language\n * @returns {() => void}\n */\n doDesktopUpdateLanguage: language => () => {\n return root.ipfsDesktop.updateLanguage(language)\n }\n}\n\n/**\n * @typedef {never} Message\n * @typedef {Object} Model\n * @typedef {Object} State\n * @property {Model} ipfsDesktop\n * @typedef {import('redux-bundler').Actions} Actions\n * @typedef {Selectors & Actions} Ext\n * @typedef {import('redux-bundler').Context} Context\n */\n\nconst actions = root.ipfsDesktop\n ? desktopActions\n : {}\n\nconst bundle = {\n name: 'ipfsDesktop',\n /**\n * @param {Model} [state]\n * @returns {Model}\n */\n reducer: (state = {}) => state,\n ...selectors,\n ...actions\n}\n\nexport default bundle\n","import { createAsyncResourceBundle, createSelector } from 'redux-bundler'\n\nconst bundle = createAsyncResourceBundle({\n name: 'repoStats',\n getPromise: async ({ getIpfs }) => {\n return getIpfs().repo.stat()\n },\n staleAfter: 60000,\n persist: false,\n checkIfOnline: false\n})\n\nbundle.selectRepoSize = createSelector(\n 'selectRepoStats',\n (repoStats) => {\n if (repoStats && repoStats.repoSize) {\n return repoStats.repoSize.toString()\n }\n }\n)\n\nbundle.selectRepoNumObjects = createSelector(\n 'selectRepoStats',\n (repoStats) => {\n if (repoStats && repoStats.numObjects) {\n return repoStats.numObjects.toString()\n }\n }\n)\n\n// Fetch the config if we don't have it or it's more than `staleAfter` ms old\nbundle.reactRepoStatsFetch = createSelector(\n 'selectRepoStatsShouldUpdate',\n 'selectIpfsReady',\n (shouldUpdate, ipfsReady) => {\n if (shouldUpdate && ipfsReady) {\n return { actionCreator: 'doFetchRepoStats' }\n }\n }\n)\n\nexport default bundle\n","// @ts-check\n\n// @ts-ignore\nimport root from 'window-or-global'\nimport changeCase from 'change-case'\nimport * as Enum from './enum'\nimport { createSelector } from 'redux-bundler'\nimport { ACTIONS as FILES } from './files/consts'\nimport { ACTIONS as CONIFG } from './config-save'\nimport { ACTIONS as INIT } from './ipfs-provider'\nimport { ACTIONS as EXP } from './experiments'\n\n/**\n * @typedef {import('./ipfs-provider').Init} Init\n * @typedef {import('./files/protocol').MakeDir} MakeDir\n * @typedef {import('./files/protocol').Write} Write\n * @typedef {import('./files/protocol').AddByPath} AddByPath\n * @typedef {import('./files/protocol').Move} Move\n * @typedef {import('./files/protocol').Delete} Delete\n * @typedef {import('./files/protocol').DownloadLink} DownloadLink\n * @typedef {import('./config-save').Message} ConfigSave\n * @typedef {import('./experiments').Toggle} ExperimentsToggle\n *\n * @typedef {Object} DesktopSettingToggleStarted\n * @property {'DESKTOP_SETTING_TOGGLE_STARTED'} type\n * @typedef {Object} DesktopSettingToggleFinished\n * @property {'DESKTOP_SETTING_TOGGLE_FINISHED'} type\n * @typedef {Object} DesktopSettingToggleFailed\n * @property {'DESKTOP_SETTING_TOGGLE_FAILED'} type\n *\n * @typedef {import('./experiments').Fail} FailToggle\n * @typedef {import('./experiments').Succeed} SucceedToggle\n * @typedef {import('./experiments').Init} InitToggle\n * @typedef {import('./task').Perform<'DESKTOP_SETTING_TOGGLE', FailToggle, SucceedToggle, InitToggle>} DesktopSettingToggle\n *\n * @typedef {Object} AnalyticsEnabled\n * @property {'ANALYTICS_ENABLED'} type\n * @property {{consent:string[]}} payload\n *\n * @typedef {Object} AnalyticsDisabled\n * @property {'ANALYTICS_DISABLED'} type\n * @property {{consent:string[]}} payload\n *\n * @typedef {Object} RemoveConsent\n * @property {'ANALYTICS_REMOVE_CONSENT'} type\n * @property {{name:string}} payload\n *\n * @typedef {Object} AddConsent\n * @property {'ANALYTICS_ADD_CONSENT'} type\n * @property {{name:string}} payload\n *\n * @typedef {ExperimentsToggle|DesktopSettingToggle} Toggle\n * @typedef {MakeDir|Write|AddByPath|Move|Delete|DownloadLink} FilesMessage\n * @typedef {AnalyticsEnabled|AnalyticsDisabled|RemoveConsent|AddConsent} AnalyticsMessage\n * @typedef {Init|ConfigSave|Toggle|FilesMessage|AnalyticsMessage} Message\n *\n * @typedef {Object} Model\n * @property {number} lastEnabledAt\n * @property {number} lastDisabledAt\n * @property {string[]} consent\n *\n * @typedef {Object} State\n * @property {Model} analytics\n */\n\n// Unknown actions (can't seem to see anything\n// dispatching those).\nconst DESKTOP = Enum.from(['DESKTOP_SETTING_TOGGLE'])\n\n// Local action types\nconst ACTIONS = Enum.from([\n 'ANALYTICS_ENABLED',\n 'ANALYTICS_DISABLED',\n 'ANALYTICS_ADD_CONSENT',\n 'ANALYTICS_REMOVE_CONSENT'\n])\n\n// Only record specific actions listed here.\nconst ASYNC_ACTIONS_TO_RECORD = [\n INIT.IPFS_INIT,\n CONIFG.CONFIG_SAVE,\n FILES.MAKE_DIR,\n FILES.WRITE,\n FILES.ADD_BY_PATH,\n FILES.MOVE,\n FILES.DELETE,\n FILES.DOWNLOAD_LINK,\n EXP.EXPERIMENTS_TOGGLE,\n DESKTOP.DESKTOP_SETTING_TOGGLE\n]\n\nconst COUNTLY_KEY_WEBUI = '8fa213e6049bff23b08e5f5fbac89e7c27397612'\nconst COUNTLY_KEY_WEBUI_TEST = '700fd825c3b257e021bd9dbc6cbf044d33477531'\n\nfunction pickAppKey () {\n const isProd = process.env.NODE_ENV === 'production'\n\n if (root.ipfsDesktop && root.ipfsDesktop.countlyAppKey) {\n return root.ipfsDesktop.countlyAppKey\n } else {\n return isProd ? COUNTLY_KEY_WEBUI : COUNTLY_KEY_WEBUI_TEST\n }\n}\n\nconst consentGroups = {\n all: ['sessions', 'events', 'views', 'location', 'crashes'],\n safe: ['sessions', 'events', 'views', 'location']\n}\n\n/**\n * @param {string|string[]} consent\n * @param {Store} store\n */\nfunction addConsent (consent, store) {\n root.Countly.q.push(['add_consent', consent])\n\n if (store.selectIsIpfsDesktop()) {\n store.doDesktopAddConsent(consent)\n }\n}\n\n/**\n * @param {string|string[]} consent\n * @param {Store} store\n */\nfunction removeConsent (consent, store) {\n root.Countly.q.push(['remove_consent', consent])\n\n if (store.selectIsIpfsDesktop()) {\n store.doDesktopRemoveConsent(consent)\n }\n}\n\n/**\n * @typedef {import('redux-bundler').Selectors} Selectors\n */\n\nconst selectors = {\n /**\n * @param {State} state\n */\n selectAnalytics: (state) => state.analytics,\n /**\n * @param {State} state\n */\n selectAnalyticsConsent: (state) => state.analytics.consent,\n /**\n * @param {State} state\n */\n selectAnalyticsEnabled: (state) => state.analytics.consent.length > 0,\n /**\n * Ask the user if we may enable analytics.\n * @param {State} state\n */\n selectAnalyticsAskToEnable: (state) => {\n const { lastEnabledAt, lastDisabledAt, consent } = state.analytics\n // user has not explicitly chosen\n if (!lastEnabledAt && !lastDisabledAt && consent.length === 0) {\n // ask to enable.\n return true\n }\n // user has already made an explicit choice; dont ask again.\n return false\n },\n\n selectAnalyticsActionsToRecord: createSelector(\n 'selectIsIpfsDesktop',\n 'selectDesktopCountlyActions',\n /**\n * @param {boolean} isDesktop\n * @param {string[]} desktopActions\n * @returns {string[]}\n */\n (isDesktop, desktopActions) => {\n return isDesktop\n ? desktopActions.concat(ASYNC_ACTIONS_TO_RECORD).sort()\n : Array.from(ASYNC_ACTIONS_TO_RECORD).sort()\n }\n )\n}\n\n/**\n * @typedef {import('./ipfs-desktop').Ext} DesktopExt\n * @typedef {import('redux-bundler').Actions} Actions\n * @typedef {Selectors & Actions & DesktopExt} Ext\n * @typedef {import('redux-bundler').Store} Store\n * @typedef {import('redux-bundler').Context} Context\n */\n\nconst actions = {\n /**\n * @returns {function(Context):void}\n */\n doToggleAnalytics: () => ({ store }) => {\n const enable = !store.selectAnalyticsEnabled()\n if (enable) {\n store.doEnableAnalytics()\n } else {\n store.doDisableAnalytics()\n }\n },\n /**\n * @returns {function(Context):void}\n */\n doDisableAnalytics: () => ({ dispatch, store }) => {\n root.Countly.opt_out()\n removeConsent(consentGroups.all, store)\n dispatch({ type: 'ANALYTICS_DISABLED', payload: { consent: [] } })\n },\n /**\n * @returns {function(Context):void}\n */\n doEnableAnalytics: () => ({ dispatch, store }) => {\n removeConsent(consentGroups.all, store)\n root.Countly.opt_in()\n addConsent(consentGroups.safe, store)\n dispatch({ type: 'ANALYTICS_ENABLED', payload: { consent: consentGroups.safe } })\n },\n /**\n * @param {string} name\n * @returns {function(Context):void}\n */\n doToggleConsent: (name) => ({ store }) => {\n const isEnabled = store.selectAnalyticsConsent().includes(name)\n if (isEnabled) {\n store.doRemoveConsent(name)\n } else {\n store.doAddConsent(name)\n }\n },\n /**\n * @param {string} name\n * @returns {function(Context):void}\n */\n doRemoveConsent: (name) => ({ dispatch, store }) => {\n const existingConsents = store.selectAnalyticsConsent()\n const remainingConsents = existingConsents.filter(item => item !== name)\n // Ensure the users is fully opted out of analytics if they remove all consents.\n // This means the consent removal event is not sent to countly, which is good.\n // If a user tells us to send nothing, we send nothing.\n // see: https://github.com/ipfs/ipfs-webui/issues/1041\n if (remainingConsents.length === 0) {\n root.Countly.opt_out()\n }\n removeConsent(name, store)\n dispatch({ type: 'ANALYTICS_REMOVE_CONSENT', payload: { name } })\n },\n /**\n * @param {string} name\n * @returns {function(Context):void}\n */\n doAddConsent: (name) => ({ dispatch, store }) => {\n const existingConsents = store.selectAnalyticsConsent()\n if (existingConsents.length === 0) {\n // Going from 0 to 1 consents opts you in to analytics\n root.Countly.opt_in()\n }\n addConsent(name, store)\n dispatch({ type: 'ANALYTICS_ADD_CONSENT', payload: { name } })\n }\n}\n\nconst createAnalyticsBundle = ({\n countlyUrl = 'https://countly.ipfs.io',\n countlyAppKey = pickAppKey(),\n appVersion = process.env.REACT_APP_VERSION,\n // @ts-ignore - declared but never used\n appGitRevision = process.env.REACT_APP_GIT_REV,\n debug = false\n}) => {\n return {\n name: 'analytics',\n\n persistActions: [\n ACTIONS.ANALYTICS_ENABLED,\n ACTIONS.ANALYTICS_DISABLED,\n ACTIONS.ANALYTICS_DISABLED,\n ACTIONS.ANALYTICS_ADD_CONSENT,\n ACTIONS.ANALYTICS_REMOVE_CONSENT\n ],\n\n /**\n * @param {Store} store\n */\n init: async (store) => {\n // test code sets a mock Counly instance on the global.\n if (!root.Countly) {\n root.Countly = {}\n root.Countly.q = []\n // @ts-ignore\n await import('countly-sdk-web')\n }\n const Countly = root.Countly\n\n Countly.require_consent = true\n Countly.url = countlyUrl\n Countly.app_key = countlyAppKey\n Countly.app_version = appVersion\n Countly.debug = debug\n\n if (store.selectIsIpfsDesktop()) {\n Countly.app_version = store.selectDesktopVersion()\n Countly.q.push(['change_id', store.selectDesktopCountlyDeviceId(), true])\n }\n\n // Configure what to track. Nothing is sent without user consent.\n Countly.q.push(['track_sessions'])\n Countly.q.push(['track_errors'])\n\n // Don't track clicks or links as it can include full url.\n // Countly.q.push(['track_clicks'])\n // Countly.q.push(['track_links'])\n\n if (store.selectAnalyticsEnabled()) {\n const consent = store.selectAnalyticsConsent()\n addConsent(consent, store)\n }\n\n store.subscribeToSelectors(['selectRouteInfo'], ({ routeInfo }) => {\n // skip routes with no hash, as we'll be immediately redirected to `/#`\n if (!root.location || !root.location.hash) return\n /*\n By tracking the pattern rather than the window.location, we limit the info\n we collect to just the app sections that are viewed, and avoid recording\n specific CIDs or local repo paths that would contain personal information.\n */\n root.Countly.q.push(['track_pageview', routeInfo.pattern])\n })\n\n Countly.init()\n },\n\n // Listen to redux actions\n getMiddleware: () => () => {\n /**\n * @param {function(Message):void} next\n * @returns {function(Message):void}\n */\n const middleware = next => action => {\n const payload = parseTask(action)\n if (payload) {\n const { id, duration, error } = payload\n root.Countly.q.push(['add_event', {\n key: id,\n count: 1,\n dur: duration\n }])\n\n // Record errors. Only from explicitly selected actions.\n if (error) {\n root.Countly.q.push(['add_log', action.type])\n root.Countly.q.push(['log_error', error])\n }\n }\n\n return next(action)\n }\n\n return middleware\n },\n\n /**\n * @param {Model|void} state\n * @param {Message} action\n * @returns {Model}\n */\n reducer: (state, action) => {\n state = state || {\n lastEnabledAt: 0,\n lastDisabledAt: 0,\n consent: []\n }\n\n switch (action.type) {\n case ACTIONS.ANALYTICS_ENABLED:\n return { ...state, lastEnabledAt: Date.now(), consent: action.payload.consent }\n case ACTIONS.ANALYTICS_DISABLED:\n return { ...state, lastDisabledAt: Date.now(), consent: action.payload.consent }\n case ACTIONS.ANALYTICS_ADD_CONSENT: {\n const consent = state.consent.filter(item => item !== action.payload.name).concat(action.payload.name)\n return { ...state, lastEnabledAt: Date.now(), consent }\n }\n case ACTIONS.ANALYTICS_REMOVE_CONSENT: {\n const consent = state.consent.filter(item => item !== action.payload.name)\n const lastDisabledAt = (consent.length === 0) ? Date.now() : state.lastDisabledAt\n return { ...state, lastDisabledAt, consent }\n }\n default: {\n // deal with missing consent state from 2.4.0 release.\n if (!state.consent) {\n if (state.lastEnabledAt > state.lastDisabledAt) {\n return { ...state, consent: consentGroups.safe }\n } else {\n return { ...state, consent: [] }\n }\n }\n\n return state\n }\n }\n },\n\n ...selectors,\n\n ...actions\n }\n}\n\n/**\n * @param {Message} action\n */\nconst parseTask = (action) => {\n switch (action.type) {\n case FILES.MAKE_DIR:\n case FILES.WRITE:\n case FILES.ADD_BY_PATH:\n case FILES.MOVE:\n case FILES.DELETE:\n case FILES.DOWNLOAD_LINK:\n case INIT.IPFS_INIT:\n case CONIFG.CONFIG_SAVE:\n return parseTaskResult(action.task, action.type)\n case EXP.EXPERIMENTS_TOGGLE:\n return parseToggleResult(action.task, 'EXPERIMENTS')\n case DESKTOP.DESKTOP_SETTING_TOGGLE:\n return parseToggleResult(action.task, 'DESKTOP_SETTING')\n default:\n return null\n }\n}\n\n/**\n * @param {Init['task']|ConfigSave['task']|FilesMessage['task']} task\n * @param {string} name\n */\nconst parseTaskResult = (task, name) => {\n if (task.status === 'Exit') {\n const { duration, result } = task\n const id = result.ok ? name : `${name}_FAILED`\n const error = result.ok ? null : result.error\n return { id, duration, error }\n } else {\n return null\n }\n}\n\n/**\n * @param {Toggle['task']} task\n * @param {string} name\n */\nconst parseToggleResult = (task, name) => {\n if (task.status === 'Exit') {\n const { result, duration } = task\n const { key } = result.ok ? result.value : result.error\n const error = result.ok ? null : result.error\n const status = !result.ok\n ? 'FAILED'\n : result.value.value\n ? 'ENABLED'\n : 'DISABLED'\n\n const id = `${name}_${changeCase.constantCase(key)}_${status}`\n\n return { id, duration, error }\n }\n return null\n}\n\nexport default createAnalyticsBundle\n","import { createAsyncResourceBundle, createSelector } from 'redux-bundler'\n\nconst bundle = createAsyncResourceBundle({\n name: 'cliTutorMode',\n actionBaseType: 'CLI_TUTOR_MODE_TOGGLE',\n persist: true,\n checkIfOnline: false,\n getPromise: () => {}\n})\n\nbundle.reactIsCliTutorModeEnabled = createSelector(\n 'selectIsCliTutorModeEnabled',\n (isCliTutorModeEnabled) => {\n const isEnabled = Boolean(JSON.parse(localStorage.getItem('isCliTutorModeEnabled')))\n\n if (isCliTutorModeEnabled !== undefined && isCliTutorModeEnabled !== isEnabled) {\n localStorage.setItem('isCliTutorModeEnabled', isCliTutorModeEnabled)\n }\n }\n)\n\nbundle.selectIsCliTutorModeEnabled = state => state.cliTutorMode.isCliTutorModeEnabled\n\nbundle.selectIsCliTutorModalOpen = state => !!state.cliTutorMode.showCliTutorModal\n\nbundle.selectCliOptions = state => state.cliTutorMode.cliOptions\n\nbundle.reducer = (state = {}, action) => {\n if (action.type === 'CLI_TUTOR_MODE_TOGGLE') {\n return { ...state, isCliTutorModeEnabled: action.payload }\n }\n if (action.type === 'CLI_TUTOR_MODAL_ENABLE') {\n return { ...state, showCliTutorModal: action.payload }\n }\n if (action.type === 'CLI_OPTIONS') {\n return { ...state, cliOptions: action.payload }\n }\n\n return state\n}\n\nbundle.doToggleCliTutorMode = key => ({ dispatch }) => {\n dispatch({\n type: 'CLI_TUTOR_MODE_TOGGLE',\n payload: key\n })\n}\n\nbundle.doSetCliOptions = cliOptions => ({ dispatch }) => {\n dispatch({\n type: 'CLI_OPTIONS',\n payload: cliOptions\n })\n}\n\nbundle.doOpenCliTutorModal = openModal => ({ dispatch }) => {\n dispatch({\n type: 'CLI_TUTOR_MODAL_ENABLE',\n payload: openModal\n })\n}\n\nbundle.doOpenCliTutorModal = openModal => ({ dispatch }) => {\n dispatch({\n type: 'CLI_TUTOR_MODAL_ENABLE',\n payload: openModal\n })\n}\n\nbundle.init = store => {\n const isEnabled = Boolean(JSON.parse(localStorage.getItem('isCliTutorModeEnabled')))\n return store.doToggleCliTutorMode(isEnabled)\n}\nexport default bundle\n","import { composeBundles, createCacheBundle } from 'redux-bundler'\nimport ipfsProvider from './ipfs-provider'\nimport { exploreBundle } from 'ipld-explorer-components'\nimport appIdle from './app-idle'\nimport nodeBandwidthChartBundle from './node-bandwidth-chart'\nimport nodeBandwidthBundle from './node-bandwidth'\nimport peersBundle from './peers'\nimport peerLocationsBundle from './peer-locations'\nimport pinningBundle from './pinning'\nimport routesBundle from './routes'\nimport redirectsBundle from './redirects'\nimport filesBundle from './files'\nimport configBundle from './config'\nimport configSaveBundle from './config-save'\nimport toursBundle from './tours'\nimport notifyBundle from './notify'\nimport connectedBundle from './connected'\nimport retryInitBundle from './retry-init'\nimport identityBundle from './identity'\nimport bundleCache from '../lib/bundle-cache'\nimport ipfsDesktop from './ipfs-desktop'\nimport repoStats from './repo-stats'\nimport createAnalyticsBundle from './analytics'\nimport experimentsBundle from './experiments'\nimport cliTutorModeBundle from './cli-tutor-mode'\nimport gatewayBundle from './gateway'\n\nexport default composeBundles(\n createCacheBundle({\n cacheFn: bundleCache.set\n }),\n appIdle({ idleTimeout: 5000 }),\n ipfsProvider,\n identityBundle,\n routesBundle,\n redirectsBundle,\n toursBundle,\n filesBundle(),\n exploreBundle(),\n configBundle,\n configSaveBundle,\n gatewayBundle,\n nodeBandwidthBundle,\n nodeBandwidthChartBundle(),\n peersBundle,\n peerLocationsBundle(),\n pinningBundle,\n notifyBundle,\n connectedBundle,\n retryInitBundle,\n experimentsBundle,\n ipfsDesktop,\n repoStats,\n cliTutorModeBundle,\n createAnalyticsBundle({})\n)\n","import { getFilesFromDataTransferItems } from 'datatransfer-files-promise'\nimport { HTML5Backend } from 'react-dnd-html5-backend'\n\n// If you drop a dir \"foo\" which contains \"cat.jpg\" & \"dog.png\" we receive a\n// single item in the `event.dataTransfer.items` for the directory.\n//\n// We use 'datatransfer-files-promise' to map the dir tree to a flat list of\n// FileSystemEntry objects, each with a filepath propety, that captures the\n// files relative path within the dir that was dropped.\n//\n// so the \"foo\" becomes:\n// [\n// { filepath: 'foo/cat.jpg' name: 'cat.jpg', size: Number },\n// { filepath: 'foo/dog.png' name: 'dog.png', size: Number }\n// ]\n//\n// Which is a useful shape for passing to ipfs.add, with the caveat that each\n// FileSystemEntry object must be passed to pull-file-reader or similar to\n// convert to a stream style that ipfs.add accepts.\n//\n// ReactDnD doesn't give the calling code access to the `event.dataTransfer.items`\n// so we have to work around it here by plugin a custom drop handler that does\n// the work to map from a dir entry to a flat list of files and then stash it on\n// a custom `filesPromise` prop on the return object, which we check for in our\n// dropTarget drop handler functions.\n//\n// See: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry\n// See: https://github.com/grabantot/datatransfer-files-promise/blob/72b6cc763f9b400c59197bcc787268965310c260/index.js\nconst createBackend = (manager) => {\n const backend = HTML5Backend(manager)\n const handler = backend.handleTopDropCapture\n backend.handleTopDropCapture = (event) => {\n handler.call(backend, event)\n if (backend.currentNativeSource && event.dataTransfer.items) {\n // Prevent handling drag & drop of text inside webui\n if ([...event.dataTransfer.items].every(({ kind }) => kind === 'string')) return\n\n const filesPromise = getFilesFromDataTransferItems(event.dataTransfer.items)\n backend.currentNativeSource.item.filesPromise = filesPromise\n }\n }\n return backend\n}\nexport default createBackend\n","import React from 'react'\nimport ReactDOM from 'react-dom'\nimport { Provider } from 'redux-bundler-react'\nimport './index.css'\nimport 'react-virtualized/styles.css'\nimport App from './App'\nimport getStore from './bundles'\nimport bundleCache from './lib/bundle-cache'\nimport { I18nextProvider } from 'react-i18next'\nimport i18n from './i18n'\nimport { DndProvider } from 'react-dnd'\nimport DndBackend from './lib/dnd-backend'\n\nconst appVersion = process.env.REACT_APP_VERSION\nconst gitRevision = process.env.REACT_APP_GIT_REV\n\nconsole.log(`IPFS Web UI - v${appVersion} - https://github.com/ipfs-shipyard/ipfs-webui/commit/${gitRevision}`)\n\nasync function render () {\n const initialData = await bundleCache.getAll()\n if (initialData && process.env.NODE_ENV !== 'production') {\n console.log('intialising store with data from cache', initialData)\n }\n const store = getStore(initialData)\n ReactDOM.render(\n \n \n \n \n \n \n ,\n document.getElementById('root')\n )\n}\n\nrender()\n"],"sourceRoot":""}