Browse Source

Fix image CORS violations with comprehensive proxy and component updates

- Applied transformImageUrlForCors to all image-displaying components
- Added followRedirects: true to image proxy to serve actual content
- Proxy now returns 200 OK with image data instead of 301 redirects
- Maintains CORS headers required for SharedArrayBuffer support
- Added debug logging for proxy response monitoring

Resolves all image loading failures in development environment.
pull/142/head
Matthew Raymer 3 days ago
parent
commit
0343b4cd0e
  1. 4
      .cursor/rules/development_guide.mdc
  2. 223
      package-lock.json
  3. 14
      src/components/ActivityListItem.vue
  4. 3
      src/components/EntityIcon.vue
  5. 7
      src/components/ImageViewer.vue
  6. 3
      src/components/ProjectIcon.vue
  7. 6
      src/constants/app.ts
  8. 37
      src/libs/util.ts
  9. 13
      src/views/NewEditProjectView.vue
  10. 13
      src/views/ProjectViewView.vue
  11. 66
      vite.config.common.mts

4
.cursor/rules/development_guide.mdc

@ -1,5 +1,5 @@
---
description:
description: rules used while developing
globs:
alwaysApply: true
---
@ -7,6 +7,8 @@ alwaysApply: true
✅ python script files must always have a blank line at their end
✅ remove whitespace at the end of lines
✅ use npm run lint-fix to check for warnings
✅ do not use npm run dev let me handle running and supplying feedback
✅ do not add or commit for the user; let him control that process
always preview changes and commit message to use and allow me to copy and paste
✅ Preferred Commit Message Format

223
package-lock.json

@ -5231,9 +5231,9 @@
}
},
"node_modules/@expo/cli": {
"version": "0.24.16",
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.24.16.tgz",
"integrity": "sha512-nzfWj5iN8kBeH5iVsfw0XjrUxIK4Td+WayxgnBbBZ27iczGnRPaWT+SSea3EoaaiWqcwkV0xgLQkd0qpTsaEhg==",
"version": "0.24.17",
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.24.17.tgz",
"integrity": "sha512-1Rz8EU6v7/gyMSLkRxXqoO6Tzkvkk4vR6DT49lfXElpHqWIu+wtgSpCxw5UfEfZKh6sK3Goswxb3g9LVtL/bCw==",
"license": "MIT",
"optional": true,
"peer": true,
@ -5242,20 +5242,20 @@
"@babel/runtime": "^7.20.0",
"@expo/code-signing-certificates": "^0.0.5",
"@expo/config": "~11.0.11",
"@expo/config-plugins": "~10.0.3",
"@expo/config-plugins": "~10.1.0",
"@expo/devcert": "^1.1.2",
"@expo/env": "~1.0.6",
"@expo/image-utils": "^0.7.5",
"@expo/json-file": "^9.1.4",
"@expo/metro-config": "~0.20.15",
"@expo/metro-config": "~0.20.16",
"@expo/osascript": "^2.2.4",
"@expo/package-manager": "^1.8.5",
"@expo/plist": "^0.3.4",
"@expo/prebuild-config": "^9.0.8",
"@expo/prebuild-config": "^9.0.9",
"@expo/spawn-async": "^1.7.2",
"@expo/ws-tunnel": "^1.0.1",
"@expo/xcpretty": "^4.3.0",
"@react-native/dev-middleware": "0.79.4",
"@react-native/dev-middleware": "0.79.5",
"@urql/core": "^5.0.6",
"@urql/exchange-retry": "^1.3.0",
"accepts": "^1.3.8",
@ -5686,9 +5686,9 @@
}
},
"node_modules/@expo/config-plugins": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.0.3.tgz",
"integrity": "sha512-fjCckkde67pSDf48x7wRuPsgQVIqlDwN7NlOk9/DFgQ1hCH0L5pGqoSmikA1vtAyiA83MOTpkGl3F3wyATyUog==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.1.0.tgz",
"integrity": "sha512-u83yPJl1tfm8l+grzmwURgCAgeEX0mnMZfBcEonoIR1tpba61+j13FDEbdXq2hVuheki4CeFzoA5mnYnQl9opA==",
"license": "MIT",
"optional": true,
"peer": true,
@ -5842,6 +5842,49 @@
"@babel/highlight": "^7.10.4"
}
},
"node_modules/@expo/config/node_modules/@expo/config-plugins": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.0.3.tgz",
"integrity": "sha512-fjCckkde67pSDf48x7wRuPsgQVIqlDwN7NlOk9/DFgQ1hCH0L5pGqoSmikA1vtAyiA83MOTpkGl3F3wyATyUog==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@expo/config-types": "^53.0.4",
"@expo/json-file": "~9.1.4",
"@expo/plist": "^0.3.4",
"@expo/sdk-runtime-versions": "^1.0.0",
"chalk": "^4.1.2",
"debug": "^4.3.5",
"getenv": "^2.0.0",
"glob": "^10.4.2",
"resolve-from": "^5.0.0",
"semver": "^7.5.4",
"slash": "^3.0.0",
"slugify": "^1.6.6",
"xcode": "^3.0.1",
"xml2js": "0.6.0"
}
},
"node_modules/@expo/config/node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@expo/config/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@ -5892,6 +5935,14 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/@expo/config/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/@expo/config/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
@ -5903,6 +5954,32 @@
"node": ">=8"
}
},
"node_modules/@expo/config/node_modules/xml2js": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
"integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/@expo/config/node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/@expo/devcert": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.0.tgz",
@ -6146,9 +6223,9 @@
}
},
"node_modules/@expo/metro-config": {
"version": "0.20.15",
"resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.20.15.tgz",
"integrity": "sha512-m8i58IQ7I8iOdVRfOhFmhPMHuhgeTVfQp1+mxW7URqPZaeVbuDVktPqOiNoHraKBoGPLKMUSsD+qdUuJVL3wMg==",
"version": "0.20.16",
"resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.20.16.tgz",
"integrity": "sha512-qsCQqYCGYR1t2KNDoPESxMbuvrkljsdQBblfJoOsRB/9TC+1OtmYsbIUuXZDFm1/mgYA42j4dAQqP/mck5ouuw==",
"license": "MIT",
"optional": true,
"peer": true,
@ -6157,8 +6234,8 @@
"@babel/generator": "^7.20.5",
"@babel/parser": "^7.20.0",
"@babel/types": "^7.20.0",
"@expo/config": "~11.0.10",
"@expo/env": "~1.0.5",
"@expo/config": "~11.0.11",
"@expo/env": "~1.0.6",
"@expo/json-file": "~9.1.4",
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.1.0",
@ -6548,19 +6625,19 @@
}
},
"node_modules/@expo/prebuild-config": {
"version": "9.0.8",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.8.tgz",
"integrity": "sha512-vzORt1zrgjIYOKAmHk0SUka0rlYo0AIZpOF6019ms2XaQnxyvcQACz1BxTk2++fTAzSAxerufNQOm2owPcAz+g==",
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.9.tgz",
"integrity": "sha512-uGt5K6FGDhK12WJXcgcm3Cirakt4llil99rLxFx/WgQQdE5BK58O0irrhKz5kwhXmWCco0CRSUdAoddq1JK91w==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@expo/config": "~11.0.10",
"@expo/config-plugins": "~10.0.3",
"@expo/config": "~11.0.11",
"@expo/config-plugins": "~10.1.0",
"@expo/config-types": "^53.0.4",
"@expo/image-utils": "^0.7.4",
"@expo/image-utils": "^0.7.5",
"@expo/json-file": "^9.1.4",
"@react-native/normalize-colors": "0.79.4",
"@react-native/normalize-colors": "0.79.5",
"debug": "^4.3.1",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
@ -7981,24 +8058,24 @@
}
},
"node_modules/@react-native/babel-plugin-codegen": {
"version": "0.79.4",
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.79.4.tgz",
"integrity": "sha512-quhytIlDedR3ircRwifa22CaWVUVnkxccrrgztroCZaemSJM+HLurKJrjKWm0J5jV9ed+d+9Qyb1YB0syTHDjg==",
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.79.5.tgz",
"integrity": "sha512-Rt/imdfqXihD/sn0xnV4flxxb1aLLjPtMF1QleQjEhJsTUPpH4TFlfOpoCvsrXoDl4OIcB1k4FVM24Ez92zf5w==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@babel/traverse": "^7.25.3",
"@react-native/codegen": "0.79.4"
"@react-native/codegen": "0.79.5"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@react-native/babel-preset": {
"version": "0.79.4",
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.79.4.tgz",
"integrity": "sha512-El9JvYKiNfnkQ3qR7zJvvRdP3DX2i4BGYlIricWQishI3gWAfm88FQYFC2CcGoMQWJQEPN4jnDMpoISAJDEN4g==",
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.79.5.tgz",
"integrity": "sha512-GDUYIWslMLbdJHEgKNfrOzXk8EDKxKzbwmBXUugoiSlr6TyepVZsj3GZDLEFarOcTwH1EXXHJsixihk8DCRQDA==",
"license": "MIT",
"optional": true,
"peer": true,
@ -8044,7 +8121,7 @@
"@babel/plugin-transform-typescript": "^7.25.2",
"@babel/plugin-transform-unicode-regex": "^7.24.7",
"@babel/template": "^7.25.0",
"@react-native/babel-plugin-codegen": "0.79.4",
"@react-native/babel-plugin-codegen": "0.79.5",
"babel-plugin-syntax-hermes-parser": "0.25.1",
"babel-plugin-transform-flow-enums": "^0.0.2",
"react-refresh": "^0.14.0"
@ -8057,9 +8134,9 @@
}
},
"node_modules/@react-native/codegen": {
"version": "0.79.4",
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.79.4.tgz",
"integrity": "sha512-K0moZDTJtqZqSs+u9tnDPSxNsdxi5irq8Nu4mzzOYlJTVNGy5H9BiIDg/NeKGfjAdo43yTDoaPSbUCvVV8cgIw==",
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.79.5.tgz",
"integrity": "sha512-FO5U1R525A1IFpJjy+KVznEinAgcs3u7IbnbRJUG9IH/MBXi2lEU2LtN+JarJ81MCfW4V2p0pg6t/3RGHFRrlQ==",
"license": "MIT",
"optional": true,
"peer": true,
@ -8198,9 +8275,9 @@
}
},
"node_modules/@react-native/debugger-frontend": {
"version": "0.79.4",
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.79.4.tgz",
"integrity": "sha512-Gg4LhxHIK86Bi2RiT1rbFAB6fuwANRsaZJ1sFZ1OZEMQEx6stEnzaIrmfgzcv4z0bTQdQ8lzCrpsz0qtdaD4eA==",
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.79.5.tgz",
"integrity": "sha512-WQ49TRpCwhgUYo5/n+6GGykXmnumpOkl4Lr2l2o2buWU9qPOwoiBqJAtmWEXsAug4ciw3eLiVfthn5ufs0VB0A==",
"license": "BSD-3-Clause",
"optional": true,
"peer": true,
@ -8209,15 +8286,15 @@
}
},
"node_modules/@react-native/dev-middleware": {
"version": "0.79.4",
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.79.4.tgz",
"integrity": "sha512-OWRDNkgrFEo+OSC5QKfiiBmGXKoU8gmIABK8rj2PkgwisFQ/22p7MzE5b6oB2lxWaeJT7jBX5KVniNqO46VhHA==",
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.79.5.tgz",
"integrity": "sha512-U7r9M/SEktOCP/0uS6jXMHmYjj4ESfYCkNAenBjFjjsRWekiHE+U/vRMeO+fG9gq4UCcBAUISClkQCowlftYBw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@isaacs/ttlcache": "^1.4.1",
"@react-native/debugger-frontend": "0.79.4",
"@react-native/debugger-frontend": "0.79.5",
"chrome-launcher": "^0.15.2",
"chromium-edge-launcher": "^0.2.0",
"connect": "^3.6.5",
@ -8303,9 +8380,9 @@
}
},
"node_modules/@react-native/normalize-colors": {
"version": "0.79.4",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.4.tgz",
"integrity": "sha512-247/8pHghbYY2wKjJpUsY6ZNbWcdUa5j5517LZMn6pXrbSSgWuj3JA4OYibNnocCHBaVrt+3R8XC3VEJqLlHFg==",
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.5.tgz",
"integrity": "sha512-nGXMNMclZgzLUxijQQ38Dm3IAEhgxuySAWQHnljFtfB0JdaMwpe0Ox9H7Tp2OgrEA+EMEv+Od9ElKlHwGKmmvQ==",
"license": "MIT",
"optional": true,
"peer": true
@ -9037,9 +9114,9 @@
}
},
"node_modules/@stencil/core": {
"version": "4.35.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.35.1.tgz",
"integrity": "sha512-u65m3TbzOtpn679gUV4Yvi8YpInhRJ62js30a7YtXief9Ej/vzrhwDE22U0w4DMWJOYwAsJl133BUaZkWwnmzg==",
"version": "4.35.3",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.35.3.tgz",
"integrity": "sha512-RH5/I+amV31QI8TMXhXkAkjzs2eod6Y07jkUYTl9kMB+X7c5wUpv95Y/2LtcAx0Rqdhh4SHbJiwpr0ApBZmv0g==",
"license": "MIT",
"bin": {
"stencil": "bin/stencil"
@ -11855,9 +11932,9 @@
}
},
"node_modules/babel-preset-expo": {
"version": "13.2.1",
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-13.2.1.tgz",
"integrity": "sha512-Ol3w0uLJNQ5tDfCf4L+IDTDMgJkVMQHhvYqMxs18Ib0DcaBQIfE8mneSSk7FcuI6FS0phw/rZhoEquQh1/Q3wA==",
"version": "13.2.2",
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-13.2.2.tgz",
"integrity": "sha512-mfKDEConzFEtNbfsY+VH+YXFbTj0VjOPN8A6NtWh8hFIXJHEa6J032gWysVMHgwlUhE1wbpX3ncJohmEuYWUsg==",
"license": "MIT",
"optional": true,
"peer": true,
@ -11876,7 +11953,7 @@
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.0",
"@react-native/babel-preset": "0.79.4",
"@react-native/babel-preset": "0.79.5",
"babel-plugin-react-native-web": "~0.19.13",
"babel-plugin-syntax-hermes-parser": "^0.25.1",
"babel-plugin-transform-flow-enums": "^0.0.2",
@ -12092,9 +12169,9 @@
}
},
"node_modules/better-sqlite3-multiple-ciphers": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-12.1.1.tgz",
"integrity": "sha512-5/YNegVYyZlqOvetZK05zebDRFtGjN+QJ0OeTZ4sXF7N5JDNXK+5j7qqwI+mTzGlJjmNL76Alu3SDz2aFDmnQA==",
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-12.2.0.tgz",
"integrity": "sha512-RWNb++urDg03mRKoHTLWOgZijiQmDnwdvm2SavauOOniJUBPBh1HMisJ9NxDF0Fqf0ml5iGunXhWu9fM7ALSyA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@ -15294,9 +15371,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.178",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz",
"integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==",
"version": "1.5.179",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz",
"integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==",
"devOptional": true,
"license": "ISC"
},
@ -16121,9 +16198,9 @@
}
},
"node_modules/ethers": {
"version": "6.14.4",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.4.tgz",
"integrity": "sha512-Jm/dzRs2Z9iBrT6e9TvGxyb5YVKAPLlpna7hjxH7KH/++DSh2T/JVmQUv7iHI5E55hDbp/gEVvstWYXVxXFzsA==",
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz",
"integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==",
"funding": [
{
"type": "individual",
@ -16328,28 +16405,28 @@
}
},
"node_modules/expo": {
"version": "53.0.15",
"resolved": "https://registry.npmjs.org/expo/-/expo-53.0.15.tgz",
"integrity": "sha512-tOMnpM1iHHUKcG6QzpE8Pm+d56XIyaxiOkXyDHiHrDziBMAI+ngBlZZ3xU/vuyMFiJWBCx5KAqiKsliKTEQi6Q==",
"version": "53.0.16",
"resolved": "https://registry.npmjs.org/expo/-/expo-53.0.16.tgz",
"integrity": "sha512-ogHgh11/H3tU/k4bwT4OuCpSPb+hh66H4aro137BiODDhkECfGx90TwQE3XXPV4jRZMSUlVcyNexmp8bQ4Di1g==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "0.24.16",
"@expo/cli": "0.24.17",
"@expo/config": "~11.0.11",
"@expo/config-plugins": "~10.0.3",
"@expo/config-plugins": "~10.1.0",
"@expo/fingerprint": "0.13.3",
"@expo/metro-config": "0.20.15",
"@expo/metro-config": "0.20.16",
"@expo/vector-icons": "^14.0.0",
"babel-preset-expo": "~13.2.1",
"babel-preset-expo": "~13.2.2",
"expo-asset": "~11.1.6",
"expo-constants": "~17.1.6",
"expo-file-system": "~18.1.11",
"expo-font": "~13.3.2",
"expo-keep-awake": "~14.1.4",
"expo-modules-autolinking": "2.1.13",
"expo-modules-core": "2.4.1",
"expo-modules-core": "2.4.2",
"react-native-edge-to-edge": "1.6.0",
"whatwg-url-without-unicode": "8.0.0-3"
},
@ -16549,9 +16626,9 @@
}
},
"node_modules/expo/node_modules/expo-modules-core": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-2.4.1.tgz",
"integrity": "sha512-yCtYqvIDMLHu6nxo/ve5EOekTBNgSVb6F0B+0fCKCD0Fqid23DqdOhFUscsEltnhdKGfMyD1JRETkvhaJaXmJQ==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-2.4.2.tgz",
"integrity": "sha512-RCb0wniYCJkxwpXrkiBA/WiNGxzYsEpL0sB50gTnS/zEfX3DImS2npc4lfZ3hPZo1UF9YC6OSI9Do+iacV0NUg==",
"license": "MIT",
"optional": true,
"peer": true,
@ -30730,9 +30807,9 @@
}
},
"node_modules/zod": {
"version": "3.25.67",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
"integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
"version": "3.25.70",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.70.tgz",
"integrity": "sha512-2WWzL2X2GpPU6as2xK1HFb6U5BZdJMdpB5Qtlan4a1KLYcZ6Gvox+mqZpxOd66sfe5AP3pEWpd2BC3f0yLH/nQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"

14
src/components/ActivityListItem.vue

@ -63,7 +63,7 @@
<div
v-if="record.image"
class="bg-cover mb-2 -mt-3 sm:-mt-4 -mx-3 sm:-mx-4"
:style="`background-image: url(${record.image});`"
:style="`background-image: url(${transformImageUrlForCors(record.image)});`"
>
<a
class="block bg-slate-100/50 backdrop-blur-md px-6 py-4 cursor-pointer"
@ -71,7 +71,7 @@
>
<img
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
:src="record.image"
:src="transformImageUrlForCors(record.image)"
alt="Activity image"
@load="cacheImage(record.image)"
/>
@ -251,7 +251,11 @@
import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
import { GiveRecordWithContactInfo } from "../types";
import EntityIcon from "./EntityIcon.vue";
import { isGiveClaimType, notifyWhyCannotConfirm } from "../libs/util";
import {
isGiveClaimType,
notifyWhyCannotConfirm,
transformImageUrlForCors,
} from "../libs/util";
import { containsHiddenDid, isHiddenDid } from "../libs/endorserServer";
import ProjectIcon from "./ProjectIcon.vue";
import { NotificationIface } from "../constants/app";
@ -360,5 +364,9 @@ export default class ActivityListItem extends Vue {
day: "numeric",
});
}
transformImageUrlForCors(imageUrl: string): string {
return transformImageUrlForCors(imageUrl);
}
}
</script>

3
src/components/EntityIcon.vue

@ -7,6 +7,7 @@ import { createAvatar, StyleOptions } from "@dicebear/core";
import { avataaars } from "@dicebear/collection";
import { Vue, Component, Prop } from "vue-facing-decorator";
import { Contact } from "../db/tables/contacts";
import { transformImageUrlForCors } from "../libs/util";
@Component
export default class EntityIcon extends Vue {
@ -18,7 +19,7 @@ export default class EntityIcon extends Vue {
generateIcon() {
const imageUrl = this.contact?.profileImageUrl || this.profileImageUrl;
if (imageUrl) {
return `<img src="${imageUrl}" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
return `<img src="${transformImageUrlForCors(imageUrl)}" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
} else {
const identifier = this.contact?.did || this.entityId;
if (!identifier) {

7
src/components/ImageViewer.vue

@ -25,7 +25,7 @@
<div class="flex-1 flex items-center justify-center p-2">
<div class="w-full h-full flex items-center justify-center">
<img
:src="imageUrl"
:src="transformedImageUrl"
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
alt="expanded shared content"
@click="close"
@ -41,6 +41,7 @@
import { Component, Vue, Prop } from "vue-facing-decorator";
import { UAParser } from "ua-parser-js";
import { logger } from "../utils/logger";
import { transformImageUrlForCors } from "../libs/util";
@Component({ emits: ["update:isOpen"] })
export default class ImageViewer extends Vue {
@ -79,6 +80,10 @@ export default class ImageViewer extends Vue {
window.open(this.imageUrl, "_blank");
}
}
get transformedImageUrl() {
return transformImageUrlForCors(this.imageUrl);
}
}
</script>

3
src/components/ProjectIcon.vue

@ -13,6 +13,7 @@
<script lang="ts">
import { toSvg } from "jdenticon";
import { Vue, Component, Prop } from "vue-facing-decorator";
import { transformImageUrlForCors } from "../libs/util";
const BLANK_CONFIG = {
lightness: {
@ -35,7 +36,7 @@ export default class ProjectIcon extends Vue {
generateIcon() {
if (this.imageUrl) {
return `<img src="${this.imageUrl}" class="w-full h-full object-contain" />`;
return `<img src="${transformImageUrlForCors(this.imageUrl)}" class="w-full h-full object-contain" />`;
} else {
const config = this.entityId ? undefined : BLANK_CONFIG;
const svgString = toSvg(this.entityId, this.iconSize, config);

6
src/constants/app.ts

@ -11,15 +11,15 @@ export enum AppString {
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",
LOCAL_ENDORSER_API_SERVER = "/api",
PROD_IMAGE_API_SERVER = "https://image-api.timesafari.app",
TEST_IMAGE_API_SERVER = "https://test-image-api.timesafari.app",
LOCAL_IMAGE_API_SERVER = "http://localhost:3001",
LOCAL_IMAGE_API_SERVER = "/image-api",
PROD_PARTNER_API_SERVER = "https://partner-api.endorser.ch",
TEST_PARTNER_API_SERVER = "https://test-partner-api.endorser.ch",
LOCAL_PARTNER_API_SERVER = LOCAL_ENDORSER_API_SERVER,
LOCAL_PARTNER_API_SERVER = "/partner-api",
PROD_PUSH_SERVER = "https://timesafari.app",
TEST1_PUSH_SERVER = "https://test.timesafari.app",

37
src/libs/util.ts

@ -926,3 +926,40 @@ export async function importFromMnemonic(
// Save the new identity
await saveNewIdentity(newId, mne, derivationPath);
}
/**
* Transforms direct image URLs to use proxy endpoints in development to avoid CORS issues
* with restrictive headers required for SharedArrayBuffer support.
*
* @param imageUrl - The original image URL
* @returns Transformed URL that uses proxy in development, original URL otherwise
*
* @example
* transformImageUrlForCors('https://image.timesafari.app/abc123.jpg')
* // Returns: '/image-proxy/abc123.jpg' in development
* // Returns: 'https://image.timesafari.app/abc123.jpg' in production
*/
export function transformImageUrlForCors(
imageUrl: string | undefined | null,
): string {
if (!imageUrl) return "";
// Only transform in development mode
if (process.env.NODE_ENV === "production") {
return imageUrl;
}
// Transform direct image.timesafari.app URLs to use proxy
if (imageUrl.startsWith("https://image.timesafari.app/")) {
const imagePath = imageUrl.replace("https://image.timesafari.app/", "");
return `/image-proxy/${imagePath}`;
}
// Transform other timesafari.app subdomains if needed
if (imageUrl.includes(".timesafari.app/")) {
const imagePath = imageUrl.split(".timesafari.app/")[1];
return `/image-proxy/${imagePath}`;
}
return imageUrl;
}

13
src/views/NewEditProjectView.vue

@ -34,7 +34,10 @@
<div class="flex justify-center mt-4">
<span v-if="imageUrl" class="flex justify-between">
<a :href="imageUrl" target="_blank" class="text-blue-500 ml-4">
<img :src="imageUrl" class="h-24 rounded-xl" />
<img
:src="transformImageUrlForCors(imageUrl)"
class="h-24 rounded-xl"
/>
</a>
<font-awesome
icon="trash-can"
@ -243,6 +246,7 @@ import {
import {
retrieveAccountCount,
retrieveFullyDecryptedAccount,
transformImageUrlForCors,
} from "../libs/util";
import {
@ -828,5 +832,12 @@ export default class NewEditProjectView extends Vue {
this.latitude = event.latlng.lat;
this.longitude = event.latlng.lng;
}
/**
* Transforms image URLs to avoid CORS issues in development
* @param imageUrl - Original image URL
* @returns Transformed URL for proxy or original URL
*/
transformImageUrlForCors = transformImageUrlForCors;
}
</script>

13
src/views/ProjectViewView.vue

@ -608,7 +608,10 @@
</div>
<div v-if="give.fullClaim.image" class="flex justify-center">
<a :href="give.fullClaim.image" target="_blank">
<img :src="give.fullClaim.image" class="h-24 mt-2 rounded-xl" />
<img
:src="transformImageUrlForCors(give.fullClaim.image)"
class="h-24 mt-2 rounded-xl"
/>
</a>
</div>
</li>
@ -653,6 +656,7 @@ import HiddenDidDialog from "../components/HiddenDidDialog.vue";
import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { useClipboard } from "@vueuse/core";
import { transformImageUrlForCors } from "../libs/util";
/**
* Project View Component
* @author Matthew Raymer
@ -1552,5 +1556,12 @@ export default class ProjectViewView extends Vue {
this.givesTotalsByUnit.find((total) => total.unit === "HUR")?.amount || 0
);
}
/**
* Transforms image URLs to avoid CORS issues in development
* @param imageUrl - Original image URL
* @returns Transformed URL for proxy or original URL
*/
transformImageUrlForCors = transformImageUrlForCors;
}
</script>

66
vite.config.common.mts

@ -35,6 +35,72 @@ export async function createBuildConfig(mode: string): Promise<UserConfig> {
// Enable SharedArrayBuffer for absurd-sql
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
},
proxy: {
// Proxy API requests to avoid CORS issues with restrictive headers
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
secure: false,
configure: (proxy) => {
proxy.on('error', (err, req, res) => {
console.log('[Proxy Error]', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('[Proxy Request]', req.method, req.url, '->', proxyReq.path);
});
}
},
// Proxy partner API requests (redirect to main API)
'/partner-api': {
target: 'http://localhost:3000',
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/partner-api/, '/api'),
configure: (proxy) => {
proxy.on('error', (err, req, res) => {
console.log('[Partner API Proxy Error]', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('[Partner API Proxy Request]', req.method, req.url, '->', proxyReq.path);
});
}
},
// Proxy image API requests
'/image-api': {
target: 'https://test-image-api.timesafari.app',
changeOrigin: true,
secure: true,
rewrite: (path) => path.replace(/^\/image-api/, ''),
configure: (proxy) => {
proxy.on('error', (err, req, res) => {
console.log('[Image API Proxy Error]', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('[Image API Proxy Request]', req.method, req.url, '->', proxyReq.path);
});
}
},
// Proxy direct image requests from image.timesafari.app to avoid CORS issues
'/image-proxy': {
target: 'https://image.timesafari.app',
changeOrigin: true,
secure: true,
followRedirects: true,
rewrite: (path) => path.replace(/^\/image-proxy/, ''),
configure: (proxy) => {
proxy.on('error', (err, req, res) => {
console.log('[Image Proxy Error]', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('[Image Proxy Request]', req.method, req.url, '->', proxyReq.path);
});
proxy.on('proxyRes', (proxyRes, req, res) => {
// Log the response to debug redirects
console.log('[Image Proxy Response]', req.url, '->', proxyRes.statusCode, proxyRes.headers.location || 'no redirect');
});
}
}
}
},
build: {

Loading…
Cancel
Save