Skip to content

Commit d67c6ab

Browse files
Merge pull request #4 from RyosukeDTomita/feature/decode-text
feat: encode decode memo
2 parents dc80a1a + db42533 commit d67c6ab

File tree

7 files changed

+53
-35
lines changed

7 files changed

+53
-35
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
URLのクエリパラメータにメモの内容を保存するブラウザベースのメモアプリケーションです。サーバー不要で動作し、URLを保存・共有するだけで複数デバイス間でのメモ同期や他のユーザーとの共有が可能です。
1818

19-
https://sigma.github.io/url-query-memo/
19+
https://ryosukedtomita.github.io/url-query-memo
2020

2121
---
2222

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { decodeTextFromUrl } from '@/utils/urlDecoder';
2+
import { encodeTextToUrl } from '@/utils/urlEncoder';
23

34
describe('decodeTextFromUrl', () => {
4-
it('should decode Base64 to simple text', () => {
5-
const encoded = 'SGVsbG8gV29ybGQ=';
5+
it('should decode compressed text correctly', () => {
6+
const originalText = 'Hello World';
7+
const encoded = encodeTextToUrl(originalText);
68
const decoded = decodeTextFromUrl(encoded);
7-
expect(decoded).toBe('Hello World');
9+
expect(decoded).toBe(originalText);
810
});
911

1012
it('should handle empty string', () => {
@@ -15,40 +17,42 @@ describe('decodeTextFromUrl', () => {
1517

1618
it('should decode Japanese text correctly', () => {
1719
const originalText = 'こんにちは世界';
18-
const encoded = Buffer.from(originalText, 'utf-8').toString('base64');
20+
const encoded = encodeTextToUrl(originalText);
1921
const decoded = decodeTextFromUrl(encoded);
2022
expect(decoded).toBe(originalText);
2123
});
2224

2325
it('should handle special characters', () => {
2426
const originalText = '!@#$%^&*()_+-=[]{}|;\':",./<>?';
25-
const encoded = Buffer.from(originalText, 'utf-8').toString('base64');
27+
const encoded = encodeTextToUrl(originalText);
2628
const decoded = decodeTextFromUrl(encoded);
2729
expect(decoded).toBe(originalText);
2830
});
2931

3032
it('should handle multi-line text', () => {
3133
const originalText = 'Line 1\nLine 2\nLine 3';
32-
const encoded = Buffer.from(originalText, 'utf-8').toString('base64');
34+
const encoded = encodeTextToUrl(originalText);
3335
const decoded = decodeTextFromUrl(encoded);
3436
expect(decoded).toBe(originalText);
3537
});
3638

3739
it('should decode emoji correctly', () => {
3840
const originalText = '😀😃😄😁😆';
39-
const encoded = Buffer.from(originalText, 'utf-8').toString('base64');
41+
const encoded = encodeTextToUrl(originalText);
4042
const decoded = decodeTextFromUrl(encoded);
4143
expect(decoded).toBe(originalText);
4244
});
4345

44-
it('should handle invalid Base64 gracefully', () => {
45-
const invalidEncoded = 'This is not valid Base64!@#$%';
46-
expect(() => decodeTextFromUrl(invalidEncoded)).toThrow('Invalid encoded text');
46+
it('should handle invalid compressed data gracefully', () => {
47+
// LZ-string returns null for invalid data, which should result in an error
48+
const invalidEncoded = 'This is not valid compressed data!@#$%';
49+
expect(() => decodeTextFromUrl(invalidEncoded)).toThrowError('Invalid compressed data');
4750
});
4851

49-
it('should handle corrupted Base64', () => {
50-
const corruptedEncoded = 'SGVsbG8gV29ybGQ'; // Missing padding
51-
const decoded = decodeTextFromUrl(corruptedEncoded);
52-
expect(decoded).toBe('Hello World');
52+
it('should handle very long text', () => {
53+
const originalText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '.repeat(50);
54+
const encoded = encodeTextToUrl(originalText);
55+
const decoded = decodeTextFromUrl(encoded);
56+
expect(decoded).toBe(originalText);
5357
});
5458
});
Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { encodeTextToUrl } from '@/utils/urlEncoder';
2+
import { decodeTextFromUrl } from '@/utils/urlDecoder';
23

34
describe('encodeTextToUrl', () => {
4-
it('should encode simple text to Base64', () => {
5+
it('should encode simple text', () => {
56
const text = 'Hello World';
67
const encoded = encodeTextToUrl(text);
7-
expect(encoded).toBe('SGVsbG8gV29ybGQ=');
8+
const decoded = decodeTextFromUrl(encoded);
9+
expect(decoded).toBe(text);
810
});
911

1012
it('should handle empty string', () => {
@@ -16,40 +18,43 @@ describe('encodeTextToUrl', () => {
1618
it('should encode Japanese text correctly', () => {
1719
const text = 'こんにちは世界';
1820
const encoded = encodeTextToUrl(text);
19-
const decoded = Buffer.from(encoded, 'base64').toString('utf-8');
21+
const decoded = decodeTextFromUrl(encoded);
2022
expect(decoded).toBe(text);
2123
});
2224

2325
it('should handle special characters', () => {
2426
const text = '!@#$%^&*()_+-=[]{}|;\':",./<>?';
2527
const encoded = encodeTextToUrl(text);
26-
const decoded = Buffer.from(encoded, 'base64').toString('utf-8');
28+
const decoded = decodeTextFromUrl(encoded);
2729
expect(decoded).toBe(text);
2830
});
2931

3032
it('should handle multi-line text', () => {
3133
const text = 'Line 1\nLine 2\nLine 3';
3234
const encoded = encodeTextToUrl(text);
33-
const decoded = Buffer.from(encoded, 'base64').toString('utf-8');
35+
const decoded = decodeTextFromUrl(encoded);
3436
expect(decoded).toBe(text);
3537
});
3638

3739
it('should encode emoji correctly', () => {
3840
const text = '😀😃😄😁😆';
3941
const encoded = encodeTextToUrl(text);
40-
const decoded = Buffer.from(encoded, 'base64').toString('utf-8');
42+
const decoded = decodeTextFromUrl(encoded);
4143
expect(decoded).toBe(text);
4244
});
4345

44-
it('should handle very long text', () => {
46+
it('should handle very long text with compression', () => {
4547
const text = 'a'.repeat(1000);
4648
const encoded = encodeTextToUrl(text);
47-
const decoded = Buffer.from(encoded, 'base64').toString('utf-8');
49+
const decoded = decodeTextFromUrl(encoded);
4850
expect(decoded).toBe(text);
51+
// Compression should make it much smaller than base64
52+
expect(encoded.length).toBeLessThan(text.length);
4953
});
5054

5155
it('should throw error for text exceeding URL limit', () => {
52-
const text = 'a'.repeat(3000);
56+
// Create a very large text that will exceed 2048 chars when compressed
57+
const text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '.repeat(500) + 'a'.repeat(10000);
5358
expect(() => encodeTextToUrl(text)).toThrow('Text is too long to be safely encoded in URL');
5459
});
5560
});

url-memo-app/package-lock.json

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

url-memo-app/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"test:watch": "jest --watch"
1212
},
1313
"dependencies": {
14+
"@types/lz-string": "^1.3.34",
15+
"lz-string": "^1.5.0",
1416
"next": "15.4.2",
1517
"react": "19.1.0",
1618
"react-dom": "19.1.0"

url-memo-app/utils/urlDecoder.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1+
import LZString from 'lz-string';
2+
13
export function decodeTextFromUrl(urlParam: string): string {
24
if (urlParam === '') {
35
return '';
46
}
57

68
try {
7-
// Check if the string is valid Base64
8-
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
9-
if (!base64Regex.test(urlParam)) {
9+
const decompressed = LZString.decompressFromEncodedURIComponent(urlParam);
10+
if (decompressed === null) {
1011
throw new Error('Invalid encoded text');
1112
}
12-
13-
const decoded = Buffer.from(urlParam, 'base64').toString('utf-8');
14-
return decoded;
13+
return decompressed;
1514
} catch (error) {
1615
throw new Error('Invalid encoded text');
1716
}

url-memo-app/utils/urlEncoder.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
import LZString from 'lz-string';
2+
13
const MAX_URL_LENGTH = 2048;
24

35
export function encodeTextToUrl(text: string): string {
46
if (text === '') {
57
return '';
68
}
79

8-
const base64Encoded = Buffer.from(text, 'utf-8').toString('base64');
10+
const compressed = LZString.compressToEncodedURIComponent(text);
911

10-
if (base64Encoded.length > MAX_URL_LENGTH) {
12+
if (compressed.length > MAX_URL_LENGTH) {
1113
throw new Error('Text is too long to be safely encoded in URL');
1214
}
1315

14-
return base64Encoded;
16+
return compressed;
1517
}

0 commit comments

Comments
 (0)