diff --git a/companion/.env.example b/companion/.env.example index e1f34bd3cc..4df8ceaad3 100644 --- a/companion/.env.example +++ b/companion/.env.example @@ -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 diff --git a/companion/app/(tabs)/(event-types)/index.tsx b/companion/app/(tabs)/(event-types)/index.tsx index 7a2732b38b..946a25335d 100644 --- a/companion/app/(tabs)/(event-types)/index.tsx +++ b/companion/app/(tabs)/(event-types)/index.tsx @@ -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 */} diff --git a/companion/app/(tabs)/availability.tsx b/companion/app/(tabs)/availability.tsx index d7331e7324..eb0fd129e4 100644 --- a/companion/app/(tabs)/availability.tsx +++ b/companion/app/(tabs)/availability.tsx @@ -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 */} diff --git a/companion/app/_layout.tsx b/companion/app/_layout.tsx index dc1bd0c8a7..268eb25824 100644 --- a/companion/app/_layout.tsx +++ b/companion/app/_layout.tsx @@ -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 ( - + {content} diff --git a/companion/app/booking-detail.tsx b/companion/app/booking-detail.tsx index a3d21d6876..950512bd7d 100644 --- a/companion/app/booking-detail.tsx +++ b/companion/app/booking-detail.tsx @@ -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 */} diff --git a/companion/bun.lock b/companion/bun.lock index 45bbd532ea..5bd5b0b201 100644 --- a/companion/bun.lock +++ b/companion/bun.lock @@ -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=="], diff --git a/companion/components/LoadingSpinner.tsx b/companion/components/LoadingSpinner.tsx index 2d89e169bf..44f080c169 100644 --- a/companion/components/LoadingSpinner.tsx +++ b/companion/components/LoadingSpinner.tsx @@ -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 ( @@ -34,7 +23,6 @@ export function LoadingSpinner({ ); } - // Styled container for other platforms if (showBackground) { return ( @@ -43,7 +31,6 @@ export function LoadingSpinner({ ); } - // Simple spinner without background return ; } @@ -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, + }, + }), }, }); diff --git a/companion/components/LoginScreen.tsx b/companion/components/LoginScreen.tsx index bdeb47bd3e..3e3c2cbc75 100644 --- a/companion/components/LoginScreen.tsx +++ b/companion/components/LoginScreen.tsx @@ -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} > Continue with Cal.com diff --git a/companion/components/Tooltip.tsx b/companion/components/Tooltip.tsx index 08757504c2..b5ff850c25 100644 --- a/companion/components/Tooltip.tsx +++ b/companion/components/Tooltip.tsx @@ -11,7 +11,6 @@ export function Tooltip({ text, children }: TooltipProps) { const [position, setPosition] = useState({ top: 0, left: 0 }); const containerRef = useRef(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 && ( - + {/* @ts-ignore - web-only styles */} + {text} diff --git a/companion/extension/entrypoints/background/index.ts b/companion/extension/entrypoints/background/index.ts index c3dfc361f9..d62fd15d75 100644 --- a/companion/extension/entrypoints/background/index.ts +++ b/companion/extension/entrypoints/background/index.ts @@ -1,26 +1,27 @@ /// -// ============================================ -// 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 { 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, tokenEndpoint: string, state?: string -): Promise { - // CSRF protection: validate state parameter (defense-in-depth) - // State is already validated in iframe context; this validates in background script +): Promise { 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 { + 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 { 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 { + 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; } diff --git a/companion/extension/entrypoints/content.ts b/companion/extension/entrypoints/content.ts index 0d814bd9cb..61a8403c47 100644 --- a/companion/extension/entrypoints/content.ts +++ b/companion/extension/entrypoints/content.ts @@ -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"; diff --git a/companion/extension/env.d.ts b/companion/extension/env.d.ts new file mode 100644 index 0000000000..30d62bf239 --- /dev/null +++ b/companion/extension/env.d.ts @@ -0,0 +1,12 @@ +/// + +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; +} diff --git a/companion/global.css b/companion/global.css index 28214b8222..cdd19a3136 100644 --- a/companion/global.css +++ b/companion/global.css @@ -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 { diff --git a/companion/package.json b/companion/package.json index aaf1d0e166..23b2a44f3b 100644 --- a/companion/package.json +++ b/companion/package.json @@ -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", diff --git a/companion/services/oauthService.ts b/companion/services/oauthService.ts index 2f15fce4bf..dfabdb1f49 100644 --- a/companion/services/oauthService.ts +++ b/companion/services/oauthService.ts @@ -1,12 +1,24 @@ /// 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 { + const parsed = new URL(url); + const params: Record = {}; + 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 { 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 | 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 } | { 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 } | { 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 { - 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 { - const parsed = new URL(url); - const params: Record = {}; - - parsed.searchParams.forEach((v, k) => { - params[k] = v; - }); - - return params; - } - - private async exchangeCodeForTokens(code: string): Promise { + private async exchangeCodeForTokens(code: string, state?: string): Promise { 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, + tokenEndpoint: string + ): Promise { + 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, + tokenEndpoint: string, + state?: string + ): Promise { + 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; } diff --git a/companion/tailwind.config.js b/companion/tailwind.config.js index 6288219191..bdcfdd978a 100644 --- a/companion/tailwind.config.js +++ b/companion/tailwind.config.js @@ -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: {}, }, diff --git a/companion/utils/shadows.ts b/companion/utils/shadows.ts new file mode 100644 index 0000000000..761eb0835c --- /dev/null +++ b/companion/utils/shadows.ts @@ -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 => ({}), +}; diff --git a/companion/wxt.config.ts b/companion/wxt.config.ts index cf1fc88aa4..3b1f1c4a71 100644 --- a/companion/wxt.config.ts +++ b/companion/wxt.config.ts @@ -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(