HeaderMapEditor.stories.tsx 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { useState } from 'react';
  2. import type { Meta, StoryObj } from '@storybook/react-vite';
  3. import HeaderMapEditor, { type HeaderMapValue } from './HeaderMapEditor';
  4. const meta = {
  5. title: 'Form/HeaderMapEditor',
  6. component: HeaderMapEditor,
  7. tags: ['autodocs'],
  8. parameters: {
  9. layout: 'padded',
  10. docs: {
  11. description: {
  12. component:
  13. 'Row-based editor for Xray HTTP header maps, used in the inbound/outbound stream forms. Mode `v1` emits one string per header name (WS / HTTPUpgrade / Hysteria masquerade); mode `v2` emits string arrays so headers can repeat (TCP HTTP camouflage request/response).',
  14. },
  15. },
  16. },
  17. argTypes: {
  18. mode: {
  19. description:
  20. 'Wire shape: `v1` = string per name, `v2` = string[] per name (repeatable headers).',
  21. },
  22. value: {
  23. description:
  24. 'Header map in the wire shape matching `mode`; converted to editable rows internally.',
  25. },
  26. onChange: {
  27. description: 'Called with the rebuilt wire-shape map after every row edit, add, or remove.',
  28. },
  29. },
  30. } satisfies Meta<typeof HeaderMapEditor>;
  31. export default meta;
  32. type Story = StoryObj<typeof meta>;
  33. export const Empty: Story = {
  34. args: { mode: 'v1', onChange: () => undefined },
  35. };
  36. export const WsHostHeaders: Story = {
  37. args: {
  38. mode: 'v1',
  39. value: {
  40. Host: 'cdn.example.com',
  41. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
  42. },
  43. onChange: () => undefined,
  44. },
  45. };
  46. export const TcpCamouflageRequest: Story = {
  47. args: {
  48. mode: 'v2',
  49. value: {
  50. Accept: ['text/html,application/xhtml+xml', 'application/json'],
  51. 'Accept-Encoding': ['gzip, deflate'],
  52. Connection: ['keep-alive'],
  53. Pragma: ['no-cache'],
  54. },
  55. onChange: () => undefined,
  56. },
  57. };
  58. function WireShapeDemo() {
  59. const [value, setValue] = useState<HeaderMapValue>({
  60. Accept: ['text/html', 'application/json'],
  61. 'X-Forwarded-For': ['203.0.113.7'],
  62. });
  63. return (
  64. <div style={{ maxWidth: 560 }}>
  65. <HeaderMapEditor mode="v2" value={value} onChange={setValue} />
  66. <pre
  67. style={{
  68. marginTop: 16,
  69. padding: 12,
  70. borderRadius: 8,
  71. background: 'rgba(128, 128, 128, 0.12)',
  72. }}
  73. >
  74. {JSON.stringify(value ?? {}, null, 2)}
  75. </pre>
  76. </div>
  77. );
  78. }
  79. export const LiveWireShape: Story = {
  80. args: { mode: 'v2', onChange: () => undefined },
  81. render: () => <WireShapeDemo />,
  82. };