@sparkvault/sdk-mobile 5.1.0 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/notify.d.ts +63 -3
- package/dist/notify.js +57 -1
- package/dist/notify.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/notify.ts +102 -3
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type { CreateSparkOptions, CreateSparkResponse, ListSparksOptions, ListSp
|
|
|
14
14
|
export { MobileEntropyClient, } from './entropy.js';
|
|
15
15
|
export type { EntropyFormat, EntropyResponse, GenerateEntropyOptions, } from './entropy.js';
|
|
16
16
|
export { MobileNotifyClient, } from './notify.js';
|
|
17
|
-
export type { NotificationInboxState, NotificationMarkState, NotificationRow, ListNotificationsOptions, ListNotificationsResponse, UnreadCountResponse, MarkNotificationStateOptions, MarkNotificationStateResponse, RegisterDeviceResponse, } from './notify.js';
|
|
17
|
+
export type { NotificationInboxState, NotificationMarkState, NotificationRow, ListNotificationsOptions, ListNotificationsResponse, UnreadCountResponse, MarkNotificationStateOptions, MarkNotificationStateResponse, NotificationBulkMarkState, MarkAllNotificationsStateOptions, MarkAllNotificationsStateResponse, MarkAllReadResult, RegisterDeviceResponse, } from './notify.js';
|
|
18
18
|
export { MobileBillingClient, } from './billing.js';
|
|
19
19
|
export type { PortalSession, } from './billing.js';
|
|
20
20
|
export { MobileUsersClient, } from './users.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AASrB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAYrB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAKrB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAUrB,OAAO,EACL,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAMtB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AASrB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAYrB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAKrB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAUrB,OAAO,EACL,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAMtB,OAAO,EACL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAgBrB,OAAO,EACL,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAItB,OAAO,EACL,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,aAAa,GACd,MAAM,UAAU,CAAC;AAKlB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,aAAa,EACb,aAAa,EACb,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,6BAA6B,EAC7B,4BAA4B,EAC5B,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,yBAAyB,EACzB,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC"}
|
package/dist/notify.d.ts
CHANGED
|
@@ -16,8 +16,10 @@ export interface NotificationRow {
|
|
|
16
16
|
created_at: number;
|
|
17
17
|
title: string;
|
|
18
18
|
/**
|
|
19
|
-
* The
|
|
20
|
-
*
|
|
19
|
+
* The row's visible cleartext line, shown natively: a plaintext send's inline
|
|
20
|
+
* body, or a SEALED send's non-secret `instructions`. A sealed row can and
|
|
21
|
+
* normally should carry one — it is the readable line above the reveal. Never
|
|
22
|
+
* the sealed content itself. Absent when the sender set neither.
|
|
21
23
|
*/
|
|
22
24
|
body?: string;
|
|
23
25
|
category: string;
|
|
@@ -66,6 +68,33 @@ export interface MarkNotificationStateResponse {
|
|
|
66
68
|
state: NotificationMarkState;
|
|
67
69
|
updated_at: number;
|
|
68
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* States a BULK drain can apply. `archived` is per-row only — it has no GSI for
|
|
73
|
+
* the server to page over, so the bulk endpoint rejects it.
|
|
74
|
+
*/
|
|
75
|
+
export type NotificationBulkMarkState = 'seen' | 'read';
|
|
76
|
+
export interface MarkAllNotificationsStateOptions {
|
|
77
|
+
state: NotificationBulkMarkState;
|
|
78
|
+
/** Cursor from the prior page's response; omit to start at the newest page. */
|
|
79
|
+
cursor?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface MarkAllNotificationsStateResponse {
|
|
82
|
+
/** Rows marked by THIS page — the server marks one bounded page per call. */
|
|
83
|
+
updated: number;
|
|
84
|
+
/** Cursor for the next page, or null once the inbox is drained. */
|
|
85
|
+
cursor: string | null;
|
|
86
|
+
}
|
|
87
|
+
/** Total across a full `markAllRead()` drain. */
|
|
88
|
+
export interface MarkAllReadResult {
|
|
89
|
+
updated: number;
|
|
90
|
+
/**
|
|
91
|
+
* True when the drain reached the end of the inbox (the server returned a null
|
|
92
|
+
* cursor). False when it stopped at the page bound with rows still unmarked —
|
|
93
|
+
* call again to continue. Without this a truncated drain is indistinguishable
|
|
94
|
+
* from a completed one.
|
|
95
|
+
*/
|
|
96
|
+
complete: boolean;
|
|
97
|
+
}
|
|
69
98
|
/** Response to registering/refreshing this device's push token. */
|
|
70
99
|
export interface RegisterDeviceResponse {
|
|
71
100
|
success: boolean;
|
|
@@ -90,8 +119,39 @@ export declare class MobileNotifyClient {
|
|
|
90
119
|
* `createdAt` MUST be the row's own `created_at` — the id alone is insufficient.
|
|
91
120
|
*/
|
|
92
121
|
markState(notificationId: string, options: MarkNotificationStateOptions): Promise<MarkNotificationStateResponse>;
|
|
122
|
+
/**
|
|
123
|
+
* Bulk-mark ONE bounded page of the caller's inbox and return the server's
|
|
124
|
+
* payload verbatim. `cursor` comes back non-null while rows remain — thread it
|
|
125
|
+
* into the next call. `archived` is per-row only (no GSI to page over), so the
|
|
126
|
+
* bulk states are `seen` and `read`.
|
|
127
|
+
*
|
|
128
|
+
* Prefer `markAllRead()` unless the caller needs to own the paging.
|
|
129
|
+
*/
|
|
130
|
+
markAllState(options: MarkAllNotificationsStateOptions): Promise<MarkAllNotificationsStateResponse>;
|
|
131
|
+
/**
|
|
132
|
+
* Drain the caller's whole inbox to read: loop `markAllState` on the returned
|
|
133
|
+
* cursor until it comes back null, and return the total marked. Marking read
|
|
134
|
+
* clears the unseen marker too, so the badge zeroes with it. Idempotent.
|
|
135
|
+
*
|
|
136
|
+
* `complete` is false when the loop stopped at the page bound with rows still
|
|
137
|
+
* unmarked — an inbox deeper than the bound finishes on a second call. Without
|
|
138
|
+
* it a truncated drain looks exactly like a finished one.
|
|
139
|
+
*
|
|
140
|
+
* An error mid-drain propagates — the pages already marked stay marked, and a
|
|
141
|
+
* re-run picks up the remainder.
|
|
142
|
+
*/
|
|
143
|
+
markAllRead(): Promise<MarkAllReadResult>;
|
|
93
144
|
/** Register/refresh this device's Expo push token under the session user. */
|
|
94
145
|
registerDevice(pushToken: string): Promise<RegisterDeviceResponse>;
|
|
95
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Deregister this device by the raw push token it already holds (logout /
|
|
148
|
+
* disable notifications). The server derives the storage hash of the token
|
|
149
|
+
* itself, so the caller never reimplements that derivation.
|
|
150
|
+
*/
|
|
151
|
+
unregisterDeviceToken(pushToken: string): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Deregister a device addressed by the server's storage hash of its token.
|
|
154
|
+
* Use `unregisterDeviceToken` when the caller holds the token itself.
|
|
155
|
+
*/
|
|
96
156
|
unregisterDevice(tokenHash: string): Promise<void>;
|
|
97
157
|
}
|
package/dist/notify.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard bound on `markAllRead()`'s drain loop: 100 server pages of 100 rows. A
|
|
3
|
+
* cursor that never resolves to null can therefore never spin forever, and an
|
|
4
|
+
* inbox deeper than the bound finishes on a second call (the drain is idempotent).
|
|
5
|
+
*/
|
|
6
|
+
const MARK_ALL_MAX_PAGES = 100;
|
|
1
7
|
/**
|
|
2
8
|
* The signed-in user's first-party Notify surface: their own metadata-only inbox
|
|
3
9
|
* plus this device's push registration. The recipient is ALWAYS derived from the
|
|
@@ -34,6 +40,45 @@ export class MobileNotifyClient {
|
|
|
34
40
|
const response = await this.http.post(`/me/notifications/${encodeURIComponent(notificationId)}/state`, { created_at: options.createdAt, state: options.state });
|
|
35
41
|
return response.data;
|
|
36
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Bulk-mark ONE bounded page of the caller's inbox and return the server's
|
|
45
|
+
* payload verbatim. `cursor` comes back non-null while rows remain — thread it
|
|
46
|
+
* into the next call. `archived` is per-row only (no GSI to page over), so the
|
|
47
|
+
* bulk states are `seen` and `read`.
|
|
48
|
+
*
|
|
49
|
+
* Prefer `markAllRead()` unless the caller needs to own the paging.
|
|
50
|
+
*/
|
|
51
|
+
async markAllState(options) {
|
|
52
|
+
const body = { state: options.state };
|
|
53
|
+
if (options.cursor)
|
|
54
|
+
body.cursor = options.cursor;
|
|
55
|
+
const response = await this.http.post('/me/notifications/state', body);
|
|
56
|
+
return response.data;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Drain the caller's whole inbox to read: loop `markAllState` on the returned
|
|
60
|
+
* cursor until it comes back null, and return the total marked. Marking read
|
|
61
|
+
* clears the unseen marker too, so the badge zeroes with it. Idempotent.
|
|
62
|
+
*
|
|
63
|
+
* `complete` is false when the loop stopped at the page bound with rows still
|
|
64
|
+
* unmarked — an inbox deeper than the bound finishes on a second call. Without
|
|
65
|
+
* it a truncated drain looks exactly like a finished one.
|
|
66
|
+
*
|
|
67
|
+
* An error mid-drain propagates — the pages already marked stay marked, and a
|
|
68
|
+
* re-run picks up the remainder.
|
|
69
|
+
*/
|
|
70
|
+
async markAllRead() {
|
|
71
|
+
let cursor;
|
|
72
|
+
let updated = 0;
|
|
73
|
+
for (let page = 0; page < MARK_ALL_MAX_PAGES; page += 1) {
|
|
74
|
+
const result = await this.markAllState(cursor ? { state: 'read', cursor } : { state: 'read' });
|
|
75
|
+
updated += result?.updated ?? 0;
|
|
76
|
+
if (!result?.cursor)
|
|
77
|
+
return { updated, complete: true };
|
|
78
|
+
cursor = result.cursor;
|
|
79
|
+
}
|
|
80
|
+
return { updated, complete: false };
|
|
81
|
+
}
|
|
37
82
|
/** Register/refresh this device's Expo push token under the session user. */
|
|
38
83
|
async registerDevice(pushToken) {
|
|
39
84
|
const response = await this.http.post('/me/notifications/devices', {
|
|
@@ -41,7 +86,18 @@ export class MobileNotifyClient {
|
|
|
41
86
|
});
|
|
42
87
|
return response.data;
|
|
43
88
|
}
|
|
44
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Deregister this device by the raw push token it already holds (logout /
|
|
91
|
+
* disable notifications). The server derives the storage hash of the token
|
|
92
|
+
* itself, so the caller never reimplements that derivation.
|
|
93
|
+
*/
|
|
94
|
+
async unregisterDeviceToken(pushToken) {
|
|
95
|
+
await this.http.delete('/me/notifications/devices', { body: { token: pushToken } });
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Deregister a device addressed by the server's storage hash of its token.
|
|
99
|
+
* Use `unregisterDeviceToken` when the caller holds the token itself.
|
|
100
|
+
*/
|
|
45
101
|
async unregisterDevice(tokenHash) {
|
|
46
102
|
await this.http.delete(`/me/notifications/devices/${encodeURIComponent(tokenHash)}`);
|
|
47
103
|
}
|
package/dist/notify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -76,6 +76,10 @@ export type {
|
|
|
76
76
|
UnreadCountResponse,
|
|
77
77
|
MarkNotificationStateOptions,
|
|
78
78
|
MarkNotificationStateResponse,
|
|
79
|
+
NotificationBulkMarkState,
|
|
80
|
+
MarkAllNotificationsStateOptions,
|
|
81
|
+
MarkAllNotificationsStateResponse,
|
|
82
|
+
MarkAllReadResult,
|
|
79
83
|
RegisterDeviceResponse,
|
|
80
84
|
} from './notify.js';
|
|
81
85
|
export {
|
package/src/notify.ts
CHANGED
|
@@ -19,8 +19,10 @@ export interface NotificationRow {
|
|
|
19
19
|
created_at: number;
|
|
20
20
|
title: string;
|
|
21
21
|
/**
|
|
22
|
-
* The
|
|
23
|
-
*
|
|
22
|
+
* The row's visible cleartext line, shown natively: a plaintext send's inline
|
|
23
|
+
* body, or a SEALED send's non-secret `instructions`. A sealed row can and
|
|
24
|
+
* normally should carry one — it is the readable line above the reveal. Never
|
|
25
|
+
* the sealed content itself. Absent when the sender set neither.
|
|
24
26
|
*/
|
|
25
27
|
body?: string;
|
|
26
28
|
category: string;
|
|
@@ -75,6 +77,37 @@ export interface MarkNotificationStateResponse {
|
|
|
75
77
|
updated_at: number;
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
/**
|
|
81
|
+
* States a BULK drain can apply. `archived` is per-row only — it has no GSI for
|
|
82
|
+
* the server to page over, so the bulk endpoint rejects it.
|
|
83
|
+
*/
|
|
84
|
+
export type NotificationBulkMarkState = 'seen' | 'read';
|
|
85
|
+
|
|
86
|
+
export interface MarkAllNotificationsStateOptions {
|
|
87
|
+
state: NotificationBulkMarkState;
|
|
88
|
+
/** Cursor from the prior page's response; omit to start at the newest page. */
|
|
89
|
+
cursor?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface MarkAllNotificationsStateResponse {
|
|
93
|
+
/** Rows marked by THIS page — the server marks one bounded page per call. */
|
|
94
|
+
updated: number;
|
|
95
|
+
/** Cursor for the next page, or null once the inbox is drained. */
|
|
96
|
+
cursor: string | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Total across a full `markAllRead()` drain. */
|
|
100
|
+
export interface MarkAllReadResult {
|
|
101
|
+
updated: number;
|
|
102
|
+
/**
|
|
103
|
+
* True when the drain reached the end of the inbox (the server returned a null
|
|
104
|
+
* cursor). False when it stopped at the page bound with rows still unmarked —
|
|
105
|
+
* call again to continue. Without this a truncated drain is indistinguishable
|
|
106
|
+
* from a completed one.
|
|
107
|
+
*/
|
|
108
|
+
complete: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
78
111
|
/** Response to registering/refreshing this device's push token. */
|
|
79
112
|
export interface RegisterDeviceResponse {
|
|
80
113
|
success: boolean;
|
|
@@ -82,6 +115,13 @@ export interface RegisterDeviceResponse {
|
|
|
82
115
|
refreshed: boolean;
|
|
83
116
|
}
|
|
84
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Hard bound on `markAllRead()`'s drain loop: 100 server pages of 100 rows. A
|
|
120
|
+
* cursor that never resolves to null can therefore never spin forever, and an
|
|
121
|
+
* inbox deeper than the bound finishes on a second call (the drain is idempotent).
|
|
122
|
+
*/
|
|
123
|
+
const MARK_ALL_MAX_PAGES = 100;
|
|
124
|
+
|
|
85
125
|
/**
|
|
86
126
|
* The signed-in user's first-party Notify surface: their own metadata-only inbox
|
|
87
127
|
* plus this device's push registration. The recipient is ALWAYS derived from the
|
|
@@ -130,6 +170,53 @@ export class MobileNotifyClient {
|
|
|
130
170
|
return response.data;
|
|
131
171
|
}
|
|
132
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Bulk-mark ONE bounded page of the caller's inbox and return the server's
|
|
175
|
+
* payload verbatim. `cursor` comes back non-null while rows remain — thread it
|
|
176
|
+
* into the next call. `archived` is per-row only (no GSI to page over), so the
|
|
177
|
+
* bulk states are `seen` and `read`.
|
|
178
|
+
*
|
|
179
|
+
* Prefer `markAllRead()` unless the caller needs to own the paging.
|
|
180
|
+
*/
|
|
181
|
+
async markAllState(
|
|
182
|
+
options: MarkAllNotificationsStateOptions
|
|
183
|
+
): Promise<MarkAllNotificationsStateResponse> {
|
|
184
|
+
const body: { state: NotificationBulkMarkState; cursor?: string } = { state: options.state };
|
|
185
|
+
if (options.cursor) body.cursor = options.cursor;
|
|
186
|
+
|
|
187
|
+
const response = await this.http.post<MarkAllNotificationsStateResponse>(
|
|
188
|
+
'/me/notifications/state',
|
|
189
|
+
body
|
|
190
|
+
);
|
|
191
|
+
return response.data;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Drain the caller's whole inbox to read: loop `markAllState` on the returned
|
|
196
|
+
* cursor until it comes back null, and return the total marked. Marking read
|
|
197
|
+
* clears the unseen marker too, so the badge zeroes with it. Idempotent.
|
|
198
|
+
*
|
|
199
|
+
* `complete` is false when the loop stopped at the page bound with rows still
|
|
200
|
+
* unmarked — an inbox deeper than the bound finishes on a second call. Without
|
|
201
|
+
* it a truncated drain looks exactly like a finished one.
|
|
202
|
+
*
|
|
203
|
+
* An error mid-drain propagates — the pages already marked stay marked, and a
|
|
204
|
+
* re-run picks up the remainder.
|
|
205
|
+
*/
|
|
206
|
+
async markAllRead(): Promise<MarkAllReadResult> {
|
|
207
|
+
let cursor: string | undefined;
|
|
208
|
+
let updated = 0;
|
|
209
|
+
|
|
210
|
+
for (let page = 0; page < MARK_ALL_MAX_PAGES; page += 1) {
|
|
211
|
+
const result = await this.markAllState(cursor ? { state: 'read', cursor } : { state: 'read' });
|
|
212
|
+
updated += result?.updated ?? 0;
|
|
213
|
+
if (!result?.cursor) return { updated, complete: true };
|
|
214
|
+
cursor = result.cursor;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { updated, complete: false };
|
|
218
|
+
}
|
|
219
|
+
|
|
133
220
|
/** Register/refresh this device's Expo push token under the session user. */
|
|
134
221
|
async registerDevice(pushToken: string): Promise<RegisterDeviceResponse> {
|
|
135
222
|
const response = await this.http.post<RegisterDeviceResponse>('/me/notifications/devices', {
|
|
@@ -138,7 +225,19 @@ export class MobileNotifyClient {
|
|
|
138
225
|
return response.data;
|
|
139
226
|
}
|
|
140
227
|
|
|
141
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* Deregister this device by the raw push token it already holds (logout /
|
|
230
|
+
* disable notifications). The server derives the storage hash of the token
|
|
231
|
+
* itself, so the caller never reimplements that derivation.
|
|
232
|
+
*/
|
|
233
|
+
async unregisterDeviceToken(pushToken: string): Promise<void> {
|
|
234
|
+
await this.http.delete('/me/notifications/devices', { body: { token: pushToken } });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Deregister a device addressed by the server's storage hash of its token.
|
|
239
|
+
* Use `unregisterDeviceToken` when the caller holds the token itself.
|
|
240
|
+
*/
|
|
142
241
|
async unregisterDevice(tokenHash: string): Promise<void> {
|
|
143
242
|
await this.http.delete(`/me/notifications/devices/${encodeURIComponent(tokenHash)}`);
|
|
144
243
|
}
|