@remember-web/sse 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +238 -0
  2. package/dist/src/core/BaseSseClient.cjs.js +97 -0
  3. package/dist/src/core/BaseSseClient.cjs.js.map +1 -0
  4. package/dist/src/core/BaseSseClient.d.ts +33 -0
  5. package/dist/src/core/BaseSseClient.d.ts.map +1 -0
  6. package/dist/src/core/BaseSseClient.esm.js +89 -0
  7. package/dist/src/core/BaseSseClient.esm.js.map +1 -0
  8. package/dist/src/core/SseClient.cjs.js +382 -0
  9. package/dist/src/core/SseClient.cjs.js.map +1 -0
  10. package/dist/src/core/SseClient.d.ts +24 -0
  11. package/dist/src/core/SseClient.d.ts.map +1 -0
  12. package/dist/src/core/SseClient.esm.js +370 -0
  13. package/dist/src/core/SseClient.esm.js.map +1 -0
  14. package/dist/src/core/backoff.cjs.js +30 -0
  15. package/dist/src/core/backoff.cjs.js.map +1 -0
  16. package/dist/src/core/backoff.d.ts +12 -0
  17. package/dist/src/core/backoff.d.ts.map +1 -0
  18. package/dist/src/core/backoff.esm.js +28 -0
  19. package/dist/src/core/backoff.esm.js.map +1 -0
  20. package/dist/src/core/emitter.cjs.js +57 -0
  21. package/dist/src/core/emitter.cjs.js.map +1 -0
  22. package/dist/src/core/emitter.d.ts +14 -0
  23. package/dist/src/core/emitter.d.ts.map +1 -0
  24. package/dist/src/core/emitter.esm.js +51 -0
  25. package/dist/src/core/emitter.esm.js.map +1 -0
  26. package/dist/src/core/eventNames.cjs.js +64 -0
  27. package/dist/src/core/eventNames.cjs.js.map +1 -0
  28. package/dist/src/core/eventNames.d.ts +31 -0
  29. package/dist/src/core/eventNames.d.ts.map +1 -0
  30. package/dist/src/core/eventNames.esm.js +54 -0
  31. package/dist/src/core/eventNames.esm.js.map +1 -0
  32. package/dist/src/core/retryWait.cjs.js +20 -0
  33. package/dist/src/core/retryWait.cjs.js.map +1 -0
  34. package/dist/src/core/retryWait.d.ts +6 -0
  35. package/dist/src/core/retryWait.d.ts.map +1 -0
  36. package/dist/src/core/retryWait.esm.js +18 -0
  37. package/dist/src/core/retryWait.esm.js.map +1 -0
  38. package/dist/src/core/statusMachine.cjs.js +56 -0
  39. package/dist/src/core/statusMachine.cjs.js.map +1 -0
  40. package/dist/src/core/statusMachine.d.ts +9 -0
  41. package/dist/src/core/statusMachine.d.ts.map +1 -0
  42. package/dist/src/core/statusMachine.esm.js +54 -0
  43. package/dist/src/core/statusMachine.esm.js.map +1 -0
  44. package/dist/src/core/stream.cjs.js +136 -0
  45. package/dist/src/core/stream.cjs.js.map +1 -0
  46. package/dist/src/core/stream.d.ts +42 -0
  47. package/dist/src/core/stream.d.ts.map +1 -0
  48. package/dist/src/core/stream.esm.js +128 -0
  49. package/dist/src/core/stream.esm.js.map +1 -0
  50. package/dist/src/core/ticket.cjs.js +237 -0
  51. package/dist/src/core/ticket.cjs.js.map +1 -0
  52. package/dist/src/core/ticket.d.ts +31 -0
  53. package/dist/src/core/ticket.d.ts.map +1 -0
  54. package/dist/src/core/ticket.esm.js +220 -0
  55. package/dist/src/core/ticket.esm.js.map +1 -0
  56. package/dist/src/core/types.cjs.js +54 -0
  57. package/dist/src/core/types.cjs.js.map +1 -0
  58. package/dist/src/core/types.d.ts +131 -0
  59. package/dist/src/core/types.d.ts.map +1 -0
  60. package/dist/src/core/types.esm.js +50 -0
  61. package/dist/src/core/types.esm.js.map +1 -0
  62. package/dist/src/core/wire.cjs.js +29 -0
  63. package/dist/src/core/wire.cjs.js.map +1 -0
  64. package/dist/src/core/wire.d.ts +7 -0
  65. package/dist/src/core/wire.d.ts.map +1 -0
  66. package/dist/src/core/wire.esm.js +23 -0
  67. package/dist/src/core/wire.esm.js.map +1 -0
  68. package/dist/src/createSseClient.cjs.js +30 -0
  69. package/dist/src/createSseClient.cjs.js.map +1 -0
  70. package/dist/src/createSseClient.d.ts +8 -0
  71. package/dist/src/createSseClient.d.ts.map +1 -0
  72. package/dist/src/createSseClient.esm.js +28 -0
  73. package/dist/src/createSseClient.esm.js.map +1 -0
  74. package/dist/src/index.cjs.js +10 -0
  75. package/dist/src/index.cjs.js.map +1 -0
  76. package/dist/src/index.d.ts +6 -0
  77. package/dist/src/index.d.ts.map +1 -0
  78. package/dist/src/index.esm.js +3 -0
  79. package/dist/src/index.esm.js.map +1 -0
  80. package/dist/src/react/createSseContext.cjs.js +130 -0
  81. package/dist/src/react/createSseContext.cjs.js.map +1 -0
  82. package/dist/src/react/createSseContext.d.ts +32 -0
  83. package/dist/src/react/createSseContext.d.ts.map +1 -0
  84. package/dist/src/react/createSseContext.esm.js +122 -0
  85. package/dist/src/react/createSseContext.esm.js.map +1 -0
  86. package/dist/src/react/index.cjs.js +8 -0
  87. package/dist/src/react/index.cjs.js.map +1 -0
  88. package/dist/src/react/index.d.ts +3 -0
  89. package/dist/src/react/index.d.ts.map +1 -0
  90. package/dist/src/react/index.esm.js +2 -0
  91. package/dist/src/react/index.esm.js.map +1 -0
  92. package/dist/src/testing/doubles.d.ts +50 -0
  93. package/dist/src/testing/doubles.d.ts.map +1 -0
  94. package/dist/src/testing/harness.d.ts +43 -0
  95. package/dist/src/testing/harness.d.ts.map +1 -0
  96. package/dist/src/testing/index.d.ts +3 -0
  97. package/dist/src/testing/index.d.ts.map +1 -0
  98. package/dist/src/worker/WorkerSseClient.cjs.js +240 -0
  99. package/dist/src/worker/WorkerSseClient.cjs.js.map +1 -0
  100. package/dist/src/worker/WorkerSseClient.d.ts +23 -0
  101. package/dist/src/worker/WorkerSseClient.d.ts.map +1 -0
  102. package/dist/src/worker/WorkerSseClient.esm.js +228 -0
  103. package/dist/src/worker/WorkerSseClient.esm.js.map +1 -0
  104. package/dist/src/worker/hub.d.ts +19 -0
  105. package/dist/src/worker/hub.d.ts.map +1 -0
  106. package/dist/src/worker/portRegistry.d.ts +41 -0
  107. package/dist/src/worker/portRegistry.d.ts.map +1 -0
  108. package/dist/src/worker/protocol.d.ts +63 -0
  109. package/dist/src/worker/protocol.d.ts.map +1 -0
  110. package/dist/src/worker/sse.worker.d.ts +2 -0
  111. package/dist/src/worker/sse.worker.d.ts.map +1 -0
  112. package/dist/src/worker/ticketDelegate.d.ts +16 -0
  113. package/dist/src/worker/ticketDelegate.d.ts.map +1 -0
  114. package/dist/src/worker/workerConnection.d.ts +28 -0
  115. package/dist/src/worker/workerConnection.d.ts.map +1 -0
  116. package/dist/worker/sse.worker.js +1865 -0
  117. package/dist/worker/sse.worker.js.map +1 -0
  118. package/package.json +87 -0
  119. package/src/core/BaseSseClient.test.ts +142 -0
  120. package/src/core/BaseSseClient.ts +103 -0
  121. package/src/core/SseClient.connection.test.ts +863 -0
  122. package/src/core/SseClient.scenarios.test.ts +164 -0
  123. package/src/core/SseClient.test.ts +63 -0
  124. package/src/core/SseClient.ts +310 -0
  125. package/src/core/backoff.test.ts +43 -0
  126. package/src/core/backoff.ts +39 -0
  127. package/src/core/emitter.ts +56 -0
  128. package/src/core/eventNames.ts +53 -0
  129. package/src/core/retryWait.ts +18 -0
  130. package/src/core/statusMachine.ts +56 -0
  131. package/src/core/stream.ts +141 -0
  132. package/src/core/ticket.ts +146 -0
  133. package/src/core/types.ts +182 -0
  134. package/src/core/wire.ts +23 -0
  135. package/src/createSseClient.test.ts +82 -0
  136. package/src/createSseClient.ts +34 -0
  137. package/src/index.ts +21 -0
  138. package/src/react/SseProvider.test.tsx +129 -0
  139. package/src/react/createSseContext.test.ts +47 -0
  140. package/src/react/createSseContext.tsx +140 -0
  141. package/src/react/index.ts +2 -0
  142. package/src/react/useSseEvent.test.tsx +71 -0
  143. package/src/react/useSseStatus.test.tsx +40 -0
  144. package/src/testing/doubles.ts +128 -0
  145. package/src/testing/harness.ts +82 -0
  146. package/src/testing/index.ts +3 -0
  147. package/src/worker/SseClientLike.contract.test.ts +270 -0
  148. package/src/worker/WorkerSseClient.scenarios.test.ts +179 -0
  149. package/src/worker/WorkerSseClient.test.ts +377 -0
  150. package/src/worker/WorkerSseClient.ts +190 -0
  151. package/src/worker/hub.connection.test.ts +1023 -0
  152. package/src/worker/hub.test.ts +213 -0
  153. package/src/worker/hub.ts +120 -0
  154. package/src/worker/portRegistry.test.ts +59 -0
  155. package/src/worker/portRegistry.ts +86 -0
  156. package/src/worker/protocol.ts +80 -0
  157. package/src/worker/sse.worker.ts +19 -0
  158. package/src/worker/ticketDelegate.test.ts +99 -0
  159. package/src/worker/ticketDelegate.ts +80 -0
  160. package/src/worker/workerConnection.ts +134 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.worker.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@remember-web/sse",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Remember Web SSE Client SDK",
5
+ "homepage": "https://dramancompany.github.io/rwp",
6
+ "author": "Remember",
7
+ "license": "MIT",
8
+ "sideEffects": false,
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/dramancompany/rwp.git"
12
+ },
13
+ "publishConfig": {
14
+ "registry": "https://registry.npmjs.org/"
15
+ },
16
+ "scripts": {
17
+ "build": "rollup -c && rollup -c rollup.config.worker.mjs && yarn fix:dts-alias",
18
+ "fix:dts-alias": "yarn tsc-alias -p tsconfig.alias.json",
19
+ "type-check": "tsc -p ./tsconfig.cli.json",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest watch"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src"
26
+ ],
27
+ "source": "./src/index.ts",
28
+ "main": "./dist/src/index.cjs.js",
29
+ "module": "./dist/src/index.esm.js",
30
+ "types": "./dist/src/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/src/index.d.ts",
34
+ "import": "./dist/src/index.esm.js",
35
+ "require": "./dist/src/index.cjs.js"
36
+ },
37
+ "./react": {
38
+ "types": "./dist/src/react/index.d.ts",
39
+ "import": "./dist/src/react/index.esm.js",
40
+ "require": "./dist/src/react/index.cjs.js"
41
+ },
42
+ "./worker": "./dist/worker/sse.worker.js"
43
+ },
44
+ "dependencies": {
45
+ "@babel/runtime": "^7.24.4",
46
+ "@remember-web/shared": "^0.1.2"
47
+ },
48
+ "devDependencies": {
49
+ "@babel/core": "^7.24.4",
50
+ "@babel/plugin-transform-runtime": "^7.24.3",
51
+ "@babel/preset-env": "^7.24.4",
52
+ "@babel/preset-react": "^7.24.1",
53
+ "@babel/preset-typescript": "^7.24.1",
54
+ "@rollup/plugin-babel": "^6.0.4",
55
+ "@rollup/plugin-commonjs": "^28.0.6",
56
+ "@rollup/plugin-node-resolve": "^16.0.1",
57
+ "@testing-library/dom": "^10.4.0",
58
+ "@testing-library/react": "^16.3.0",
59
+ "@types/react": "^18.2.77",
60
+ "@types/react-dom": "^18.2.25",
61
+ "jsdom": "^26.1.0",
62
+ "react": "^18.2.0",
63
+ "react-dom": "^18.2.0",
64
+ "rollup": "^4.44.1",
65
+ "rollup-plugin-peer-deps-external": "^2.2.4",
66
+ "rollup-plugin-preserve-directives": "^0.4.0",
67
+ "rollup-plugin-typescript2": "^0.36.0",
68
+ "tsc-alias": "^1.8.8",
69
+ "tsconfig": "0.0.2",
70
+ "tslib": "^2.6.2",
71
+ "typescript": "^5.4.5",
72
+ "vite-tsconfig-paths": "^5.1.4",
73
+ "vitest": "^3.2.4"
74
+ },
75
+ "peerDependencies": {
76
+ "react": "^18.0.0 || ^19.0.0",
77
+ "typescript": ">=5.4"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "react": {
81
+ "optional": true
82
+ },
83
+ "typescript": {
84
+ "optional": true
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,142 @@
1
+ // @vitest-environment jsdom — attachAutoResume가 document·window에 배선하는 유일한 표면이라 이 파일만 jsdom입니다.
2
+ // 나머지 공유 표면(on·onStatus)은 SseClientLike.contract.test.ts가 두 구현으로 고정합니다
3
+ import { describe, expect, it, vi } from 'vitest';
4
+
5
+ import { createSseClient } from '../createSseClient';
6
+ import {
7
+ createHarness,
8
+ FakeEventSource,
9
+ lastSource,
10
+ openConnection,
11
+ setupFakeTimers,
12
+ } from '../testing';
13
+
14
+ setupFakeTimers();
15
+
16
+ const createClientSpy = () => {
17
+ let connects = 0;
18
+ const client = createSseClient({
19
+ baseUrl: 'https://sse.example.com',
20
+ getAuthToken: () => 'token-1',
21
+ });
22
+ // connect 자체를 재우고 호출만 셉니다 — 여기서 볼 것은 "언제 불리는가"뿐입니다
23
+ client.connect = () => {
24
+ connects += 1;
25
+ };
26
+
27
+ return { client, connects: () => connects };
28
+ };
29
+
30
+ const setVisibility = (state: DocumentVisibilityState): void => {
31
+ Object.defineProperty(document, 'visibilityState', {
32
+ value: state,
33
+ configurable: true,
34
+ });
35
+ document.dispatchEvent(new Event('visibilitychange'));
36
+ };
37
+
38
+ const showPage = (persisted: boolean): void => {
39
+ window.dispatchEvent(new PageTransitionEvent('pageshow', { persisted }));
40
+ };
41
+
42
+ describe('attachAutoResume — 탭 복귀 배선', () => {
43
+ it('탭이 다시 보이면 connect를 부릅니다 — 얼어붙은 사이 수거됐어도 돌아옵니다', () => {
44
+ const { client, connects } = createClientSpy();
45
+ client.attachAutoResume();
46
+
47
+ setVisibility('visible');
48
+
49
+ expect(connects()).toBe(1);
50
+ });
51
+
52
+ it('탭이 숨을 때는 부르지 않습니다', () => {
53
+ const { client, connects } = createClientSpy();
54
+ client.attachAutoResume();
55
+
56
+ setVisibility('hidden');
57
+
58
+ expect(connects()).toBe(0);
59
+ });
60
+
61
+ // bfcache는 SharedWorker 포트를 evict하므로 visibilitychange로는 알 수 없습니다
62
+ it('bfcache에서 돌아오면 connect를 부릅니다', () => {
63
+ const { client, connects } = createClientSpy();
64
+ client.attachAutoResume();
65
+
66
+ showPage(true);
67
+
68
+ expect(connects()).toBe(1);
69
+ });
70
+
71
+ it('bfcache가 아닌 첫 로드에는 부르지 않습니다 — connect는 소비처가 이미 불렀습니다', () => {
72
+ const { client, connects } = createClientSpy();
73
+ client.attachAutoResume();
74
+
75
+ showPage(false);
76
+
77
+ expect(connects()).toBe(0);
78
+ });
79
+
80
+ it('떼면 더 부르지 않습니다 — 클라이언트를 버릴 때 누수가 남지 않게', () => {
81
+ const { client, connects } = createClientSpy();
82
+ const detach = client.attachAutoResume();
83
+
84
+ detach();
85
+ setVisibility('visible');
86
+ showPage(true);
87
+
88
+ expect(connects()).toBe(0);
89
+ });
90
+ });
91
+
92
+ describe('attachAutoResume — 끊은 쪽의 의도 존중', () => {
93
+ it('disconnect한 뒤에는 탭이 돌아와도 되살리지 않습니다 — 끊은 건 소비처의 판단입니다', () => {
94
+ const { client, connects } = createClientSpy();
95
+ client.attachAutoResume();
96
+ client.disconnect();
97
+
98
+ setVisibility('visible');
99
+
100
+ expect(connects()).toBe(0);
101
+ });
102
+
103
+ it('서버가 goaway(action: close)로 끊은 뒤에는 탭이 돌아와도 되살리지 않습니다', async () => {
104
+ const { client } = createHarness();
105
+ await openConnection(client);
106
+ client.attachAutoResume();
107
+
108
+ lastSource().dispatch(
109
+ 'goaway',
110
+ JSON.stringify({ reason: 'user_logout', action: 'close' })
111
+ );
112
+ expect(client.status).toBe('closed');
113
+
114
+ setVisibility('visible');
115
+ await vi.advanceTimersByTimeAsync(0);
116
+
117
+ // 자동 재개가 서버의 close를 뒤집으면 재연결 티켓과 새 소스가 생깁니다
118
+ expect(FakeEventSource.instances).toHaveLength(1);
119
+ expect(client.status).toBe('closed');
120
+ });
121
+
122
+ // fatal의 회복 계약은 disconnect 후 connect라 자동 재개가 대신할 수 없습니다 — 시도하면 탭 전환마다 warn만 쌓입니다
123
+ it('fatal 상태에서는 탭이 돌아와도 connect를 시도하지 않습니다', async () => {
124
+ const warnings: string[] = [];
125
+ const { client, failTicketsWith } = createHarness({
126
+ logger: { warn: (message) => warnings.push(message), error: () => {} },
127
+ });
128
+ failTicketsWith(401);
129
+ client.connect();
130
+ await vi.advanceTimersByTimeAsync(0);
131
+ expect(client.status).toBe('fatal');
132
+ client.attachAutoResume();
133
+ const warningsBefore = warnings.length;
134
+
135
+ setVisibility('visible');
136
+ setVisibility('visible');
137
+ await vi.advanceTimersByTimeAsync(0);
138
+
139
+ expect(warnings).toHaveLength(warningsBefore);
140
+ expect(client.status).toBe('fatal');
141
+ });
142
+ });
@@ -0,0 +1,103 @@
1
+ import type { Emitter } from './emitter';
2
+ import { createEmitter } from './emitter';
3
+ import { isUndeliverableEvent } from './eventNames';
4
+ import type {
5
+ PayloadOf,
6
+ SseClientLike,
7
+ SseEventHandler,
8
+ SseEventMap,
9
+ SseEventName,
10
+ SseLogger,
11
+ SseStatus,
12
+ Unsubscribe,
13
+ } from './types';
14
+
15
+ /**
16
+ * @description 두 구현이 공유하는 구독 표면 — 구독 장부(emitter)를 소유하고 on·onStatus를 확정합니다.
17
+ * 연결을 어떻게 다루는지는 모릅니다: 구현마다 다른 것만 추상 멤버로 남깁니다.
18
+ * 같아야 할 것을 한 벌로 두기 위한 클래스입니다 — 두 구현이 각자 배선하다 갈린 이력이 있습니다.
19
+ */
20
+ export abstract class BaseSseClient<
21
+ M extends object = SseEventMap,
22
+ > implements SseClientLike<M> {
23
+ declare readonly __eventMap?: M;
24
+
25
+ protected readonly emitter: Emitter;
26
+
27
+ constructor(protected readonly logger?: SseLogger) {
28
+ this.emitter = createEmitter(logger);
29
+ }
30
+
31
+ on<K extends SseEventName<M>>(
32
+ type: K,
33
+ handler: SseEventHandler<PayloadOf<M, K>>
34
+ ): Unsubscribe {
35
+ if (isUndeliverableEvent(type)) {
36
+ this.logger?.warn(
37
+ `sse: '${type}'는 SDK가 소비하는 예약 이벤트라 구독이 배달되지 않습니다`
38
+ );
39
+ }
40
+ this.registerSubscription(type);
41
+ return this.emitter.on(type, handler as SseEventHandler<unknown>);
42
+ }
43
+
44
+ onStatus(listener: (status: SseStatus) => void): Unsubscribe {
45
+ return this.emitter.onStatus(listener);
46
+ }
47
+
48
+ /**
49
+ * @description 탭이 돌아올 때 connect()를 다시 부릅니다 — 전원이 얼어붙어 접힌 연결(휴면)은
50
+ * 깨어난 탭의 이 호출만이 다시 세울 수 있습니다. 연결이 살아 있으면 아무 일도 없습니다.
51
+ * bfcache 복귀는 visibilitychange가 아니라 pageshow로만 알 수 있어 둘 다 봅니다.
52
+ * SSR에는 붙일 곳이 없어 아무 일도 하지 않습니다.
53
+ *
54
+ * @returns 리스너를 떼는 함수 — 클라이언트를 버릴 때 함께 부르세요
55
+ */
56
+ attachAutoResume(): Unsubscribe {
57
+ if (typeof document === 'undefined' || typeof window === 'undefined') {
58
+ return () => {};
59
+ }
60
+
61
+ // 되살릴 대상은 idle입니다 — 전원 침묵으로 접힌 휴면이 이 상태입니다(workerConnection.close).
62
+ // closed(소비처 disconnect·서버 goaway close)는 끊은 의도를 되살리지 않고,
63
+ // fatal의 회복 계약은 disconnect 후 connect라 자동 재개가 대신할 수 없습니다
64
+ const notifyAlive = (): void => {
65
+ if (this.status === 'closed' || this.status === 'fatal') {
66
+ return;
67
+ }
68
+ this.connect();
69
+ };
70
+
71
+ const onVisible = (): void => {
72
+ if (document.visibilityState === 'visible') {
73
+ notifyAlive();
74
+ }
75
+ };
76
+
77
+ // persisted = bfcache 복귀, 직결 경로용 — Chrome은 SharedWorker 페이지를 bfcache에 넣지 않고,
78
+ // 넣는 브라우저라면 포트가 이미 죽어 이 connect가 닿지 않습니다(알려진 한계)
79
+ const onPageShow = (event: PageTransitionEvent): void => {
80
+ if (event.persisted) {
81
+ notifyAlive();
82
+ }
83
+ };
84
+
85
+ document.addEventListener('visibilitychange', onVisible);
86
+ window.addEventListener('pageshow', onPageShow);
87
+
88
+ return () => {
89
+ document.removeEventListener('visibilitychange', onVisible);
90
+ window.removeEventListener('pageshow', onPageShow);
91
+ };
92
+ }
93
+
94
+ /**
95
+ * @description 구독이 등록될 때 각 구현이 할 일 — 열린 소스에 리스너를 붙이거나, 워커에 알리거나.
96
+ * 연결 전 구독도 여기로 오므로, 아직 붙일 곳이 없으면 조용히 넘어가야 합니다.
97
+ */
98
+ protected abstract registerSubscription(type: string): void;
99
+
100
+ abstract get status(): SseStatus;
101
+ abstract connect(): void;
102
+ abstract disconnect(): void;
103
+ }