1
0

links.test.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { describe, it, expect } from 'vitest';
  2. import {
  3. parseLink,
  4. detectProtocol,
  5. buildVless,
  6. buildVmess,
  7. buildTrojan,
  8. buildShadowsocks,
  9. } from './links';
  10. describe('detectProtocol', () => {
  11. it('recognises supported schemes', () => {
  12. expect(detectProtocol('vless://x')).toBe('vless');
  13. expect(detectProtocol('vmess://x')).toBe('vmess');
  14. expect(detectProtocol('trojan://x')).toBe('trojan');
  15. expect(detectProtocol('ss://x')).toBe('ss');
  16. expect(detectProtocol('https://x')).toBeNull();
  17. });
  18. });
  19. describe('parseLink — vless', () => {
  20. const link =
  21. 'vless://[email protected]:443?security=reality&pbk=abc&sid=ff&sni=www.microsoft.com&fp=chrome&flow=xtls-rprx-vision&type=tcp#My%20Node';
  22. it('extracts address, port, credential, name, and params', () => {
  23. const r = parseLink(link);
  24. expect(r.protocol).toBe('vless');
  25. expect(r.address).toBe('example.com');
  26. expect(r.port).toBe(443);
  27. expect(r.credential).toBe('11111111-2222-3333-4444-555555555555');
  28. expect(r.name).toBe('My Node');
  29. expect(r.params.security).toBe('reality');
  30. expect(r.params.pbk).toBe('abc');
  31. expect(r.params.flow).toBe('xtls-rprx-vision');
  32. });
  33. });
  34. describe('parseLink — trojan', () => {
  35. it('parses password and query params', () => {
  36. const r = parseLink('trojan://p%[email protected]:8443?sni=a.com&type=ws&path=%2Fws#t');
  37. expect(r.protocol).toBe('trojan');
  38. expect(r.credential).toBe('p@ss');
  39. expect(r.address).toBe('1.2.3.4');
  40. expect(r.port).toBe(8443);
  41. expect(r.params.path).toBe('/ws');
  42. expect(r.name).toBe('t');
  43. });
  44. });
  45. describe('parseLink — vmess', () => {
  46. it('decodes the base64 JSON payload', () => {
  47. const link = buildVmess({
  48. ps: 'tehran',
  49. add: 'host.example',
  50. port: 443,
  51. id: 'uuid-123',
  52. net: 'ws',
  53. tls: 'tls',
  54. host: 'cdn.example',
  55. path: '/v2',
  56. });
  57. const r = parseLink(link);
  58. expect(r.protocol).toBe('vmess');
  59. expect(r.name).toBe('tehran');
  60. expect(r.address).toBe('host.example');
  61. expect(r.port).toBe(443);
  62. expect(r.credential).toBe('uuid-123');
  63. expect(r.params.net).toBe('ws');
  64. expect(r.params.path).toBe('/v2');
  65. });
  66. });
  67. describe('parseLink — shadowsocks', () => {
  68. it('parses SIP002 with base64 userinfo', () => {
  69. const link = buildShadowsocks({
  70. method: 'aes-256-gcm',
  71. password: 's3cret',
  72. address: '192.0.2.1',
  73. port: 8388,
  74. name: 'ss-node',
  75. });
  76. const r = parseLink(link);
  77. expect(r.protocol).toBe('ss');
  78. expect(r.credential).toBe('aes-256-gcm:s3cret');
  79. expect(r.address).toBe('192.0.2.1');
  80. expect(r.port).toBe(8388);
  81. expect(r.name).toBe('ss-node');
  82. });
  83. it('parses the legacy fully-base64 form', () => {
  84. // base64("aes-128-gcm:[email protected]:8388")
  85. const legacy =
  86. 'ss://' + Buffer.from('aes-128-gcm:[email protected]:8388').toString('base64') + '#legacy';
  87. const r = parseLink(legacy);
  88. expect(r.credential).toBe('aes-128-gcm:pw');
  89. expect(r.address).toBe('example.com');
  90. expect(r.port).toBe(8388);
  91. expect(r.name).toBe('legacy');
  92. });
  93. });
  94. describe('build → parse round-trips', () => {
  95. it('vless round-trips', () => {
  96. const link = buildVless({
  97. credential: 'uuid-abc',
  98. address: 'example.com',
  99. port: 443,
  100. params: { security: 'reality', sni: 'a.com', flow: 'xtls-rprx-vision' },
  101. name: 'node 1',
  102. });
  103. const r = parseLink(link);
  104. expect(r.credential).toBe('uuid-abc');
  105. expect(r.address).toBe('example.com');
  106. expect(r.port).toBe(443);
  107. expect(r.params.sni).toBe('a.com');
  108. expect(r.name).toBe('node 1');
  109. });
  110. it('trojan round-trips', () => {
  111. const r = parseLink(buildTrojan({ credential: 'pw', address: 'h.com', port: 443, name: 'x' }));
  112. expect(r.credential).toBe('pw');
  113. expect(r.port).toBe(443);
  114. });
  115. it('ss round-trips', () => {
  116. const r = parseLink(
  117. buildShadowsocks({ method: 'chacha20-poly1305', password: 'p', address: 'h', port: 8388 }),
  118. );
  119. expect(r.credential).toBe('chacha20-poly1305:p');
  120. });
  121. });
  122. describe('parseLink — errors', () => {
  123. it('throws on unsupported scheme', () => {
  124. expect(() => parseLink('http://example.com')).toThrow(/Unsupported/);
  125. });
  126. });