Skip to content

Commit f7aea29

Browse files
committedMar 1, 2023
init
0 parents  commit f7aea29

16 files changed

+4473
-0
lines changed
 

‎.editorconfig

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
charset = utf-8
8+
9+
[*.js]
10+
indent_style = space
11+
indent_size = 2
12+
13+
[{package.json,*.yml,*.cjson}]
14+
indent_style = space
15+
indent_size = 2

‎.eslintignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
coverage
3+
dist

‎.eslintrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": ["eslint-config-unjs"],
3+
"rules": {}
4+
}

‎.github/workflows/ci.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
ci:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v3
16+
- run: corepack enable
17+
- uses: actions/setup-node@v3
18+
with:
19+
node-version: 18
20+
cache: "pnpm"
21+
- run: pnpm install
22+
- run: pnpm lint
23+
- run: pnpm build
24+
- run: pnpm vitest --coverage
25+
- uses: codecov/codecov-action@v3

‎.gitignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.idea
2+
node_modules
3+
coverage
4+
dist
5+
types
6+
.vscode
7+
.DS_Store
8+
.eslintcache
9+
*.log*
10+
*.conf*
11+
*.env*
12+
.npmrc

‎.prettierrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

‎CHANGELOG.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Changelog
2+
3+
4+
## v0.0.2
5+
6+
7+
### 🩹 Fixes
8+
9+
- Empty lockfile (e975e46)
10+
- Add `coverage` directory to `.eslintignore` (#62)
11+
12+
### 📖 Documentation
13+
14+
- Fix yarn add command (#21)
15+
- Add node lts info (9af0642)
16+
- Fix build status badge url (#66)
17+
18+
### 🏡 Chore
19+
20+
- Add development (bc17492)
21+
- Update script (23448c8)
22+
- Update tests and add coverage (1eeb2a6)
23+
- Add `@vitest/coverage-c8` and run coverage in `test` script (#13)
24+
- Use explicit dev dependency (3f025c1)
25+
- Enable typescript strict mode (#16)
26+
- Type exports (#18)
27+
- Update renovate config (#25)
28+
- Update template (ef62c35)
29+
- Update deps (5b82f23)
30+
- **release:** V0.0.1 (9ea468e)
31+
32+
### ❤️ Contributors
33+
34+
- Ondřej Misák ([@misaon](http://github.com/misaon))
35+
- Nozomu Ikuta <nick.0508.nick@gmail.com>
36+
- Pooya Parsa ([@pi0](http://github.com/pi0))
37+
- Daniel Roe <daniel@roe.dev>
38+
39+
## v0.0.1
40+
41+
42+
### 🏡 Chore
43+
44+
- **release:** V0.0.0 (9a3d4fc)
45+
46+
### ❤️ Contributors
47+
48+
- Ondřej Misák ([@misaon](http://github.com/misaon))
49+

‎LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎README.md

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# 🖼️ @misaon/imgproxy
2+
3+
[![npm version][npm-version-src]][npm-version-href]
4+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
5+
[![Github Actions][github-actions-src]][github-actions-href]
6+
[![Codecov][codecov-src]][codecov-href]
7+
8+
> Generate [imgproxy](https://imgproxy.net/) url in browser and Node easily. Lightweight and Fast!
9+
10+
## Features
11+
12+
- 👌&nbsp; Zero configuration
13+
- 🪄&nbsp; Modern, tiny and tree shaken code
14+
- ⚙️&nbsp; Written in TypeScript (well typed)
15+
- 📦&nbsp; Extendable and customizable
16+
- 🚀&nbsp; Browser and Node support
17+
- 🧪&nbsp; Covered by tests
18+
19+
## Usage
20+
21+
Install package:
22+
23+
```sh
24+
# npm
25+
npm install @misaon/imgproxy
26+
27+
# yarn
28+
yarn add @misaon/imgproxy
29+
30+
# pnpm
31+
pnpm install @misaon/imgproxy
32+
```
33+
34+
In your code:
35+
36+
```js
37+
import { getImageUrl } from '@misaon/imgproxy'
38+
// or commonJS
39+
// const { getImageUrl } = require("misaon/imgproxy");
40+
41+
const imageUrl = getImageUrl(sourceImageUrl, {
42+
baseURL: 'https://my-imgproxy.com',
43+
secret: 'imgproxy-secret-key',
44+
salt: 'imgproxy-salt',
45+
modifiers: {
46+
width: '100',
47+
height: '75',
48+
// other modifiers...
49+
}
50+
})
51+
52+
console.log(imageUrl)
53+
```
54+
55+
### Modifiers
56+
The list of modifiers that you can use is **well typed** or can be found [here](https://github.com/misaon/imgproxy/blob/9e7b8b56187c617a1d513469fcff80e7072f085d/src/index.ts#L11).
57+
58+
## Development
59+
60+
- Clone this repository
61+
- Install latest LTS version of [Node.js](https://nodejs.org/en/)
62+
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
63+
- Install dependencies using `pnpm install`
64+
- Run interactive tests using `pnpm dev`
65+
66+
## License
67+
68+
Made with 🧡 by [@misaon](https://github.com/misaon)
69+
70+
Published under [MIT License](./LICENSE).
71+
72+
<!-- Badges -->
73+
74+
[npm-version-src]: https://img.shields.io/npm/v/@misaon/imgproxy?style=flat-square
75+
[npm-version-href]: https://npmjs.com/package/@misaon/imgproxy
76+
[npm-downloads-src]: https://img.shields.io/npm/dm/@misaon/imgproxy?style=flat-square
77+
[npm-downloads-href]: https://npmjs.com/package/@misaon/imgproxy
78+
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/misaon/imgproxy/ci.yml?branch=main&style=flat-square
79+
[github-actions-href]: https://github.com/misaon/imgproxy/actions?query=workflow%3Aci
80+
[codecov-src]: https://img.shields.io/codecov/c/gh/misaon/imgproxy/main?style=flat-square
81+
[codecov-href]: https://codecov.io/gh/misaon/imgproxy

‎examples/test.mjs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// import { getImageUrl } from '@misaon/imgproxy'
2+
import { getImageUrl } from '../dist/index.mjs'
3+
4+
const imageUrl = getImageUrl('http://object-storage:9000/oxyshop-nextgen/media/image/e9/77/151f15b8f9af244f7f61775ddb3d.png', {
5+
secret: '6F787973686F70', salt: '10D16F7A61', baseURL: '/imgproxy', modifiers: {
6+
width: 768,
7+
height: 0,
8+
}
9+
})
10+
11+
console.log(imageUrl)

‎package.json

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "@misaon/imgproxy",
3+
"version": "0.0.2",
4+
"description": "",
5+
"repository": "@misaon/imgproxy",
6+
"license": "MIT",
7+
"sideEffects": false,
8+
"type": "module",
9+
"exports": {
10+
".": {
11+
"types": "./dist/index.d.ts",
12+
"import": "./dist/index.mjs",
13+
"require": "./dist/index.cjs"
14+
}
15+
},
16+
"main": "./dist/index.cjs",
17+
"module": "./dist/index.mjs",
18+
"types": "./dist/index.d.ts",
19+
"files": [
20+
"dist"
21+
],
22+
"scripts": {
23+
"build": "unbuild",
24+
"dev": "vitest dev",
25+
"lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test",
26+
"lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test -w",
27+
"prepack": "pnpm run build",
28+
"release": "pnpm test && changelogen --release && npm publish && git push --follow-tags",
29+
"test": "pnpm lint && vitest run --coverage"
30+
},
31+
"devDependencies": {
32+
"@types/node": "^18.14.2",
33+
"@vitest/coverage-c8": "^0.29.1",
34+
"changelogen": "^0.4.1",
35+
"eslint": "^8.34.0",
36+
"eslint-config-unjs": "^0.1.0",
37+
"prettier": "^2.8.4",
38+
"typescript": "^4.9.5",
39+
"unbuild": "^1.1.2",
40+
"vitest": "^0.29.1"
41+
},
42+
"packageManager": "pnpm@7.28.0",
43+
"dependencies": {
44+
"crypto-es": "^1.2.7",
45+
"ufo": "^1.1.1"
46+
}
47+
}

‎pnpm-lock.yaml

+3,950
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎renovate.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": ["github>unjs/renovate-config"]
3+
}

‎src/index.ts

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { joinURL } from "ufo";
2+
// @ts-expect-error crypto-es missing types
3+
import { HMAC } from "crypto-es/lib/hmac.js";
4+
// @ts-expect-error crypto-es missing types
5+
import { SHA256Algo } from "crypto-es/lib/sha256.js";
6+
// @ts-expect-error crypto-es missing types
7+
import { Hex } from "crypto-es/lib/core.js";
8+
// @ts-expect-error crypto-es missing types
9+
import { Base64 } from "crypto-es/lib/enc-base64.js";
10+
11+
export interface Modifiers {
12+
"min-height"?: string;
13+
"min-width"?: string;
14+
adjust?: string;
15+
auto_rotate?: string;
16+
autoquality?: string;
17+
background?: string;
18+
background_alpha?: string;
19+
blur?: string;
20+
blur_detections?: string;
21+
brightness?: string;
22+
cachebuster?: string;
23+
contrast?: string;
24+
crop?: string;
25+
disable_animation?: string;
26+
dpr?: string;
27+
draw_detections?: string;
28+
enforce_thumbnail?: string;
29+
enlarge?: string;
30+
expires?: string;
31+
extend?: string;
32+
fallback_image_url?: string;
33+
filename?: string;
34+
format?: string;
35+
format_quality?: string;
36+
gradient?: string;
37+
gravity?: string;
38+
height?: string;
39+
jpeg_options?: string;
40+
keep_copyright?: string;
41+
max_bytes?: string;
42+
padding?: string;
43+
page?: string;
44+
pixelate?: string;
45+
png_options?: string;
46+
preset?: string;
47+
quality?: string;
48+
raw?: string;
49+
resize?: string;
50+
resizing_algorithm?: string;
51+
resizing_type?: string;
52+
return_attachment?: string;
53+
rotate?: string;
54+
saturation?: string;
55+
sharpen?: string;
56+
size?: string;
57+
skip_processing?: string;
58+
strip_color_profile?: string;
59+
strip_metadata?: string;
60+
style?: string;
61+
trim?: string;
62+
unsharpening?: string;
63+
video_thumbnail_second?: string;
64+
watermark?: string;
65+
watermark_shadow?: string;
66+
watermark_size?: string;
67+
watermark_text?: string;
68+
watermark_url?: string;
69+
width?: string;
70+
zoom?: string;
71+
}
72+
73+
const modifiersKeyMap: Modifiers = {
74+
"min-height": "mh",
75+
"min-width": "mw",
76+
adjust: "a",
77+
auto_rotate: "ar",
78+
autoquality: "aq",
79+
background: "bg",
80+
background_alpha: "bga",
81+
blur: "bl",
82+
blur_detections: "bd",
83+
brightness: "br",
84+
cachebuster: "cb",
85+
contrast: "co",
86+
crop: "c",
87+
disable_animation: "da",
88+
dpr: "dpr",
89+
draw_detections: "dd",
90+
enforce_thumbnail: "eth",
91+
enlarge: "el",
92+
expires: "exp",
93+
extend: "ex",
94+
fallback_image_url: "fiu",
95+
filename: "fn",
96+
format: "f",
97+
format_quality: "fq",
98+
gradient: "gr",
99+
gravity: "g",
100+
height: "h",
101+
jpeg_options: "jpgo",
102+
keep_copyright: "kcr",
103+
max_bytes: "mb",
104+
padding: "pd",
105+
page: "pg",
106+
pixelate: "pix",
107+
png_options: "pngo",
108+
preset: "pr",
109+
quality: "q",
110+
raw: "raw",
111+
resize: "rs",
112+
resizing_algorithm: "ra",
113+
resizing_type: "rt",
114+
return_attachment: "att",
115+
rotate: "rot",
116+
saturation: "sa",
117+
sharpen: "sh",
118+
size: "s",
119+
skip_processing: "skp",
120+
strip_color_profile: "scp",
121+
strip_metadata: "sm",
122+
style: "st",
123+
trim: "t",
124+
unsharpening: "ush",
125+
video_thumbnail_second: "vts",
126+
watermark: "wm",
127+
watermark_shadow: "wmsh",
128+
watermark_size: "wms",
129+
watermark_text: "wmt",
130+
watermark_url: "wmu",
131+
width: "w",
132+
zoom: "z",
133+
};
134+
135+
const isBrowser = typeof window !== "undefined";
136+
137+
export const toBase64 = (value: string) =>
138+
isBrowser
139+
? window.btoa(value)
140+
: Buffer.from(value, "binary").toString("base64");
141+
142+
export const trimBase64 = (value: string) =>
143+
value.replaceAll("=", "").replaceAll("+", "-").replaceAll("/", "_");
144+
145+
export const generateHmac = (secret: string, salt: string) => {
146+
const hmac = HMAC.create(SHA256Algo, Hex.parse(secret));
147+
hmac.update(Hex.parse(salt));
148+
149+
return hmac;
150+
};
151+
152+
export const generateSignature = (hmac: any, path: string): string => {
153+
const hash = Base64.stringify(hmac.finalize(path));
154+
155+
return trimBase64(hash);
156+
};
157+
158+
export const modifiersGenerator = (modifiers: Modifiers) => {
159+
return Object.entries(Object.fromEntries(Object.entries(modifiers).sort()))
160+
.map((modifier) => {
161+
return `${(modifiersKeyMap as Record<string, string>)[modifier[0]]}:${
162+
modifier[1]
163+
}`;
164+
})
165+
.join("/");
166+
};
167+
168+
export function getImageUrl(
169+
imageSource: string,
170+
options: {
171+
secret: string;
172+
salt: string;
173+
baseURL: string;
174+
modifiers: Modifiers;
175+
}
176+
) {
177+
const encodedUrl = trimBase64(toBase64(imageSource));
178+
const operations = modifiersGenerator(options.modifiers);
179+
const encodedUrlWithModifiers = joinURL("/", operations, encodedUrl);
180+
const hmac = generateHmac(options.secret, options.salt);
181+
const signature = generateSignature(hmac, encodedUrlWithModifiers);
182+
183+
return joinURL(options.baseURL, signature, encodedUrlWithModifiers);
184+
}

‎test/index.test.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { expect, describe, test } from "vitest";
2+
import {
3+
toBase64,
4+
trimBase64,
5+
modifiersGenerator,
6+
generateSignature,
7+
generateHmac,
8+
getImageUrl,
9+
} from "../src";
10+
11+
const imgproxySecret = "6F787973686F70";
12+
const imgproxySalt = "10D16F7A61";
13+
const imageUrl =
14+
"http://object-storage:9000/oxyshop-nextgen/media/image/e9/77/151f15b8f9af244f7f61775ddb3d.png";
15+
16+
describe("@misaon/imgproxy", () => {
17+
test("toBase64", () => {
18+
expect(toBase64(imageUrl)).toBe(
19+
"aHR0cDovL29iamVjdC1zdG9yYWdlOjkwMDAvb3h5c2hvcC1uZXh0Z2VuL21lZGlhL2ltYWdlL2U5Lzc3LzE1MWYxNWI4ZjlhZjI0NGY3ZjYxNzc1ZGRiM2QucG5n"
20+
);
21+
});
22+
23+
test("trimBase64", () => {
24+
expect(trimBase64(imageUrl)).toBe(
25+
"http:__object-storage:9000_oxyshop-nextgen_media_image_e9_77_151f15b8f9af244f7f61775ddb3d.png"
26+
);
27+
});
28+
29+
test("modifiersGenerator", () => {
30+
expect(modifiersGenerator({ width: "768", height: "0" })).toBe("h:0/w:768");
31+
});
32+
33+
test("generateSignature", () => {
34+
expect(
35+
generateSignature(
36+
generateHmac(imgproxySecret, imgproxySalt),
37+
"/h:0/w:768/aHR0cDovL29iamVjdC1zdG9yYWdlOjkwMDAvb3h5c2hvcC1uZXh0Z2VuL21lZGlhL2ltYWdlL2U5Lzc3LzE1MWYxNWI4ZjlhZjI0NGY3ZjYxNzc1ZGRiM2QucG5n"
38+
)
39+
).toBe("Cx5iQmOHCbKutYvXOMuWX-wdEbifMFwgrMXtNJEPgow");
40+
});
41+
42+
test("getImageUrl", () => {
43+
expect(
44+
getImageUrl(imageUrl, {
45+
secret: imgproxySecret,
46+
salt: imgproxySalt,
47+
baseURL: "/imgproxy",
48+
modifiers: {
49+
width: "768",
50+
height: "0",
51+
},
52+
})
53+
).toBe(
54+
"/imgproxy/Cx5iQmOHCbKutYvXOMuWX-wdEbifMFwgrMXtNJEPgow/h:0/w:768/aHR0cDovL29iamVjdC1zdG9yYWdlOjkwMDAvb3h5c2hvcC1uZXh0Z2VuL21lZGlhL2ltYWdlL2U5Lzc3LzE1MWYxNWI4ZjlhZjI0NGY3ZjYxNzc1ZGRiM2QucG5n"
55+
);
56+
});
57+
});

‎tsconfig.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ESNext",
4+
"module": "ESNext",
5+
"moduleResolution": "Node",
6+
"esModuleInterop": true,
7+
"strict": true
8+
},
9+
"include": ["src"]
10+
}

0 commit comments

Comments
 (0)
Please sign in to comment.