@usergist/feedback-react-native 0.1.1 → 0.2.0-beta.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/CHANGELOG.md +8 -0
- package/EXPO-ACCEPTANCE.md +57 -0
- package/EXPO.md +103 -0
- package/README.md +10 -2
- package/UserGistFeedback.podspec +4 -1
- package/UserGistFeedbackExtension.podspec +1 -1
- package/android/build.gradle +3 -1
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/studio/usergist/feedback/UserGistFirebaseMessagingService.kt +78 -12
- package/android/src/main/java/studio/usergist/feedback/UserGistNotificationDismissReceiver.kt +17 -0
- package/android/src/main/java/studio/usergist/feedback/UserGistPushModule.kt +46 -5
- package/android/src/main/java/studio/usergist/feedback/UserGistPushState.kt +69 -0
- package/app.plugin.js +2 -0
- package/dist/NativeUserGistPush.d.ts +10 -0
- package/dist/NativeUserGistPush.d.ts.map +1 -1
- package/dist/NativeUserGistPush.js.map +1 -1
- package/dist/Push.d.ts.map +1 -1
- package/dist/Push.js +9 -0
- package/dist/Push.js.map +1 -1
- package/dist/UserGist.d.ts +1 -0
- package/dist/UserGist.d.ts.map +1 -1
- package/dist/UserGist.js +218 -79
- package/dist/UserGist.js.map +1 -1
- package/dist/expo/index.d.ts +68 -0
- package/dist/expo/index.d.ts.map +1 -0
- package/dist/expo/index.js +120 -0
- package/dist/expo/index.js.map +1 -0
- package/dist/internal/consent.d.ts.map +1 -1
- package/dist/internal/consent.js +4 -1
- package/dist/internal/consent.js.map +1 -1
- package/dist/internal/context.d.ts +1 -1
- package/dist/internal/context.d.ts.map +1 -1
- package/dist/internal/context.js +1 -1
- package/dist/internal/context.js.map +1 -1
- package/dist/internal/engine.d.ts.map +1 -1
- package/dist/internal/engine.js +6 -1
- package/dist/internal/engine.js.map +1 -1
- package/dist/internal/events.d.ts +1 -0
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +6 -0
- package/dist/internal/events.js.map +1 -1
- package/dist/internal/push-dedupe.d.ts +3 -0
- package/dist/internal/push-dedupe.d.ts.map +1 -0
- package/dist/internal/push-dedupe.js +33 -0
- package/dist/internal/push-dedupe.js.map +1 -0
- package/dist/internal/storage.d.ts +1 -0
- package/dist/internal/storage.d.ts.map +1 -1
- package/dist/internal/storage.js +1 -0
- package/dist/internal/storage.js.map +1 -1
- package/dist/internal/transport.d.ts +5 -1
- package/dist/internal/transport.d.ts.map +1 -1
- package/dist/internal/transport.js +2 -2
- package/dist/internal/transport.js.map +1 -1
- package/dist/native/events.d.ts +2 -0
- package/dist/native/events.d.ts.map +1 -1
- package/dist/native/events.js +2 -0
- package/dist/native/events.js.map +1 -1
- package/dist/native/push-adapter.d.ts +11 -0
- package/dist/native/push-adapter.d.ts.map +1 -0
- package/dist/native/push-adapter.js +10 -0
- package/dist/native/push-adapter.js.map +1 -0
- package/dist/native/push-bridge.d.ts +9 -0
- package/dist/native/push-bridge.d.ts.map +1 -1
- package/dist/native/push-bridge.js +39 -4
- package/dist/native/push-bridge.js.map +1 -1
- package/dist/ui/requests/RequestsHost.d.ts.map +1 -1
- package/dist/ui/requests/RequestsHost.js +8 -0
- package/dist/ui/requests/RequestsHost.js.map +1 -1
- package/expo-module.config.json +8 -0
- package/expo-support/UserGistExpoAppDelegateSubscriber.swift +34 -0
- package/expo-support/UserGistFeedbackExpo.podspec +17 -0
- package/ios/Extension/UserGistNotificationService.swift +52 -5
- package/ios/Shared/UserGistNotificationActions.swift +27 -0
- package/ios/Shared/UserGistPushState.swift +110 -0
- package/ios/UserGistPush.h +1 -6
- package/ios/UserGistPush.mm +18 -0
- package/ios/UserGistPushImpl.swift +46 -4
- package/ios/UserGistPushInterop.h +5 -0
- package/package.json +33 -7
- package/plugin/index.cjs +269 -0
- package/plugin/index.d.cts +14 -0
- package/plugin/templates/UserGistExpoMessagingService.kt +20 -0
package/dist/UserGist.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Philosophy:
|
|
4
|
-
// - `init()` remains synchronous and backwards compatible. `initAsync()` can
|
|
5
|
-
// bind a server-proven identity before any lifecycle event is emitted.
|
|
6
|
-
// - All public methods catch their own errors and report via debug. The
|
|
7
|
-
// SDK never throws across its boundary.
|
|
8
|
-
// - Track is synchronous for the caller: it enqueues, schedules a flush,
|
|
9
|
-
// and runs the trigger matcher — zero awaits.
|
|
1
|
+
import { hydratePushDedupe } from './internal/push-dedupe.js';
|
|
10
2
|
import { REQUEST_COMMENTED_EVENT_NAME, REQUEST_COMMENT_EDITED_EVENT_NAME, REQUEST_COMMENT_DELETED_EVENT_NAME, } from '@usergist/sdk-core/mobile';
|
|
11
3
|
import { asEventProps, clearAllState, createEngine, emitAppOpenWhenConsentReady, emitAppVersionChanged, ensureHydrated, enqueueAndEvaluate, flushNow, flushMutations, pollInstructions, refreshTargetingRules, recordServerBackedEventLocally, submitResponse, } from './internal/engine.js';
|
|
12
4
|
import { setDebugEnabled, reportError, debugLog, logTrace, getTraces, setDiagnosticHandler, } from './internal/debug.js';
|
|
13
5
|
import { DEFAULT_THEME, mergeTheme } from './ui/theme.js';
|
|
14
6
|
import { createSurveyStore } from './internal/survey-store.js';
|
|
15
|
-
import { UserGistPushNative, onTokenReceived, onNotificationReceived, onNotificationOpened, onTokenError, } from './native/push-bridge.js';
|
|
7
|
+
import { UserGistPushNative, onTokenReceived, onNotificationReceived, onNotificationOpened, onNotificationDisplayed, onNotificationDismissed, onTokenError, } from './native/push-bridge.js';
|
|
16
8
|
import { Push } from './Push.js';
|
|
17
9
|
import { AppState, Platform } from 'react-native';
|
|
18
10
|
import { generateEventId, validateIdentifyTransition } from './internal/identity.js';
|
|
19
|
-
import { configureStorageAdapter, } from './internal/storage.js';
|
|
11
|
+
import { configureStorageAdapter, STORAGE_KEYS, } from './internal/storage.js';
|
|
20
12
|
import { surveyCompletionOutcome } from './internal/survey-completion.js';
|
|
21
13
|
import { USERGIST_SDK_VERSION } from './internal/context.js';
|
|
22
14
|
let engine = null;
|
|
15
|
+
let pushRegistrationQueue = Promise.resolve();
|
|
16
|
+
function serializePushRegistration(operation) {
|
|
17
|
+
const task = pushRegistrationQueue.then(operation);
|
|
18
|
+
pushRegistrationQueue = task.catch(() => undefined);
|
|
19
|
+
return task;
|
|
20
|
+
}
|
|
23
21
|
let runtimeStarted = false;
|
|
24
22
|
let runtimeStartPromise = null;
|
|
25
23
|
let resetPromise = null;
|
|
@@ -37,6 +35,37 @@ function ensureRequestsCache() {
|
|
|
37
35
|
}
|
|
38
36
|
return requestsCache;
|
|
39
37
|
}
|
|
38
|
+
function requestSessionIsCurrent(e) {
|
|
39
|
+
const generation = e.resetGeneration;
|
|
40
|
+
const consentVersion = e.consent.get().version;
|
|
41
|
+
const identity = e.identity.get();
|
|
42
|
+
return () => !e.resetting && generation === e.resetGeneration &&
|
|
43
|
+
consentVersion === e.consent.get().version && e.consent.allowsFeedback() &&
|
|
44
|
+
identity.anonymousId === e.identity.get().anonymousId &&
|
|
45
|
+
identity.externalId === e.identity.get().externalId;
|
|
46
|
+
}
|
|
47
|
+
async function syncNativePushState(e, push = e.consent.allowsPush()) {
|
|
48
|
+
const anonymousId = e.identity.get().anonymousId;
|
|
49
|
+
const generation = e.resetGeneration;
|
|
50
|
+
const version = e.consent.get().version;
|
|
51
|
+
const operation = nativeStateQueue.then(async () => {
|
|
52
|
+
if (generation !== e.resetGeneration || anonymousId !== e.identity.get().anonymousId || version !== e.consent.get().version)
|
|
53
|
+
return;
|
|
54
|
+
await UserGistPushNative.syncState({ writeKey: e.config.writeKey, apiUrl: e.config.apiUrl, anonymousId, push: push && !e.resetting });
|
|
55
|
+
});
|
|
56
|
+
nativeStateQueue = operation.catch(() => undefined);
|
|
57
|
+
await operation;
|
|
58
|
+
}
|
|
59
|
+
let nativeStateQueue = Promise.resolve();
|
|
60
|
+
async function disableNativePushForConsent(e, version) {
|
|
61
|
+
const operation = nativeStateQueue.then(async () => {
|
|
62
|
+
if (e.consent.get().version !== version || e.consent.allowsPush())
|
|
63
|
+
return;
|
|
64
|
+
await UserGistPushNative.disablePush();
|
|
65
|
+
});
|
|
66
|
+
nativeStateQueue = operation.catch(() => undefined);
|
|
67
|
+
await operation;
|
|
68
|
+
}
|
|
40
69
|
function requireEngine() {
|
|
41
70
|
if (!engine) {
|
|
42
71
|
throw new Error('UserGist.init must be called before using the SDK');
|
|
@@ -116,7 +145,7 @@ function attachEnginePushListeners() {
|
|
|
116
145
|
try {
|
|
117
146
|
const platform = (p.platform === 'ios' ? 'ios' : 'android');
|
|
118
147
|
await UserGist.registerPushToken(p.token, platform, {
|
|
119
|
-
environment: lastEnablePushOptions?.environment ?? defaultEnvironment(),
|
|
148
|
+
environment: lastEnablePushOptions?.environment ?? await UserGistPushNative.defaultEnvironment() ?? defaultEnvironment(),
|
|
120
149
|
});
|
|
121
150
|
}
|
|
122
151
|
catch (err) {
|
|
@@ -130,12 +159,18 @@ function attachEnginePushListeners() {
|
|
|
130
159
|
onNotificationReceived((p) => {
|
|
131
160
|
forwardToPushHandler(p, 'received');
|
|
132
161
|
});
|
|
162
|
+
onNotificationDisplayed(p => forwardToPushHandler(p, 'displayed'));
|
|
163
|
+
onNotificationDismissed(p => forwardToPushHandler(p, 'dismissed'));
|
|
133
164
|
onNotificationOpened((p) => {
|
|
134
165
|
forwardToPushHandler(p, 'opened');
|
|
135
166
|
});
|
|
136
167
|
}
|
|
137
168
|
function forwardToPushHandler(p, kind) {
|
|
169
|
+
if (!engine?.consent.allowsPush() || engine.resetting)
|
|
170
|
+
return;
|
|
138
171
|
const data = p.data ?? {};
|
|
172
|
+
if (data.usergist_anonymous_id && data.usergist_anonymous_id !== engine.identity.get().anonymousId)
|
|
173
|
+
return;
|
|
139
174
|
if (p.deliveryId) {
|
|
140
175
|
;
|
|
141
176
|
data.usergist_delivery_id = p.deliveryId;
|
|
@@ -153,6 +188,12 @@ function forwardToPushHandler(p, kind) {
|
|
|
153
188
|
if (kind === 'received') {
|
|
154
189
|
Push.handleReceived({ userInfo, notification: { title: p.title, body: p.body } });
|
|
155
190
|
}
|
|
191
|
+
else if (kind === 'displayed') {
|
|
192
|
+
Push.handleDisplayed({ userInfo });
|
|
193
|
+
}
|
|
194
|
+
else if (kind === 'dismissed') {
|
|
195
|
+
Push.handleDismissed({ userInfo });
|
|
196
|
+
}
|
|
156
197
|
else {
|
|
157
198
|
Push.handleOpened({ userInfo, actionIdentifier: p.actionIdentifier });
|
|
158
199
|
}
|
|
@@ -165,6 +206,12 @@ function forwardToPushHandler(p, kind) {
|
|
|
165
206
|
if (kind === 'received') {
|
|
166
207
|
Push.handleReceived({ data: dataStr, notification: { title: p.title, body: p.body } });
|
|
167
208
|
}
|
|
209
|
+
else if (kind === 'displayed') {
|
|
210
|
+
Push.handleDisplayed({ data: dataStr });
|
|
211
|
+
}
|
|
212
|
+
else if (kind === 'dismissed') {
|
|
213
|
+
Push.handleDismissed({ data: dataStr });
|
|
214
|
+
}
|
|
168
215
|
else {
|
|
169
216
|
Push.handleOpened({ data: dataStr, actionIdentifier: p.actionIdentifier });
|
|
170
217
|
}
|
|
@@ -202,6 +249,8 @@ export const UserGist = {
|
|
|
202
249
|
if (result !== 'synced')
|
|
203
250
|
return result;
|
|
204
251
|
}
|
|
252
|
+
await hydratePushDedupe(e.config.writeKey, e.identity.get().anonymousId);
|
|
253
|
+
await syncNativePushState(e);
|
|
205
254
|
await startRuntime(e);
|
|
206
255
|
return 'synced';
|
|
207
256
|
}
|
|
@@ -221,6 +270,8 @@ export const UserGist = {
|
|
|
221
270
|
}
|
|
222
271
|
const e = requireEngine();
|
|
223
272
|
await ensureHydrated(e);
|
|
273
|
+
if (e.resetting)
|
|
274
|
+
return 'rejected';
|
|
224
275
|
const identity = e.identity.get();
|
|
225
276
|
const pendingIdentity = e.mutations.peek();
|
|
226
277
|
const transition = validateIdentifyTransition(identity.externalId, pendingIdentity?.kind === 'identify' ? pendingIdentity.payload.externalId : null, userId);
|
|
@@ -252,6 +303,7 @@ export const UserGist = {
|
|
|
252
303
|
debugLog('identify is saved locally and pending retry');
|
|
253
304
|
return 'queued';
|
|
254
305
|
}
|
|
306
|
+
await syncNativePushState(e);
|
|
255
307
|
void UserGist.rebindPushToken(userId);
|
|
256
308
|
return 'synced';
|
|
257
309
|
}
|
|
@@ -278,7 +330,29 @@ export const UserGist = {
|
|
|
278
330
|
try {
|
|
279
331
|
const e = requireEngine();
|
|
280
332
|
await ensureHydrated(e);
|
|
333
|
+
if (e.resetting)
|
|
334
|
+
return false;
|
|
281
335
|
const next = await e.consent.set(purposes);
|
|
336
|
+
if (e.resetting || next.version !== e.consent.get().version)
|
|
337
|
+
return false;
|
|
338
|
+
if (!next.feedback) {
|
|
339
|
+
requestsCache = null;
|
|
340
|
+
e.events.emit('dismissRequests', undefined);
|
|
341
|
+
}
|
|
342
|
+
await syncNativePushState(e);
|
|
343
|
+
if (e.resetting || next.version !== e.consent.get().version)
|
|
344
|
+
return false;
|
|
345
|
+
if (purposes.push === false) {
|
|
346
|
+
lastEnablePushOptions = null;
|
|
347
|
+
await pushRegistrationQueue;
|
|
348
|
+
if (e.resetting || next.version !== e.consent.get().version)
|
|
349
|
+
return false;
|
|
350
|
+
if (e.lastPushToken)
|
|
351
|
+
await UserGist.invalidatePushToken(e.lastPushToken);
|
|
352
|
+
await disableNativePushForConsent(e, next.version);
|
|
353
|
+
}
|
|
354
|
+
if (e.resetting || next.version !== e.consent.get().version)
|
|
355
|
+
return false;
|
|
282
356
|
const id = e.identity.get();
|
|
283
357
|
try {
|
|
284
358
|
await e.transport.consent({
|
|
@@ -318,13 +392,18 @@ export const UserGist = {
|
|
|
318
392
|
return resetPromise ?? Promise.resolve();
|
|
319
393
|
e.resetting = true;
|
|
320
394
|
e.resetGeneration += 1;
|
|
395
|
+
requestsCache = null;
|
|
396
|
+
lastEnablePushOptions = null;
|
|
321
397
|
e.events.emit('resetSurfaces', undefined);
|
|
322
398
|
const operation = (async () => {
|
|
323
399
|
await ensureHydrated(e);
|
|
400
|
+
await syncNativePushState(e, false);
|
|
401
|
+
await pushRegistrationQueue;
|
|
324
402
|
if (e.lastPushToken)
|
|
325
403
|
await UserGist.invalidatePushToken(e.lastPushToken);
|
|
326
404
|
await UserGistPushNative.disablePush().catch(() => undefined);
|
|
327
405
|
await clearAllState(e);
|
|
406
|
+
await hydratePushDedupe(e.config.writeKey, e.identity.get().anonymousId);
|
|
328
407
|
})();
|
|
329
408
|
const finalized = operation
|
|
330
409
|
.catch((err) => reportError('reset failed', err))
|
|
@@ -443,42 +522,48 @@ export const UserGist = {
|
|
|
443
522
|
return;
|
|
444
523
|
const e = requireEngine();
|
|
445
524
|
await ensureHydrated(e);
|
|
446
|
-
if (!e.consent.allowsPush())
|
|
447
|
-
return;
|
|
448
|
-
// Consent is enforced server-side (the register-token route reads
|
|
449
|
-
// consent_log and refuses if push was explicitly declined). We no
|
|
450
|
-
// longer short-circuit here because the host app typically calls
|
|
451
|
-
// setConsent and registerPushToken in parallel; racing the local
|
|
452
|
-
// consent hydration is not our problem to solve.
|
|
453
|
-
const id = e.identity.get();
|
|
454
|
-
const environment = opts?.environment ??
|
|
455
|
-
(e.config.environment === 'production' ? 'production' : 'sandbox');
|
|
456
|
-
const registrationKey = [
|
|
457
|
-
id.anonymousId,
|
|
458
|
-
id.externalId ?? '',
|
|
459
|
-
platform,
|
|
460
|
-
environment,
|
|
461
|
-
token,
|
|
462
|
-
].join(':');
|
|
463
|
-
if (e.lastPushRegistrationKey === registrationKey &&
|
|
464
|
-
Date.now() - e.lastPushRegistrationAt < 24 * 60 * 60_000)
|
|
525
|
+
if (!e.consent.allowsPush() || e.resetting)
|
|
465
526
|
return;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
environment
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
527
|
+
const generation = e.resetGeneration;
|
|
528
|
+
await serializePushRegistration(async () => {
|
|
529
|
+
if (e.resetting || generation !== e.resetGeneration || !e.consent.allowsPush())
|
|
530
|
+
return;
|
|
531
|
+
const id = e.identity.get();
|
|
532
|
+
const environment = opts?.environment ?? await UserGistPushNative.defaultEnvironment() ??
|
|
533
|
+
(e.config.environment === 'production' ? 'production' : 'sandbox');
|
|
534
|
+
const registrationKey = [
|
|
535
|
+
id.anonymousId,
|
|
536
|
+
id.externalId ?? '',
|
|
537
|
+
platform,
|
|
538
|
+
environment,
|
|
539
|
+
token,
|
|
540
|
+
].join(':');
|
|
541
|
+
if (e.lastPushRegistrationKey === registrationKey &&
|
|
542
|
+
Date.now() - e.lastPushRegistrationAt < 24 * 60 * 60_000)
|
|
543
|
+
return;
|
|
544
|
+
if (e.resetting || generation !== e.resetGeneration || !e.consent.allowsPush())
|
|
545
|
+
return;
|
|
546
|
+
// Persist before the request so logout can invalidate a token even when
|
|
547
|
+
// the response is lost or the process exits after the server accepts it.
|
|
548
|
+
e.lastPushToken = token;
|
|
549
|
+
await e.storage.setJson(STORAGE_KEYS.pushToken, token);
|
|
550
|
+
await e.transport.pushRegisterToken({
|
|
551
|
+
anonymousId: id.anonymousId,
|
|
552
|
+
externalId: id.externalId ?? null,
|
|
553
|
+
token,
|
|
554
|
+
platform,
|
|
555
|
+
environment,
|
|
556
|
+
language: undefined,
|
|
557
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
558
|
+
appVersion: undefined,
|
|
559
|
+
sdkVersion: `rn-${USERGIST_SDK_VERSION}`,
|
|
560
|
+
optIn: true,
|
|
561
|
+
});
|
|
562
|
+
e.lastPushToken = token;
|
|
563
|
+
e.lastPushRegistrationKey = registrationKey;
|
|
564
|
+
e.lastPushRegistrationAt = Date.now();
|
|
565
|
+
debugLog('push token registered with server');
|
|
477
566
|
});
|
|
478
|
-
e.lastPushToken = token;
|
|
479
|
-
e.lastPushRegistrationKey = registrationKey;
|
|
480
|
-
e.lastPushRegistrationAt = Date.now();
|
|
481
|
-
debugLog('push token registered with server');
|
|
482
567
|
}
|
|
483
568
|
catch (err) {
|
|
484
569
|
reportError('registerPushToken failed', err);
|
|
@@ -491,6 +576,8 @@ export const UserGist = {
|
|
|
491
576
|
const e = requireEngine();
|
|
492
577
|
await ensureHydrated(e);
|
|
493
578
|
const id = e.identity.get();
|
|
579
|
+
e.lastPushRegistrationKey = null;
|
|
580
|
+
e.lastPushRegistrationAt = 0;
|
|
494
581
|
await e.transport.pushInvalidateToken({
|
|
495
582
|
anonymousId: id.anonymousId,
|
|
496
583
|
token,
|
|
@@ -510,14 +597,19 @@ export const UserGist = {
|
|
|
510
597
|
try {
|
|
511
598
|
const e = requireEngine();
|
|
512
599
|
await ensureHydrated(e);
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
externalId
|
|
520
|
-
|
|
600
|
+
const generation = e.resetGeneration;
|
|
601
|
+
await serializePushRegistration(async () => {
|
|
602
|
+
if (generation !== e.resetGeneration)
|
|
603
|
+
return;
|
|
604
|
+
const id = e.identity.get();
|
|
605
|
+
const token = e.lastPushToken;
|
|
606
|
+
if (!token || e.resetting || !e.consent.allowsPush() || id.externalId !== externalId)
|
|
607
|
+
return;
|
|
608
|
+
await e.transport.pushRebind({
|
|
609
|
+
anonymousId: id.anonymousId,
|
|
610
|
+
externalId,
|
|
611
|
+
token,
|
|
612
|
+
});
|
|
521
613
|
});
|
|
522
614
|
}
|
|
523
615
|
catch (err) {
|
|
@@ -608,6 +700,18 @@ export const UserGist = {
|
|
|
608
700
|
// react-native-onesignal, WonderPush, or any other push SDK.
|
|
609
701
|
async enablePush(opts) {
|
|
610
702
|
try {
|
|
703
|
+
const e = requireEngine();
|
|
704
|
+
await ensureHydrated(e);
|
|
705
|
+
if (!e.consent.allowsPush() || e.resetting)
|
|
706
|
+
return { granted: false, status: 'not_determined', platform: Platform.OS === 'ios' ? 'ios' : 'android', error: 'consent_required' };
|
|
707
|
+
const generation = e.resetGeneration;
|
|
708
|
+
const consentVersion = e.consent.get().version;
|
|
709
|
+
const current = () => !e.resetting && generation === e.resetGeneration && consentVersion === e.consent.get().version && e.consent.allowsPush();
|
|
710
|
+
const cancelled = () => ({ granted: false, status: 'not_determined', platform: Platform.OS === 'ios' ? 'ios' : 'android', error: 'session_changed' });
|
|
711
|
+
opts = { ...opts, environment: opts?.environment ?? await UserGistPushNative.defaultEnvironment() ?? defaultEnvironment() };
|
|
712
|
+
await syncNativePushState(e);
|
|
713
|
+
if (!current())
|
|
714
|
+
return cancelled();
|
|
611
715
|
if (!enginePushListenersAttached)
|
|
612
716
|
attachEnginePushListeners();
|
|
613
717
|
// Snapshot the caller's options so the AppState foreground listener
|
|
@@ -618,6 +722,10 @@ export const UserGist = {
|
|
|
618
722
|
installDelegateProxy: opts?.installDelegateProxy,
|
|
619
723
|
};
|
|
620
724
|
const result = await UserGistPushNative.enablePush(opts ?? {});
|
|
725
|
+
// Reset/withdrawal owns native cleanup. An old permission callback must
|
|
726
|
+
// not disable a new account's subsequently enabled notification adapter.
|
|
727
|
+
if (!current())
|
|
728
|
+
return cancelled();
|
|
621
729
|
// Server-side registration runs on the tokenReceived event; if the
|
|
622
730
|
// native module already had a cached token it surfaces here.
|
|
623
731
|
if (result.granted && result.token) {
|
|
@@ -632,23 +740,19 @@ export const UserGist = {
|
|
|
632
740
|
reportError('enablePush failed', err);
|
|
633
741
|
return {
|
|
634
742
|
granted: false,
|
|
635
|
-
status: '
|
|
636
|
-
|
|
743
|
+
status: 'not_determined',
|
|
744
|
+
error: 'push_setup_failed',
|
|
745
|
+
platform: Platform.OS === 'ios' ? 'ios' : 'android',
|
|
637
746
|
};
|
|
638
747
|
}
|
|
639
748
|
},
|
|
640
749
|
async disablePush() {
|
|
750
|
+
lastEnablePushOptions = null;
|
|
641
751
|
try {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (e) {
|
|
647
|
-
e.lastPushToken = null;
|
|
648
|
-
e.lastPushRegistrationKey = null;
|
|
649
|
-
e.lastPushRegistrationAt = 0;
|
|
650
|
-
void UserGist.setConsent({ push: false });
|
|
651
|
-
}
|
|
752
|
+
if (engine)
|
|
753
|
+
await UserGist.setConsent({ push: false });
|
|
754
|
+
else
|
|
755
|
+
await UserGistPushNative.disablePush();
|
|
652
756
|
}
|
|
653
757
|
catch (err) {
|
|
654
758
|
reportError('disablePush failed', err);
|
|
@@ -949,6 +1053,9 @@ export const UserGist = {
|
|
|
949
1053
|
try {
|
|
950
1054
|
const e = requireEngine();
|
|
951
1055
|
await ensureHydrated(e);
|
|
1056
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1057
|
+
return { items: [], nextCursor: null };
|
|
1058
|
+
const current = requestSessionIsCurrent(e);
|
|
952
1059
|
const id = e.identity.get();
|
|
953
1060
|
const result = await e.transport.requestsList({
|
|
954
1061
|
anonymousId: id.anonymousId,
|
|
@@ -960,6 +1067,8 @@ export const UserGist = {
|
|
|
960
1067
|
cursor: options.cursor ?? null,
|
|
961
1068
|
limit: options.limit,
|
|
962
1069
|
});
|
|
1070
|
+
if (!current())
|
|
1071
|
+
return { items: [], nextCursor: null };
|
|
963
1072
|
ensureRequestsCache().upsertList(result.items.map((s) => ({
|
|
964
1073
|
id: s.id,
|
|
965
1074
|
appId: '',
|
|
@@ -1000,10 +1109,11 @@ export const UserGist = {
|
|
|
1000
1109
|
void (async () => {
|
|
1001
1110
|
try {
|
|
1002
1111
|
await ensureHydrated(e);
|
|
1003
|
-
if (!e.consent.allowsFeedback()) {
|
|
1112
|
+
if (e.resetting || !e.consent.allowsFeedback()) {
|
|
1004
1113
|
callback?.(new Error('feedback consent required'));
|
|
1005
1114
|
return;
|
|
1006
1115
|
}
|
|
1116
|
+
const current = requestSessionIsCurrent(e);
|
|
1007
1117
|
const id = e.identity.get();
|
|
1008
1118
|
const req = await e.transport.requestSubmit({
|
|
1009
1119
|
idempotencyKey: generateEventId(),
|
|
@@ -1012,6 +1122,10 @@ export const UserGist = {
|
|
|
1012
1122
|
title,
|
|
1013
1123
|
description,
|
|
1014
1124
|
});
|
|
1125
|
+
if (!current()) {
|
|
1126
|
+
callback?.(new Error('Request cancelled by identity or consent change'));
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1015
1129
|
ensureRequestsCache().upsert(req);
|
|
1016
1130
|
recordServerBackedEventLocally(e, '$request_submitted');
|
|
1017
1131
|
requestsHandlers.onSubmit?.(req);
|
|
@@ -1031,11 +1145,12 @@ export const UserGist = {
|
|
|
1031
1145
|
try {
|
|
1032
1146
|
const e = requireEngine();
|
|
1033
1147
|
await ensureHydrated(e);
|
|
1034
|
-
if (!e.consent.allowsFeedback())
|
|
1148
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1035
1149
|
return;
|
|
1036
1150
|
const cache = ensureRequestsCache();
|
|
1037
1151
|
const rollback = cache.applyOptimisticVote(requestId, vote);
|
|
1038
1152
|
try {
|
|
1153
|
+
const current = requestSessionIsCurrent(e);
|
|
1039
1154
|
const id = e.identity.get();
|
|
1040
1155
|
const result = await e.transport.requestVote({
|
|
1041
1156
|
requestId,
|
|
@@ -1043,6 +1158,10 @@ export const UserGist = {
|
|
|
1043
1158
|
externalId: id.externalId ?? null,
|
|
1044
1159
|
vote,
|
|
1045
1160
|
});
|
|
1161
|
+
if (!current()) {
|
|
1162
|
+
rollback();
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1046
1165
|
cache.commitVote(requestId, result);
|
|
1047
1166
|
recordServerBackedEventLocally(e, vote ? '$request_upvoted' : '$request_unupvoted');
|
|
1048
1167
|
requestsHandlers.onVote?.(result);
|
|
@@ -1060,11 +1179,12 @@ export const UserGist = {
|
|
|
1060
1179
|
try {
|
|
1061
1180
|
const e = requireEngine();
|
|
1062
1181
|
await ensureHydrated(e);
|
|
1063
|
-
if (!e.consent.allowsFeedback())
|
|
1182
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1064
1183
|
return;
|
|
1065
1184
|
const cache = ensureRequestsCache();
|
|
1066
1185
|
const rollback = cache.applyOptimisticFollow(requestId, follow);
|
|
1067
1186
|
try {
|
|
1187
|
+
const current = requestSessionIsCurrent(e);
|
|
1068
1188
|
const id = e.identity.get();
|
|
1069
1189
|
const result = await e.transport.requestFollow({
|
|
1070
1190
|
requestId,
|
|
@@ -1072,6 +1192,10 @@ export const UserGist = {
|
|
|
1072
1192
|
externalId: id.externalId ?? null,
|
|
1073
1193
|
follow,
|
|
1074
1194
|
});
|
|
1195
|
+
if (!current()) {
|
|
1196
|
+
rollback();
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1075
1199
|
cache.commitFollow(requestId, result);
|
|
1076
1200
|
recordServerBackedEventLocally(e, follow ? '$request_followed' : '$request_unfollowed');
|
|
1077
1201
|
requestsHandlers.onFollow?.(result);
|
|
@@ -1095,9 +1219,11 @@ export const UserGist = {
|
|
|
1095
1219
|
try {
|
|
1096
1220
|
const e = requireEngine();
|
|
1097
1221
|
await ensureHydrated(e);
|
|
1098
|
-
if (!e.consent.allowsFeedback())
|
|
1222
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1099
1223
|
return null;
|
|
1100
|
-
|
|
1224
|
+
const current = requestSessionIsCurrent(e);
|
|
1225
|
+
const branding = await e.transport.requestBranding();
|
|
1226
|
+
return current() ? branding : null;
|
|
1101
1227
|
}
|
|
1102
1228
|
catch (err) {
|
|
1103
1229
|
reportError('getRequestBranding failed', err);
|
|
@@ -1114,14 +1240,17 @@ export const UserGist = {
|
|
|
1114
1240
|
try {
|
|
1115
1241
|
const e = requireEngine();
|
|
1116
1242
|
await ensureHydrated(e);
|
|
1117
|
-
if (!e.consent.allowsFeedback())
|
|
1243
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1118
1244
|
return null;
|
|
1245
|
+
const current = requestSessionIsCurrent(e);
|
|
1119
1246
|
const id = e.identity.get();
|
|
1120
1247
|
const req = await e.transport.requestGet({
|
|
1121
1248
|
requestId,
|
|
1122
1249
|
anonymousId: id.anonymousId,
|
|
1123
1250
|
externalId: id.externalId ?? null,
|
|
1124
1251
|
});
|
|
1252
|
+
if (!current())
|
|
1253
|
+
return null;
|
|
1125
1254
|
ensureRequestsCache().upsert(req);
|
|
1126
1255
|
return req;
|
|
1127
1256
|
}
|
|
@@ -1133,7 +1262,7 @@ export const UserGist = {
|
|
|
1133
1262
|
openRequestsBoard() {
|
|
1134
1263
|
try {
|
|
1135
1264
|
const e = requireEngine();
|
|
1136
|
-
if (!e.consent.allowsFeedback())
|
|
1265
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1137
1266
|
return;
|
|
1138
1267
|
e.events.emit('showRequestsBoard', undefined);
|
|
1139
1268
|
}
|
|
@@ -1144,7 +1273,7 @@ export const UserGist = {
|
|
|
1144
1273
|
openRequestDetail(requestId) {
|
|
1145
1274
|
try {
|
|
1146
1275
|
const e = requireEngine();
|
|
1147
|
-
if (!e.consent.allowsFeedback())
|
|
1276
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1148
1277
|
return;
|
|
1149
1278
|
e.events.emit('showRequestDetail', { requestId });
|
|
1150
1279
|
}
|
|
@@ -1161,15 +1290,16 @@ export const UserGist = {
|
|
|
1161
1290
|
try {
|
|
1162
1291
|
const e = requireEngine();
|
|
1163
1292
|
await ensureHydrated(e);
|
|
1164
|
-
if (!e.consent.allowsFeedback())
|
|
1293
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1165
1294
|
return [];
|
|
1295
|
+
const current = requestSessionIsCurrent(e);
|
|
1166
1296
|
const id = e.identity.get();
|
|
1167
1297
|
const result = await e.transport.requestCommentsList({
|
|
1168
1298
|
requestId,
|
|
1169
1299
|
anonymousId: id.anonymousId,
|
|
1170
1300
|
externalId: id.externalId ?? null,
|
|
1171
1301
|
});
|
|
1172
|
-
return result.items;
|
|
1302
|
+
return current() ? result.items : [];
|
|
1173
1303
|
}
|
|
1174
1304
|
catch (err) {
|
|
1175
1305
|
reportError('getComments failed', err);
|
|
@@ -1182,8 +1312,9 @@ export const UserGist = {
|
|
|
1182
1312
|
const e = requireEngine();
|
|
1183
1313
|
validateCommentBody(body);
|
|
1184
1314
|
await ensureHydrated(e);
|
|
1185
|
-
if (!e.consent.allowsFeedback())
|
|
1315
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1186
1316
|
return null;
|
|
1317
|
+
const current = requestSessionIsCurrent(e);
|
|
1187
1318
|
const id = e.identity.get();
|
|
1188
1319
|
const comment = await e.transport.requestCommentPost({
|
|
1189
1320
|
idempotencyKey: generateEventId(),
|
|
@@ -1192,6 +1323,8 @@ export const UserGist = {
|
|
|
1192
1323
|
externalId: id.externalId ?? null,
|
|
1193
1324
|
body,
|
|
1194
1325
|
});
|
|
1326
|
+
if (!current())
|
|
1327
|
+
return null;
|
|
1195
1328
|
recordServerBackedEventLocally(e, REQUEST_COMMENTED_EVENT_NAME);
|
|
1196
1329
|
return comment;
|
|
1197
1330
|
}
|
|
@@ -1206,8 +1339,9 @@ export const UserGist = {
|
|
|
1206
1339
|
const e = requireEngine();
|
|
1207
1340
|
validateCommentBody(body);
|
|
1208
1341
|
await ensureHydrated(e);
|
|
1209
|
-
if (!e.consent.allowsFeedback())
|
|
1342
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1210
1343
|
return null;
|
|
1344
|
+
const current = requestSessionIsCurrent(e);
|
|
1211
1345
|
const id = e.identity.get();
|
|
1212
1346
|
const comment = await e.transport.requestCommentEdit({
|
|
1213
1347
|
requestId,
|
|
@@ -1215,6 +1349,8 @@ export const UserGist = {
|
|
|
1215
1349
|
anonymousId: id.anonymousId,
|
|
1216
1350
|
body,
|
|
1217
1351
|
});
|
|
1352
|
+
if (!current())
|
|
1353
|
+
return null;
|
|
1218
1354
|
recordServerBackedEventLocally(e, REQUEST_COMMENT_EDITED_EVENT_NAME);
|
|
1219
1355
|
return comment;
|
|
1220
1356
|
}
|
|
@@ -1228,14 +1364,17 @@ export const UserGist = {
|
|
|
1228
1364
|
try {
|
|
1229
1365
|
const e = requireEngine();
|
|
1230
1366
|
await ensureHydrated(e);
|
|
1231
|
-
if (!e.consent.allowsFeedback())
|
|
1367
|
+
if (e.resetting || !e.consent.allowsFeedback())
|
|
1232
1368
|
return;
|
|
1369
|
+
const current = requestSessionIsCurrent(e);
|
|
1233
1370
|
const id = e.identity.get();
|
|
1234
1371
|
await e.transport.requestCommentDelete({
|
|
1235
1372
|
requestId,
|
|
1236
1373
|
commentId,
|
|
1237
1374
|
anonymousId: id.anonymousId,
|
|
1238
1375
|
});
|
|
1376
|
+
if (!current())
|
|
1377
|
+
return;
|
|
1239
1378
|
recordServerBackedEventLocally(e, REQUEST_COMMENT_DELETED_EVENT_NAME);
|
|
1240
1379
|
}
|
|
1241
1380
|
catch (err) {
|