@comapeo/core-react 10.0.1 → 11.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.
@@ -42,7 +42,7 @@ export declare function useReceivedMapSharesState<T>(selector: (state: Array<Rec
42
42
  * @internal
43
43
  */
44
44
  export declare function useSentMapSharesActions(): {
45
- createAndSend({ projectId, receiverDeviceId, mapId, }: import("../lib/map-shares-stores.js").CreateAndSendMapShareOptions): Promise<import("@comapeo/map-server", { with: { "resolution-mode": "import" } }).MapShareState>;
45
+ createAndSend({ receiverDeviceId, mapId, }: import("../lib/map-shares-stores.js").CreateAndSendMapShareOptions): Promise<import("@comapeo/map-server", { with: { "resolution-mode": "import" } }).MapShareState>;
46
46
  cancel({ shareId }: import("../lib/map-shares-stores.js").CancelMapShareOptions): Promise<void>;
47
47
  };
48
48
  /**
@@ -156,15 +156,25 @@ export declare function useSingleReceivedMapShare({ shareId }: {
156
156
  /**
157
157
  * Accept and download a map share that has been received. The mutate promise
158
158
  * resolves once the map _starts_ downloading, before it finishes downloading.
159
- * Use `useManyMapShares` or `useSingleMapShare` to track download progress.
159
+ * Use `useManyReceivedMapShares` or `useSingleReceivedMapShare` to track
160
+ * download progress and final status.
160
161
  *
161
- * Throws if the share is not in `status="pending"` or if the download fails to
162
- * start (e.g. if the shareId if invalid).
162
+ * If the sender canceled the share before the receiver calls this, the
163
+ * mutation will still resolve (the download starts), but the share status will
164
+ * end up as `'canceled'` rather than `'completed'`. This is the only way the
165
+ * receiver discovers that a share has been canceled — check `share.status`
166
+ * after the download settles.
167
+ *
168
+ * @throws {MapShareCanceledError} If the share is already known to be canceled
169
+ * (i.e. `status` is `'canceled'` in the store, e.g. after a previous
170
+ * download attempt discovered the cancellation).
171
+ * @throws {InvalidStatusTransitionError} If the share is not in a valid state
172
+ * to start downloading (e.g. already downloading, completed, or declined).
163
173
  *
164
174
  * @example
165
175
  * ```tsx
166
176
  * function AcceptButton({ shareId }: { shareId: string }) {
167
- * const { mutate: accept } = useAcceptMapShare()
177
+ * const { mutate: accept } = useDownloadReceivedMapShare()
168
178
  *
169
179
  * return <button onClick={() => accept({ shareId })}>Accept</button>
170
180
  * }
@@ -189,17 +199,25 @@ export declare function useDownloadReceivedMapShare(): Pick<import("@tanstack/re
189
199
  }, "error" | "status" | "mutate" | "reset" | "mutateAsync">;
190
200
  /**
191
201
  * Decline a map share that has been received. Notifies the sender that the
192
- * share was declined.
202
+ * share was declined. The share status is only updated to `'declined'` after
203
+ * the server confirms the decline — there is no optimistic update.
204
+ *
205
+ * If the sender canceled the share before the decline reaches the server, the
206
+ * share status will transition to `'canceled'` (not `'error'`) and the
207
+ * mutation will throw a `MapShareCanceledError`.
193
208
  *
194
- * Throws if the share is not with `status="pending"`
195
- * Throws if shareId is invalid
196
- * Throws if decline request fails (e.g. network error)
209
+ * @throws {MapShareCanceledError} If the share is already known to be
210
+ * canceled, or if the server reports that the sender canceled the share
211
+ * while the decline was in flight. In both cases `share.status` will be
212
+ * `'canceled'`.
213
+ * @throws {InvalidStatusTransitionError} If the share is not in
214
+ * `status='pending'` (e.g. already downloading, completed, or declined).
197
215
  *
198
216
  * @example
199
217
  * ```tsx
200
218
  * import { DeclineReason } from '@comapeo/core-react'
201
219
  * function DeclineButton({ shareId }: { shareId: string }) {
202
- * const { mutate: decline } = useDeclineMapShare()
220
+ * const { mutate: decline } = useDeclineReceivedMapShare()
203
221
  *
204
222
  * return (
205
223
  * <button onClick={() => decline({ shareId, reason: DeclineReason.user_rejected })}>
@@ -229,13 +247,15 @@ export declare function useDeclineReceivedMapShare(): Pick<import("@tanstack/rea
229
247
  /**
230
248
  * Abort an in-progress map share download.
231
249
  *
232
- * Throws if the share is not in `status="downloading"`
233
- * Throws if shareId is invalid
250
+ * @throws {MapShareCanceledError} If the share is already known to be canceled.
251
+ * @throws {InvalidStatusTransitionError} If the share is not in
252
+ * `status='downloading'` (e.g. still pending, already completed, or
253
+ * declined).
234
254
  *
235
255
  * @example
236
256
  * ```tsx
237
257
  * function AbortButton({ shareId }: { shareId: string }) {
238
- * const { mutate: abort } = useAbortMapShareDownload()
258
+ * const { mutate: abort } = useAbortReceivedMapShareDownload()
239
259
  *
240
260
  * return <button onClick={() => abort({ shareId })}>Cancel Download</button>
241
261
  * }
@@ -264,8 +284,6 @@ export declare function useAbortReceivedMapShareDownload(): Pick<import("@tansta
264
284
  * mutation resolves with the created map share object, including its ID, which
265
285
  * can be used to track the share status with `useSingleSentMapShare`.
266
286
  *
267
- * @param opts.projectId Public ID of project for sending the map share: you can only send map shares to users on the same project
268
- *
269
287
  * @example
270
288
  * ```tsx
271
289
  * function SendMapButton({ projectId, deviceId }: { projectId: string; deviceId: string }) {
@@ -274,7 +292,7 @@ export declare function useAbortReceivedMapShareDownload(): Pick<import("@tansta
274
292
  * return (
275
293
  * <button
276
294
  * onClick={() =>
277
- * send({ projectId, receiverDeviceId: deviceId, mapId: 'custom' }, {
295
+ * send({ receiverDeviceId: deviceId, mapId: 'custom' }, {
278
296
  * onSuccess: (mapShare) => {
279
297
  * console.log('Share sent with id', mapShare.shareId)
280
298
  * }
@@ -182,15 +182,25 @@ function useSingleReceivedMapShare({ shareId }) {
182
182
  /**
183
183
  * Accept and download a map share that has been received. The mutate promise
184
184
  * resolves once the map _starts_ downloading, before it finishes downloading.
185
- * Use `useManyMapShares` or `useSingleMapShare` to track download progress.
185
+ * Use `useManyReceivedMapShares` or `useSingleReceivedMapShare` to track
186
+ * download progress and final status.
186
187
  *
187
- * Throws if the share is not in `status="pending"` or if the download fails to
188
- * start (e.g. if the shareId if invalid).
188
+ * If the sender canceled the share before the receiver calls this, the
189
+ * mutation will still resolve (the download starts), but the share status will
190
+ * end up as `'canceled'` rather than `'completed'`. This is the only way the
191
+ * receiver discovers that a share has been canceled — check `share.status`
192
+ * after the download settles.
193
+ *
194
+ * @throws {MapShareCanceledError} If the share is already known to be canceled
195
+ * (i.e. `status` is `'canceled'` in the store, e.g. after a previous
196
+ * download attempt discovered the cancellation).
197
+ * @throws {InvalidStatusTransitionError} If the share is not in a valid state
198
+ * to start downloading (e.g. already downloading, completed, or declined).
189
199
  *
190
200
  * @example
191
201
  * ```tsx
192
202
  * function AcceptButton({ shareId }: { shareId: string }) {
193
- * const { mutate: accept } = useAcceptMapShare()
203
+ * const { mutate: accept } = useDownloadReceivedMapShare()
194
204
  *
195
205
  * return <button onClick={() => accept({ shareId })}>Accept</button>
196
206
  * }
@@ -207,17 +217,25 @@ function useDownloadReceivedMapShare() {
207
217
  }
208
218
  /**
209
219
  * Decline a map share that has been received. Notifies the sender that the
210
- * share was declined.
220
+ * share was declined. The share status is only updated to `'declined'` after
221
+ * the server confirms the decline — there is no optimistic update.
222
+ *
223
+ * If the sender canceled the share before the decline reaches the server, the
224
+ * share status will transition to `'canceled'` (not `'error'`) and the
225
+ * mutation will throw a `MapShareCanceledError`.
211
226
  *
212
- * Throws if the share is not with `status="pending"`
213
- * Throws if shareId is invalid
214
- * Throws if decline request fails (e.g. network error)
227
+ * @throws {MapShareCanceledError} If the share is already known to be
228
+ * canceled, or if the server reports that the sender canceled the share
229
+ * while the decline was in flight. In both cases `share.status` will be
230
+ * `'canceled'`.
231
+ * @throws {InvalidStatusTransitionError} If the share is not in
232
+ * `status='pending'` (e.g. already downloading, completed, or declined).
215
233
  *
216
234
  * @example
217
235
  * ```tsx
218
236
  * import { DeclineReason } from '@comapeo/core-react'
219
237
  * function DeclineButton({ shareId }: { shareId: string }) {
220
- * const { mutate: decline } = useDeclineMapShare()
238
+ * const { mutate: decline } = useDeclineReceivedMapShare()
221
239
  *
222
240
  * return (
223
241
  * <button onClick={() => decline({ shareId, reason: DeclineReason.user_rejected })}>
@@ -239,13 +257,15 @@ function useDeclineReceivedMapShare() {
239
257
  /**
240
258
  * Abort an in-progress map share download.
241
259
  *
242
- * Throws if the share is not in `status="downloading"`
243
- * Throws if shareId is invalid
260
+ * @throws {MapShareCanceledError} If the share is already known to be canceled.
261
+ * @throws {InvalidStatusTransitionError} If the share is not in
262
+ * `status='downloading'` (e.g. still pending, already completed, or
263
+ * declined).
244
264
  *
245
265
  * @example
246
266
  * ```tsx
247
267
  * function AbortButton({ shareId }: { shareId: string }) {
248
- * const { mutate: abort } = useAbortMapShareDownload()
268
+ * const { mutate: abort } = useAbortReceivedMapShareDownload()
249
269
  *
250
270
  * return <button onClick={() => abort({ shareId })}>Cancel Download</button>
251
271
  * }
@@ -269,8 +289,6 @@ function useAbortReceivedMapShareDownload() {
269
289
  * mutation resolves with the created map share object, including its ID, which
270
290
  * can be used to track the share status with `useSingleSentMapShare`.
271
291
  *
272
- * @param opts.projectId Public ID of project for sending the map share: you can only send map shares to users on the same project
273
- *
274
292
  * @example
275
293
  * ```tsx
276
294
  * function SendMapButton({ projectId, deviceId }: { projectId: string; deviceId: string }) {
@@ -279,7 +297,7 @@ function useAbortReceivedMapShareDownload() {
279
297
  * return (
280
298
  * <button
281
299
  * onClick={() =>
282
- * send({ projectId, receiverDeviceId: deviceId, mapId: 'custom' }, {
300
+ * send({ receiverDeviceId: deviceId, mapId: 'custom' }, {
283
301
  * onSuccess: (mapShare) => {
284
302
  * console.log('Share sent with id', mapShare.shareId)
285
303
  * }
@@ -4,7 +4,7 @@ export { useCreateDocument, useDeleteDocument, useManyDocs, usePresetsSelection,
4
4
  export { useAcceptInvite, useManyInvites, useRejectInvite, useRequestCancelInvite, useSendInvite, useSingleInvite, } from './hooks/invites.js';
5
5
  export { useMapStyleUrl, useImportCustomMapFile, useRemoveCustomMapFile, useGetCustomMapInfo, useManyReceivedMapShares, useSingleReceivedMapShare, useDeclineReceivedMapShare, useDownloadReceivedMapShare, useAbortReceivedMapShareDownload, useSendMapShare, useCancelSentMapShare, useSingleSentMapShare, } from './hooks/maps.js';
6
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';
7
+ export { DeclineReason, MapShareErrorCode, getErrorCode, MapShareCanceledError, InvalidStatusTransitionError, } from './lib/map-shares-stores.js';
8
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';
9
9
  export type { SyncState } from './lib/sync.js';
10
10
  export type { WriteableDocument, WriteableDocumentType, WriteableValue, } from './lib/types.js';
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useRemoveServerPeer = exports.useProjectSettings = exports.useProjectOwnRoleChangeListener = exports.useOwnRoleInProject = exports.useManyProjects = exports.useManyMembers = exports.useLeaveProject = exports.useImportProjectConfig = exports.useImportProjectCategories = exports.useIconUrl = exports.useDocumentCreatedBy = exports.useDisconnectSyncServers = exports.useDataSyncProgress = exports.useCreateProject = exports.useCreateBlob = exports.useConnectSyncServers = exports.useAttachmentUrl = exports.useAddServerPeer = exports.DeclineReason = exports.useSingleSentMapShare = exports.useCancelSentMapShare = exports.useSendMapShare = exports.useAbortReceivedMapShareDownload = exports.useDownloadReceivedMapShare = exports.useDeclineReceivedMapShare = exports.useSingleReceivedMapShare = exports.useManyReceivedMapShares = exports.useGetCustomMapInfo = exports.useRemoveCustomMapFile = exports.useImportCustomMapFile = exports.useMapStyleUrl = exports.useSingleInvite = exports.useSendInvite = exports.useRequestCancelInvite = exports.useRejectInvite = exports.useManyInvites = exports.useAcceptInvite = exports.useUpdateDocument = exports.useSingleDocByVersionId = exports.useSingleDocByDocId = exports.usePresetsSelection = exports.useManyDocs = exports.useDeleteDocument = exports.useCreateDocument = exports.useSetOwnDeviceInfo = exports.useSetIsArchiveDevice = exports.useOwnDeviceInfo = exports.useIsArchiveDevice = exports.useClientApi = exports.ComapeoCoreProvider = void 0;
4
- exports.isHTTPError = exports.HTTPError = exports.useExportZipFile = exports.useExportGeoJSON = exports.useChangeMemberRole = exports.useUpdateProjectSettings = exports.useSyncState = exports.useStopSync = exports.useStartSync = exports.useSingleProject = exports.useSingleMember = exports.useSetAutostopDataSyncTimeout = exports.useRemoveMember = void 0;
3
+ exports.useManyProjects = exports.useManyMembers = exports.useLeaveProject = exports.useImportProjectConfig = exports.useImportProjectCategories = exports.useIconUrl = exports.useDocumentCreatedBy = exports.useDisconnectSyncServers = exports.useDataSyncProgress = exports.useCreateProject = exports.useCreateBlob = exports.useConnectSyncServers = exports.useAttachmentUrl = exports.useAddServerPeer = exports.InvalidStatusTransitionError = exports.MapShareCanceledError = exports.getErrorCode = exports.MapShareErrorCode = exports.DeclineReason = exports.useSingleSentMapShare = exports.useCancelSentMapShare = exports.useSendMapShare = exports.useAbortReceivedMapShareDownload = exports.useDownloadReceivedMapShare = exports.useDeclineReceivedMapShare = exports.useSingleReceivedMapShare = exports.useManyReceivedMapShares = exports.useGetCustomMapInfo = exports.useRemoveCustomMapFile = exports.useImportCustomMapFile = exports.useMapStyleUrl = exports.useSingleInvite = exports.useSendInvite = exports.useRequestCancelInvite = exports.useRejectInvite = exports.useManyInvites = exports.useAcceptInvite = exports.useUpdateDocument = exports.useSingleDocByVersionId = exports.useSingleDocByDocId = exports.usePresetsSelection = exports.useManyDocs = exports.useDeleteDocument = exports.useCreateDocument = exports.useSetOwnDeviceInfo = exports.useSetIsArchiveDevice = exports.useOwnDeviceInfo = exports.useIsArchiveDevice = exports.useClientApi = exports.ComapeoCoreProvider = void 0;
4
+ exports.isHTTPError = exports.HTTPError = exports.useExportZipFile = exports.useExportGeoJSON = exports.useChangeMemberRole = exports.useUpdateProjectSettings = exports.useSyncState = exports.useStopSync = exports.useStartSync = exports.useSingleProject = exports.useSingleMember = exports.useSetAutostopDataSyncTimeout = exports.useRemoveMember = exports.useRemoveServerPeer = exports.useProjectSettings = exports.useProjectOwnRoleChangeListener = exports.useOwnRoleInProject = void 0;
5
5
  var ComapeoCore_js_1 = require("./contexts/ComapeoCore.js");
6
6
  Object.defineProperty(exports, "ComapeoCoreProvider", { enumerable: true, get: function () { return ComapeoCore_js_1.ComapeoCoreProvider; } });
7
7
  var client_js_1 = require("./hooks/client.js");
@@ -40,6 +40,10 @@ Object.defineProperty(exports, "useCancelSentMapShare", { enumerable: true, get:
40
40
  Object.defineProperty(exports, "useSingleSentMapShare", { enumerable: true, get: function () { return maps_js_1.useSingleSentMapShare; } });
41
41
  var map_shares_stores_js_1 = require("./lib/map-shares-stores.js");
42
42
  Object.defineProperty(exports, "DeclineReason", { enumerable: true, get: function () { return map_shares_stores_js_1.DeclineReason; } });
43
+ Object.defineProperty(exports, "MapShareErrorCode", { enumerable: true, get: function () { return map_shares_stores_js_1.MapShareErrorCode; } });
44
+ Object.defineProperty(exports, "getErrorCode", { enumerable: true, get: function () { return map_shares_stores_js_1.getErrorCode; } });
45
+ Object.defineProperty(exports, "MapShareCanceledError", { enumerable: true, get: function () { return map_shares_stores_js_1.MapShareCanceledError; } });
46
+ Object.defineProperty(exports, "InvalidStatusTransitionError", { enumerable: true, get: function () { return map_shares_stores_js_1.InvalidStatusTransitionError; } });
43
47
  var projects_js_1 = require("./hooks/projects.js");
44
48
  Object.defineProperty(exports, "useAddServerPeer", { enumerable: true, get: function () { return projects_js_1.useAddServerPeer; } });
45
49
  Object.defineProperty(exports, "useAttachmentUrl", { enumerable: true, get: function () { return projects_js_1.useAttachmentUrl; } });
@@ -9,6 +9,107 @@ export type ReceivedMapShareState = DistributedIntersection<Simplify<MapShare>,
9
9
  export type SentMapShareState = ServerMapShareState;
10
10
  export type ReceivedMapSharesStore = ReturnType<typeof createReceivedMapSharesStore>;
11
11
  export type SentMapSharesStore = ReturnType<typeof createSentMapSharesStore>;
12
+ /**
13
+ * Error codes for map share operations. Use with {@link getErrorCode} to safely
14
+ * check the error code of an unknown error thrown by a map share mutation, or
15
+ * to check the `error.code` on a share in `status='error'`.
16
+ *
17
+ * ## Receiver errors
18
+ *
19
+ * **Mutation errors** (thrown by receiver hooks, check via
20
+ * `getErrorCode(mutation.error)`):
21
+ * - `MAP_SHARE_CANCELED` — the sender canceled the share
22
+ * - `INVALID_STATUS_TRANSITION` — the action is not valid for the share's
23
+ * current status (e.g. declining a share that is already downloading)
24
+ * - `MAP_SHARE_NOT_FOUND` — no share with the given `shareId` exists
25
+ * - `DOWNLOAD_NOT_FOUND` — abort was called but no download is tracked for
26
+ * this share
27
+ *
28
+ * **Share state errors** (on received `share.error.code` when
29
+ * `share.status === 'error'`):
30
+ * - `DOWNLOAD_ERROR` — the download failed (network, disk, or server error)
31
+ * - `DECLINE_CANNOT_CONNECT` — the decline was accepted locally but the sender
32
+ * could not be reached to notify them
33
+ *
34
+ * ## Sender errors
35
+ *
36
+ * **Mutation errors** (thrown by sender hooks, check via
37
+ * `getErrorCode(mutation.error)`):
38
+ * - `INVALID_STATUS_TRANSITION` — the action is not valid for the share's
39
+ * current status (e.g. canceling a share that is already canceled)
40
+ * - `MAP_SHARE_NOT_FOUND` — no share with the given `shareId` exists
41
+ *
42
+ * **Share state errors** (on sent `share.error.code` when
43
+ * `share.status === 'error'`):
44
+ * - `CANCEL_SHARE_NOT_CANCELABLE` — the cancel request reached the server but
45
+ * the share is already in a final state (e.g. completed or declined)
46
+ *
47
+ * ## Common
48
+ *
49
+ * - `UNKNOWN_ERROR` — fallback when the original error has no specific code.
50
+ * Can appear as both a mutation error and a share state error for either
51
+ * sender or receiver.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * import { getErrorCode, MapShareErrorCode } from '@comapeo/core-react'
56
+ *
57
+ * // Receiver: checking a mutation error
58
+ * const decline = useDeclineReceivedMapShare()
59
+ * // ... after mutation fails:
60
+ * if (getErrorCode(decline.error) === MapShareErrorCode.MAP_SHARE_CANCELED) {
61
+ * // Show "this share was canceled by the sender"
62
+ * }
63
+ * ```
64
+ *
65
+ * @example
66
+ * ```tsx
67
+ * // Receiver: checking a share state error
68
+ * const share = useSingleReceivedMapShare({ shareId })
69
+ * if (share.status === 'error') {
70
+ * if (share.error.code === MapShareErrorCode.DOWNLOAD_ERROR) {
71
+ * // Show "download failed, try again?"
72
+ * }
73
+ * }
74
+ * ```
75
+ */
76
+ export declare const MapShareErrorCode: {
77
+ /** Receiver: the sender canceled the share before the action could complete */
78
+ readonly MAP_SHARE_CANCELED: "MAP_SHARE_CANCELED";
79
+ /** Receiver/Sender: the action is not valid for the share's current status */
80
+ readonly INVALID_STATUS_TRANSITION: "INVALID_STATUS_TRANSITION";
81
+ /** Receiver/Sender: no map share with the given `shareId` exists in the store */
82
+ readonly MAP_SHARE_NOT_FOUND: "MAP_SHARE_NOT_FOUND";
83
+ /** Receiver: abort was called but no download is tracked for this share */
84
+ readonly DOWNLOAD_NOT_FOUND: "DOWNLOAD_NOT_FOUND";
85
+ /** Receiver: the download failed due to a network, disk, or server error */
86
+ readonly DOWNLOAD_ERROR: "DOWNLOAD_ERROR";
87
+ /** Receiver: could not connect to the sender to notify them of the decline */
88
+ readonly DECLINE_CANNOT_CONNECT: "DECLINE_CANNOT_CONNECT";
89
+ /** Sender: cancel failed because the share is already in a final state on the server */
90
+ readonly CANCEL_SHARE_NOT_CANCELABLE: "CANCEL_SHARE_NOT_CANCELABLE";
91
+ /** Receiver/Sender: fallback code when the original error has no specific code */
92
+ readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
93
+ };
94
+ /**
95
+ * Safely extract the `code` property from an unknown error. Returns `undefined`
96
+ * if the value is not an Error or has no string `code` property.
97
+ *
98
+ * @example
99
+ * ```tsx
100
+ * import { getErrorCode, MapShareErrorCode } from '@comapeo/core-react'
101
+ *
102
+ * try {
103
+ * await decline.mutateAsync({ shareId, reason: 'user_rejected' })
104
+ * } catch (e) {
105
+ * const code = getErrorCode(e)
106
+ * if (code === MapShareErrorCode.MAP_SHARE_CANCELED) {
107
+ * // handle cancellation
108
+ * }
109
+ * }
110
+ * ```
111
+ */
112
+ export declare function getErrorCode(error: unknown): string | undefined;
12
113
  /** Known reasons for declining a map share */
13
114
  export declare const DeclineReason: {
14
115
  /** User explicitly rejected the map share */
@@ -35,8 +136,6 @@ export type AbortMapShareOptions = {
35
136
  };
36
137
  /** Options for creating and sending a map share */
37
138
  export type CreateAndSendMapShareOptions = {
38
- /** Public ID of the project to send the share on behalf of */
39
- projectId: string;
40
139
  /** Device ID of the recipient */
41
140
  receiverDeviceId: string;
42
141
  /** ID of the map to share - not needed until we support multiple maps */
@@ -47,6 +146,24 @@ export type CancelMapShareOptions = {
47
146
  /** ID of the map share to cancel */
48
147
  shareId: string;
49
148
  };
149
+ /**
150
+ * Thrown when a receiver action (download, decline, or abort) is attempted on a
151
+ * map share that has been canceled by the sender. Has `code: 'MAP_SHARE_CANCELED'`.
152
+ */
153
+ export declare class MapShareCanceledError extends Error {
154
+ code: "MAP_SHARE_CANCELED";
155
+ constructor(shareId: string);
156
+ }
157
+ /**
158
+ * Thrown when an action is attempted on a map share whose current status does
159
+ * not allow the requested transition (e.g. declining a share that is already
160
+ * downloading, or aborting a share that is still pending).
161
+ * Has `code: 'INVALID_STATUS_TRANSITION'`.
162
+ */
163
+ export declare class InvalidStatusTransitionError extends Error {
164
+ code: "INVALID_STATUS_TRANSITION";
165
+ constructor(current: string, next: string);
166
+ }
50
167
  /**
51
168
  * Store and actions for received map shares.
52
169
  */
@@ -73,7 +190,7 @@ export declare function createSentMapSharesStore({ clientApi, mapServerApi, }: {
73
190
  subscribe: (listener: () => void) => () => boolean;
74
191
  getSnapshot: () => ServerMapShareState[];
75
192
  actions: {
76
- createAndSend({ projectId, receiverDeviceId, mapId, }: CreateAndSendMapShareOptions): Promise<ServerMapShareState>;
193
+ createAndSend({ receiverDeviceId, mapId, }: CreateAndSendMapShareOptions): Promise<ServerMapShareState>;
77
194
  cancel({ shareId }: CancelMapShareOptions): Promise<void>;
78
195
  };
79
196
  };
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DeclineReason = void 0;
6
+ exports.InvalidStatusTransitionError = exports.MapShareCanceledError = exports.DeclineReason = exports.MapShareErrorCode = void 0;
7
+ exports.getErrorCode = getErrorCode;
7
8
  exports.createReceivedMapSharesStore = createReceivedMapSharesStore;
8
9
  exports.createSentMapSharesStore = createSentMapSharesStore;
9
10
  const constants_js_1 = require("@comapeo/map-server/constants.js");
@@ -16,6 +17,118 @@ const react_query_js_1 = require("./react-query.js");
16
17
  // functions - if the documentation comments are added inline for the store
17
18
  // actions, they do not show for the mutate() function in hooks.
18
19
  // ============================================
20
+ /**
21
+ * Error codes for map share operations. Use with {@link getErrorCode} to safely
22
+ * check the error code of an unknown error thrown by a map share mutation, or
23
+ * to check the `error.code` on a share in `status='error'`.
24
+ *
25
+ * ## Receiver errors
26
+ *
27
+ * **Mutation errors** (thrown by receiver hooks, check via
28
+ * `getErrorCode(mutation.error)`):
29
+ * - `MAP_SHARE_CANCELED` — the sender canceled the share
30
+ * - `INVALID_STATUS_TRANSITION` — the action is not valid for the share's
31
+ * current status (e.g. declining a share that is already downloading)
32
+ * - `MAP_SHARE_NOT_FOUND` — no share with the given `shareId` exists
33
+ * - `DOWNLOAD_NOT_FOUND` — abort was called but no download is tracked for
34
+ * this share
35
+ *
36
+ * **Share state errors** (on received `share.error.code` when
37
+ * `share.status === 'error'`):
38
+ * - `DOWNLOAD_ERROR` — the download failed (network, disk, or server error)
39
+ * - `DECLINE_CANNOT_CONNECT` — the decline was accepted locally but the sender
40
+ * could not be reached to notify them
41
+ *
42
+ * ## Sender errors
43
+ *
44
+ * **Mutation errors** (thrown by sender hooks, check via
45
+ * `getErrorCode(mutation.error)`):
46
+ * - `INVALID_STATUS_TRANSITION` — the action is not valid for the share's
47
+ * current status (e.g. canceling a share that is already canceled)
48
+ * - `MAP_SHARE_NOT_FOUND` — no share with the given `shareId` exists
49
+ *
50
+ * **Share state errors** (on sent `share.error.code` when
51
+ * `share.status === 'error'`):
52
+ * - `CANCEL_SHARE_NOT_CANCELABLE` — the cancel request reached the server but
53
+ * the share is already in a final state (e.g. completed or declined)
54
+ *
55
+ * ## Common
56
+ *
57
+ * - `UNKNOWN_ERROR` — fallback when the original error has no specific code.
58
+ * Can appear as both a mutation error and a share state error for either
59
+ * sender or receiver.
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * import { getErrorCode, MapShareErrorCode } from '@comapeo/core-react'
64
+ *
65
+ * // Receiver: checking a mutation error
66
+ * const decline = useDeclineReceivedMapShare()
67
+ * // ... after mutation fails:
68
+ * if (getErrorCode(decline.error) === MapShareErrorCode.MAP_SHARE_CANCELED) {
69
+ * // Show "this share was canceled by the sender"
70
+ * }
71
+ * ```
72
+ *
73
+ * @example
74
+ * ```tsx
75
+ * // Receiver: checking a share state error
76
+ * const share = useSingleReceivedMapShare({ shareId })
77
+ * if (share.status === 'error') {
78
+ * if (share.error.code === MapShareErrorCode.DOWNLOAD_ERROR) {
79
+ * // Show "download failed, try again?"
80
+ * }
81
+ * }
82
+ * ```
83
+ */
84
+ exports.MapShareErrorCode = {
85
+ // --- Receiver mutation errors (thrown by receiver actions) ---
86
+ /** Receiver: the sender canceled the share before the action could complete */
87
+ MAP_SHARE_CANCELED: 'MAP_SHARE_CANCELED',
88
+ /** Receiver/Sender: the action is not valid for the share's current status */
89
+ INVALID_STATUS_TRANSITION: 'INVALID_STATUS_TRANSITION',
90
+ /** Receiver/Sender: no map share with the given `shareId` exists in the store */
91
+ MAP_SHARE_NOT_FOUND: 'MAP_SHARE_NOT_FOUND',
92
+ /** Receiver: abort was called but no download is tracked for this share */
93
+ DOWNLOAD_NOT_FOUND: 'DOWNLOAD_NOT_FOUND',
94
+ // --- Receiver share state errors (in share.error.code) ---
95
+ /** Receiver: the download failed due to a network, disk, or server error */
96
+ DOWNLOAD_ERROR: 'DOWNLOAD_ERROR',
97
+ /** Receiver: could not connect to the sender to notify them of the decline */
98
+ DECLINE_CANNOT_CONNECT: 'DECLINE_CANNOT_CONNECT',
99
+ // --- Sender share state errors (in share.error.code) ---
100
+ /** Sender: cancel failed because the share is already in a final state on the server */
101
+ CANCEL_SHARE_NOT_CANCELABLE: 'CANCEL_SHARE_NOT_CANCELABLE',
102
+ // --- Common ---
103
+ /** Receiver/Sender: fallback code when the original error has no specific code */
104
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
105
+ };
106
+ /**
107
+ * Safely extract the `code` property from an unknown error. Returns `undefined`
108
+ * if the value is not an Error or has no string `code` property.
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * import { getErrorCode, MapShareErrorCode } from '@comapeo/core-react'
113
+ *
114
+ * try {
115
+ * await decline.mutateAsync({ shareId, reason: 'user_rejected' })
116
+ * } catch (e) {
117
+ * const code = getErrorCode(e)
118
+ * if (code === MapShareErrorCode.MAP_SHARE_CANCELED) {
119
+ * // handle cancellation
120
+ * }
121
+ * }
122
+ * ```
123
+ */
124
+ function getErrorCode(error) {
125
+ if (error instanceof Error &&
126
+ 'code' in error &&
127
+ typeof error.code === 'string') {
128
+ return error.code;
129
+ }
130
+ return undefined;
131
+ }
19
132
  /** Known reasons for declining a map share */
20
133
  exports.DeclineReason = {
21
134
  /** User explicitly rejected the map share */
@@ -23,6 +136,32 @@ exports.DeclineReason = {
23
136
  /** Device storage is full */
24
137
  storage_full: 'storage_full',
25
138
  };
139
+ /**
140
+ * Thrown when a receiver action (download, decline, or abort) is attempted on a
141
+ * map share that has been canceled by the sender. Has `code: 'MAP_SHARE_CANCELED'`.
142
+ */
143
+ class MapShareCanceledError extends Error {
144
+ code = 'MAP_SHARE_CANCELED';
145
+ constructor(shareId) {
146
+ super(`Map share ${shareId} has been canceled by the sender`);
147
+ this.name = 'MapShareCanceledError';
148
+ }
149
+ }
150
+ exports.MapShareCanceledError = MapShareCanceledError;
151
+ /**
152
+ * Thrown when an action is attempted on a map share whose current status does
153
+ * not allow the requested transition (e.g. declining a share that is already
154
+ * downloading, or aborting a share that is still pending).
155
+ * Has `code: 'INVALID_STATUS_TRANSITION'`.
156
+ */
157
+ class InvalidStatusTransitionError extends Error {
158
+ code = 'INVALID_STATUS_TRANSITION';
159
+ constructor(current, next) {
160
+ super(`Invalid status transition from ${current} to ${next}`);
161
+ this.name = 'InvalidStatusTransitionError';
162
+ }
163
+ }
164
+ exports.InvalidStatusTransitionError = InvalidStatusTransitionError;
26
165
  /**
27
166
  * This is like a mini zustand store. Keeping the map shares in an external
28
167
  * store avoids unnecessary re-renders of the entire app when map shares are
@@ -143,6 +282,13 @@ function createReceivedMapSharesStore({ clientApi, mapServerApi, queryClient, })
143
282
  const actions = {
144
283
  async download({ shareId }) {
145
284
  const mapShare = get(shareId);
285
+ // This path should be be impossible, because the map share only receives
286
+ // status updates from the receiver after the download starts, but adding
287
+ // for completeness, and the edge-case of the receiving trying to
288
+ // download() a second time.
289
+ if (mapShare.status === 'canceled') {
290
+ throw new MapShareCanceledError(shareId);
291
+ }
146
292
  update(shareId, { status: 'downloading', bytesDownloaded: 0 });
147
293
  try {
148
294
  const downloadIdPromise = mapServerApi
@@ -184,22 +330,39 @@ function createReceivedMapSharesStore({ clientApi, mapServerApi, queryClient, })
184
330
  },
185
331
  async decline({ shareId, reason }) {
186
332
  const mapShare = get(shareId);
187
- update(shareId, { status: 'declined', reason });
333
+ if (mapShare.status === 'canceled') {
334
+ throw new MapShareCanceledError(shareId);
335
+ }
336
+ if (mapShare.status !== 'pending') {
337
+ throw new InvalidStatusTransitionError(mapShare.status, 'declined');
338
+ }
188
339
  try {
189
- await mapServerApi.post(`mapShares/${shareId}/decline`, {
340
+ await mapServerApi
341
+ .post(`mapShares/${shareId}/decline`, {
190
342
  json: {
191
343
  senderDeviceId: mapShare.senderDeviceId,
192
344
  mapShareUrls: mapShare.mapShareUrls,
193
345
  reason,
194
346
  },
195
- });
347
+ })
348
+ .json();
349
+ update(shareId, { status: 'declined', reason });
196
350
  }
197
351
  catch (e) {
352
+ const error = (0, ensure_error_1.default)(e);
353
+ if ('code' in error && error.code === 'MAP_SHARE_CANCELED') {
354
+ update(shareId, { status: 'canceled' });
355
+ throw new MapShareCanceledError(shareId);
356
+ }
198
357
  handleError(shareId, e);
199
358
  throw e;
200
359
  }
201
360
  },
202
361
  async abort({ shareId }) {
362
+ const mapShare = get(shareId);
363
+ if (mapShare.status === 'canceled') {
364
+ throw new MapShareCanceledError(shareId);
365
+ }
203
366
  update(shareId, { status: 'aborted' });
204
367
  try {
205
368
  const downloadId = await downloads.get(shareId);
@@ -229,15 +392,14 @@ function createReceivedMapSharesStore({ clientApi, mapServerApi, queryClient, })
229
392
  function createSentMapSharesStore({ clientApi, mapServerApi, }) {
230
393
  const { subscribe, getSnapshot, update, add, handleError, monitor } = createMapSharesStore({ mapServerApi });
231
394
  const actions = {
232
- async createAndSend({ projectId, receiverDeviceId, mapId = constants_js_1.CUSTOM_MAP_ID, }) {
395
+ async createAndSend({ receiverDeviceId, mapId = constants_js_1.CUSTOM_MAP_ID, }) {
233
396
  const mapShare = await mapServerApi
234
397
  .post('mapShares', {
235
398
  json: { receiverDeviceId, mapId },
236
399
  })
237
400
  .json();
238
401
  try {
239
- const project = await clientApi.getProject(projectId);
240
- await project.$sendMapShare(mapShare);
402
+ await clientApi.sendMapShare(mapShare);
241
403
  }
242
404
  catch (e) {
243
405
  await mapServerApi.post(`mapShares/${mapShare.shareId}/cancel`);
@@ -279,7 +441,7 @@ const allowedStatusTransitions = {
279
441
  */
280
442
  function assertValidStatusTransition(current, next) {
281
443
  if (!allowedStatusTransitions[current].includes(next)) {
282
- throw new Error(`Invalid status transition from ${current} to ${next}`);
444
+ throw new InvalidStatusTransitionError(current, next);
283
445
  }
284
446
  }
285
447
  const finalStatuses = [
@@ -42,7 +42,7 @@ export declare function useReceivedMapSharesState<T>(selector: (state: Array<Rec
42
42
  * @internal
43
43
  */
44
44
  export declare function useSentMapSharesActions(): {
45
- createAndSend({ projectId, receiverDeviceId, mapId, }: import("../lib/map-shares-stores.js").CreateAndSendMapShareOptions): Promise<import("@comapeo/map-server").MapShareState>;
45
+ createAndSend({ receiverDeviceId, mapId, }: import("../lib/map-shares-stores.js").CreateAndSendMapShareOptions): Promise<import("@comapeo/map-server").MapShareState>;
46
46
  cancel({ shareId }: import("../lib/map-shares-stores.js").CancelMapShareOptions): Promise<void>;
47
47
  };
48
48
  /**