image
image copied to clipboard
feat(provider): imgproxy
resolves #378
I ran yarn install with the latest version of yarn 1 but it has removed all " from the lock file. I also had to set the test src for imgproxy to a static string of /test.png I think there was something changing the signature.
Codecov Report
Merging #385 (2cb8fc4) into main (026be95) will increase coverage by
1.08%. The diff coverage is100.00%.
@@ Coverage Diff @@
## main #385 +/- ##
==========================================
+ Coverage 59.32% 60.40% +1.08%
==========================================
Files 27 28 +1
Lines 622 639 +17
Branches 196 156 -40
==========================================
+ Hits 369 386 +17
Misses 253 253
| Impacted Files | Coverage Δ | |
|---|---|---|
| src/provider.ts | 74.28% <ø> (ø) |
|
| src/runtime/providers/imgproxy.ts | 100.00% <100.00%> (ø) |
Continue to review full report at Codecov.
Legend - Click here to learn more
Δ = absolute <relative> (impact),ø = not affected,? = missing dataPowered by Codecov. Last update 026be95...2cb8fc4. Read the comment docs.
Hi! I'm pending this issue for #276. In order to being able generate hashes without exposing secret to the client we need possibilities form nuxt 3 / nitro.
when will this be released :)
Is this MR still block because there is no way to sign request?
Is possible to see it in the new V1 module, because imgproxy is quite faster then IPX
I've made my own custom provider for imgproxy to use with Nuxt 3 and Cloudflare. I haven't worried about the imgproxy keys being public, it would be nice to see how to handle the provider being server-side only and how it work at runtime.
Here is the code for my custom nuxt 3 imgproxy provider.
import { encodeURI } from 'js-base64'
import { Buffer } from 'buffer'
import { OperationGeneratorConfig, OperationMapper } from '~/types/image'
import { joinURL, withBase } from 'ufo'
import hmacSHA256 from 'crypto-js/hmac-sha256.js'
import Base64url from 'crypto-js/enc-base64url.js'
import hex from 'crypto-js/enc-hex.js'
const hexDecode = (hex: string) => Buffer.from(hex, 'hex')
function createMapper(map: Record<string, string>): OperationMapper {
return (key?: string) => {
return key ? map[key] ?? key : map.missingValue
}
}
function createOperationsGenerator({
formatter,
keyMap,
joinWith = '/',
valueMap,
}: OperationGeneratorConfig = {}) {
if (!formatter) {
formatter = (key, value: string) => `${key}=${value}`
}
if (keyMap && typeof keyMap !== 'function') {
keyMap = createMapper(keyMap)
}
const map = valueMap ?? {}
Object.keys(map).forEach((valueKey) => {
if (typeof map[valueKey] !== 'function') {
map[valueKey] = createMapper(map[valueKey])
}
})
return (modifiers: { [key: string]: string } = {}) => {
const operations = Object.entries(modifiers)
.filter(([_, value]) => typeof value !== 'undefined')
.map(([key, value]) => {
const mapper = map[key]
if (typeof mapper === 'function') {
value = mapper(modifiers[key])
}
key = typeof keyMap === 'function' ? keyMap(key) : key
return formatter?.(key, value)
})
return operations.join(joinWith)
}
}
const sign = (salt: string, target: string, secret: string) => {
const msg = hexDecode(salt + Buffer.from(target).toString('hex')) // Uint8Array of arbitrary length
const hmac = hmacSHA256(hex.parse(msg.toString('hex')), hex.parse(secret))
const digest = hmac.toString(Base64url)
return digest
}
const operationsGenerator = createOperationsGenerator({
keyMap: {
resize: 'rs',
size: 's',
fit: 'rt',
width: 'w',
height: 'h',
dpr: 'dpr',
enlarge: 'el',
extend: 'ex',
gravity: 'g',
crop: 'c',
padding: 'pd',
trim: 't',
rotate: 'rot',
quality: 'q',
maxBytes: 'mb',
background: 'bg',
backgroundAlpha: 'bga',
blur: 'bl',
sharpen: 'sh',
watermark: 'wm',
preset: 'pr',
cacheBuster: 'cb',
stripMetadata: 'sm',
stripColorProfile: 'scp',
autoRotate: 'ar',
filename: 'fn',
format: 'f',
},
formatter: (key, value) => `${key}:${value}`,
})
export const getImage: ProviderGetImage = (
src,
{ modifiers = {}, baseURL = '', key, salt } = {},
) => {
const encodedUrl = encodeURI(src)
const path = joinURL('/', operationsGenerator(modifiers), encodedUrl)
const signature = sign(salt, path, key)
return {
url: withBase(joinURL(signature, path), baseURL),
}
}