@comapeo/map-server 1.0.0-pre.0

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 (179) hide show
  1. package/README.md +610 -0
  2. package/dist/context.d.ts +46 -0
  3. package/dist/context.d.ts.map +1 -0
  4. package/dist/context.js +181 -0
  5. package/dist/index.d.ts +25 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +112 -0
  8. package/dist/lib/constants.d.ts +7 -0
  9. package/dist/lib/constants.d.ts.map +1 -0
  10. package/dist/lib/constants.js +6 -0
  11. package/dist/lib/download-request.d.ts +17 -0
  12. package/dist/lib/download-request.d.ts.map +1 -0
  13. package/dist/lib/download-request.js +113 -0
  14. package/dist/lib/errors.d.ts +88 -0
  15. package/dist/lib/errors.d.ts.map +1 -0
  16. package/dist/lib/errors.js +158 -0
  17. package/dist/lib/event-stream-response.d.ts +17 -0
  18. package/dist/lib/event-stream-response.d.ts.map +1 -0
  19. package/dist/lib/event-stream-response.js +39 -0
  20. package/dist/lib/event-target.d.ts +9 -0
  21. package/dist/lib/event-target.d.ts.map +1 -0
  22. package/dist/lib/event-target.js +4 -0
  23. package/dist/lib/fetch-api.d.ts +3 -0
  24. package/dist/lib/fetch-api.d.ts.map +1 -0
  25. package/dist/lib/fetch-api.js +16 -0
  26. package/dist/lib/map-share.d.ts +52 -0
  27. package/dist/lib/map-share.d.ts.map +1 -0
  28. package/dist/lib/map-share.js +142 -0
  29. package/dist/lib/secret-stream-fetch.d.ts +7 -0
  30. package/dist/lib/secret-stream-fetch.d.ts.map +1 -0
  31. package/dist/lib/secret-stream-fetch.js +34 -0
  32. package/dist/lib/self-evicting-map.d.ts +16 -0
  33. package/dist/lib/self-evicting-map.d.ts.map +1 -0
  34. package/dist/lib/self-evicting-map.js +29 -0
  35. package/dist/lib/state-update-event.d.ts +8 -0
  36. package/dist/lib/state-update-event.d.ts.map +1 -0
  37. package/dist/lib/state-update-event.js +10 -0
  38. package/dist/lib/utils.d.ts +32 -0
  39. package/dist/lib/utils.d.ts.map +1 -0
  40. package/dist/lib/utils.js +96 -0
  41. package/dist/middlewares/localhost-only.d.ts +11 -0
  42. package/dist/middlewares/localhost-only.d.ts.map +1 -0
  43. package/dist/middlewares/localhost-only.js +10 -0
  44. package/dist/middlewares/parse-request.d.ts +11 -0
  45. package/dist/middlewares/parse-request.d.ts.map +1 -0
  46. package/dist/middlewares/parse-request.js +25 -0
  47. package/dist/routes/downloads.d.ts +15 -0
  48. package/dist/routes/downloads.d.ts.map +1 -0
  49. package/dist/routes/downloads.js +60 -0
  50. package/dist/routes/map-shares.d.ts +19 -0
  51. package/dist/routes/map-shares.d.ts.map +1 -0
  52. package/dist/routes/map-shares.js +192 -0
  53. package/dist/routes/maps.d.ts +6 -0
  54. package/dist/routes/maps.d.ts.map +1 -0
  55. package/dist/routes/maps.js +118 -0
  56. package/dist/routes/root.d.ts +6 -0
  57. package/dist/routes/root.d.ts.map +1 -0
  58. package/dist/routes/root.js +29 -0
  59. package/dist/types.d.ts +110 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +96 -0
  62. package/node_modules/@envelop/instrumentation/LICENSE +21 -0
  63. package/node_modules/@envelop/instrumentation/README.md +30 -0
  64. package/node_modules/@envelop/instrumentation/cjs/index.js +5 -0
  65. package/node_modules/@envelop/instrumentation/cjs/instrumentation.js +89 -0
  66. package/node_modules/@envelop/instrumentation/cjs/package.json +1 -0
  67. package/node_modules/@envelop/instrumentation/esm/index.js +2 -0
  68. package/node_modules/@envelop/instrumentation/esm/instrumentation.js +82 -0
  69. package/node_modules/@envelop/instrumentation/package.json +57 -0
  70. package/node_modules/@envelop/instrumentation/typings/index.d.cts +1 -0
  71. package/node_modules/@envelop/instrumentation/typings/index.d.ts +1 -0
  72. package/node_modules/@envelop/instrumentation/typings/instrumentation.d.cts +44 -0
  73. package/node_modules/@envelop/instrumentation/typings/instrumentation.d.ts +44 -0
  74. package/node_modules/@whatwg-node/disposablestack/cjs/AsyncDisposableStack.js +73 -0
  75. package/node_modules/@whatwg-node/disposablestack/cjs/DisposableStack.js +62 -0
  76. package/node_modules/@whatwg-node/disposablestack/cjs/SupressedError.js +16 -0
  77. package/node_modules/@whatwg-node/disposablestack/cjs/index.js +11 -0
  78. package/node_modules/@whatwg-node/disposablestack/cjs/package.json +1 -0
  79. package/node_modules/@whatwg-node/disposablestack/cjs/symbols.js +20 -0
  80. package/node_modules/@whatwg-node/disposablestack/cjs/utils.js +11 -0
  81. package/node_modules/@whatwg-node/disposablestack/esm/AsyncDisposableStack.js +69 -0
  82. package/node_modules/@whatwg-node/disposablestack/esm/DisposableStack.js +58 -0
  83. package/node_modules/@whatwg-node/disposablestack/esm/SupressedError.js +12 -0
  84. package/node_modules/@whatwg-node/disposablestack/esm/index.js +7 -0
  85. package/node_modules/@whatwg-node/disposablestack/esm/symbols.js +16 -0
  86. package/node_modules/@whatwg-node/disposablestack/esm/utils.js +7 -0
  87. package/node_modules/@whatwg-node/disposablestack/package.json +44 -0
  88. package/node_modules/@whatwg-node/disposablestack/typings/AsyncDisposableStack.d.cts +15 -0
  89. package/node_modules/@whatwg-node/disposablestack/typings/AsyncDisposableStack.d.ts +15 -0
  90. package/node_modules/@whatwg-node/disposablestack/typings/DisposableStack.d.cts +14 -0
  91. package/node_modules/@whatwg-node/disposablestack/typings/DisposableStack.d.ts +14 -0
  92. package/node_modules/@whatwg-node/disposablestack/typings/SupressedError.d.cts +5 -0
  93. package/node_modules/@whatwg-node/disposablestack/typings/SupressedError.d.ts +5 -0
  94. package/node_modules/@whatwg-node/disposablestack/typings/index.d.cts +4 -0
  95. package/node_modules/@whatwg-node/disposablestack/typings/index.d.ts +4 -0
  96. package/node_modules/@whatwg-node/disposablestack/typings/symbols.d.cts +5 -0
  97. package/node_modules/@whatwg-node/disposablestack/typings/symbols.d.ts +5 -0
  98. package/node_modules/@whatwg-node/disposablestack/typings/utils.d.cts +2 -0
  99. package/node_modules/@whatwg-node/disposablestack/typings/utils.d.ts +2 -0
  100. package/node_modules/@whatwg-node/promise-helpers/cjs/index.js +270 -0
  101. package/node_modules/@whatwg-node/promise-helpers/cjs/package.json +1 -0
  102. package/node_modules/@whatwg-node/promise-helpers/esm/index.js +257 -0
  103. package/node_modules/@whatwg-node/promise-helpers/package.json +43 -0
  104. package/node_modules/@whatwg-node/promise-helpers/typings/index.d.cts +31 -0
  105. package/node_modules/@whatwg-node/promise-helpers/typings/index.d.ts +31 -0
  106. package/node_modules/@whatwg-node/server/README.md +590 -0
  107. package/node_modules/@whatwg-node/server/cjs/createServerAdapter.js +368 -0
  108. package/node_modules/@whatwg-node/server/cjs/index.js +17 -0
  109. package/node_modules/@whatwg-node/server/cjs/package.json +1 -0
  110. package/node_modules/@whatwg-node/server/cjs/plugins/types.js +0 -0
  111. package/node_modules/@whatwg-node/server/cjs/plugins/useContentEncoding.js +73 -0
  112. package/node_modules/@whatwg-node/server/cjs/plugins/useCors.js +124 -0
  113. package/node_modules/@whatwg-node/server/cjs/plugins/useErrorHandling.js +52 -0
  114. package/node_modules/@whatwg-node/server/cjs/types.js +0 -0
  115. package/node_modules/@whatwg-node/server/cjs/utils.js +599 -0
  116. package/node_modules/@whatwg-node/server/cjs/uwebsockets.js +241 -0
  117. package/node_modules/@whatwg-node/server/esm/createServerAdapter.js +365 -0
  118. package/node_modules/@whatwg-node/server/esm/index.js +11 -0
  119. package/node_modules/@whatwg-node/server/esm/plugins/types.js +0 -0
  120. package/node_modules/@whatwg-node/server/esm/plugins/useContentEncoding.js +70 -0
  121. package/node_modules/@whatwg-node/server/esm/plugins/useCors.js +120 -0
  122. package/node_modules/@whatwg-node/server/esm/plugins/useErrorHandling.js +46 -0
  123. package/node_modules/@whatwg-node/server/esm/types.js +0 -0
  124. package/node_modules/@whatwg-node/server/esm/utils.js +588 -0
  125. package/node_modules/@whatwg-node/server/esm/uwebsockets.js +234 -0
  126. package/node_modules/@whatwg-node/server/package.json +46 -0
  127. package/node_modules/@whatwg-node/server/typings/createServerAdapter.d.cts +19 -0
  128. package/node_modules/@whatwg-node/server/typings/createServerAdapter.d.ts +19 -0
  129. package/node_modules/@whatwg-node/server/typings/index.d.cts +11 -0
  130. package/node_modules/@whatwg-node/server/typings/index.d.ts +11 -0
  131. package/node_modules/@whatwg-node/server/typings/plugins/types.d.cts +76 -0
  132. package/node_modules/@whatwg-node/server/typings/plugins/types.d.ts +76 -0
  133. package/node_modules/@whatwg-node/server/typings/plugins/useContentEncoding.d.cts +2 -0
  134. package/node_modules/@whatwg-node/server/typings/plugins/useContentEncoding.d.ts +2 -0
  135. package/node_modules/@whatwg-node/server/typings/plugins/useCors.d.cts +14 -0
  136. package/node_modules/@whatwg-node/server/typings/plugins/useCors.d.ts +14 -0
  137. package/node_modules/@whatwg-node/server/typings/plugins/useErrorHandling.d.cts +13 -0
  138. package/node_modules/@whatwg-node/server/typings/plugins/useErrorHandling.d.ts +13 -0
  139. package/node_modules/@whatwg-node/server/typings/types.d.cts +100 -0
  140. package/node_modules/@whatwg-node/server/typings/types.d.ts +100 -0
  141. package/node_modules/@whatwg-node/server/typings/utils.d.cts +42 -0
  142. package/node_modules/@whatwg-node/server/typings/utils.d.ts +42 -0
  143. package/node_modules/@whatwg-node/server/typings/uwebsockets.d.cts +32 -0
  144. package/node_modules/@whatwg-node/server/typings/uwebsockets.d.ts +32 -0
  145. package/node_modules/tslib/CopyrightNotice.txt +15 -0
  146. package/node_modules/tslib/LICENSE.txt +12 -0
  147. package/node_modules/tslib/README.md +164 -0
  148. package/node_modules/tslib/SECURITY.md +41 -0
  149. package/node_modules/tslib/modules/index.d.ts +38 -0
  150. package/node_modules/tslib/modules/index.js +70 -0
  151. package/node_modules/tslib/modules/package.json +3 -0
  152. package/node_modules/tslib/package.json +47 -0
  153. package/node_modules/tslib/tslib.d.ts +460 -0
  154. package/node_modules/tslib/tslib.es6.html +1 -0
  155. package/node_modules/tslib/tslib.es6.js +402 -0
  156. package/node_modules/tslib/tslib.es6.mjs +401 -0
  157. package/node_modules/tslib/tslib.html +1 -0
  158. package/node_modules/tslib/tslib.js +484 -0
  159. package/package.json +87 -0
  160. package/src/context.ts +203 -0
  161. package/src/index.ts +193 -0
  162. package/src/lib/constants.ts +6 -0
  163. package/src/lib/download-request.ts +142 -0
  164. package/src/lib/errors.ts +187 -0
  165. package/src/lib/event-stream-response.ts +57 -0
  166. package/src/lib/event-target.ts +11 -0
  167. package/src/lib/fetch-api.ts +18 -0
  168. package/src/lib/map-share.ts +185 -0
  169. package/src/lib/secret-stream-fetch.ts +42 -0
  170. package/src/lib/self-evicting-map.ts +35 -0
  171. package/src/lib/state-update-event.ts +14 -0
  172. package/src/lib/utils.ts +110 -0
  173. package/src/middlewares/localhost-only.ts +16 -0
  174. package/src/middlewares/parse-request.ts +34 -0
  175. package/src/routes/downloads.ts +92 -0
  176. package/src/routes/map-shares.ts +246 -0
  177. package/src/routes/maps.ts +146 -0
  178. package/src/routes/root.ts +37 -0
  179. package/src/types.ts +152 -0
@@ -0,0 +1,158 @@
1
+ import { json } from 'itty-router';
2
+ export class StatusError extends Error {
3
+ status;
4
+ constructor(status = 500, body) {
5
+ super(typeof body === 'object' ? body.message : body);
6
+ if (typeof body === 'object')
7
+ Object.assign(this, body);
8
+ this.status = status;
9
+ }
10
+ }
11
+ const errorsList = [
12
+ // Download errors (receiver-side)
13
+ {
14
+ code: 'DOWNLOAD_NOT_FOUND',
15
+ message: 'Download not found',
16
+ status: 404,
17
+ },
18
+ {
19
+ code: 'DOWNLOAD_ERROR',
20
+ message: 'Download failed',
21
+ status: 500,
22
+ },
23
+ {
24
+ code: 'DOWNLOAD_SHARE_CANCELED',
25
+ message: 'Download canceled by sender',
26
+ status: 409,
27
+ },
28
+ {
29
+ code: 'DOWNLOAD_SHARE_DECLINED',
30
+ message: 'Cannot download: share was declined',
31
+ status: 409,
32
+ },
33
+ {
34
+ code: 'DOWNLOAD_SHARE_NOT_PENDING',
35
+ message: 'Cannot download: share is not pending',
36
+ status: 409,
37
+ },
38
+ {
39
+ code: 'ABORT_NOT_DOWNLOADING',
40
+ message: 'Cannot abort: download is not in progress',
41
+ status: 409,
42
+ },
43
+ {
44
+ code: 'INVALID_SENDER_DEVICE_ID',
45
+ message: 'Invalid sender device ID',
46
+ status: 400,
47
+ },
48
+ // Map share errors (sender-side)
49
+ {
50
+ code: 'MAP_SHARE_NOT_FOUND',
51
+ message: 'Map share not found',
52
+ status: 404,
53
+ },
54
+ {
55
+ code: 'CANCEL_SHARE_NOT_CANCELABLE',
56
+ message: 'Cannot cancel: share is not pending or downloading',
57
+ status: 409,
58
+ },
59
+ {
60
+ code: 'DECLINE_SHARE_NOT_PENDING',
61
+ message: 'Cannot decline: share is not pending',
62
+ status: 409,
63
+ },
64
+ {
65
+ code: 'DECLINE_CANNOT_CONNECT',
66
+ message: 'Cannot decline: unable to connect to sender',
67
+ status: 502,
68
+ },
69
+ // Map errors
70
+ {
71
+ code: 'MAP_NOT_FOUND',
72
+ message: 'Map not found',
73
+ status: 404,
74
+ },
75
+ {
76
+ code: 'RESOURCE_NOT_FOUND',
77
+ message: 'Resource not found',
78
+ status: 404,
79
+ },
80
+ {
81
+ code: 'INVALID_MAP_FILE',
82
+ message: 'Invalid map file',
83
+ status: 400,
84
+ },
85
+ // Generic errors
86
+ {
87
+ code: 'FORBIDDEN',
88
+ message: 'Forbidden',
89
+ status: 403,
90
+ },
91
+ {
92
+ code: 'INVALID_REQUEST',
93
+ message: 'Invalid request',
94
+ status: 400,
95
+ },
96
+ ];
97
+ export const errors = {};
98
+ for (const { code, message, status } of errorsList) {
99
+ errors[code] = class extends StatusError {
100
+ constructor(body) {
101
+ body = typeof body === 'string' ? { message: body } : body;
102
+ super(status, { code, message, ...body });
103
+ }
104
+ };
105
+ }
106
+ export class ExhaustivenessError extends Error {
107
+ constructor(value) {
108
+ super(`Exhaustiveness check failed. ${value} should be impossible`);
109
+ this.name = 'ExhaustivenessError';
110
+ }
111
+ }
112
+ export function jsonError(err) {
113
+ if (err === null) {
114
+ return { message: 'Unknown error', code: 'UNKNOWN_ERROR' };
115
+ }
116
+ else if (typeof err !== 'object') {
117
+ return { message: String(err), code: 'UNKNOWN_ERROR' };
118
+ }
119
+ else {
120
+ return {
121
+ message: 'message' in err
122
+ ? String(err.message)
123
+ : String(err.error),
124
+ code: err.code || 'UNKNOWN_ERROR',
125
+ };
126
+ }
127
+ }
128
+ const getMessage = (code) => ({
129
+ 400: 'Bad Request',
130
+ 401: 'Unauthorized',
131
+ 403: 'Forbidden',
132
+ 404: 'Not Found',
133
+ 500: 'Internal Server Error',
134
+ })[code] || 'Unknown Error';
135
+ const getCode = (status) => ({
136
+ 400: 'BAD_REQUEST',
137
+ 401: 'UNAUTHORIZED',
138
+ 403: 'FORBIDDEN',
139
+ 404: 'NOT_FOUND',
140
+ 500: 'INTERNAL_SERVER_ERROR',
141
+ })[status] || 'UNKNOWN_ERROR';
142
+ export const error = (a = 500, b) => {
143
+ // handle passing an Error | StatusError directly in
144
+ if (a instanceof Error) {
145
+ const { message, code, ...err } = a;
146
+ a = a.status || 500;
147
+ b = {
148
+ message: message || getMessage(a),
149
+ code: code || getCode(a),
150
+ ...err,
151
+ };
152
+ }
153
+ b = {
154
+ status: a,
155
+ ...(typeof b === 'object' ? b : { message: b || getMessage(a) }),
156
+ };
157
+ return json(b, { status: a });
158
+ };
@@ -0,0 +1,17 @@
1
+ import type { TypedEventTarget } from 'typed-event-target';
2
+ type EventTargetStateUpdater = TypedEventTarget<Readonly<Event & {
3
+ type: 'update';
4
+ }>> & {
5
+ state: any;
6
+ };
7
+ /**
8
+ * Create a Server-Sent Events stream for an EventTarget with a `state` property
9
+ * that emits 'update' events with state updates.
10
+ *
11
+ * You must pass an AbortSignal that will cancel the stream if the client disconnects.
12
+ */
13
+ export declare function createEventStreamResponse(eventTarget: EventTargetStateUpdater, { signal }: {
14
+ signal: AbortSignal;
15
+ }): Response;
16
+ export {};
17
+ //# sourceMappingURL=event-stream-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-stream-response.d.ts","sourceRoot":"","sources":["../../src/lib/event-stream-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAM1D,KAAK,uBAAuB,GAAG,gBAAgB,CAC9C,QAAQ,CAAC,KAAK,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CACpC,GAAG;IACH,KAAK,EAAE,GAAG,CAAA;CACV,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACxC,WAAW,EAAE,uBAAuB,EACpC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACjC,QAAQ,CAmCV"}
@@ -0,0 +1,39 @@
1
+ import { noop } from './utils.js';
2
+ const encoder = new TextEncoder();
3
+ /**
4
+ * Create a Server-Sent Events stream for an EventTarget with a `state` property
5
+ * that emits 'update' events with state updates.
6
+ *
7
+ * You must pass an AbortSignal that will cancel the stream if the client disconnects.
8
+ */
9
+ export function createEventStreamResponse(eventTarget, { signal }) {
10
+ let listener;
11
+ const stream = new ReadableStream({
12
+ start(controller) {
13
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(eventTarget.state)}\n\n`));
14
+ listener = (event) => {
15
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
+ const { type, ...update } = event;
17
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(update)}\n\n`));
18
+ };
19
+ eventTarget.addEventListener('update', listener);
20
+ },
21
+ cancel() {
22
+ signal.removeEventListener('abort', onAbort);
23
+ if (listener) {
24
+ eventTarget.removeEventListener('update', listener);
25
+ }
26
+ },
27
+ });
28
+ const onAbort = () => {
29
+ stream.cancel().catch(noop);
30
+ };
31
+ signal.addEventListener('abort', onAbort, { once: true });
32
+ return new Response(stream, {
33
+ headers: {
34
+ 'Content-Type': 'text/event-stream',
35
+ 'Cache-Control': 'no-cache',
36
+ Connection: 'keep-alive',
37
+ },
38
+ });
39
+ }
@@ -0,0 +1,9 @@
1
+ import { type TypedEventTarget as TypedEventTargetOrig } from 'typed-event-target';
2
+ /**
3
+ * A strongly typed EventTarget - no runtime overhead
4
+ */
5
+ export declare const TypedEventTarget: {
6
+ new <PossibleEvents extends Readonly<Event>>(): TypedEventTargetOrig<PossibleEvents>;
7
+ prototype: EventTarget;
8
+ };
9
+ //# sourceMappingURL=event-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-target.d.ts","sourceRoot":"","sources":["../../src/lib/event-target.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAElF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAkB;IAC9C,KACC,cAAc,SAAS,QAAQ,CAAC,KAAK,CAAC,KAClC,oBAAoB,CAAC,cAAc,CAAC,CAAA;IACzC,SAAS,EAAE,WAAW,CAAA;CACtB,CAAA"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * A strongly typed EventTarget - no runtime overhead
3
+ */
4
+ export const TypedEventTarget = EventTarget;
@@ -0,0 +1,3 @@
1
+ import type { ServerAdapterOptions } from '@whatwg-node/server';
2
+ export declare const fetchAPI: ServerAdapterOptions<any>['fetchAPI'];
3
+ //# sourceMappingURL=fetch-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-api.d.ts","sourceRoot":"","sources":["../../src/lib/fetch-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAM/D,eAAO,MAAM,QAAQ,EAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC,UAAU,CAW1D,CAAA"}
@@ -0,0 +1,16 @@
1
+ // @whatwg-node/server ponyfills the fetch API by default, which has bugs in the
2
+ // ReadableStream implementation that was causing issues with stream error
3
+ // propagation. To avoid these issues, we explicitly provide the native fetch
4
+ // API implementation from Node.js.
5
+ export const fetchAPI = {
6
+ ReadableStream: globalThis.ReadableStream,
7
+ WritableStream: globalThis.WritableStream,
8
+ TransformStream: globalThis.TransformStream,
9
+ Response: globalThis.Response,
10
+ Request: globalThis.Request,
11
+ Headers: globalThis.Headers,
12
+ FormData: globalThis.FormData,
13
+ File: globalThis.File,
14
+ Blob: globalThis.Blob,
15
+ fetch: globalThis.fetch,
16
+ };
@@ -0,0 +1,52 @@
1
+ import { TypedEventTarget } from '../lib/event-target.js';
2
+ import { MapShareState, type MapShareStateUpdate, type DownloadStateUpdate, type MapInfo } from '../types.js';
3
+ import { StateUpdateEvent } from './state-update-event.js';
4
+ export type MapShareOptions = MapInfo & {
5
+ /**
6
+ * Base URLs to construct the download URLs for the map share. Multiple URLs
7
+ * are supported because the server might have multiple network interfaces
8
+ * with different IP addresses
9
+ */
10
+ baseUrls: string[];
11
+ /** The device ID of the receiver */
12
+ receiverDeviceId: string;
13
+ };
14
+ /**
15
+ * Maintains the state of a map share and handles downloading from the sharer side
16
+ */
17
+ export declare class MapShare extends TypedEventTarget<InstanceType<typeof StateUpdateEvent>> {
18
+ #private;
19
+ constructor({ baseUrls, receiverDeviceId, ...mapInfo }: MapShareOptions);
20
+ get shareId(): string;
21
+ get state(): MapShareState;
22
+ /**
23
+ * Create a download response for the map share
24
+ */
25
+ downloadResponse(readable: ReadableStream): Response;
26
+ /**
27
+ * Decline the map share with a given reason
28
+ */
29
+ decline(reason: Extract<MapShareStateUpdate, {
30
+ status: 'declined';
31
+ }>['reason']): void;
32
+ /**
33
+ * Cancel the map share
34
+ */
35
+ cancel(): void;
36
+ }
37
+ /**
38
+ * Handles the download response of a map share and tracks its state.
39
+ *
40
+ * Currently we only support a single download per map share, but I'm keeping
41
+ * this as a separate class in case we want to support multiple downloads per
42
+ * share in the future (multiple downloads per share will make the "state" of a
43
+ * MapShare harder to reason about and define).
44
+ */
45
+ export declare class DownloadResponse extends TypedEventTarget<InstanceType<typeof StateUpdateEvent<DownloadStateUpdate>>> {
46
+ #private;
47
+ constructor(readable: ReadableStream<Uint8Array>);
48
+ get response(): Response;
49
+ get state(): DownloadStateUpdate;
50
+ cancel(): void;
51
+ }
52
+ //# sourceMappingURL=map-share.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-share.d.ts","sourceRoot":"","sources":["../../src/lib/map-share.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EACN,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,OAAO,EACZ,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAG1D,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG;IACvC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,oCAAoC;IACpC,gBAAgB,EAAE,MAAM,CAAA;CACxB,CAAA;AAED;;GAEG;AACH,qBAAa,QAAS,SAAQ,gBAAgB,CAC7C,YAAY,CAAC,OAAO,gBAAgB,CAAC,CACrC;;gBAGY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,OAAO,EAAE,EAAE,eAAe;IAevE,IAAI,OAAO,WAEV;IAED,IAAI,KAAK,kBAER;IAED;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,cAAc,GAAG,QAAQ;IAkBpD;;OAEG;IACH,OAAO,CACN,MAAM,EAAE,OAAO,CAAC,mBAAmB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC,QAAQ,CAAC;IAUvE;;OAEG;IACH,MAAM;CAiBN;AAED;;;;;;;GAOG;AACH,qBAAa,gBAAiB,SAAQ,gBAAgB,CACrD,YAAY,CAAC,OAAO,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,CAC1D;;gBAOY,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC;IAyChD,IAAI,QAAQ,aAEX;IAED,IAAI,KAAK,wBAER;IAED,MAAM;CAQN"}
@@ -0,0 +1,142 @@
1
+ import { TypedEventTarget } from '../lib/event-target.js';
2
+ import { errors } from './errors.js';
3
+ import { StateUpdateEvent } from './state-update-event.js';
4
+ import { addTrailingSlash, generateId, getErrorCode } from './utils.js';
5
+ /**
6
+ * Maintains the state of a map share and handles downloading from the sharer side
7
+ */
8
+ export class MapShare extends TypedEventTarget {
9
+ #state;
10
+ #download;
11
+ constructor({ baseUrls, receiverDeviceId, ...mapInfo }) {
12
+ super();
13
+ const shareId = generateId();
14
+ this.#state = {
15
+ ...mapInfo,
16
+ shareId,
17
+ mapShareUrls: baseUrls.map((baseUrl) => new URL(`${shareId}`, addTrailingSlash(baseUrl)).href),
18
+ receiverDeviceId,
19
+ mapShareCreated: Date.now(),
20
+ status: 'pending',
21
+ };
22
+ }
23
+ get shareId() {
24
+ return this.#state.shareId;
25
+ }
26
+ get state() {
27
+ return this.#state;
28
+ }
29
+ /**
30
+ * Create a download response for the map share
31
+ */
32
+ downloadResponse(readable) {
33
+ if (this.#state.status === 'canceled') {
34
+ throw new errors.DOWNLOAD_SHARE_CANCELED();
35
+ }
36
+ else if (this.#state.status === 'declined') {
37
+ throw new errors.DOWNLOAD_SHARE_DECLINED();
38
+ }
39
+ else if (this.#state.status !== 'pending') {
40
+ throw new errors.DOWNLOAD_SHARE_NOT_PENDING(`Cannot download: share status is '${this.#state.status}'`);
41
+ }
42
+ this.#download?.removeAllEventListeners();
43
+ this.#download = new DownloadResponse(readable);
44
+ this.#download.addEventListener('update', (event) => {
45
+ this.#updateState(event);
46
+ });
47
+ return this.#download.response;
48
+ }
49
+ /**
50
+ * Decline the map share with a given reason
51
+ */
52
+ decline(reason) {
53
+ if (this.#state.status !== 'pending') {
54
+ throw new errors.DECLINE_SHARE_NOT_PENDING(`Cannot decline: share status is '${this.#state.status}'`);
55
+ }
56
+ this.#updateState({ status: 'declined', reason });
57
+ }
58
+ /**
59
+ * Cancel the map share
60
+ */
61
+ cancel() {
62
+ if (this.#state.status !== 'pending' &&
63
+ this.#state.status !== 'downloading') {
64
+ throw new errors.CANCEL_SHARE_NOT_CANCELABLE(`Cannot cancel: share status is '${this.#state.status}'`);
65
+ }
66
+ this.#download?.cancel();
67
+ this.#updateState({ status: 'canceled' });
68
+ }
69
+ #updateState(update) {
70
+ this.#state = { ...this.#state, ...update };
71
+ queueMicrotask(() => this.dispatchEvent(new StateUpdateEvent(update)));
72
+ }
73
+ }
74
+ /**
75
+ * Handles the download response of a map share and tracks its state.
76
+ *
77
+ * Currently we only support a single download per map share, but I'm keeping
78
+ * this as a separate class in case we want to support multiple downloads per
79
+ * share in the future (multiple downloads per share will make the "state" of a
80
+ * MapShare harder to reason about and define).
81
+ */
82
+ export class DownloadResponse extends TypedEventTarget {
83
+ #stream;
84
+ #bytesDownloaded = 0;
85
+ #abortController = new AbortController();
86
+ #state = { status: 'downloading', bytesDownloaded: 0 };
87
+ #response;
88
+ constructor(readable) {
89
+ super();
90
+ this.#stream = new TransformStream({
91
+ start: () => {
92
+ this.#updateState({ status: 'downloading', bytesDownloaded: 0 });
93
+ },
94
+ transform: (chunk, controller) => {
95
+ this.#bytesDownloaded += chunk.length;
96
+ this.#updateState({
97
+ status: 'downloading',
98
+ bytesDownloaded: this.#bytesDownloaded,
99
+ });
100
+ controller.enqueue(chunk);
101
+ },
102
+ flush: () => {
103
+ this.#updateState({ status: 'completed' });
104
+ },
105
+ });
106
+ readable
107
+ .pipeTo(this.#stream.writable, {
108
+ signal: this.#abortController.signal,
109
+ // preventAbort: true,
110
+ // preventCancel: true,
111
+ })
112
+ .catch((error) => {
113
+ if (error.name === 'AbortError') {
114
+ this.#updateState({ status: 'canceled' });
115
+ }
116
+ else if (getErrorCode(error) === 'ECONNRESET') {
117
+ this.#updateState({ status: 'aborted' });
118
+ }
119
+ else {
120
+ this.#updateState({ status: 'error', error });
121
+ }
122
+ });
123
+ this.#response = new Response(this.#stream.readable, {
124
+ headers: {
125
+ 'Content-Type': 'application/vnd.smp+zip',
126
+ },
127
+ });
128
+ }
129
+ get response() {
130
+ return this.#response;
131
+ }
132
+ get state() {
133
+ return this.#state;
134
+ }
135
+ cancel() {
136
+ this.#abortController.abort();
137
+ }
138
+ #updateState(update) {
139
+ this.#state = update;
140
+ this.dispatchEvent(new StateUpdateEvent(update));
141
+ }
142
+ }
@@ -0,0 +1,7 @@
1
+ import { fetch as secretStreamFetchOrig } from 'secret-stream-http';
2
+ /**
3
+ * A wrapper around secret-stream-http's fetch that tries multiple URLs until one works.
4
+ * This is useful when the server has multiple IPs for different network interfaces.
5
+ */
6
+ export declare function secretStreamFetch(urls: string | URL | Array<string | URL>, options: Parameters<typeof secretStreamFetchOrig>[1]): Promise<Response>;
7
+ //# sourceMappingURL=secret-stream-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-stream-fetch.d.ts","sourceRoot":"","sources":["../../src/lib/secret-stream-fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAInE;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,EACxC,OAAO,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC,CAAC,CAAC,qBA+BpD"}
@@ -0,0 +1,34 @@
1
+ import { fetch as secretStreamFetchOrig } from 'secret-stream-http';
2
+ import { errors } from './errors.js';
3
+ /**
4
+ * A wrapper around secret-stream-http's fetch that tries multiple URLs until one works.
5
+ * This is useful when the server has multiple IPs for different network interfaces.
6
+ */
7
+ export async function secretStreamFetch(urls, options) {
8
+ if (!Array.isArray(urls)) {
9
+ urls = [urls];
10
+ }
11
+ let response;
12
+ let error;
13
+ // The server could have multiple IPs for different network interfaces, and
14
+ // not all of them may be on the same network as us, so try each URL until
15
+ // one works
16
+ for (const url of urls) {
17
+ try {
18
+ response = (await secretStreamFetchOrig(url, options)); // Subtle difference bewteen Undici fetch Response and whatwg Response
19
+ break; // Exit loop on successful fetch
20
+ }
21
+ catch (err) {
22
+ error = err;
23
+ // Ignore errors and try the next URL
24
+ }
25
+ }
26
+ if (!response) {
27
+ throw new errors.DOWNLOAD_ERROR({
28
+ message: 'Could not connect to map share sender',
29
+ urls,
30
+ cause: error,
31
+ });
32
+ }
33
+ return response;
34
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * "Not a LRU": A Map that automatically evicts entries after a specified
3
+ * timeout. Used for MapShares which we don't want to keep indefinitely. NB: The
4
+ * use of the Typescript `object` type is intentional: this must be used with
5
+ * non-primitive values, otherwise behaviour would be unexpected because
6
+ * removing a value and re-adding it could result in it being evicted with the
7
+ * original timeout. This has limited applications, but works for our needs.
8
+ */
9
+ export declare class SelfEvictingTimeoutMap<K, V extends object> extends Map<K, V> {
10
+ #private;
11
+ constructor(iterable?: ConstructorParameters<typeof Map<K, V>>[0], { evictionTimeoutMs }?: {
12
+ evictionTimeoutMs?: number | undefined;
13
+ });
14
+ set(key: K, value: V): this;
15
+ }
16
+ //# sourceMappingURL=self-evicting-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-evicting-map.d.ts","sourceRoot":"","sources":["../../src/lib/self-evicting-map.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,qBAAa,sBAAsB,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAE,SAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;;gBAKxE,QAAQ,CAAC,EAAE,qBAAqB,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACrD,EAAE,iBAA+C,EAAE;;KAAK;IAMhD,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;CAYpC"}
@@ -0,0 +1,29 @@
1
+ const DEFAULT_EVICTION_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
2
+ /**
3
+ * "Not a LRU": A Map that automatically evicts entries after a specified
4
+ * timeout. Used for MapShares which we don't want to keep indefinitely. NB: The
5
+ * use of the Typescript `object` type is intentional: this must be used with
6
+ * non-primitive values, otherwise behaviour would be unexpected because
7
+ * removing a value and re-adding it could result in it being evicted with the
8
+ * original timeout. This has limited applications, but works for our needs.
9
+ */
10
+ export class SelfEvictingTimeoutMap extends Map {
11
+ #evictionTimeoutMs;
12
+ #timeouts = new Set();
13
+ constructor(iterable, { evictionTimeoutMs = DEFAULT_EVICTION_TIMEOUT_MS } = {}) {
14
+ super(iterable);
15
+ this.#evictionTimeoutMs = evictionTimeoutMs;
16
+ }
17
+ set(key, value) {
18
+ super.set(key, value);
19
+ const timeout = setTimeout(() => {
20
+ this.#timeouts.delete(timeout);
21
+ if (this.get(key) === value) {
22
+ this.delete(key);
23
+ }
24
+ }, this.#evictionTimeoutMs);
25
+ timeout.unref();
26
+ this.#timeouts.add(timeout);
27
+ return this;
28
+ }
29
+ }
@@ -0,0 +1,8 @@
1
+ import type { MapShareStateUpdate } from '../types.js';
2
+ /**
3
+ * Event representing a state update in a map share
4
+ */
5
+ export declare const StateUpdateEvent: new <TUpdate extends MapShareStateUpdate>(update: TUpdate) => Event & {
6
+ type: "update";
7
+ } & TUpdate;
8
+ //# sourceMappingURL=state-update-event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-update-event.d.ts","sourceRoot":"","sources":["../../src/lib/state-update-event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEtD;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAMxB,KAAK,OAAO,SAAS,mBAAmB,EAC5C,MAAM,EAAE,OAAO,KACX,KAAK,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,OAAO,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Event representing a state update in a map share
3
+ */
4
+ export const StateUpdateEvent = class extends Event {
5
+ static type = 'update';
6
+ constructor(update) {
7
+ super('update');
8
+ Object.assign(this, update);
9
+ }
10
+ };
@@ -0,0 +1,32 @@
1
+ import type { SMPStyle } from 'styled-map-package';
2
+ import type { BBox } from '../types.js';
3
+ /**
4
+ * If the argument is an `Error` instance, return its `code` property if it is a string.
5
+ * Otherwise, returns `undefined`.
6
+ *
7
+ * @param {unknown} maybeError
8
+ * @returns {undefined | string}
9
+ * @example
10
+ * try {
11
+ * // do something
12
+ * } catch (err) {
13
+ * console.error(getErrorCode(err))
14
+ * }
15
+ */
16
+ export declare function getErrorCode(maybeError: unknown): string | undefined;
17
+ export declare function noop(): void;
18
+ export declare function generateId(): string;
19
+ export declare function getOrInsert<K, V>(map: Map<K, V>, key: K, value: V): V;
20
+ export declare function timingSafeEqual(a: string, b: string): boolean;
21
+ /**
22
+ * Returns a bbox that is the smallest bounding box that contains all the input bboxes.
23
+ *
24
+ * @param bboxes
25
+ * @returns Bounding Box [w, s, e, n] of all input bboxes
26
+ */
27
+ export declare function unionBBox(bboxes: [BBox, ...BBox[]]): BBox;
28
+ export declare function getStyleBbox(style: SMPStyle): BBox;
29
+ export declare function getStyleMaxZoom(style: SMPStyle): number;
30
+ export declare function getStyleMinZoom(style: SMPStyle): number;
31
+ export declare function addTrailingSlash(url: string): string;
32
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAEvC;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,OAAO,sBAS/C;AAED,wBAAgB,IAAI,SAAK;AAEzB,wBAAgB,UAAU,WAEzB;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAMrE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAO7D;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,GAAG,IAAI,CAUzD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAUlD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAOvD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAOvD;AAMD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD"}