@comapeo/core-react 8.0.0 → 9.0.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.
- package/README.md +38 -0
- package/dist/commonjs/contexts/ClientApi.d.ts +5 -5
- package/dist/commonjs/contexts/ClientApi.js +6 -5
- package/dist/commonjs/contexts/ComapeoCore.d.ts +8 -0
- package/dist/commonjs/contexts/ComapeoCore.js +9 -0
- package/dist/commonjs/contexts/MapServer.d.ts +69 -0
- package/dist/commonjs/contexts/MapServer.js +92 -0
- package/dist/commonjs/contexts/MapShares.d.ts +52 -0
- package/dist/commonjs/contexts/MapShares.js +74 -0
- package/dist/commonjs/hooks/maps.d.ts +460 -3
- package/dist/commonjs/hooks/maps.js +261 -4
- package/dist/commonjs/index.d.ts +5 -2
- package/dist/commonjs/index.js +20 -3
- package/dist/commonjs/lib/http.d.ts +45 -0
- package/dist/commonjs/lib/http.js +103 -0
- package/dist/commonjs/lib/map-shares-stores.d.ts +80 -0
- package/dist/commonjs/lib/map-shares-stores.js +299 -0
- package/dist/commonjs/lib/react-query/maps.d.ts +66 -20
- package/dist/commonjs/lib/react-query/maps.js +113 -11
- package/dist/commonjs/lib/react-query/mutation-result.d.ts +8 -0
- package/dist/commonjs/lib/react-query/mutation-result.js +22 -0
- package/dist/esm/contexts/ClientApi.d.ts +5 -5
- package/dist/esm/contexts/ClientApi.js +6 -5
- package/dist/esm/contexts/ComapeoCore.d.ts +8 -0
- package/dist/esm/contexts/ComapeoCore.js +6 -0
- package/dist/esm/contexts/MapServer.d.ts +69 -0
- package/dist/esm/contexts/MapServer.js +86 -0
- package/dist/esm/contexts/MapShares.d.ts +52 -0
- package/dist/esm/contexts/MapShares.js +65 -0
- package/dist/esm/hooks/maps.d.ts +460 -3
- package/dist/esm/hooks/maps.js +252 -6
- package/dist/esm/index.d.ts +5 -2
- package/dist/esm/index.js +4 -2
- package/dist/esm/lib/http.d.ts +45 -0
- package/dist/esm/lib/http.js +98 -0
- package/dist/esm/lib/map-shares-stores.d.ts +80 -0
- package/dist/esm/lib/map-shares-stores.js +291 -0
- package/dist/esm/lib/react-query/maps.d.ts +66 -20
- package/dist/esm/lib/react-query/maps.js +109 -12
- package/dist/esm/lib/react-query/mutation-result.d.ts +8 -0
- package/dist/esm/lib/react-query/mutation-result.js +19 -0
- package/docs/API.md +567 -60
- package/package.json +15 -4
package/dist/esm/hooks/maps.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { CUSTOM_MAP_ID } from '@comapeo/map-server/constants.js';
|
|
2
|
+
import { errors } from '@comapeo/map-server/errors.js';
|
|
3
|
+
import { useMutation, useQuery, useQueryClient, useSuspenseQuery, } from '@tanstack/react-query';
|
|
4
|
+
import { useCallback } from 'react';
|
|
5
|
+
import { useMapServerApi } from '../contexts/MapServer.js';
|
|
6
|
+
import { useReceivedMapSharesActions, useReceivedMapSharesState, useSentMapSharesActions, useSentMapSharesState, } from '../contexts/MapShares.js';
|
|
7
|
+
import { mapImportMutationOptions, mapInfoQueryOptions, mapRemoveMutationOptions, mapSharesMutationOptions, mapStyleJsonUrlQueryOptions, } from '../lib/react-query/maps.js';
|
|
8
|
+
import { filterMutationResult } from '../lib/react-query/mutation-result.js';
|
|
4
9
|
/**
|
|
5
10
|
* Get a URL that points to a StyleJSON resource served by the embedded HTTP server.
|
|
6
11
|
*
|
|
@@ -27,8 +32,249 @@ import { useClientApi } from './client.js';
|
|
|
27
32
|
* }
|
|
28
33
|
* ```
|
|
29
34
|
*/
|
|
30
|
-
export function useMapStyleUrl(
|
|
31
|
-
const
|
|
32
|
-
const { data, error, isRefetching } = useSuspenseQuery(mapStyleJsonUrlQueryOptions({
|
|
35
|
+
export function useMapStyleUrl() {
|
|
36
|
+
const mapServerApi = useMapServerApi();
|
|
37
|
+
const { data, error, isRefetching } = useSuspenseQuery(mapStyleJsonUrlQueryOptions({ mapServerApi }));
|
|
33
38
|
return { data, error, isRefetching };
|
|
34
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Import a custom SMP map file, replacing any existing custom map. The mutation
|
|
42
|
+
* resolves once the file is successfully uploaded and processed by the server.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* function MapImportExample() {
|
|
47
|
+
* const { mutate: importMap } = useImportCustomMapFile()
|
|
48
|
+
*
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function useImportCustomMapFile() {
|
|
53
|
+
const mapServerApi = useMapServerApi();
|
|
54
|
+
const queryClient = useQueryClient();
|
|
55
|
+
const options = mapImportMutationOptions({ mapServerApi, queryClient });
|
|
56
|
+
const result = useMutation(options);
|
|
57
|
+
return filterMutationResult(result);
|
|
58
|
+
}
|
|
59
|
+
export function useRemoveCustomMapFile() {
|
|
60
|
+
const mapServerApi = useMapServerApi();
|
|
61
|
+
const queryClient = useQueryClient();
|
|
62
|
+
const options = mapRemoveMutationOptions({ mapServerApi, queryClient });
|
|
63
|
+
const result = useMutation(options);
|
|
64
|
+
return filterMutationResult(result);
|
|
65
|
+
}
|
|
66
|
+
export function useGetCustomMapInfo() {
|
|
67
|
+
const mapServerApi = useMapServerApi();
|
|
68
|
+
const { data, error, isRefetching } = useQuery(mapInfoQueryOptions({ mapServerApi, mapId: CUSTOM_MAP_ID }));
|
|
69
|
+
return { data, error, isRefetching };
|
|
70
|
+
}
|
|
71
|
+
// ============================================
|
|
72
|
+
// RECEIVER HOOKS
|
|
73
|
+
// ============================================
|
|
74
|
+
/**
|
|
75
|
+
* Get all map shares that the device has received. Automatically updates when
|
|
76
|
+
* new shares arrive or share states change.
|
|
77
|
+
*
|
|
78
|
+
* IMPORTANT: This hook will not trigger a re-render when download progress
|
|
79
|
+
* updates, only when the status changes. This is to avoid excessive re-renders
|
|
80
|
+
* during downloads. Use `useSingleReceivedMapShare` to get real-time updates on
|
|
81
|
+
* a specific share, including download progress.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* function MapSharesList() {
|
|
86
|
+
* const shares = useManyReceivedMapShares()
|
|
87
|
+
*
|
|
88
|
+
* return shares.map(share => (
|
|
89
|
+
* <div key={share.shareId}>
|
|
90
|
+
* {share.mapName} from {share.senderDeviceName} - {share.state}
|
|
91
|
+
* </div>
|
|
92
|
+
* ))
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function useManyReceivedMapShares() {
|
|
97
|
+
return useReceivedMapSharesState();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get a single received map share based on its shareId.
|
|
101
|
+
*
|
|
102
|
+
* @param opts.shareId ID of the map share
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```tsx
|
|
106
|
+
* function MapShareDetail({ shareId }: { shareId: string }) {
|
|
107
|
+
* const share = useSingleReceivedMapShare({ shareId })
|
|
108
|
+
*
|
|
109
|
+
* return <div>{share.mapName} - {share.state}</div>
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function useSingleReceivedMapShare({ shareId }) {
|
|
114
|
+
const mapShare = useReceivedMapSharesState(useCallback((shares) => shares.find((s) => s.shareId === shareId), [shareId]));
|
|
115
|
+
if (!mapShare) {
|
|
116
|
+
throw new Error(`Map share with id ${shareId} not found`);
|
|
117
|
+
}
|
|
118
|
+
return mapShare;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Accept and download a map share that has been received. The mutate promise
|
|
122
|
+
* resolves once the map _starts_ downloading, before it finishes downloading.
|
|
123
|
+
* Use `useManyMapShares` or `useSingleMapShare` to track download progress.
|
|
124
|
+
*
|
|
125
|
+
* Throws if the share is not in `status="pending"` or if the download fails to
|
|
126
|
+
* start (e.g. if the shareId if invalid).
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```tsx
|
|
130
|
+
* function AcceptButton({ shareId }: { shareId: string }) {
|
|
131
|
+
* const { mutate: accept } = useAcceptMapShare()
|
|
132
|
+
*
|
|
133
|
+
* return <button onClick={() => accept({ shareId })}>Accept</button>
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export function useDownloadReceivedMapShare() {
|
|
138
|
+
const { download } = useReceivedMapSharesActions();
|
|
139
|
+
const options = mapSharesMutationOptions({ action: download });
|
|
140
|
+
const result = useMutation(options);
|
|
141
|
+
return filterMutationResult(result);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Decline a map share that has been received. Notifies the sender that the
|
|
145
|
+
* share was declined.
|
|
146
|
+
*
|
|
147
|
+
* Throws if the share is not with `status="pending"`
|
|
148
|
+
* Throws if shareId is invalid
|
|
149
|
+
* Throws if decline request fails (e.g. network error)
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* import { DeclineReason } from '@comapeo/core-react'
|
|
154
|
+
* function DeclineButton({ shareId }: { shareId: string }) {
|
|
155
|
+
* const { mutate: decline } = useDeclineMapShare()
|
|
156
|
+
*
|
|
157
|
+
* return (
|
|
158
|
+
* <button onClick={() => decline({ shareId, reason: DeclineReason.user_rejected })}>
|
|
159
|
+
* Decline
|
|
160
|
+
* </button>
|
|
161
|
+
* )
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export function useDeclineReceivedMapShare() {
|
|
166
|
+
const { decline } = useReceivedMapSharesActions();
|
|
167
|
+
const options = mapSharesMutationOptions({ action: decline });
|
|
168
|
+
const result = useMutation(options);
|
|
169
|
+
return filterMutationResult(result);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Abort an in-progress map share download.
|
|
173
|
+
*
|
|
174
|
+
* Throws if the share is not in `status="downloading"`
|
|
175
|
+
* Throws if shareId is invalid
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```tsx
|
|
179
|
+
* function AbortButton({ shareId }: { shareId: string }) {
|
|
180
|
+
* const { mutate: abort } = useAbortMapShareDownload()
|
|
181
|
+
*
|
|
182
|
+
* return <button onClick={() => abort({ shareId })}>Cancel Download</button>
|
|
183
|
+
* }
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export function useAbortReceivedMapShareDownload() {
|
|
187
|
+
const { abort } = useReceivedMapSharesActions();
|
|
188
|
+
const options = mapSharesMutationOptions({ action: abort });
|
|
189
|
+
const result = useMutation(options);
|
|
190
|
+
return filterMutationResult(result);
|
|
191
|
+
}
|
|
192
|
+
// ============================================
|
|
193
|
+
// SENDER HOOKS
|
|
194
|
+
// ============================================
|
|
195
|
+
/**
|
|
196
|
+
* Share a map with a device. The mutation resolves immediately after sending
|
|
197
|
+
* the share offer, without waiting for the recipient to accept or reject. The
|
|
198
|
+
* mutation resolves with the created map share object, including its ID, which
|
|
199
|
+
* can be used to track the share status with `useSingleSentMapShare`.
|
|
200
|
+
*
|
|
201
|
+
* @param opts.projectId Public ID of project for sending the map share: you can only send map shares to users on the same project
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```tsx
|
|
205
|
+
* function SendMapButton({ projectId, deviceId }: { projectId: string; deviceId: string }) {
|
|
206
|
+
* const { mutate: send } = useSendMapShare({ projectId }, {
|
|
207
|
+
* onSuccess: (mapShare) => {
|
|
208
|
+
* console.log('Share sent with id', mapShare.shareId)
|
|
209
|
+
* }
|
|
210
|
+
* })
|
|
211
|
+
*
|
|
212
|
+
* return (
|
|
213
|
+
* <button onClick={() => send({ receiverDeviceId: deviceId, mapId: 'custom' })}>
|
|
214
|
+
* Send Map
|
|
215
|
+
* </button>
|
|
216
|
+
* )
|
|
217
|
+
* }
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
export function useSendMapShare({ projectId }) {
|
|
221
|
+
const { createAndSend } = useSentMapSharesActions();
|
|
222
|
+
const options = mapSharesMutationOptions({ action: createAndSend, projectId });
|
|
223
|
+
const result = useMutation(options);
|
|
224
|
+
return filterMutationResult(result);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Cancel a map share that was previously sent. If the recipient has not yet
|
|
228
|
+
* started downloading the share, they will not be notified until they attempt
|
|
229
|
+
* to accept the share and begin downloading it. If they are already downloading
|
|
230
|
+
* the share, the download will be canceled before completion. If the download
|
|
231
|
+
* is already complete, this action will throw an error.
|
|
232
|
+
*
|
|
233
|
+
* @param opts.projectId Public ID of project to request the map share cancellation for.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```tsx
|
|
237
|
+
* function CancelShareButton({ projectId, shareId }: { projectId: string; shareId: string }) {
|
|
238
|
+
* const { mutate: cancel } = useRequestCancelMapShare({ projectId })
|
|
239
|
+
*
|
|
240
|
+
* return <button onClick={() => cancel({ shareId })}>Cancel Share</button>
|
|
241
|
+
* }
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
export function useCancelSentMapShare() {
|
|
245
|
+
const { cancel } = useSentMapSharesActions();
|
|
246
|
+
const options = mapSharesMutationOptions({ action: cancel });
|
|
247
|
+
const result = useMutation(options);
|
|
248
|
+
return filterMutationResult(result);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Track the status and progress of a sent map share. Returns the current state
|
|
252
|
+
* of the share, updated in real-time. When the recipient starts downloading, or
|
|
253
|
+
* if they decline the share, then the returned share will update.
|
|
254
|
+
*
|
|
255
|
+
* Throws if no share with the specified ID is found.
|
|
256
|
+
*
|
|
257
|
+
* @param opts.shareId ID of the sent map share
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```tsx
|
|
261
|
+
* function SentShareStatus({ shareId }: { shareId: string }) {
|
|
262
|
+
* const mapShare = useSingleSentMapShare({ shareId })
|
|
263
|
+
*
|
|
264
|
+
* return (<div>
|
|
265
|
+
* <div>Share status: {mapShare.status}</div>
|
|
266
|
+
* {mapShare.status === 'pending' && <div>Waiting for recipient to accept...</div>}
|
|
267
|
+
* {mapShare.status === 'downloading' && (<div>Download in progress: {mapShare.downloadProgress}%</div>)}
|
|
268
|
+
* {mapShare.status === 'declined' && <div>Share was declined by recipient</div>}
|
|
269
|
+
* {mapShare.status === 'canceled' && <div>Share was canceled</div>}
|
|
270
|
+
* </div>)
|
|
271
|
+
* }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
export function useSingleSentMapShare({ shareId, }) {
|
|
275
|
+
const mapShare = useSentMapSharesState(useCallback((shares) => shares.find((s) => s.shareId === shareId), [shareId]));
|
|
276
|
+
if (!mapShare) {
|
|
277
|
+
throw new errors.MAP_SHARE_NOT_FOUND(`Sent map share with id ${shareId} not found`);
|
|
278
|
+
}
|
|
279
|
+
return mapShare;
|
|
280
|
+
}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ComapeoCoreProvider } from './contexts/ComapeoCore.js';
|
|
2
2
|
export { useClientApi, useIsArchiveDevice, useOwnDeviceInfo, useSetIsArchiveDevice, useSetOwnDeviceInfo, } from './hooks/client.js';
|
|
3
3
|
export { useCreateDocument, useDeleteDocument, useManyDocs, usePresetsSelection, useSingleDocByDocId, useSingleDocByVersionId, useUpdateDocument, } from './hooks/documents.js';
|
|
4
4
|
export { useAcceptInvite, useManyInvites, useRejectInvite, useRequestCancelInvite, useSendInvite, useSingleInvite, } from './hooks/invites.js';
|
|
5
|
-
export { useMapStyleUrl } from './hooks/maps.js';
|
|
5
|
+
export { useMapStyleUrl, useImportCustomMapFile, useRemoveCustomMapFile, useGetCustomMapInfo, useManyReceivedMapShares, useSingleReceivedMapShare, useDeclineReceivedMapShare, useDownloadReceivedMapShare, useAbortReceivedMapShareDownload, useSendMapShare, useCancelSentMapShare, useSingleSentMapShare, } from './hooks/maps.js';
|
|
6
|
+
export type { SentMapShareState, ReceivedMapShareState, AbortMapShareOptions, CancelMapShareOptions, DeclineMapShareOptions, DownloadMapShareOptions, CreateAndSendMapShareOptions, } from './lib/map-shares-stores.js';
|
|
7
|
+
export { DeclineReason } from './lib/map-shares-stores.js';
|
|
6
8
|
export { useAddServerPeer, useAttachmentUrl, useConnectSyncServers, useCreateBlob, useCreateProject, useDataSyncProgress, useDisconnectSyncServers, useDocumentCreatedBy, useIconUrl, useImportProjectCategories, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useOwnRoleInProject, useProjectOwnRoleChangeListener, useProjectSettings, useRemoveServerPeer, useRemoveMember, useSetAutostopDataSyncTimeout, useSingleMember, useSingleProject, useStartSync, useStopSync, useSyncState, useUpdateProjectSettings, useChangeMemberRole, useExportGeoJSON, useExportZipFile, } from './hooks/projects.js';
|
|
7
9
|
export { type SyncState } from './lib/sync.js';
|
|
8
10
|
export { type WriteableDocument, type WriteableDocumentType, type WriteableValue, } from './lib/types.js';
|
|
11
|
+
export { HTTPError, isHTTPError } from './lib/http.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ComapeoCoreProvider } from './contexts/ComapeoCore.js';
|
|
2
2
|
export { useClientApi, useIsArchiveDevice, useOwnDeviceInfo, useSetIsArchiveDevice, useSetOwnDeviceInfo, } from './hooks/client.js';
|
|
3
3
|
export { useCreateDocument, useDeleteDocument, useManyDocs, usePresetsSelection, useSingleDocByDocId, useSingleDocByVersionId, useUpdateDocument, } from './hooks/documents.js';
|
|
4
4
|
export { useAcceptInvite, useManyInvites, useRejectInvite, useRequestCancelInvite, useSendInvite, useSingleInvite, } from './hooks/invites.js';
|
|
5
|
-
export { useMapStyleUrl } from './hooks/maps.js';
|
|
5
|
+
export { useMapStyleUrl, useImportCustomMapFile, useRemoveCustomMapFile, useGetCustomMapInfo, useManyReceivedMapShares, useSingleReceivedMapShare, useDeclineReceivedMapShare, useDownloadReceivedMapShare, useAbortReceivedMapShareDownload, useSendMapShare, useCancelSentMapShare, useSingleSentMapShare, } from './hooks/maps.js';
|
|
6
|
+
export { DeclineReason } from './lib/map-shares-stores.js';
|
|
6
7
|
export { useAddServerPeer, useAttachmentUrl, useConnectSyncServers, useCreateBlob, useCreateProject, useDataSyncProgress, useDisconnectSyncServers, useDocumentCreatedBy, useIconUrl, useImportProjectCategories, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useOwnRoleInProject, useProjectOwnRoleChangeListener, useProjectSettings, useRemoveServerPeer, useRemoveMember, useSetAutostopDataSyncTimeout, useSingleMember, useSingleProject, useStartSync, useStopSync, useSyncState, useUpdateProjectSettings, useChangeMemberRole, useExportGeoJSON, useExportZipFile, } from './hooks/projects.js';
|
|
8
|
+
export { HTTPError, isHTTPError } from './lib/http.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { JsonValue } from 'type-fest';
|
|
2
|
+
type HttpInit = RequestInit & {
|
|
3
|
+
json?: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
type ResponsePromise = Promise<Response> & {
|
|
6
|
+
json<T = unknown>(): Promise<T>;
|
|
7
|
+
text(): Promise<string>;
|
|
8
|
+
};
|
|
9
|
+
type Input = string | URL;
|
|
10
|
+
/**
|
|
11
|
+
* http - A minimal fetch wrapper to reduce some boilerplate.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```js
|
|
15
|
+
* import { createHttp } from './http.js'
|
|
16
|
+
* const http = createHttp(myCustomFetch)
|
|
17
|
+
*
|
|
18
|
+
* const data = await http.get('https://api.example.com/items').json()
|
|
19
|
+
* await http.post('https://api.example.com/items', { json: { name: 'foo' } }).json()
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function createHttp(fetchFn?: (input: string | URL, init?: RequestInit) => Promise<Response>): Record<"head" | "get" | "post" | "put" | "patch" | "delete", (input: Input, options?: HttpInit) => ResponsePromise>;
|
|
23
|
+
type HTTPErrorObject = {
|
|
24
|
+
message?: string;
|
|
25
|
+
code?: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* HTTPError - Custom error class to represent HTTP errors with additional context.
|
|
30
|
+
*/
|
|
31
|
+
declare class HTTPError extends Error {
|
|
32
|
+
readonly response: Response;
|
|
33
|
+
readonly status: number;
|
|
34
|
+
readonly code: string;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
constructor(response: Response, { method, url, ...body }: {
|
|
37
|
+
method: string;
|
|
38
|
+
url: string;
|
|
39
|
+
} & HTTPErrorObject);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Type guard to check if an error is an instance of HTTPError.
|
|
43
|
+
*/
|
|
44
|
+
declare function isHTTPError(error: unknown): error is HTTPError;
|
|
45
|
+
export { createHttp, HTTPError, isHTTPError };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const requestMethods = [
|
|
2
|
+
'get',
|
|
3
|
+
'post',
|
|
4
|
+
'put',
|
|
5
|
+
'patch',
|
|
6
|
+
'head',
|
|
7
|
+
'delete',
|
|
8
|
+
];
|
|
9
|
+
/**
|
|
10
|
+
* http - A minimal fetch wrapper to reduce some boilerplate.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```js
|
|
14
|
+
* import { createHttp } from './http.js'
|
|
15
|
+
* const http = createHttp(myCustomFetch)
|
|
16
|
+
*
|
|
17
|
+
* const data = await http.get('https://api.example.com/items').json()
|
|
18
|
+
* await http.post('https://api.example.com/items', { json: { name: 'foo' } }).json()
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function createHttp(fetchFn = globalThis.fetch) {
|
|
22
|
+
const alias = (method) => (input, options = {}) => {
|
|
23
|
+
const { json, headers, ...rest } = options;
|
|
24
|
+
const h = new Headers(headers);
|
|
25
|
+
if (json !== undefined) {
|
|
26
|
+
rest.body = JSON.stringify(json);
|
|
27
|
+
if (!h.has('content-type'))
|
|
28
|
+
h.set('content-type', 'application/json');
|
|
29
|
+
}
|
|
30
|
+
if (!h.has('accept'))
|
|
31
|
+
h.set('accept', 'application/json');
|
|
32
|
+
const responsePromise = fetchFn(input, {
|
|
33
|
+
...rest,
|
|
34
|
+
method,
|
|
35
|
+
headers: h,
|
|
36
|
+
}).then((response) => {
|
|
37
|
+
if (!response.ok)
|
|
38
|
+
throw new HTTPError(response, { method, url: input.toString() });
|
|
39
|
+
return response;
|
|
40
|
+
});
|
|
41
|
+
responsePromise.json = () => responsePromise
|
|
42
|
+
.catch((e) => {
|
|
43
|
+
// For http errors, parse the body as json to get the error details, but rethrow other errors (e.g. network errors)
|
|
44
|
+
if (isHTTPError(e))
|
|
45
|
+
return e.response;
|
|
46
|
+
throw e;
|
|
47
|
+
})
|
|
48
|
+
.then(async (r) => {
|
|
49
|
+
if (r.status === 204)
|
|
50
|
+
return '';
|
|
51
|
+
const text = await r.text();
|
|
52
|
+
const parsed = text === '' ? '' : JSON.parse(text);
|
|
53
|
+
if (r.ok)
|
|
54
|
+
return parsed;
|
|
55
|
+
throw new HTTPError(r, {
|
|
56
|
+
method: options.method ?? 'GET',
|
|
57
|
+
url: input.toString(),
|
|
58
|
+
...parsed,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
responsePromise.text = () => responsePromise.then((r) => r.text());
|
|
62
|
+
return responsePromise;
|
|
63
|
+
};
|
|
64
|
+
const http = {};
|
|
65
|
+
for (const method of requestMethods) {
|
|
66
|
+
http[method] = alias(upperCase(method));
|
|
67
|
+
}
|
|
68
|
+
return http;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* HTTPError - Custom error class to represent HTTP errors with additional context.
|
|
72
|
+
*/
|
|
73
|
+
class HTTPError extends Error {
|
|
74
|
+
response;
|
|
75
|
+
status;
|
|
76
|
+
code = 'UNKNOWN_ERROR';
|
|
77
|
+
constructor(response, { method, url, ...body }) {
|
|
78
|
+
const status = response.status || 500;
|
|
79
|
+
const message = body.message || `Request failed with ${status}: ${method} ${url}`;
|
|
80
|
+
super(message);
|
|
81
|
+
Object.assign(this, body);
|
|
82
|
+
// override status in body with status from response
|
|
83
|
+
this.status = status;
|
|
84
|
+
this.name = 'HTTPError';
|
|
85
|
+
this.response = response;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Type guard to check if an error is an instance of HTTPError.
|
|
90
|
+
*/
|
|
91
|
+
function isHTTPError(error) {
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
return error instanceof HTTPError || error?.name === HTTPError.name;
|
|
94
|
+
}
|
|
95
|
+
export { createHttp, HTTPError, isHTTPError };
|
|
96
|
+
function upperCase(str) {
|
|
97
|
+
return str.toUpperCase();
|
|
98
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { MapShare } from '@comapeo/core';
|
|
2
|
+
import type { MapeoClientApi } from '@comapeo/ipc';
|
|
3
|
+
import { type MapShareState as ServerMapShareState } from '@comapeo/map-server';
|
|
4
|
+
import { type QueryClient } from '@tanstack/react-query';
|
|
5
|
+
import type { Simplify } from 'type-fest';
|
|
6
|
+
import type { MapServerApi } from '../contexts/MapServer.js';
|
|
7
|
+
type DistributedIntersection<T, U> = U extends unknown ? Simplify<T & U> : never;
|
|
8
|
+
export type ReceivedMapShareState = DistributedIntersection<Simplify<MapShare>, ServerMapShareState>;
|
|
9
|
+
export type SentMapShareState = ServerMapShareState;
|
|
10
|
+
export type ReceivedMapSharesStore = ReturnType<typeof createReceivedMapSharesStore>;
|
|
11
|
+
export type SentMapSharesStore = ReturnType<typeof createSentMapSharesStore>;
|
|
12
|
+
/** Known reasons for declining a map share */
|
|
13
|
+
export declare const DeclineReason: {
|
|
14
|
+
/** User explicitly rejected the map share */
|
|
15
|
+
readonly user_rejected: "user_rejected";
|
|
16
|
+
/** Device storage is full */
|
|
17
|
+
readonly storage_full: "storage_full";
|
|
18
|
+
};
|
|
19
|
+
/** Options for downloading a received map share */
|
|
20
|
+
export type DownloadMapShareOptions = {
|
|
21
|
+
/** ID of the map share to download */
|
|
22
|
+
shareId: string;
|
|
23
|
+
};
|
|
24
|
+
/** Options for declining a received map share */
|
|
25
|
+
export type DeclineMapShareOptions = {
|
|
26
|
+
/** ID of the map share to decline */
|
|
27
|
+
shareId: string;
|
|
28
|
+
/** Reason for declining (e.g., 'user_rejected', 'storage_full') */
|
|
29
|
+
reason: (typeof DeclineReason)[keyof typeof DeclineReason] | (string & {});
|
|
30
|
+
};
|
|
31
|
+
/** Options for aborting an in-progress map share download */
|
|
32
|
+
export type AbortMapShareOptions = {
|
|
33
|
+
/** ID of the map share download to abort */
|
|
34
|
+
shareId: string;
|
|
35
|
+
};
|
|
36
|
+
/** Options for creating and sending a map share */
|
|
37
|
+
export type CreateAndSendMapShareOptions = {
|
|
38
|
+
/** Public ID of the project to send the share on behalf of */
|
|
39
|
+
projectId: string;
|
|
40
|
+
/** Device ID of the recipient */
|
|
41
|
+
receiverDeviceId: string;
|
|
42
|
+
/** ID of the map to share - not needed until we support multiple maps */
|
|
43
|
+
mapId?: string;
|
|
44
|
+
};
|
|
45
|
+
/** Options for canceling a sent map share */
|
|
46
|
+
export type CancelMapShareOptions = {
|
|
47
|
+
/** ID of the map share to cancel */
|
|
48
|
+
shareId: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Store and actions for received map shares.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createReceivedMapSharesStore({ clientApi, mapServerApi, queryClient, }: {
|
|
54
|
+
clientApi: MapeoClientApi;
|
|
55
|
+
mapServerApi: MapServerApi;
|
|
56
|
+
queryClient: QueryClient;
|
|
57
|
+
}): {
|
|
58
|
+
subscribe: (listener: () => void) => () => boolean;
|
|
59
|
+
getSnapshot: () => ReceivedMapShareState[];
|
|
60
|
+
actions: {
|
|
61
|
+
download({ shareId }: DownloadMapShareOptions): Promise<void>;
|
|
62
|
+
decline({ shareId, reason }: DeclineMapShareOptions): Promise<void>;
|
|
63
|
+
abort({ shareId }: AbortMapShareOptions): Promise<void>;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Store and actions for sent map share.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createSentMapSharesStore({ clientApi, mapServerApi, }: {
|
|
70
|
+
clientApi: MapeoClientApi;
|
|
71
|
+
mapServerApi: MapServerApi;
|
|
72
|
+
}): {
|
|
73
|
+
subscribe: (listener: () => void) => () => boolean;
|
|
74
|
+
getSnapshot: () => ServerMapShareState[];
|
|
75
|
+
actions: {
|
|
76
|
+
createAndSend({ projectId, receiverDeviceId, mapId, }: CreateAndSendMapShareOptions): Promise<ServerMapShareState>;
|
|
77
|
+
cancel({ shareId }: CancelMapShareOptions): Promise<void>;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
export {};
|