"axios": "^0.21.1",
"core-js": "^3.6.5",
"electron-is-dev": "^2.0.0",
+ "electron-store": "^8.0.0",
"electron-updater": "^4.3.8",
"mitt": "^2.1.0",
"naudiodon": "^2.3.2",
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
import { IpcRendererEvent } from "electron";
import { useIpc } from "@/modules/ipc";
-import { useAuth } from "@/modules/auth"
+import { useProfile } from "@/modules/auth"
import Splash from "@/components/splash.vue";
import Messenger from "@/components/messenger.vue";
const { post, invoke } = useIpc();
- const { user } = useAuth();
+ const { profile, setProfile, clearProfile } = useProfile();
// Whether browser has received user info yet.
const ready = ref(false);
// Information about current user.
- const profile = ref(false);
+ // const profile = ref(false);
// New messages that browser missed while closed.
const newMessages = ref([]);
// Set profile and newMessages.
// profile.value = payload.message.profile;
- profile.value = true;
+ // profile.value = true;
newMessages.value = payload.message.newMessages;
ready.value = true;
post("app-mounted", "");
- if(user.value.key) {
- try {
- const res = await invoke('user-auth', JSON.stringify(user.value));
- profile.value = res;
- } catch(e) {
- profile.value = false;
- console.log('[AUTH]', e);
- } finally {
- ready.value = true;
- if (profile.value) {
- // start session
- post("init-session", user.value.email);
- }
- }
- } else {
- profile.value = false;
+ try {
+
+ const res = await invoke('user-profile', "");
+ console.log('auth response', res);
+ setProfile(res);
+ } catch(e) {
+ console.log('[AUTH]', e);
+ } finally {
ready.value = true;
+ if (profile.value.crimataId) {
+ console.log('TESTING')
+ // start session
+ post("init-session", JSON.stringify(profile.value.crimataId));
+ }
}
});
return {
ready,
- profile,
post,
- newMessages
+ newMessages,
+ profile
}
}
})
--- /dev/null
+
+import { useHttp } from "@/modules/http";
+import axios from "axios";
+
+const { post } = useHttp();
+
+export const submit =
+ async (email: string, password: string) => (
+
+ await post('/account/login', {
+ email,
+ password
+ })
+
+)
+
+
+export const logout =
+ async () : Promise<null | Error> => (await post('/account/logout'));
+
+
+
+export const fetchProfile = async(email: string, token: string) => (
+
+ await axios({
+ url: "http://127.0.0.1:3010/api/account/profile",
+ headers: {
+ Cookie: `jwt=${token}`
+ },
+ method: 'GET',
+ data: {
+ email,
+ }
+ })
+)
+
import { initSession, onAppMounted } from './session';
import { initAudioIO, stopStream } from './audio';
import { backgroundMitt } from '@/modules/emitter';
-import { Profile } from "@/types";
+import { initAccountListener } from "@/background/ipc/account";
let win: boolean;
})
-interface AccountAuth {
- email: string;
- password?: string;
- key: string | null;
-}
-
-const onUserAuth = async (_event: any, payload: AccountAuth)
-:Promise<Error | Profile> => (
- new Promise((resolve, reject) => {
-
- // check for login
- if (payload.password) {
- // await user login
- } else {
- // authenticate token
- }
- })
-)
-
-const onSessionInit = (_event: IpcMainInvokeEvent, payload: string) => {
+const onSessionInit = (_event: IpcMainInvokeEvent, cid: string) => {
// Instantiate socket session with crimata-platorm.
- initSession();
+ initSession(cid);
// Begin audio stream.
initAudioIO();
}
-
// Run when electron app is initialized.
-async function main() {
+async function main(): Promise<void> {
console.log("MAIN:Initializing Electron App.");
ipcMain.removeAllListeners("app-mounted");
ipcMain.on("app-mounted", onAppMounted);
- ipcMain.removeHandler("user-auth");
- ipcMain.handle("user-auth", onUserAuth);
-
ipcMain.removeAllListeners("init-session");
ipcMain.on("init-session", onSessionInit);
+ // handler user auth, login, and logout asynchrounously
+ initAccountListener();
+
// Must wait til window is created.
await createWindow();
--- /dev/null
+
+"use strict";
+
+import { Profile } from "@/types";
+import { submit, fetchProfile, logout } from "@/api/account";
+import { ipcMain } from "electron";
+import { store } from "@/background/store";
+
+
+const parseAuthRes = (authRes: any) => {
+ const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
+ const profile = authRes.data as Profile;
+ return {
+ token,
+ profile
+ }
+};
+
+
+const onProfile = async (_event: any, _payload: string) => (
+
+ new Promise(async (resolve, reject) => {
+
+ // get jwt token and crimataId from store
+ const token = store.get('key');
+ const crimataId = store.get('crimataId');
+
+ // authenticate and fetch profile
+ try {
+
+ const res = await fetchProfile(
+ crimataId,
+ token
+ );
+
+ const parsed = parseAuthRes(res);
+ resolve(parsed.profile);
+
+ } catch(e) {
+ reject(new Error('Failed to fetch profile.'));
+ }
+ })
+)
+
+
+const onLogin = async (_event: any, payload: string) => (
+
+ new Promise(async (resolve, reject) => {
+
+ const account = JSON.parse(payload);
+
+ if ( account.password && account.email ) {
+ try {
+ const res = await submit(account.email, account.password);
+ const parsed = parseAuthRes(res);
+
+ // save jwt token and profile
+ store.set('key', parsed.token);
+ store.set('crimataId', parsed.profile.crimataId);
+
+ // return profile to renderer
+ resolve(parsed.profile);
+
+ } catch(e) {
+ console.log('[API]', e.response);
+ reject(new Error('Failed to authenticate'));
+ }
+ }
+ })
+)
+
+const onLogout = async (_event: any, _payload: string) => (
+
+ new Promise(async (resolve, reject) => {
+
+ try {
+ // post logout to backend
+ await logout();
+
+ // remove key and crimataId
+ store.delete('key');
+ store.delete('crimataId');
+
+ // TODO: kill crimata platform session
+
+ resolve(null);
+ } catch(e) {
+ reject(new Error('Failed to logout. Please try again.'));
+ }
+ })
+)
+
+
+export const initAccountListener = () => {
+
+ ipcMain.removeHandler("user-profile");
+ ipcMain.handle("user-profile", onProfile);
+
+ ipcMain.removeHandler("user-login");
+ ipcMain.handle("user-login", onLogin);
+
+ ipcMain.removeHandler("user-logout");
+ ipcMain.handle("user-logout", onLogout);
+
+}
import { play } from "./audio";
import { renderMessage } from "@/modules/message";
-import { AuthProtocol, SessionState, Profile } from "@/types";
+import { SessionState } from "@/types";
let win = true;
// Info saved to json on quit (key, newMessages).
let state: SessionState;
-// Profile of current user.
-let profile: Profile | boolean;
-
-// Called when server sends auth message.
-export const updateState = (res: AuthProtocol) => {
- console.log("SESS:Auth message received: \n" +
- ` key: ${res.key}\n` +
- ` alias: ${res.profile}`)
-
- if (state) {
-
- // Update key.
- state.key = res.key;
-
- // Update the user profile.
- profile = res.profile;
-
- // Send upated profile to frontend.
- console.log("SESS:Sending updated user profile to browser.")
- ipcEmit("update-state", {
- profile: profile,
- newMessages: state.newMessages
- })
-
- // Save the updated state to json.
- console.log("SESS:Saving session state.")
- saveToJson("session.json", state)
-
- }
-
-}
-
// Send state on new window.
export const onAppMounted = (_event: IpcMainInvokeEvent, _payload: any) => {
- profile = true;
- console.log('testing!!!', profile)
- if (typeof profile !== 'undefined') {
- console.log("SESS:Sending user profile to browser.");
- ipcEmit("update-state", {
- profile: profile,
- newMessages: [],
- });
- }
+ ipcEmit("update-state", {
+ newMessages: state.newMessages,
+ });
+
}
// Calls appropriate endpoint for a server message.
const { createSocket, send } = useWebSockets(onMessage);
// Handle messages from window/client.
-const onClientMessage = async (_event: IpcMainEvent, payload: any):Promise<void> => {
+const onClientMessage = async (_event: IpcMainEvent, payload: any): Promise<void> => {
console.log("New client message")
if (payload.hasOwnProperty("key")) {
// Call this to initialize session with Crimata servers.
-export const initSession = () => {
+export const initSession = (cid: string) => {
console.log("SESS:Creating new session.")
// Load Json or createState.
// Open socket connection.
createSocket();
- // Attack browser window init listener.
- // ipcMain.removeAllListeners("app-mounted");
- // ipcMain.on("app-mounted", onAppMounted);
-
// Attach listeners for frontend.
ipcMain.removeAllListeners("client-message");
ipcMain.on("client-message", onClientMessage);
--- /dev/null
+
+const Store = require('electron-store');
+
+const schema = {
+ key: {
+ type: 'string',
+ },
+ crimataId: {
+ type: 'string'
+ }
+};
+
+export const store = new Store({
+ schema,
+ encryptionKey: "super user test"
+});
}
- const send = async (data: Record<string, any>):Promise<boolean> => (
+ const send = async (data: Record<string, any>): Promise<boolean> => (
new Promise((resolve, reject) => {
if (socket.readyState !== 1) {
reject(false);
<!-- submit button; position: fixed -->
<button class="submitButton button" type="submit">Submit</button>
-
+
</form>
<!-- back to login button: position: fixed -->
import { defineComponent, ref } from "vue";
import { useIpc } from "@/modules/ipc";
import { authRequest } from '@/modules/message';
+import { useProfile } from '@/modules/auth';
export default defineComponent({
name: "Login",
setup() {
- const { post } = useIpc();
+ const { post, invoke } = useIpc();
+ const { setProfile } = useProfile();
const usr = ref("");
const pwd = ref("");
// Submit login credentials to the backend.
- const submitForm = () => {
- console.log(`Submitting login form: ${usr.value}, ${pwd.value}`)
- post("client-message", authRequest(false, usr.value, pwd.value))
+ const submitForm = async () => {
+ try {
+ const res = await invoke('user-login', JSON.stringify({
+ email: usr.value,
+ password: pwd.value
+ }));
+ setProfile(res);
+ } catch(e) {
+
+ }
+ // console.log(`Submitting login form: ${usr.value}, ${pwd.value}`)
+ // post("client-message", authRequest(false, usr.value, pwd.value))
}
return {
import { defineComponent, ref } from "vue";
import { useIpc } from "@/modules/ipc";
import { logoutRequest } from '@/modules/message';
+ import { useProfile } from "@/modules/auth"
export default defineComponent({
name: "Settings",
setup() {
const toggleSettings = ref(false);
- const { post } = useIpc();
+ const { post, invoke } = useIpc();
+
+ const { clearProfile } = useProfile();
// Listen for escape key to close settings.
const onEscape = (e: any) => {
}
// We ask server to log us out.
- const onLogout = () => {
- console.log("Submitting logout request.")
- post("client-message", logoutRequest())
+ const onLogout = async () => {
+
+ console.log("Submitting logout request.");
+
+ try {
+ clearProfile();
+ await invoke("user-logout", "");
+ } catch(e) {
+ console.log('error')
+ }
+
}
return {
-import { ref, Ref} from "vue";
+import { ref } from "vue";
+import { Profile } from "@/types";
-interface UserState {
- email: string | null;
- password?: string;
- key: string | null;
-}
-
-const user:Ref<UserState> = ref({
- email: null,
- key: "testing"
-});
-const CRIMATA_KEY = "CRIMATA_KEY";
+const profile = ref();
-const raw = window.localStorage.getItem(CRIMATA_KEY);
+export const useProfile = () => {
-if (raw) {
- user.value = JSON.parse(raw);
-}
-
-export const useAuth = () => {
+ const setProfile = (payload: Profile) => {
+ profile.value = payload;
+ }
- const setUser = (token: UserState) => {
- window.localStorage.setItem(CRIMATA_KEY, JSON.stringify(token));
- user.value = token;
+ const clearProfile = () => {
+ profile.value = null;
}
return {
- setUser,
- user
+ setProfile,
+ clearProfile,
+ profile
}
}
--- /dev/null
+
+import axios, { AxiosRequestConfig } from 'axios';
+
+const baseURL = 'http://127.0.0.1:3010/api';
+
+
+interface Request {
+ endpoint: string;
+ query?: Record<string, any>;
+ config?: Record<string, any>;
+}
+
+
+const makeQuery = (reqQuery: Record<string, any>) => {
+
+ let result = '';
+
+ result = '?' + Object.entries(reqQuery)
+ .map(([ key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
+ .join('&')
+
+ return result;
+};
+
+
+export const useHttp = () => {
+
+ const api = axios.create({
+ baseURL,
+ withCredentials: true,
+ });
+
+
+ const post = async (endpoint: string, payload?: Record<string, any>): Promise<any> => (
+ await api.post(endpoint, payload)
+ )
+
+
+ const get = async (req: Request) => {
+
+ if (req.query) {
+ req.endpoint += makeQuery(req.query);
+ }
+
+ const res = await api.get(req.endpoint, req.config);
+ return res;
+ };
+
+ return {
+ get, post
+ }
+}
}
export interface ClientRequest {
- intent: string;
+ intent: string;
params: object;
epic: string | boolean;
confidence: number;
}
export interface AuthProtocol {
- key: boolean | string;
- profile: boolean | Profile;
+ token: null | string;
+ profile: null | Profile;
+ password?: string;
+ email?: string;
}
export interface LogoutRequest {
};
context: string;
modifier: string;
-}
\ No newline at end of file
+}
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
+ajv-formats@^2.0.2:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.0.tgz#96eaf83e38d32108b66d82a9cb0cfa24886cdfeb"
+ integrity sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==
+ dependencies:
+ ajv "^8.0.0"
+
ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+ajv@^8.0.0, ajv@^8.1.0:
+ version "8.5.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.5.0.tgz#695528274bcb5afc865446aa275484049a18ae4b"
+ integrity sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.2.2"
+
alphanum-sort@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+atomically@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe"
+ integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
+
autoprefixer@^9.8.6:
version "9.8.6"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f"
is-whitespace "^0.3.0"
kind-of "^3.0.2"
+conf@^10.0.0:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/conf/-/conf-10.0.1.tgz#038093e5cbddc0e59bc14f63382c4ce732a4781d"
+ integrity sha512-QClEoNcruwBL84QgMEPHibL3ERxWIrRKhbjJKG1VsFBadm5QpS0jsu4QjY/maxUvhyAKXeyrs+ws+lC6PajnEg==
+ dependencies:
+ ajv "^8.1.0"
+ ajv-formats "^2.0.2"
+ atomically "^1.7.0"
+ debounce-fn "^4.0.0"
+ dot-prop "^6.0.1"
+ env-paths "^2.2.1"
+ json-schema-typed "^7.0.3"
+ onetime "^5.1.2"
+ pkg-up "^3.1.0"
+ semver "^7.3.5"
+
config-chain@^1.1.11, config-chain@^1.1.12:
version "1.1.12"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
bindings "^1.5.0"
node-addon-api "^1.7.1"
+debounce-fn@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7"
+ integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==
+ dependencies:
+ mimic-fn "^3.0.0"
+
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
is-obj "^2.0.0"
+dot-prop@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083"
+ integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==
+ dependencies:
+ is-obj "^2.0.0"
+
dotenv-expand@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
lazy-val "^1.0.4"
mime "^2.4.6"
+electron-store@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-8.0.0.tgz#81a4e687958e2dae1c5c84cc099a8148be776337"
+ integrity sha512-ZgRPUZkfrrjWSqxZeaxu7lEvmYf6tgl49dLMqxXGnEmliSiwv3u4rJPG+mH3fBQP9PBqgSh4TCuxHZImMMUgWg==
+ dependencies:
+ conf "^10.0.0"
+ type-fest "^1.0.2"
+
electron-to-chromium@^1.3.585:
version "1.3.589"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.589.tgz#bd26183ed8697dde6ac19acbc16a3bf33b1f8220"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
+env-paths@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
+ integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
+
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+json-schema-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+ integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
+json-schema-typed@^7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9"
+ integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==
+
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+mimic-fn@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74"
+ integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==
+
mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
dependencies:
find-up "^4.0.0"
+pkg-up@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
+ integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
+ dependencies:
+ find-up "^3.0.0"
+
please-upgrade-node@^3.1.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
-semver@^7.3.4:
+semver@^7.3.4, semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
+type-fest@^1.0.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.1.3.tgz#ea1a602e98e5a968a56a289886a52f04c686fc81"
+ integrity sha512-CsiQeFMR1jZEq8R+H59qe+bBevnjoV5N2WZTTdlyqxeoODQOOepN2+msQOywcieDq5sBjabKzTn3U+sfHZlMdw==
+
type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"