From a4dca1d4f004abcc4be23328ca6a1c6b3ee8da97 Mon Sep 17 00:00:00 2001 From: leon-morival Date: Tue, 7 Oct 2025 16:18:05 +0200 Subject: [PATCH] notifications --- App.js | 1 + functions/index.js | 8 + functions/package-lock.json | 69 +++++++++ functions/package.json | 1 + functions/src/notifications.js | 213 ++++++++++++++++++++++++++ package.json | 1 + src/providers/NotificationProvider.js | 103 +++++++++++++ src/providers/SharedProviders.js | 2 + src/screens/Playbacks/Playbacks.js | 12 ++ yarn.lock | 93 ++++++++++- 10 files changed, 499 insertions(+), 4 deletions(-) create mode 100644 functions/src/notifications.js create mode 100644 src/providers/NotificationProvider.js diff --git a/App.js b/App.js index d661ab6..cf00f86 100644 --- a/App.js +++ b/App.js @@ -50,6 +50,7 @@ SplashScreen.preventAutoHideAsync(); const App = () => { const [currentUID, setCurrentUID] = useGlobal("currentUID"); + const [pendingData, setPendingData] = useGlobal("pendingData"); const [, setCurrentUserRoles] = useGlobal("currentUserRoles"); const [appIsReady, setAppIsReady] = useState(false); diff --git a/functions/index.js b/functions/index.js index 134d496..b75108c 100644 --- a/functions/index.js +++ b/functions/index.js @@ -8,6 +8,13 @@ exports.refList = { projects: admin.firestore().collection("projects"), playlists: admin.firestore().collection("playlists"), tasks: admin.firestore().collection("tasks"), + notifications: admin.firestore().collection("notifications"), +}; + +exports.ALERT_TYPE = { + NEW_LIKE: "NEW_LIKE", + NEW_COMMENT: "NEW_COMMENT", + NEW_FOLLOWER: "NEW_FOLLOWER", }; // Exporter toutes les fonctions @@ -19,3 +26,4 @@ exports.projects = require("./src/project"); exports.thumbnail = require("./src/thumbnail"); exports.upload = require("./src/upload"); exports.algolia = require("./src/algolia"); +exports.notifications = require("./src/notifications"); diff --git a/functions/package-lock.json b/functions/package-lock.json index 41f986c..dcc95c5 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -10,6 +10,7 @@ "@genkit-ai/googleai": "^1.18.0", "@google-cloud/storage": "^7.17.1", "axios": "^1.11.0", + "expo-server-sdk": "^4.0.0", "ffmpeg-static": "^5.2.0", "firebase-admin": "^12.7.0", "firebase-functions": "^6.4.0", @@ -3416,6 +3417,12 @@ "node": ">=6" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" + }, "node_modules/error": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", @@ -3703,6 +3710,40 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/expo-server-sdk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-4.0.0.tgz", + "integrity": "sha512-zi83XtG2pqyP3gyn1JIRYkydo2i6HU3CYaWo/VvhZG/F29U+QIDv6LBEUsWf4ddZlVE7c9WN1N8Be49rHgO8OQ==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.0", + "promise-limit": "^2.7.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/expo-server-sdk/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -5707,6 +5748,34 @@ "node": ">=0.4.0" } }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/proto3-json-serializer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", diff --git a/functions/package.json b/functions/package.json index b490321..03fe9fb 100644 --- a/functions/package.json +++ b/functions/package.json @@ -18,6 +18,7 @@ "@genkit-ai/googleai": "^1.18.0", "@google-cloud/storage": "^7.17.1", "axios": "^1.11.0", + "expo-server-sdk": "^4.0.0", "ffmpeg-static": "^5.2.0", "firebase-admin": "^12.7.0", "firebase-functions": "^6.4.0", diff --git a/functions/src/notifications.js b/functions/src/notifications.js new file mode 100644 index 0000000..5e80aff --- /dev/null +++ b/functions/src/notifications.js @@ -0,0 +1,213 @@ +const { onDocumentCreated } = require("firebase-functions/v2/firestore"); +const admin = require("firebase-admin"); +const { refList, ALERT_TYPE } = require("../index"); +const { Expo } = require("expo-server-sdk"); + +// Initialisation de Expo SDK +let expo = new Expo(); +const db = admin.firestore(); + +exports.sendNotificationWhenDocIsCreated = onDocumentCreated( + { region: "europe-west1", document: "notifications/{notificationId}" }, + async (event) => { + try { + const { + receiver = null, + title = "MusicLand", + receiverCollection = "users", + message = "", + data: notifData = {}, + } = event.data.data(); + + const { pushToken = null } = + (await db.collection(receiverCollection).doc(receiver).get())?.data() || + {}; + + if (!receiver || !message) { + throw new Error("Receiver and message are required"); + } + + if (pushToken) { + await sendExpoNotification(pushToken, { + title: title || "MusicLand", + message: message, + data: notifData || {}, + }); + } else { + console.warn("User push token not found"); + } + } catch (e) { + console.log(e); + return e; + } + } +); + +// Fonction pour envoyer la notification via Expo SDK +async function sendExpoNotification( + pushToken, + { title = "", message = "", data = {} } +) { + try { + if (!Expo.isExpoPushToken(pushToken)) { + throw new Error(`Invalid push token: ${pushToken}`); + } else { + console.log("Valid push token:", pushToken); + } + + const receipts = await expo.sendPushNotificationsAsync([ + { + to: pushToken, + sound: "default", + title: title, + body: message, + data: data || {}, + priority: "high", + badge: 1, + channelId: "default", + }, + ]); + console.log("Sent push notifications:", receipts); + + return { sent: true }; + } catch (e) { + console.log("Error sending notification:", e); + throw e; + } +} + +// Fonction pour ajouter une notification à la base de données +const sendNotification = async ({ + sender = "SYSTEM", + receiver = null, + receiverCollection = "users", + title = "", + message = null, + data = {}, +}) => { + try { + if (!receiver || !message) { + throw new Error("Receiver and message are required"); + } + const { id } = await refList.notifications.add({ + sender, + receiver, + receiverCollection, + title, + message, + time: new Date(), + data, + }); + console.log(`Notification ${id} created`); + + return id; + } catch (e) { + console.log(e); + } +}; + +exports.sendNotification = sendNotification; + +exports.createProjectCommentNotification = onDocumentCreated( + { + region: "europe-west1", + document: "projects/{projectId}/comments/{commentId}", + }, + async (event) => { + try { + console.log( + "[createProjectCommentNotification] Trigger received", + JSON.stringify(event.params || {}, null, 2) + ); + const { data: snap } = event; + const { projectId, commentId } = event.params || {}; + const comment = snap?.data(); + + if (!projectId || !comment) { + console.log( + "[createProjectCommentNotification] Missing project/comment data", + { hasProjectId: !!projectId, hasComment: !!comment } + ); + return null; + } + + console.log( + "[createProjectCommentNotification] Comment payload", + JSON.stringify(comment, null, 2) + ); + + const projectSnap = await refList.projects.doc(projectId).get(); + if (!projectSnap.exists) { + console.log( + "[createProjectCommentNotification] Project not found", + projectId + ); + return null; + } + + const project = projectSnap.data() || {}; + const receiver = project.userId || null; + + if (!receiver || receiver === comment.userId) { + console.log( + "[createProjectCommentNotification] Invalid receiver", + JSON.stringify({ receiver, commentUserId: comment.userId }) + ); + return null; + } + + const commenterName = + typeof comment?.userName === "string" && comment.userName.trim() + ? comment.userName.trim() + : "Un utilisateur"; + const projectTitle = + typeof project.title === "string" && project.title.trim() + ? project.title.trim() + : "ton projet"; + const message = `${commenterName} a commenté ton projet "${projectTitle}"`; + + console.log( + "[createProjectCommentNotification] Creating notification", + JSON.stringify( + { + receiver, + message, + commentId, + projectId, + }, + null, + 2 + ) + ); + + await sendNotification({ + sender: comment.userId || "SYSTEM", + receiver, + receiverCollection: "users", + title: "Nouveau commentaire", + message, + data: { + type: ALERT_TYPE?.NEW_COMMENT, + projectId, + commentId, + commenterId: comment.userId || null, + commenterName: commenterName, + commenterProfilePicture: comment?.profilePicture || "", + text: + typeof comment?.text === "string" && comment.text.trim() + ? comment.text.trim() + : "", + }, + }); + + console.log( + "[createProjectCommentNotification] Notification creation complete" + ); + + return null; + } catch (error) { + console.log("createProjectCommentNotification error:", error); + return error; + } + } +); diff --git a/package.json b/package.json index 13d5735..481f120 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "expo-linear-gradient": "~14.0.2", "expo-linking": "~7.0.5", "expo-location": "~18.0.10", + "expo-notifications": "~0.29.14", "expo-speech": "~13.0.1", "expo-splash-screen": "~0.29.24", "expo-status-bar": "~2.0.1", diff --git a/src/providers/NotificationProvider.js b/src/providers/NotificationProvider.js new file mode 100644 index 0000000..a91ecf5 --- /dev/null +++ b/src/providers/NotificationProvider.js @@ -0,0 +1,103 @@ +import Constants from "expo-constants"; +import * as Device from "expo-device"; +import * as Notifications from "expo-notifications"; +import React, { createContext, useEffect, useRef, useState } from "react"; +import { useGlobal } from "reactn"; +import { usersRef } from "../config/firebase"; + +export const NotificationContext = createContext(null); + +export default function NotificationProvider({ children }) { + const [user] = useGlobal("currentUserData"); + const [uid] = useGlobal("currentUID"); + + const [pendingData, setPendingData] = useGlobal("pendingData"); + const [notifInit, setNotifInit] = useState(false); + const [allowNotifications, setAllowNotifications] = useState(false); + + const notificationListener = useRef(); + const responseListener = useRef(); + + useEffect(() => { + if (!!uid && !!user && !notifInit) { + registerForPushNotificationsAsync(); + } else if (notifInit && allowNotifications) { + notificationListener.current = + Notifications.addNotificationReceivedListener(async (notification) => { + console.log("Notification received", notification); + await Notifications.scheduleNotificationAsync(notification?.request); + }); + + responseListener.current = + Notifications.addNotificationResponseReceivedListener( + async (response) => { + console.log("Notification response", response); + if ( + response?.actionIdentifier === + "expo.modules.notifications.actions.DEFAULT" + ) { + const { data } = response.notification.request.content; + if (data) { + console.log("Notification data", data); + await setPendingData(data); + } + } + } + ); + } + + return () => { + if (notifInit && allowNotifications) { + Notifications.removeNotificationSubscription( + notificationListener.current + ); + Notifications.removeNotificationSubscription(responseListener.current); + } + }; + }, [user, uid, notifInit]); + + async function registerForPushNotificationsAsync() { + try { + if (Device.isDevice) { + const { status } = await Notifications.requestPermissionsAsync(); + if (status !== "granted") { + console.log("Notification permissions denied"); + return; + } + setAllowNotifications(true); + setNotifInit(true); + + // Get the push token + const pushToken = ( + await Notifications.getExpoPushTokenAsync({ + projectId: Constants.expoConfig?.extra?.eas?.projectId || "", + }) + ).data; + if (user?.pushToken !== pushToken && !!user) { + await usersRef.doc(uid).update({ pushToken }); + // if (!user?.alerts) { + // await usersRef.doc(uid).update({ + // alerts: Object.values(ALERT_TYPE), + // }); + // } + } + } else { + console.log("Must use physical device for Push Notifications"); + } + } catch (e) { + console.log(e); + } + } + + return ( + + {children} + + ); +} diff --git a/src/providers/SharedProviders.js b/src/providers/SharedProviders.js index dad50dd..e6c57e3 100644 --- a/src/providers/SharedProviders.js +++ b/src/providers/SharedProviders.js @@ -4,6 +4,7 @@ import React from "react"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { SheetProvider } from "react-native-actions-sheet"; +import NotificationProvider from "./NotificationProvider"; import PlayerProvider from "./PlayerProvider"; import SplashAnimationProvider from "./SplashAnimationProvider"; import StripeEmbeddedProvider from "./StripeEmbeddedProvider"; @@ -23,6 +24,7 @@ const SharedProviders = ({ children }) => { [BottomSheetModalProvider, {}], [SheetProvider, {}], [PlayerProvider, {}], + [NotificationProvider, {}], ]; // Dynamically nest the providers using reduce diff --git a/src/screens/Playbacks/Playbacks.js b/src/screens/Playbacks/Playbacks.js index 18f7500..33d7b57 100644 --- a/src/screens/Playbacks/Playbacks.js +++ b/src/screens/Playbacks/Playbacks.js @@ -100,6 +100,7 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => { // Follow state derived from owner.followedBy const [isFollowing, setIsFollowing] = useState(false); + const [isFollowActionPending, setIsFollowActionPending] = useState(false); useEffect(() => { const list = Array.isArray(owner?.followedBy) ? owner.followedBy : []; setIsFollowing(currentUID ? list.includes(currentUID) : false); @@ -278,9 +279,14 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => { {owner?.id && currentUID && owner.id !== currentUID && ( { + if (isFollowActionPending) return; + try { + setIsFollowActionPending(true); const next = !isFollowing; + console.log("follwin"); setIsFollowing(next); // Optimistic update of local owner.followedBy setOwner((prev) => { @@ -294,9 +300,15 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => { }); if (next) await followUser?.(owner.id); else await unfollowUser?.(owner.id); + + // Timeout de 2 secondes avant de pouvoir recliquer + global.setTimeout(() => { + setIsFollowActionPending(false); + }, 1000); } catch (e) { // rollback on failure setIsFollowing((v) => !v); + setIsFollowActionPending(false); } }} > diff --git a/yarn.lock b/yarn.lock index a277b00..5241160 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1898,6 +1898,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== +"@ide/backoff@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@ide/backoff/-/backoff-1.0.0.tgz#466842c25bd4a4833e0642fab41ccff064010176" + integrity sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -3155,6 +3160,17 @@ asap@~2.0.3, asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +assert@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd" + integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== + dependencies: + call-bind "^1.0.2" + is-nan "^1.3.2" + object-is "^1.1.5" + object.assign "^4.1.4" + util "^0.12.5" + ast-types@0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.15.2.tgz#39ae4809393c4b16df751ee563411423e85fb49d" @@ -3322,6 +3338,11 @@ babel-preset-jest@^29.6.3: babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" +badgin@^1.1.5: + version "1.2.3" + resolved "https://registry.yarnpkg.com/badgin/-/badgin-1.2.3.tgz#994b5f519827d7d5422224825b2c8faea2bc43ad" + integrity sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -3557,7 +3578,7 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply- es-errors "^1.3.0" function-bind "^1.1.2" -call-bind@^1.0.7, call-bind@^1.0.8: +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== @@ -5233,6 +5254,19 @@ expo-modules-core@2.2.3: dependencies: invariant "^2.2.4" +expo-notifications@~0.29.14: + version "0.29.14" + resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.29.14.tgz#77beb6bc74b1b1abfa3adcab77fb6c9ea5d7d1b0" + integrity sha512-AVduNx9mKOgcAqBfrXS1OHC9VAQZrDQLbVbcorMjPDGXW7m0Q5Q+BG6FYM/saVviF2eO8fhQRsTT40yYv5/bhQ== + dependencies: + "@expo/image-utils" "^0.6.5" + "@ide/backoff" "^1.0.0" + abort-controller "^3.0.0" + assert "^2.0.0" + badgin "^1.1.5" + expo-application "~6.0.2" + expo-constants "~17.0.8" + expo-speech@~13.0.1: version "13.0.1" resolved "https://registry.yarnpkg.com/expo-speech/-/expo-speech-13.0.1.tgz#f49fc2c8a73f9bada030e52fc86a235cd3963f4a" @@ -5791,6 +5825,11 @@ functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -6332,7 +6371,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6392,6 +6431,14 @@ ipaddr.js@^1.9.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-arguments@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b" + integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" @@ -6520,6 +6567,17 @@ is-generator-function@^1.0.10: has-tostringtag "^1.0.2" safe-regex-test "^1.1.0" +is-generator-function@^1.0.7: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== + dependencies: + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-gif@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-gif/-/is-gif-3.0.0.tgz#c4be60b26a301d695bb833b20d9b5d66c6cf83b1" @@ -6552,6 +6610,14 @@ is-map@^2.0.3: resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== +is-nan@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -6678,7 +6744,7 @@ is-symbol@^1.0.4, is-symbol@^1.1.1: has-symbols "^1.1.0" safe-regex-test "^1.1.0" -is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15, is-typed-array@^1.1.3: version "1.1.15" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== @@ -7945,6 +8011,14 @@ object-inspect@^1.13.3, object-inspect@^1.13.4: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-is@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -10497,6 +10571,17 @@ util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +util@^0.12.5: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -10650,7 +10735,7 @@ which-collection@^1.0.2: is-weakmap "^2.0.2" is-weakset "^2.0.3" -which-typed-array@^1.1.16, which-typed-array@^1.1.19: +which-typed-array@^1.1.16, which-typed-array@^1.1.19, which-typed-array@^1.1.2: version "1.1.19" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==