fix: companion extension oauth (#25939)

* fix: companion extension oauth

* fix: ios build

* address cubics comments
This commit is contained in:
Dhairyashil Shinde
2025-12-16 22:22:44 +00:00
committed by GitHub
parent ee298af4eb
commit a2a77376b3
18 changed files with 525 additions and 295 deletions
+3
View File
@@ -26,3 +26,6 @@ EXPO_PUBLIC_SCHEDULES_CACHE_STALE_TIME_MINUTES=-1
# User Profile cache stale time (default: -1 = never stale)
EXPO_PUBLIC_USER_PROFILE_CACHE_STALE_TIME_MINUTES=-1
# required for dev/local
EXPO_PUBLIC_COMPANION_DEV_URL=http://localhost:8081
+2 -7
View File
@@ -22,6 +22,7 @@ import { LoadingSpinner } from "../../../components/LoadingSpinner";
import { EmptyScreen } from "../../../components/EmptyScreen";
import { slugify } from "../../../utils/slugify";
import { showErrorAlert } from "../../../utils/alerts";
import { shadows } from "../../../utils/shadows";
import { EventTypeListItem } from "../../../components/event-type-list-item/EventTypeListItem";
import { offlineAwareRefresh } from "../../../utils/network";
import { openInAppBrowser } from "../../../utils/browser";
@@ -570,13 +571,7 @@ export default function EventTypes() {
className="max-h-[90%] w-[90%] max-w-[500px] rounded-2xl bg-white"
activeOpacity={1}
onPress={(e) => e.stopPropagation()}
style={{
shadowColor: "#000",
shadowOffset: { width: 0, height: 20 },
shadowOpacity: 0.25,
shadowRadius: 25,
elevation: 24,
}}
style={shadows.xl()}
>
{/* Header */}
<View className="px-8 pb-4 pt-6">
+2 -7
View File
@@ -23,6 +23,7 @@ import { LoadingSpinner } from "../../components/LoadingSpinner";
import { EmptyScreen } from "../../components/EmptyScreen";
import { showErrorAlert } from "../../utils/alerts";
import { offlineAwareRefresh } from "../../utils/network";
import { shadows } from "../../utils/shadows";
import {
useSchedules,
useCreateSchedule,
@@ -458,13 +459,7 @@ export default function Availability() {
className="w-[90%] max-w-[500px] rounded-2xl bg-white"
activeOpacity={1}
onPress={(e) => e.stopPropagation()}
style={{
shadowColor: "#000",
shadowOffset: { width: 0, height: 20 },
shadowOpacity: 0.25,
shadowRadius: 25,
elevation: 24,
}}
style={shadows.xl()}
>
{/* Header */}
<View className="px-8 pb-4 pt-6">
+6 -4
View File
@@ -27,14 +27,16 @@ function RootLayoutContent() {
? {
width: 400,
flex: 1,
display: "flex",
flexDirection: "column",
pointerEvents: "auto",
display: "flex" as const,
flexDirection: "column" as const,
}
: { flex: 1 };
return (
<View style={containerStyle} className={containerClass}>
<View
style={[containerStyle, Platform.OS === "web" && { pointerEvents: "auto" as const }]}
className={containerClass}
>
<StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
{content}
<NetworkStatusBanner />
+2 -7
View File
@@ -15,6 +15,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { CalComAPIService, Booking } from "../services/calcom";
import { showErrorAlert } from "../utils/alerts";
import { openInAppBrowser } from "../utils/browser";
import { shadows } from "../utils/shadows";
import { SvgImage } from "../components/SvgImage";
import { FullScreenModal } from "../components/FullScreenModal";
import { BookingActionsModal } from "../components/BookingActionsModal";
@@ -571,13 +572,7 @@ export default function BookingDetail() {
className="w-[90%] max-w-[500px] rounded-2xl bg-white"
activeOpacity={1}
onPress={(e) => e.stopPropagation()}
style={{
shadowColor: "#000",
shadowOffset: { width: 0, height: 20 },
shadowOpacity: 0.25,
shadowRadius: 25,
elevation: 24,
}}
style={shadows.xl()}
>
{/* Header */}
<View className="px-8 pb-4 pt-6">
+97 -20
View File
@@ -43,6 +43,7 @@
"@types/react": "19.2.3",
"@types/react-dom": "19.2.3",
"babel-preset-expo": "^54.0.7",
"glob": "10.3.10",
"husky": "^9.0.11",
"lint-staged": "^15.2.0",
"prettier": "^3.2.5",
@@ -376,6 +377,8 @@
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="],
@@ -414,6 +417,8 @@
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@pnpm/config.env-replace": ["@pnpm/config.env-replace@1.1.0", "", {}, "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w=="],
"@pnpm/network.ca-file": ["@pnpm/network.ca-file@1.0.2", "", { "dependencies": { "graceful-fs": "4.2.10" } }, "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA=="],
@@ -880,6 +885,8 @@
"dotenv-expand": ["dotenv-expand@12.0.3", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="],
@@ -1024,6 +1031,8 @@
"fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"form-data-encoder": ["form-data-encoder@4.1.0", "", {}, "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw=="],
"formdata-node": ["formdata-node@6.0.3", "", {}, "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg=="],
@@ -1060,7 +1069,7 @@
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"glob": ["glob@10.3.10", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.5", "minimatch": "^9.0.1", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", "path-scurry": "^1.10.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
@@ -1184,6 +1193,8 @@
"istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
"jackspeak": ["jackspeak@2.3.6", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ=="],
"jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
"jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
@@ -1288,7 +1299,7 @@
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
@@ -1352,7 +1363,7 @@
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -1454,7 +1465,7 @@
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -1726,10 +1737,14 @@
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-bom": ["strip-bom@5.0.0", "", {}, "sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A=="],
"strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
@@ -1896,6 +1911,8 @@
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
@@ -1936,6 +1953,8 @@
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -1964,8 +1983,6 @@
"@expo/cli/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
"@expo/cli/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@expo/cli/ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
"@expo/cli/picomatch": ["picomatch@3.0.1", "", {}, "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag=="],
@@ -2002,8 +2019,6 @@
"@expo/fingerprint/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
"@expo/fingerprint/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@expo/fingerprint/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"@expo/image-utils/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -2026,8 +2041,6 @@
"@expo/metro-config/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
"@expo/metro-config/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@expo/metro-config/postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="],
"@expo/package-manager/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -2040,6 +2053,10 @@
"@expo/xcpretty/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
"@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
@@ -2060,6 +2077,8 @@
"@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g=="],
"@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"@react-native/codegen/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
"@react-native/community-cli-plugin/metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="],
@@ -2156,16 +2175,12 @@
"fx-runner/which": ["which@1.2.4", "", { "dependencies": { "is-absolute": "^0.1.7", "isexe": "^1.1.1" }, "bin": { "which": "./bin/which" } }, "sha512-zDRAqDSBudazdfM9zpiI30Fu9ve47htYXcGi3ln0wfKu2a7SmrT6F3VDoYONu//48V8Vz4TdCRNPjtvyRO3yBA=="],
"glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"global-directory/ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="],
"global-dirs/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
"istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -2190,8 +2205,6 @@
"loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"metro/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
"metro/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -2238,8 +2251,6 @@
"parse-json/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
"path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
"rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
@@ -2248,6 +2259,8 @@
"react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
"react-native/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"react-native/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"react-native-css-interop/lightningcss": ["lightningcss@1.27.0", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.27.0", "lightningcss-darwin-x64": "1.27.0", "lightningcss-freebsd-x64": "1.27.0", "lightningcss-linux-arm-gnueabihf": "1.27.0", "lightningcss-linux-arm64-gnu": "1.27.0", "lightningcss-linux-arm64-musl": "1.27.0", "lightningcss-linux-x64-gnu": "1.27.0", "lightningcss-linux-x64-musl": "1.27.0", "lightningcss-win32-arm64-msvc": "1.27.0", "lightningcss-win32-x64-msvc": "1.27.0" } }, "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ=="],
@@ -2266,6 +2279,8 @@
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
@@ -2282,6 +2297,10 @@
"stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
@@ -2290,6 +2309,8 @@
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"test-exclude/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
@@ -2312,10 +2333,18 @@
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"wxt/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"wxt/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
"xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
@@ -2328,6 +2357,8 @@
"@babel/core/@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"@babel/highlight/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
"@babel/highlight/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
@@ -2344,6 +2375,8 @@
"@expo/cli/glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"@expo/cli/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
"@expo/cli/ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
"@expo/cli/ora/cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="],
@@ -2360,6 +2393,14 @@
"@expo/config-plugins/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@expo/config-plugins/glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"@expo/config-plugins/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
"@expo/config/glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"@expo/config/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
"@expo/devtools/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@expo/env/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -2368,6 +2409,8 @@
"@expo/fingerprint/glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"@expo/fingerprint/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
"@expo/image-utils/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@expo/local-build-cache-provider/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -2378,6 +2421,8 @@
"@expo/metro-config/glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"@expo/metro-config/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
"@expo/metro/metro-source-map/metro-symbolicate": ["metro-symbolicate@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.2", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-KoU9BLwxxED6n33KYuQQuc5bXkIxF3fSwlc3ouxrrdLWwhu64muYZNQrukkWzhVKRNFIXW7X2iM8JXpi2heIPw=="],
"@expo/metro/metro-source-map/ob1": ["ob1@0.83.2", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-XlK3w4M+dwd1g1gvHzVbxiXEbUllRONEgcF2uEO0zm4nxa0eKlh41c6N65q1xbiDOeKKda1tvNOAD33fNjyvCg=="],
@@ -2396,6 +2441,10 @@
"@expo/xcpretty/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
"@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
@@ -2404,6 +2453,10 @@
"@jest/types/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@react-native/babel-plugin-codegen/@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"@react-native/codegen/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
"@react-native/community-cli-plugin/metro/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
@@ -2472,8 +2525,6 @@
"fx-runner/which/isexe": ["isexe@1.1.2", "", {}, "sha512-d2eJzK691yZwPHcv1LbeAOa91yMJ9QmfTgSO1oXB65ezVhXQsxBac2vEB4bMVms9cGzaA99n6V2viHMq82VLDw=="],
"glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"jest-message-util/@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jest-message-util/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -2532,6 +2583,10 @@
"react-native/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
"react-native/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"serve-static/send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@@ -2542,6 +2597,8 @@
"test-exclude/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"wxt/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -2552,6 +2609,8 @@
"@babel/highlight/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
"@expo/cli/glob/path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
"@expo/cli/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
"@expo/cli/ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
@@ -2564,6 +2623,14 @@
"@expo/cli/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"@expo/config-plugins/glob/path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
"@expo/config/glob/path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
"@expo/fingerprint/glob/path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
"@expo/metro-config/glob/path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
"@expo/package-manager/ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
"@expo/package-manager/ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
@@ -2576,6 +2643,10 @@
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
"@react-native/babel-plugin-codegen/@react-native/codegen/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"@react-native/codegen/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"@react-native/community-cli-plugin/metro/@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"@react-native/community-cli-plugin/metro/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -2594,6 +2665,10 @@
"react-native/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
"react-native/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"@babel/highlight/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
@@ -2616,6 +2691,8 @@
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"@react-native/babel-plugin-codegen/@react-native/codegen/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"expo/babel-preset-expo/@react-native/babel-preset/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
+12 -18
View File
@@ -1,20 +1,10 @@
/**
* LoadingSpinner Component
*
* A stylish loading spinner with iOS glass effect support when available.
* Falls back to a nice styled container on other platforms.
*/
import React from "react";
import { View, ActivityIndicator, Platform, StyleSheet } from "react-native";
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
interface LoadingSpinnerProps {
/** Size of the spinner - defaults to large */
size?: "small" | "large";
/** Color of the spinner - defaults to system color */
color?: string;
/** Whether to show the container background */
showBackground?: boolean;
}
@@ -25,7 +15,6 @@ export function LoadingSpinner({
}: LoadingSpinnerProps) {
const supportsGlass = isLiquidGlassAvailable();
// Use glass effect on supported iOS devices
if (supportsGlass && Platform.OS === "ios") {
return (
<GlassView style={styles.glassContainer} glassEffectStyle="regular">
@@ -34,7 +23,6 @@ export function LoadingSpinner({
);
}
// Styled container for other platforms
if (showBackground) {
return (
<View style={styles.styledContainer}>
@@ -43,7 +31,6 @@ export function LoadingSpinner({
);
}
// Simple spinner without background
return <ActivityIndicator size={size} color={color || "#000000"} />;
}
@@ -60,11 +47,18 @@ const styles = StyleSheet.create({
padding: 24,
borderRadius: 20,
backgroundColor: "rgba(255, 255, 255, 0.9)",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
...Platform.select({
web: {
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
},
default: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
}),
},
});
+16 -9
View File
@@ -1,5 +1,5 @@
import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { View, Text, TouchableOpacity, Platform, StyleSheet } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useAuth } from "../contexts/AuthContext";
import { CalComLogo } from "./CalComLogo";
@@ -42,14 +42,21 @@ export function LoginScreen() {
onPress={handleOAuthLogin}
disabled={loading}
className="flex-row items-center justify-center rounded-2xl py-[18px]"
style={{
backgroundColor: loading ? "#9CA3AF" : "#111827",
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: loading ? 0 : 0.2,
shadowRadius: 12,
elevation: loading ? 0 : 6,
}}
style={[
{ backgroundColor: loading ? "#9CA3AF" : "#111827" },
Platform.select({
web: {
boxShadow: loading ? "none" : "0 4px 12px rgba(0, 0, 0, 0.2)",
},
default: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: loading ? 0 : 0.2,
shadowRadius: 12,
elevation: loading ? 0 : 6,
},
}),
]}
activeOpacity={0.9}
>
<Text className="text-[17px] font-semibold text-white">Continue with Cal.com</Text>
+8 -14
View File
@@ -11,7 +11,6 @@ export function Tooltip({ text, children }: TooltipProps) {
const [position, setPosition] = useState({ top: 0, left: 0 });
const containerRef = useRef<any>(null);
// Only show tooltips on web platform
if (Platform.OS !== "web") {
return children;
}
@@ -42,30 +41,25 @@ export function Tooltip({ text, children }: TooltipProps) {
{children}
{showTooltip && (
<View
// @ts-ignore - web-only styles
style={{
position: "fixed",
top: position.top,
left: position.left,
transform: [{ translateX: -50 }, { translateY: -100 }],
transform: "translate(-50%, -100%)",
backgroundColor: "#1a1a1a",
paddingHorizontal: 10,
paddingVertical: 6,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 6,
paddingBottom: 6,
borderRadius: 6,
zIndex: 10002,
pointerEvents: "none",
// @ts-ignore - web-only props
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
}}
>
<Text
style={{
color: "white",
fontSize: 13,
fontWeight: "600",
whiteSpace: "nowrap",
lineHeight: 18,
}}
>
{/* @ts-ignore - web-only styles */}
<Text style={{ color: "white", fontSize: 13, fontWeight: "600", whiteSpace: "nowrap" }}>
{text}
</Text>
</View>
@@ -1,26 +1,27 @@
/// <reference types="chrome" />
// ============================================
// DEV ONLY: API Key for localhost testing
// TODO: REMOVE THIS ENTIRE SECTION BEFORE PRODUCTION
// ============================================
import type { OAuthTokens } from "../../../services/oauthService";
const DEV_API_KEY = import.meta.env.EXPO_PUBLIC_CAL_API_KEY as string | undefined;
const IS_DEV_MODE = DEV_API_KEY && DEV_API_KEY.length > 0;
if (IS_DEV_MODE) {
console.log("Cal.com Extension: DEV MODE - API Key authentication enabled for testing");
}
// ============================================
// END DEV ONLY SECTION
// ============================================
const IS_DEV_MODE = Boolean(DEV_API_KEY && DEV_API_KEY.length > 0);
const devLog = {
log: (...args: unknown[]) => IS_DEV_MODE && console.log("[Cal.com]", ...args),
warn: (...args: unknown[]) => IS_DEV_MODE && console.warn("[Cal.com]", ...args),
error: (...args: unknown[]) => console.error("[Cal.com]", ...args),
};
// @ts-ignore - WXT provides this globally
export default defineBackground(() => {
if (IS_DEV_MODE) {
devLog.log("DEV MODE - API Key authentication enabled for testing");
}
chrome.action.onClicked.addListener((tab) => {
if (tab.id) {
chrome.tabs.sendMessage(tab.id, { action: "icon-clicked" }, (response) => {
if (chrome.runtime.lastError) {
// Expected on pages where content script isn't loaded
}
chrome.tabs.sendMessage(tab.id, { action: "icon-clicked" }, () => {
// Ignore errors - expected on pages where content script isn't loaded
void chrome.runtime.lastError;
});
}
});
@@ -28,49 +29,33 @@ export default defineBackground(() => {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "fetch-event-types") {
fetchEventTypes()
.then((eventTypes) => {
sendResponse({ data: eventTypes });
})
.catch((error) => {
sendResponse({ error: error.message });
});
.then((eventTypes) => sendResponse({ data: eventTypes }))
.catch((error) => sendResponse({ error: error.message }));
return true;
}
if (message.action === "start-extension-oauth") {
const authUrl = message.authUrl;
const urlObj = new URL(authUrl);
const state = urlObj.searchParams.get("state");
const authUrl = message.authUrl as string;
const state = new URL(authUrl).searchParams.get("state");
if (state) {
chrome.storage.local.set({ oauth_state: state }, () => {
if (chrome.runtime.lastError) {
console.warn("Failed to store OAuth state:", chrome.runtime.lastError.message);
devLog.warn("Failed to store OAuth state:", chrome.runtime.lastError.message);
}
});
}
handleExtensionOAuth(authUrl)
.then((responseUrl) => {
sendResponse({ success: true, responseUrl });
})
.catch((error) => {
sendResponse({ success: false, error: error.message });
});
.then((responseUrl) => sendResponse({ success: true, responseUrl }))
.catch((error) => sendResponse({ success: false, error: error.message }));
return true;
}
if (message.action === "exchange-oauth-tokens") {
handleTokenExchange(message.tokenRequest, message.tokenEndpoint, message.state)
.then((tokens) => {
sendResponse({ success: true, tokens });
})
.catch((error) => {
sendResponse({ success: false, error: error.message });
});
.then((tokens) => sendResponse({ success: true, tokens }))
.catch((error) => sendResponse({ success: false, error: error.message }));
return true;
}
@@ -85,126 +70,122 @@ async function handleExtensionOAuth(authUrl: string): Promise<string> {
return;
}
chrome.identity.launchWebAuthFlow(
{
url: authUrl,
interactive: true,
},
(responseUrl) => {
if (chrome.runtime.lastError) {
console.error("OAuth flow failed:", chrome.runtime.lastError.message);
reject(new Error(`OAuth flow failed: ${chrome.runtime.lastError.message}`));
} else if (responseUrl) {
resolve(responseUrl);
} else {
console.error("OAuth flow returned no response URL");
reject(new Error("OAuth flow cancelled or failed"));
}
if (IS_DEV_MODE) {
const expectedRedirectUrl = chrome.identity.getRedirectURL();
const redirectUri = new URL(authUrl).searchParams.get("redirect_uri");
devLog.log("Starting OAuth flow");
devLog.log("Expected redirect URL:", expectedRedirectUrl);
devLog.log("Auth URL redirect_uri:", redirectUri);
if (redirectUri && !redirectUri.startsWith(expectedRedirectUrl.replace(/\/$/, ""))) {
devLog.warn(
"MISMATCH! redirect_uri does not match Chrome's expected URL.",
"\nExpected:",
expectedRedirectUrl,
"\nGot:",
redirectUri
);
}
);
}
chrome.identity.launchWebAuthFlow({ url: authUrl, interactive: true }, (responseUrl) => {
if (chrome.runtime.lastError) {
devLog.error("OAuth flow failed:", chrome.runtime.lastError.message);
reject(new Error(`OAuth flow failed: ${chrome.runtime.lastError.message}`));
} else if (responseUrl) {
devLog.log("OAuth successful");
resolve(responseUrl);
} else {
devLog.error("OAuth flow returned no response URL");
reject(new Error("OAuth flow cancelled or failed"));
}
});
});
}
async function handleTokenExchange(
tokenRequest: any,
tokenRequest: Record<string, string>,
tokenEndpoint: string,
state?: string
): Promise<any> {
// CSRF protection: validate state parameter (defense-in-depth)
// State is already validated in iframe context; this validates in background script
): Promise<OAuthTokens> {
if (state) {
try {
const result = await chrome.storage.local.get(["oauth_state"]);
const storedState = result.oauth_state;
if (storedState && storedState !== state) {
await chrome.storage.local.remove("oauth_state");
console.error("State parameter mismatch - possible CSRF attack");
throw new Error("Invalid state parameter - possible CSRF attack");
}
if (storedState && storedState === state) {
await chrome.storage.local.remove("oauth_state");
}
} catch (error) {
if (error instanceof Error && error.message.includes("Invalid state parameter")) {
throw error;
}
// For storage errors, log but continue (non-blocking)
console.warn("State validation warning (non-blocking):", error);
}
await validateOAuthState(state);
}
const body = new URLSearchParams();
Object.keys(tokenRequest).forEach((key) => {
body.append(key, tokenRequest[key]);
const body = new URLSearchParams(tokenRequest);
const response = await fetch(tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: body.toString(),
});
try {
const response = await fetch(tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: body.toString(),
});
if (!response.ok) {
const errorData = await response.text();
devLog.error("Token exchange error response:", errorData);
throw new Error(`Token exchange failed: ${response.status} ${errorData}`);
}
if (!response.ok) {
const errorData = await response.text();
console.error("Token exchange error response:", errorData);
throw new Error(`Token exchange failed: ${response.status} ${errorData}`);
const tokenData = await response.json();
return {
accessToken: tokenData.access_token,
refreshToken: tokenData.refresh_token,
tokenType: tokenData.token_type || "Bearer",
expiresAt: tokenData.expires_in ? Date.now() + tokenData.expires_in * 1000 : undefined,
scope: tokenData.scope,
};
}
async function validateOAuthState(state: string): Promise<void> {
try {
const result = await chrome.storage.local.get(["oauth_state"]);
const storedState = result.oauth_state as string | undefined;
if (storedState && storedState !== state) {
await chrome.storage.local.remove("oauth_state");
devLog.error("State parameter mismatch - possible CSRF attack");
throw new Error("Invalid state parameter - possible CSRF attack");
}
const tokenData = await response.json();
const tokens = {
accessToken: tokenData.access_token,
refreshToken: tokenData.refresh_token,
tokenType: tokenData.token_type || "Bearer",
expiresAt: tokenData.expires_in ? Date.now() + tokenData.expires_in * 1000 : undefined,
scope: tokenData.scope,
};
return tokens;
if (storedState === state) {
await chrome.storage.local.remove("oauth_state");
}
} catch (error) {
console.error("Token exchange failed:", error);
throw error instanceof Error ? error : new Error(String(error));
if (error instanceof Error && error.message.includes("Invalid state parameter")) {
throw error;
}
devLog.warn("State validation warning:", error);
}
}
async function fetchEventTypes() {
const API_BASE_URL = "https://api.cal.com/v2";
// Determine authentication method
let authHeader: string;
let authMethod: "oauth" | "apikey";
const API_BASE_URL = "https://api.cal.com/v2";
async function getAuthHeader(): Promise<string> {
const result = await chrome.storage.local.get(["cal_oauth_tokens"]);
const oauthTokens = result.cal_oauth_tokens
? JSON.parse(result.cal_oauth_tokens as string)
? (JSON.parse(result.cal_oauth_tokens as string) as OAuthTokens)
: null;
if (oauthTokens?.accessToken) {
// Use OAuth token if available
authHeader = `Bearer ${oauthTokens.accessToken}`;
authMethod = "oauth";
} else if (IS_DEV_MODE && DEV_API_KEY) {
// ============================================
// DEV ONLY: Fallback to API key for localhost testing
// TODO: REMOVE THIS BLOCK BEFORE PRODUCTION
// ============================================
console.log("Cal.com Extension: Using API Key for authentication (DEV MODE)");
authHeader = `Bearer ${DEV_API_KEY}`;
authMethod = "apikey";
// ============================================
// END DEV ONLY BLOCK
// ============================================
} else {
throw new Error("No OAuth access token found. Please sign in with OAuth.");
return `Bearer ${oauthTokens.accessToken}`;
}
// Get current user to retrieve username
if (IS_DEV_MODE && DEV_API_KEY) {
devLog.log("Using API Key for authentication (DEV MODE)");
return `Bearer ${DEV_API_KEY}`;
}
throw new Error("No OAuth access token found. Please sign in with OAuth.");
}
async function fetchEventTypes(): Promise<unknown[]> {
const authHeader = await getAuthHeader();
const userResponse = await fetch(`${API_BASE_URL}/me`, {
headers: {
Authorization: authHeader,
@@ -218,13 +199,12 @@ async function fetchEventTypes() {
}
const userData = await userResponse.json();
const username = userData?.data?.username;
const username = userData?.data?.username as string | undefined;
const params = new URLSearchParams();
if (username) {
params.append("username", username);
}
const queryString = params.toString();
const endpoint = `${API_BASE_URL}/event-types${queryString ? `?${queryString}` : ""}`;
@@ -242,12 +222,13 @@ async function fetchEventTypes() {
const data = await response.json();
// Handle different response formats
if (Array.isArray(data)) {
return data;
} else if (data?.data && Array.isArray(data.data)) {
}
if (data?.data && Array.isArray(data.data)) {
return data.data;
} else if (data?.eventTypes && Array.isArray(data.eventTypes)) {
}
if (data?.eventTypes && Array.isArray(data.eventTypes)) {
return data.eventTypes;
}
+3 -2
View File
@@ -46,9 +46,10 @@ export default defineContentScript({
iframeContainer.style.justifyContent = "flex-end";
iframeContainer.style.pointerEvents = "none";
// Create iframe
const iframe = document.createElement("iframe");
iframe.src = "https://companion.cal.com";
const COMPANION_URL =
(import.meta.env.EXPO_PUBLIC_COMPANION_DEV_URL as string) || "https://companion.cal.com";
iframe.src = COMPANION_URL;
iframe.style.width = "400px";
iframe.style.height = "100%";
iframe.style.border = "none";
+12
View File
@@ -0,0 +1,12 @@
/// <reference types="wxt/vite" />
interface ImportMetaEnv {
readonly EXPO_PUBLIC_CAL_API_KEY?: string;
readonly EXPO_PUBLIC_COMPANION_DEV_URL?: string;
readonly EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID?: string;
readonly EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+2 -10
View File
@@ -3,17 +3,9 @@
@tailwind utilities;
@layer base {
/* Force light color scheme for all web components */
:root {
color-scheme: light only;
}
html {
color-scheme: light;
}
* {
color-scheme: light;
--background: #ffffff;
--foreground: #000000;
}
input:focus {
+1
View File
@@ -55,6 +55,7 @@
"private": true,
"devDependencies": {
"@types/chrome": "^0.1.31",
"glob": "10.3.10",
"@types/react": "19.2.3",
"@types/react-dom": "19.2.3",
"babel-preset-expo": "^54.0.7",
+182 -54
View File
@@ -1,12 +1,24 @@
/// <reference types="chrome" />
import { fromByteArray } from "base64-js";
import * as AuthSession from "expo-auth-session";
import type * as AuthSession from "expo-auth-session";
import * as Crypto from "expo-crypto";
import * as WebBrowser from "expo-web-browser";
import { Platform } from "react-native";
WebBrowser.maybeCompleteAuthSession();
// Message types for extension communication
const EXTENSION_MESSAGE_TYPES = {
OAUTH_REQUEST: "cal-extension-oauth-request",
OAUTH_RESULT: "cal-extension-oauth-result",
TOKEN_EXCHANGE_REQUEST: "cal-extension-token-exchange-request",
TOKEN_EXCHANGE_RESULT: "cal-extension-token-exchange-result",
} as const;
// Timeouts
const OAUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes for user interaction
const TOKEN_EXCHANGE_TIMEOUT_MS = 30 * 1000; // 30 seconds for API call
export interface OAuthTokens {
accessToken: string;
refreshToken?: string;
@@ -47,16 +59,13 @@ export class CalComOAuthService {
codeVerifier,
{ encoding: Crypto.CryptoEncoding.BASE64 }
);
return base64Hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
private generateRandomBase64Url(): string {
const bytes = new Uint8Array(Crypto.getRandomBytes(32));
const base64 =
typeof btoa !== "undefined" ? btoa(String.fromCharCode(...bytes)) : fromByteArray(bytes);
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
@@ -73,26 +82,30 @@ export class CalComOAuthService {
return `${this.config.calcomBaseUrl}/auth/oauth2/authorize?${params.toString()}`;
}
private parseCallbackUrl(url: string): Record<string, string> {
const parsed = new URL(url);
const params: Record<string, string> = {};
parsed.searchParams.forEach((v, k) => {
params[k] = v;
});
return params;
}
private isMobileApp(): boolean {
return Platform.OS !== "web";
}
private isRunningInIframe(): boolean {
return Platform.OS === "web" && typeof window !== "undefined" && window.parent !== window;
}
async startAuthorizationFlow(): Promise<OAuthTokens> {
const { codeChallenge, state } = await this.generatePKCEParams();
if (Platform.OS === "web" && typeof window !== "undefined") {
const storedCode = window.localStorage.getItem(`oauth_callback_code_${state}`);
const storedState = window.localStorage.getItem(`oauth_callback_state_${state}`);
if (storedCode && storedState) {
if (storedState !== state) {
throw new Error("Invalid state parameter");
}
window.localStorage.removeItem(`oauth_callback_code_${state}`);
window.localStorage.removeItem(`oauth_callback_state_${state}`);
return this.exchangeCodeForTokens(storedCode, state);
const storedTokens = this.checkForStoredCallback(state);
if (storedTokens) {
return storedTokens;
}
}
@@ -102,8 +115,7 @@ export class CalComOAuthService {
throw new Error("OAuth flow failed");
}
const code = result.params.code;
const returnedState = result.params.state;
const { code, state: returnedState } = result.params;
if (!code) {
throw new Error("No authorization code received");
@@ -113,7 +125,25 @@ export class CalComOAuthService {
throw new Error("Invalid state parameter");
}
return this.exchangeCodeForTokens(code, state);
return this.exchangeCodeForTokens(code, returnedState);
}
private checkForStoredCallback(state: string): Promise<OAuthTokens> | null {
const storedCode = window.localStorage.getItem(`oauth_callback_code_${state}`);
const storedState = window.localStorage.getItem(`oauth_callback_state_${state}`);
if (!storedCode || !storedState) {
return null;
}
if (storedState !== state) {
throw new Error("Invalid state parameter");
}
window.localStorage.removeItem(`oauth_callback_code_${state}`);
window.localStorage.removeItem(`oauth_callback_state_${state}`);
return this.exchangeCodeForTokens(storedCode, storedState);
}
private async getAuthorizationResult(
@@ -123,22 +153,34 @@ export class CalComOAuthService {
const authUrl = this.buildAuthorizationUrl(codeChallenge, state);
if (this.isMobileApp()) {
const result = await WebBrowser.openAuthSessionAsync(authUrl, this.config.redirectUri, {
preferEphemeralSession: false,
});
return this.getMobileAuthResult(authUrl);
}
if (result.type === "success") {
return { type: "success", params: this.parseCallbackUrl(result.url) };
}
return this.getExtensionAuthResult(authUrl);
}
private async getMobileAuthResult(
authUrl: string
): Promise<{ type: "success"; params: Record<string, string> } | { type: "error" }> {
const result = await WebBrowser.openAuthSessionAsync(authUrl, this.config.redirectUri, {
preferEphemeralSession: false,
});
if (result.type === "success") {
return { type: "success", params: this.parseCallbackUrl(result.url) };
}
return { type: "error" };
}
private async getExtensionAuthResult(
authUrl: string
): Promise<{ type: "success"; params: Record<string, string> } | { type: "error" }> {
try {
const responseUrl = await this.launchExtensionAuthFlow(authUrl);
return { type: "success", params: this.parseCallbackUrl(responseUrl) };
} catch {
return { type: "error" };
} else {
try {
const responseUrl = await this.launchExtensionAuthFlow(authUrl);
return { type: "success", params: this.parseCallbackUrl(responseUrl) };
} catch {
return { type: "error" };
}
}
}
@@ -157,49 +199,83 @@ export class CalComOAuthService {
return;
}
if (this.isRunningInIframe()) {
this.requestOAuthViaPostMessage(authUrl, resolve, reject);
return;
}
reject(new Error("Extension context not available"));
});
}
private async getDiscoveryEndpoints(): Promise<AuthSession.DiscoveryDocument> {
return {
authorizationEndpoint: `${this.config.calcomBaseUrl}/auth/oauth2/authorize`,
tokenEndpoint: `${this.config.calcomBaseUrl}/api/auth/oauth/token`,
revocationEndpoint: `${this.config.calcomBaseUrl}/api/auth/oauth/revoke`,
private requestOAuthViaPostMessage(
authUrl: string,
resolve: (url: string) => void,
reject: (error: Error) => void
): void {
const timeoutId = setTimeout(() => {
window.removeEventListener("message", messageHandler);
reject(new Error("OAuth flow timed out"));
}, OAUTH_TIMEOUT_MS);
const messageHandler = (event: MessageEvent) => {
// Validate message source - must come from parent window (content script)
if (event.source !== window.parent) {
return;
}
if (event.data.type !== EXTENSION_MESSAGE_TYPES.OAUTH_RESULT) {
return;
}
clearTimeout(timeoutId);
window.removeEventListener("message", messageHandler);
if (event.data.success && event.data.responseUrl) {
resolve(event.data.responseUrl);
} else {
reject(new Error(event.data.error || "OAuth flow failed"));
}
};
window.addEventListener("message", messageHandler);
// Using "*" for targetOrigin because parent is the host page (e.g., gmail.com).
// Security: source validation above ensures responses only come from parent window.
window.parent.postMessage({ type: EXTENSION_MESSAGE_TYPES.OAUTH_REQUEST, authUrl }, "*");
}
private parseCallbackUrl(url: string): Record<string, string> {
const parsed = new URL(url);
const params: Record<string, string> = {};
parsed.searchParams.forEach((v, k) => {
params[k] = v;
});
return params;
}
private async exchangeCodeForTokens(code: string): Promise<OAuthTokens> {
private async exchangeCodeForTokens(code: string, state?: string): Promise<OAuthTokens> {
if (!this.codeVerifier) {
throw new Error("Missing code verifier");
}
const body = new URLSearchParams({
const tokenRequest = {
grant_type: "authorization_code",
client_id: this.config.clientId,
code,
redirect_uri: this.config.redirectUri,
code_verifier: this.codeVerifier,
});
};
const response = await fetch(`${this.config.calcomBaseUrl}/api/auth/oauth/token`, {
const tokenEndpoint = `${this.config.calcomBaseUrl}/api/auth/oauth/token`;
if (this.isRunningInIframe()) {
return this.exchangeCodeForTokensViaExtension(tokenRequest, tokenEndpoint, state);
}
return this.exchangeCodeForTokensDirect(tokenRequest, tokenEndpoint);
}
private async exchangeCodeForTokensDirect(
tokenRequest: Record<string, string>,
tokenEndpoint: string
): Promise<OAuthTokens> {
const response = await fetch(tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: body.toString(),
body: new URLSearchParams(tokenRequest).toString(),
});
if (!response.ok) {
@@ -217,12 +293,64 @@ export class CalComOAuthService {
};
}
private async exchangeCodeForTokensViaExtension(
tokenRequest: Record<string, string>,
tokenEndpoint: string,
state?: string
): Promise<OAuthTokens> {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
window.removeEventListener("message", messageHandler);
reject(new Error("Token exchange timed out"));
}, TOKEN_EXCHANGE_TIMEOUT_MS);
const messageHandler = (event: MessageEvent) => {
// Validate message source - must come from parent window (content script)
if (event.source !== window.parent) {
return;
}
if (event.data.type !== EXTENSION_MESSAGE_TYPES.TOKEN_EXCHANGE_RESULT) {
return;
}
clearTimeout(timeoutId);
window.removeEventListener("message", messageHandler);
if (event.data.success && event.data.tokens) {
resolve(event.data.tokens);
} else {
reject(new Error(event.data.error || "Token exchange failed"));
}
};
window.addEventListener("message", messageHandler);
// See comment in requestOAuthViaPostMessage for security rationale
window.parent.postMessage(
{
type: EXTENSION_MESSAGE_TYPES.TOKEN_EXCHANGE_REQUEST,
tokenRequest,
tokenEndpoint,
state,
},
"*"
);
});
}
getDiscoveryEndpoints(): AuthSession.DiscoveryDocument {
return {
authorizationEndpoint: `${this.config.calcomBaseUrl}/auth/oauth2/authorize`,
tokenEndpoint: `${this.config.calcomBaseUrl}/api/auth/oauth/token`,
revocationEndpoint: `${this.config.calcomBaseUrl}/api/auth/oauth/revoke`,
};
}
isTokenExpired(tokens: OAuthTokens): boolean {
if (!tokens.expiresAt) return false;
return Date.now() >= tokens.expiresAt - 5 * 60 * 1000;
}
clearPKCEParams() {
clearPKCEParams(): void {
this.codeVerifier = null;
this.state = null;
}
+1
View File
@@ -2,6 +2,7 @@
module.exports = {
content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
presets: [require("nativewind/preset")],
darkMode: "class",
theme: {
extend: {},
},
+47
View File
@@ -0,0 +1,47 @@
import { Platform, ViewStyle } from "react-native";
interface ShadowConfig {
color?: string;
offsetY?: number;
offsetX?: number;
opacity?: number;
radius?: number;
elevation?: number;
}
export function createShadow(config: ShadowConfig = {}): ViewStyle {
const {
color = "#000",
offsetX = 0,
offsetY = 2,
opacity = 0.1,
radius = 8,
elevation = 4,
} = config;
if (Platform.OS === "web") {
const alpha = Math.round(opacity * 255)
.toString(16)
.padStart(2, "0");
return {
// @ts-ignore - web-only property
boxShadow: `${offsetX}px ${offsetY}px ${radius}px rgba(0, 0, 0, ${opacity})`,
};
}
return {
shadowColor: color,
shadowOffset: { width: offsetX, height: offsetY },
shadowOpacity: opacity,
shadowRadius: radius,
elevation,
};
}
export const shadows = {
sm: () => createShadow({ offsetY: 1, radius: 3, opacity: 0.1, elevation: 2 }),
md: () => createShadow({ offsetY: 4, radius: 6, opacity: 0.1, elevation: 4 }),
lg: () => createShadow({ offsetY: 10, radius: 15, opacity: 0.15, elevation: 8 }),
xl: () => createShadow({ offsetY: 20, radius: 25, opacity: 0.25, elevation: 24 }),
none: (): ViewStyle => ({}),
};
+10 -5
View File
@@ -1,5 +1,8 @@
import { defineConfig } from "wxt";
const devUrl = process.env.EXPO_PUBLIC_COMPANION_DEV_URL;
const isLocalDev = Boolean(devUrl && devUrl.includes("localhost"));
export default defineConfig({
hooks: {
"build:manifestGenerated": (_wxt, manifest) => {
@@ -24,10 +27,12 @@ export default defineConfig({
"https://api.cal.com/*",
"https://app.cal.com/*",
"https://mail.google.com/*",
...(isLocalDev ? ["http://localhost:*/*"] : []),
],
content_security_policy: {
extension_pages:
"script-src 'self'; object-src 'self'; frame-src 'self' https://companion.cal.com",
extension_pages: isLocalDev
? "script-src 'self'; object-src 'self'; frame-src 'self' https://companion.cal.com http://localhost:*"
: "script-src 'self'; object-src 'self'; frame-src 'self' https://companion.cal.com",
},
data_collection_permissions: {
collects_data: false,
@@ -55,14 +60,15 @@ export default defineConfig({
define: {
global: "globalThis",
__DEV__: JSON.stringify(false),
// Expose environment variables to the extension
"import.meta.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID": JSON.stringify(
process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID
),
"import.meta.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI": JSON.stringify(
process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI
),
// Cache configuration environment variables
"import.meta.env.EXPO_PUBLIC_COMPANION_DEV_URL": JSON.stringify(
process.env.EXPO_PUBLIC_COMPANION_DEV_URL
),
"import.meta.env.EXPO_PUBLIC_CACHE_STALE_TIME_MINUTES": JSON.stringify(
process.env.EXPO_PUBLIC_CACHE_STALE_TIME_MINUTES
),
@@ -81,7 +87,6 @@ export default defineConfig({
"import.meta.env.EXPO_PUBLIC_USER_PROFILE_CACHE_STALE_TIME_MINUTES": JSON.stringify(
process.env.EXPO_PUBLIC_USER_PROFILE_CACHE_STALE_TIME_MINUTES
),
// DEV ONLY: API Key for testing - only included in development builds
...(process.env.NODE_ENV !== "production" && process.env.EXPO_PUBLIC_CAL_API_KEY
? {
"import.meta.env.EXPO_PUBLIC_CAL_API_KEY": JSON.stringify(