mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(electron): add plebbit rpc
This commit is contained in:
@@ -24,7 +24,7 @@ jobs:
|
|||||||
node-version: 16
|
node-version: 16
|
||||||
- run: yarn install --frozen-lockfile
|
- run: yarn install --frozen-lockfile
|
||||||
# make sure the ipfs executable is executable
|
# make sure the ipfs executable is executable
|
||||||
- run: node electron/downloadIpfs && sudo chmod +x bin/linux/ipfs
|
- run: node electron/download-ipfs && sudo chmod +x bin/linux/ipfs
|
||||||
- run: CI='' yarn build
|
- run: CI='' yarn build
|
||||||
- run: yarn electron:build:linux
|
- run: yarn electron:build:linux
|
||||||
- run: ls dist
|
- run: ls dist
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
node-version: 16
|
node-version: 16
|
||||||
- run: yarn install --frozen-lockfile
|
- run: yarn install --frozen-lockfile
|
||||||
# make sure the ipfs executable is executable
|
# make sure the ipfs executable is executable
|
||||||
- run: node electron/downloadIpfs && sudo chmod +x bin/mac/ipfs
|
- run: node electron/download-ipfs && sudo chmod +x bin/mac/ipfs
|
||||||
- run: CI='' yarn build
|
- run: CI='' yarn build
|
||||||
- run: yarn electron:build:mac
|
- run: yarn electron:build:mac
|
||||||
- run: ls dist
|
- run: ls dist
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ const downloadIpfsClients = async () => {
|
|||||||
await download(ipfsClientLinuxPUrl, ipfsClientLinuxPath);
|
await download(ipfsClientLinuxPUrl, ipfsClientLinuxPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.downloadIpfsClients = downloadIpfsClients
|
||||||
|
|
||||||
exports.default = async (context) => {
|
exports.default = async (context) => {
|
||||||
await downloadIpfsClients();
|
await downloadIpfsClients();
|
||||||
};
|
};
|
||||||
@@ -8,7 +8,7 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# download ipfs clients
|
# download ipfs clients
|
||||||
node electron/downloadIpfs || { echo "Error: failed script 'node electron/downloadIpfs'" ; exit 1; }
|
node electron/download-ipfs || { echo "Error: failed script 'node electron/download-ipfs'" ; exit 1; }
|
||||||
|
|
||||||
dockerfile='
|
dockerfile='
|
||||||
FROM electronuserland/builder:16
|
FROM electronuserland/builder:16
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
const downloadIpfsClients = require('./before-pack').downloadIpfsClients;
|
||||||
|
downloadIpfsClients();
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
const downloadIpfsClients = require('./beforePack').default;
|
|
||||||
downloadIpfsClients();
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
// require this file to log to file in case there's a crash
|
// require this file to log to file in case there's a crash
|
||||||
|
|
||||||
const envPaths = require('env-paths').default('plebchan', { suffix: false });
|
const envPaths = require('env-paths').default('plebbit', { suffix: false });
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|||||||
+22
-8
@@ -7,18 +7,32 @@ const {
|
|||||||
Tray,
|
Tray,
|
||||||
screen: electronScreen,
|
screen: electronScreen,
|
||||||
shell,
|
shell,
|
||||||
dialog,
|
dialog
|
||||||
} = require('electron')
|
} = require('electron')
|
||||||
const isDev = require('electron-is-dev')
|
const isDev = require('electron-is-dev')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const startIpfs = require('./startIpfs')
|
const startIpfs = require('./start-ipfs')
|
||||||
|
const startPlebbitRpcServer = require('./start-plebbit-rpc')
|
||||||
const { URL } = require('node:url')
|
const { URL } = require('node:url')
|
||||||
|
const tcpPortUsed = require('tcp-port-used')
|
||||||
|
|
||||||
|
// retry starting ipfs every 10 second,
|
||||||
|
// in case it was started by another client that shut down and shut down ipfs with it
|
||||||
let startIpfsError
|
let startIpfsError
|
||||||
startIpfs().catch((e) => {
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const started = await tcpPortUsed.check(5001, '127.0.0.1')
|
||||||
|
if (started) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await startIpfs()
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e)
|
||||||
startIpfsError = e
|
startIpfsError = e
|
||||||
console.error(e)
|
dialog.showErrorBox('IPFS error', startIpfsError.message)
|
||||||
})
|
}
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
// use common user agent instead of electron so img, video, audio, iframe elements don't get blocked
|
// use common user agent instead of electron so img, video, audio, iframe elements don't get blocked
|
||||||
// https://www.whatismybrowser.com/guides/the-latest-version/chrome
|
// https://www.whatismybrowser.com/guides/the-latest-version/chrome
|
||||||
@@ -69,7 +83,7 @@ const createMainWindow = () => {
|
|||||||
width: 1000,
|
width: 1000,
|
||||||
height: 600,
|
height: 600,
|
||||||
show: false,
|
show: false,
|
||||||
backgroundColor: 'white',
|
backgroundColor: '#181818',
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
webSecurity: true, // must be true or iframe embeds like youtube can do remote code execution
|
webSecurity: true, // must be true or iframe embeds like youtube can do remote code execution
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
@@ -84,7 +98,7 @@ const createMainWindow = () => {
|
|||||||
|
|
||||||
// set custom user agent and other headers for window.fetch requests to prevent origin errors
|
// set custom user agent and other headers for window.fetch requests to prevent origin errors
|
||||||
mainWindow.webContents.session.webRequest.onBeforeSendHeaders({urls: ['*://*/*']}, (details, callback) => {
|
mainWindow.webContents.session.webRequest.onBeforeSendHeaders({urls: ['*://*/*']}, (details, callback) => {
|
||||||
const isIframe = details.frame.parent !== null
|
const isIframe = !!details.frame?.parent
|
||||||
// if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
|
// if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
|
||||||
if (details.resourceType !== 'xhr' || isIframe) {
|
if (details.resourceType !== 'xhr' || isIframe) {
|
||||||
return callback({requestHeaders: details.requestHeaders})
|
return callback({requestHeaders: details.requestHeaders})
|
||||||
@@ -104,7 +118,7 @@ const createMainWindow = () => {
|
|||||||
|
|
||||||
// fix cors errors for window.fetch. must not be enabled for iframe or can cause remote code execution
|
// fix cors errors for window.fetch. must not be enabled for iframe or can cause remote code execution
|
||||||
mainWindow.webContents.session.webRequest.onHeadersReceived({urls: ['*://*/*']}, (details, callback) => {
|
mainWindow.webContents.session.webRequest.onHeadersReceived({urls: ['*://*/*']}, (details, callback) => {
|
||||||
const isIframe = details.frame.parent !== null
|
const isIframe = !!details.frame?.parent
|
||||||
// if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
|
// if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
|
||||||
if (details.resourceType !== 'xhr' || isIframe) {
|
if (details.resourceType !== 'xhr' || isIframe) {
|
||||||
return callback({responseHeaders: details.responseHeaders})
|
return callback({responseHeaders: details.responseHeaders})
|
||||||
|
|||||||
+8
-41
@@ -1,49 +1,16 @@
|
|||||||
// force IpfsHttpClient to use node-fetch to fix max 6 connections
|
|
||||||
// temporary, eventually we will fork ipfs-http-client and replace
|
|
||||||
// 'native-fetch'
|
|
||||||
const mock = require('mock-require');
|
|
||||||
const fetch = require('node-fetch');
|
|
||||||
mock('ipfs-utils/src/env.js', {
|
|
||||||
isTest: false,
|
|
||||||
isElectron: false,
|
|
||||||
isElectronMain: false,
|
|
||||||
isElectronRenderer: false,
|
|
||||||
isNode: true,
|
|
||||||
isBrowser: false,
|
|
||||||
isWebWorker: false,
|
|
||||||
isEnvWithDom: false,
|
|
||||||
isReactNative: false,
|
|
||||||
});
|
|
||||||
mock('native-fetch', {
|
|
||||||
default: fetch.default,
|
|
||||||
Headers: fetch.Headers,
|
|
||||||
Request: fetch.Request,
|
|
||||||
Response: fetch.Response,
|
|
||||||
});
|
|
||||||
|
|
||||||
const envPaths = require('env-paths').default('plebchan', { suffix: false });
|
|
||||||
const { contextBridge } = require('electron')
|
const { contextBridge } = require('electron')
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
// dev uses http://localhost, prod uses file://...index.html
|
// dev uses http://localhost, prod uses file://...index.html
|
||||||
const isDev = window.location.protocol === 'http:';
|
const isDev = window.location.protocol === 'http:'
|
||||||
|
|
||||||
const defaultPlebbitOptions = {
|
const defaultPlebbitOptions = {
|
||||||
// find the user's OS data path
|
plebbitRpcClientsOptions: ['ws://localhost:9138']
|
||||||
dataPath: !isDev ? envPaths.data : path.join(__dirname, '..', '.plebbit'),
|
}
|
||||||
ipfsHttpClientsOptions: ['http://localhost:5001/api/v0'] || undefined,
|
|
||||||
// TODO: having to define pubsubHttpClientsOptions and ipfsHttpClientsOptions is a bug with plebbit-js
|
|
||||||
pubsubHttpClientsOptions: ['http://localhost:5001/api/v0'] || undefined,
|
|
||||||
// electron starts the local ipfs gateway on port 11028 because 8080 is too common
|
|
||||||
ipfsGatewayUrls: ['http://localhost:11028'] || undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
// expose a flag to indicate that we are running in electron
|
|
||||||
contextBridge.exposeInMainWorld('electron', { isElectron: true });
|
|
||||||
|
|
||||||
// expose plebbit-js native functions into electron's renderer
|
|
||||||
contextBridge.exposeInMainWorld('plebbitJsNativeFunctions', require('@plebbit/plebbit-js').nativeFunctions.node)
|
|
||||||
contextBridge.exposeInMainWorld('defaultPlebbitOptions', defaultPlebbitOptions)
|
contextBridge.exposeInMainWorld('defaultPlebbitOptions', defaultPlebbitOptions)
|
||||||
|
|
||||||
// uncomment to log
|
// expose a flag to indicate that we are running in electron
|
||||||
// localStorage.debug = 'plebbit-js:*,plebbit-react-hooks:*,plebchan:*';
|
contextBridge.exposeInMainWorld('electron', { isElectron: true })
|
||||||
|
|
||||||
|
// uncomment for logs
|
||||||
|
// localStorage.debug = 'plebbit-js:*,plebbit-react-hooks:*,plebchan:*'
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ const isDev = require('electron-is-dev');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const envPaths = require('env-paths').default('plebchan', { suffix: false });
|
const envPaths = require('env-paths').default('plebbit', { suffix: false });
|
||||||
const ps = require('node:process');
|
const ps = require('node:process');
|
||||||
const proxyServer = require('./proxyServer');
|
const proxyServer = require('./proxy-server');
|
||||||
|
|
||||||
// use this custom function instead of spawnSync for better logging
|
// use this custom function instead of spawnSync for better logging
|
||||||
// also spawnSync might have been causing crash on start on windows
|
// also spawnSync might have been causing crash on start on windows
|
||||||
@@ -59,7 +59,7 @@ const startIpfs = async () => {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// dont use 8080 port because it's too common
|
// dont use 8080 port because it's too common
|
||||||
await spawnAsync(ipfsPath, ['config', 'Addresses.Gateway', '/ip4/127.0.0.1/tcp/11028'], {
|
await spawnAsync(ipfsPath, ['config', '--json', 'Addresses.Gateway', "null"], {
|
||||||
env,
|
env,
|
||||||
hideWindows: true,
|
hideWindows: true,
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
const tcpPortUsed = require('tcp-port-used')
|
||||||
|
const {PlebbitWsServer} = require('@plebbit/plebbit-js/rpc')
|
||||||
|
const path = require('path')
|
||||||
|
const envPaths = require('env-paths').default('plebbit', { suffix: false })
|
||||||
|
const {randomBytes} = require('crypto')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
|
||||||
|
let isDev = true
|
||||||
|
try {
|
||||||
|
isDev = require('electron-is-dev')
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// PLEB, always run plebbit rpc on this port so all clients can use it
|
||||||
|
const port = 9138
|
||||||
|
const defaultPlebbitOptions = {
|
||||||
|
// find the user's OS data path
|
||||||
|
dataPath: !isDev ? envPaths.data : path.join(__dirname, '..', '.plebbit'),
|
||||||
|
ipfsHttpClientsOptions: ['http://localhost:5001/api/v0'],
|
||||||
|
// TODO: having to define pubsubHttpClientsOptions and ipfsHttpClientsOptions is a bug with plebbit-js
|
||||||
|
pubsubHttpClientsOptions: ['http://localhost:5001/api/v0'],
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate plebbit rpc auth key if doesn't exist
|
||||||
|
const plebbitRpcAuthKeyPath = path.join(defaultPlebbitOptions.dataPath, 'auth-key')
|
||||||
|
let plebbitRpcAuthKey
|
||||||
|
try {
|
||||||
|
plebbitRpcAuthKey = fs.readFileSync(plebbitRpcAuthKeyPath, 'utf8')
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
plebbitRpcAuthKey = randomBytes(32).toString('base64').replace(/[/+=]/g, '').substring(0, 40)
|
||||||
|
fs.ensureFileSync(plebbitRpcAuthKeyPath)
|
||||||
|
fs.writeFileSync(plebbitRpcAuthKeyPath, plebbitRpcAuthKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
let pendingStart = false
|
||||||
|
const start = async () => {
|
||||||
|
if (pendingStart) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pendingStart = true
|
||||||
|
try {
|
||||||
|
const started = await tcpPortUsed.check(port, '127.0.0.1')
|
||||||
|
if (started) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const plebbitWebSocketServer = await PlebbitWsServer({port, plebbitOptions: defaultPlebbitOptions, authKey: plebbitRpcAuthKey})
|
||||||
|
|
||||||
|
console.log(`plebbit rpc: listening on ws://localhost:${port} (local connections only)`)
|
||||||
|
console.log(`plebbit rpc: listening on ws://localhost:${port}/${plebbitRpcAuthKey} (secret auth key for remote connections)`)
|
||||||
|
plebbitWebSocketServer.ws.on('connection', (socket, request) => {
|
||||||
|
console.log('plebbit rpc: new connection')
|
||||||
|
// debug raw JSON RPC messages in console
|
||||||
|
if (isDev) {
|
||||||
|
socket.on('message', (message) => console.log(`plebbit rpc: ${message.toString()}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log('failed starting plebbit rpc server', e)
|
||||||
|
}
|
||||||
|
pendingStart = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// retry starting the plebbit rpc server every 1 second,
|
||||||
|
// in case it was started by another client that shut down and shut down the server with it
|
||||||
|
start()
|
||||||
|
setInterval(() => {
|
||||||
|
start()
|
||||||
|
}, 1000)
|
||||||
+2
-1
@@ -45,6 +45,7 @@
|
|||||||
"remark-breaks": "3.0.2",
|
"remark-breaks": "3.0.2",
|
||||||
"styled-components": "5.3.9",
|
"styled-components": "5.3.9",
|
||||||
"web-vitals": "3.3.0",
|
"web-vitals": "3.3.0",
|
||||||
|
"tcp-port-used": "1.0.2",
|
||||||
"zustand": "4.3.6"
|
"zustand": "4.3.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
"electron:build:windows": "electron-builder build --publish never -w",
|
"electron:build:windows": "electron-builder build --publish never -w",
|
||||||
"electron:build:mac": "electron-builder build --publish never -m",
|
"electron:build:mac": "electron-builder build --publish never -m",
|
||||||
"electron:before": "yarn electron:before:download-ipfs && yarn electron:before:delete-data",
|
"electron:before": "yarn electron:before:download-ipfs && yarn electron:before:delete-data",
|
||||||
"electron:before:download-ipfs": "node electron/downloadIpfs",
|
"electron:before:download-ipfs": "node electron/download-ipfs",
|
||||||
"electron:before:delete-data": "rimraf .plebbit",
|
"electron:before:delete-data": "rimraf .plebbit",
|
||||||
"android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'",
|
"android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'",
|
||||||
"prettier": "prettier src/**/*.{js,jsx} --write",
|
"prettier": "prettier src/**/*.{js,jsx} --write",
|
||||||
|
|||||||
@@ -6345,6 +6345,13 @@ debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, de
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
|
debug@4.3.1:
|
||||||
|
version "4.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||||
|
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
debug@4.3.3:
|
debug@4.3.3:
|
||||||
version "4.3.3"
|
version "4.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||||
@@ -9069,7 +9076,7 @@ invariant@^2.2.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.0.0"
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
ip-regex@^4.0.0:
|
ip-regex@^4.0.0, ip-regex@^4.1.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5"
|
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5"
|
||||||
integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==
|
integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==
|
||||||
@@ -9578,6 +9585,11 @@ is-unicode-supported@^0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
|
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
|
||||||
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
||||||
|
|
||||||
|
is-url@^1.2.4:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
|
||||||
|
integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
|
||||||
|
|
||||||
is-utf8@^0.2.1:
|
is-utf8@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||||
@@ -9620,6 +9632,15 @@ is-yarn-global@^0.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
|
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
|
||||||
integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
|
integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
|
||||||
|
|
||||||
|
is2@^2.0.6:
|
||||||
|
version "2.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/is2/-/is2-2.0.9.tgz#ff63b441f90de343fa8fac2125ee170da8e8240d"
|
||||||
|
integrity sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==
|
||||||
|
dependencies:
|
||||||
|
deep-is "^0.1.3"
|
||||||
|
ip-regex "^4.1.0"
|
||||||
|
is-url "^1.2.4"
|
||||||
|
|
||||||
isarray@^2.0.5:
|
isarray@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||||
@@ -15101,6 +15122,14 @@ tarn@^3.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693"
|
resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693"
|
||||||
integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==
|
integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==
|
||||||
|
|
||||||
|
tcp-port-used@1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/tcp-port-used/-/tcp-port-used-1.0.2.tgz#9652b7436eb1f4cfae111c79b558a25769f6faea"
|
||||||
|
integrity sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==
|
||||||
|
dependencies:
|
||||||
|
debug "4.3.1"
|
||||||
|
is2 "^2.0.6"
|
||||||
|
|
||||||
temp-dir@^2.0.0:
|
temp-dir@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
|
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
|
||||||
|
|||||||
Reference in New Issue
Block a user