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: globs:
alwaysApply: true alwaysApply: true
--- ---
@ -7,6 +7,8 @@ alwaysApply: true
✅ python script files must always have a blank line at their end ✅ python script files must always have a blank line at their end
✅ remove whitespace at the end of lines ✅ remove whitespace at the end of lines
✅ use npm run lint-fix to check for warnings ✅ 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 always preview changes and commit message to use and allow me to copy and paste
✅ Preferred Commit Message Format ✅ Preferred Commit Message Format

223
package-lock.json

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

14
src/components/ActivityListItem.vue

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

3
src/components/EntityIcon.vue

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

7
src/components/ImageViewer.vue

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

3
src/components/ProjectIcon.vue

@ -13,6 +13,7 @@
<script lang="ts"> <script lang="ts">
import { toSvg } from "jdenticon"; import { toSvg } from "jdenticon";
import { Vue, Component, Prop } from "vue-facing-decorator"; import { Vue, Component, Prop } from "vue-facing-decorator";
import { transformImageUrlForCors } from "../libs/util";
const BLANK_CONFIG = { const BLANK_CONFIG = {
lightness: { lightness: {
@ -35,7 +36,7 @@ export default class ProjectIcon extends Vue {
generateIcon() { generateIcon() {
if (this.imageUrl) { 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 { } else {
const config = this.entityId ? undefined : BLANK_CONFIG; const config = this.entityId ? undefined : BLANK_CONFIG;
const svgString = toSvg(this.entityId, this.iconSize, 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", PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
TEST_ENDORSER_API_SERVER = "https://test-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", PROD_IMAGE_API_SERVER = "https://image-api.timesafari.app",
TEST_IMAGE_API_SERVER = "https://test-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", PROD_PARTNER_API_SERVER = "https://partner-api.endorser.ch",
TEST_PARTNER_API_SERVER = "https://test-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", PROD_PUSH_SERVER = "https://timesafari.app",
TEST1_PUSH_SERVER = "https://test.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 // Save the new identity
await saveNewIdentity(newId, mne, derivationPath); 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"> <div class="flex justify-center mt-4">
<span v-if="imageUrl" class="flex justify-between"> <span v-if="imageUrl" class="flex justify-between">
<a :href="imageUrl" target="_blank" class="text-blue-500 ml-4"> <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> </a>
<font-awesome <font-awesome
icon="trash-can" icon="trash-can"
@ -243,6 +246,7 @@ import {
import { import {
retrieveAccountCount, retrieveAccountCount,
retrieveFullyDecryptedAccount, retrieveFullyDecryptedAccount,
transformImageUrlForCors,
} from "../libs/util"; } from "../libs/util";
import { import {
@ -828,5 +832,12 @@ export default class NewEditProjectView extends Vue {
this.latitude = event.latlng.lat; this.latitude = event.latlng.lat;
this.longitude = event.latlng.lng; 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> </script>

13
src/views/ProjectViewView.vue

@ -608,7 +608,10 @@
</div> </div>
<div v-if="give.fullClaim.image" class="flex justify-center"> <div v-if="give.fullClaim.image" class="flex justify-center">
<a :href="give.fullClaim.image" target="_blank"> <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> </a>
</div> </div>
</li> </li>
@ -653,6 +656,7 @@ import HiddenDidDialog from "../components/HiddenDidDialog.vue";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import { transformImageUrlForCors } from "../libs/util";
/** /**
* Project View Component * Project View Component
* @author Matthew Raymer * @author Matthew Raymer
@ -1552,5 +1556,12 @@ export default class ProjectViewView extends Vue {
this.givesTotalsByUnit.find((total) => total.unit === "HUR")?.amount || 0 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> </script>

66
vite.config.common.mts

@ -35,6 +35,72 @@ export async function createBuildConfig(mode: string): Promise<UserConfig> {
// Enable SharedArrayBuffer for absurd-sql // Enable SharedArrayBuffer for absurd-sql
'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp' '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: { build: {

Loading…
Cancel
Save