@bigbinary/neeto-commons-rn 2.2.0 → 2.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/lib/typescript/components/PushPermissionGate.d.ts +20 -0
  2. package/lib/typescript/components/PushPermissionGate.d.ts.map +1 -0
  3. package/lib/typescript/components/SocialButton.d.ts.map +1 -1
  4. package/lib/typescript/components/index.d.ts +1 -0
  5. package/lib/typescript/components/index.d.ts.map +1 -1
  6. package/lib/typescript/config/axios/caseConversion.d.ts.map +1 -1
  7. package/lib/typescript/config/axios/constants.d.ts +3 -0
  8. package/lib/typescript/config/axios/constants.d.ts.map +1 -1
  9. package/lib/typescript/config/axios/index.d.ts.map +1 -1
  10. package/lib/typescript/config/axios/redact.d.ts +2 -0
  11. package/lib/typescript/config/axios/redact.d.ts.map +1 -0
  12. package/lib/typescript/config/bugsnag.d.ts +3 -2
  13. package/lib/typescript/config/bugsnag.d.ts.map +1 -1
  14. package/lib/typescript/constants/index.d.ts +11 -0
  15. package/lib/typescript/constants/index.d.ts.map +1 -1
  16. package/lib/typescript/helpers/notifications/index.d.ts +1 -0
  17. package/lib/typescript/helpers/notifications/index.d.ts.map +1 -1
  18. package/lib/typescript/helpers/notifications/permission.d.ts +40 -0
  19. package/lib/typescript/helpers/notifications/permission.d.ts.map +1 -0
  20. package/lib/typescript/helpers/notifications/registerBackgroundHandlers.d.ts.map +1 -1
  21. package/lib/typescript/helpers/notifications/usePushNotification.d.ts.map +1 -1
  22. package/lib/typescript/hooks/index.d.ts +2 -0
  23. package/lib/typescript/hooks/index.d.ts.map +1 -1
  24. package/lib/typescript/hooks/query/useAuth.d.ts.map +1 -1
  25. package/lib/typescript/hooks/useNotificationPermissionStatus.d.ts +16 -0
  26. package/lib/typescript/hooks/useNotificationPermissionStatus.d.ts.map +1 -0
  27. package/lib/typescript/hooks/usePushNotificationPermissions.d.ts +8 -0
  28. package/lib/typescript/hooks/usePushNotificationPermissions.d.ts.map +1 -1
  29. package/lib/typescript/hooks/useRequestNotificationPermission.d.ts +13 -0
  30. package/lib/typescript/hooks/useRequestNotificationPermission.d.ts.map +1 -0
  31. package/lib/typescript/providers/NeetoCommonsRNProvider.d.ts.map +1 -1
  32. package/lib/typescript/screens/authentication/AuthOptions.d.ts.map +1 -1
  33. package/lib/typescript/screens/authentication/OTPVerificationScreen.d.ts.map +1 -1
  34. package/package.json +32 -4
  35. package/src/components/PushPermissionGate.ts +25 -0
  36. package/src/components/SocialButton.tsx +2 -1
  37. package/src/components/index.ts +1 -0
  38. package/src/config/axios/caseConversion.ts +6 -2
  39. package/src/config/axios/constants.ts +19 -0
  40. package/src/config/axios/index.ts +7 -31
  41. package/src/config/axios/redact.ts +50 -0
  42. package/src/config/bugsnag.ts +86 -6
  43. package/src/constants/index.ts +12 -0
  44. package/src/helpers/notifications/index.ts +4 -0
  45. package/src/helpers/notifications/permission.ts +56 -0
  46. package/src/helpers/notifications/registerBackgroundHandlers.ts +17 -6
  47. package/src/helpers/notifications/usePushNotification.ts +20 -13
  48. package/src/hooks/index.ts +2 -0
  49. package/src/hooks/query/useAuth.ts +6 -1
  50. package/src/hooks/query/useNotification.ts +2 -2
  51. package/src/hooks/useNotificationPermissionStatus.ts +50 -0
  52. package/src/hooks/usePushNotificationPermissions.ts +24 -13
  53. package/src/hooks/useRequestNotificationPermission.ts +55 -0
  54. package/src/providers/NeetoCommonsRNProvider.tsx +11 -8
  55. package/src/screens/authentication/AuthOptions.tsx +5 -0
  56. package/src/screens/authentication/OTPVerificationScreen.tsx +7 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Raises the notification permission prompt, once, and renders nothing.
3
+ *
4
+ * Mount it inside the signed-in branch of the navigator:
5
+ *
6
+ * {isLoggedIn && <PushPermissionGate />}
7
+ *
8
+ * A component rather than a hook call because the request happens as soon as
9
+ * it runs and hooks cannot be called conditionally -- mounting is what gates
10
+ * it on auth, and what keeps the prompt off the login screen. Unmounting on
11
+ * sign-out means the next account on a shared device is asked on its own
12
+ * first login rather than inheriting the previous session's state.
13
+ *
14
+ * This exists because every app that put the request on its Profile screen
15
+ * shipped the same bug: Profile is a lazily mounted tab, so a user who never
16
+ * opened it was never asked, while the app went on registering a device token
17
+ * for them. See neetozone/neeto-chat-rn#788.
18
+ */
19
+ export declare const PushPermissionGate: () => null;
20
+ //# sourceMappingURL=PushPermissionGate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PushPermissionGate.d.ts","sourceRoot":"","sources":["../../../src/components/PushPermissionGate.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,YAI9B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"SocialButton.d.ts","sourceRoot":"","sources":["../../../src/components/SocialButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAiB1E,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAKD,eAAO,MAAM,YAAY,GAAI,mDAM1B,iBAAiB,sBAyBnB,CAAC"}
1
+ {"version":3,"file":"SocialButton.d.ts","sourceRoot":"","sources":["../../../src/components/SocialButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAkB1E,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAKD,eAAO,MAAM,YAAY,GAAI,mDAM1B,iBAAiB,sBAyBnB,CAAC"}
@@ -11,4 +11,5 @@ export { EmptyView } from "./EmptyView";
11
11
  export { TextWithUrl } from "./TextWithUrl";
12
12
  export { OfflineScreen } from "./OfflineScreen";
13
13
  export { WorkspaceSwitcherPane } from "./WorkspaceSwitcherPane";
14
+ export { PushPermissionGate } from "./PushPermissionGate";
14
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"caseConversion.d.ts","sourceRoot":"","sources":["../../../../src/config/axios/caseConversion.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAkBrD,eAAO,MAAM,kBAAkB,GAC7B,MAAM,OAAO,EACb,UAAS,qBAA0B,YAcpC,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,MAAM,OAAO,EACb,UAAS,qBAA0B,QAcpC,CAAC"}
1
+ {"version":3,"file":"caseConversion.d.ts","sourceRoot":"","sources":["../../../../src/config/axios/caseConversion.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAsBrD,eAAO,MAAM,kBAAkB,GAC7B,MAAM,OAAO,EACb,UAAS,qBAA0B,YAcpC,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,MAAM,OAAO,EACb,UAAS,qBAA0B,QAcpC,CAAC"}
@@ -2,4 +2,7 @@ import { type CaseConversionOptions } from "./types";
2
2
  export declare const DEFAULT_CASE_CONVERSION: boolean;
3
3
  export declare const UUID_SEGMENT: RegExp;
4
4
  export declare const shouldEnableCaseConversion: (options?: CaseConversionOptions) => boolean;
5
+ export declare const SENSITIVE_KEY_PATTERNS: RegExp[];
6
+ export declare const REDACTED = "<redacted>";
7
+ export declare const MAX_REDACT_DEPTH = 6;
5
8
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/config/axios/constants.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD,eAAO,MAAM,uBAAuB,SAA2C,CAAC;AAEhF,eAAO,MAAM,YAAY,QAC0C,CAAC;AAEpE,eAAO,MAAM,0BAA0B,GACrC,UAAS,qBAA0B,YASpC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/config/axios/constants.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD,eAAO,MAAM,uBAAuB,SAA2C,CAAC;AAEhF,eAAO,MAAM,YAAY,QAC0C,CAAC;AAEpE,eAAO,MAAM,0BAA0B,GACrC,UAAS,qBAA0B,YASpC,CAAC;AAOF,eAAO,MAAM,sBAAsB,UAQlC,CAAC;AAEF,eAAO,MAAM,QAAQ,eAAe,CAAC;AAErC,eAAO,MAAM,gBAAgB,IAAI,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/config/axios/index.ts"],"names":[],"mappings":"AAIA,OAAc,EACZ,KAAK,UAAU,EAEf,KAAK,0BAA0B,EAChC,MAAM,OAAO,CAAC;AAoBf,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AA2CD,eAAO,MAAM,cAAc,GAAI,eAAe;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,WACG,CAAC;AAgCvE,eAAO,MAAM,eAAe,GAC1B,MAAM,YAAY,GAAG,SAAS,EAC9B,OAAO,UAAU,KAChB,MAMF,CAAC;AA+IF,eAAO,MAAM,wBAAwB,YAUpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,SAAS,0BAA0B,kDAepC,CAAC;AAEF,eAAO,MAAM,cAAc,YAI1B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/config/axios/index.ts"],"names":[],"mappings":"AAIA,OAAc,EACZ,KAAK,UAAU,EAEf,KAAK,0BAA0B,EAChC,MAAM,OAAO,CAAC;AAqBf,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAYD,eAAO,MAAM,cAAc,GAAI,eAAe;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,WACG,CAAC;AAgCvE,eAAO,MAAM,eAAe,GAC1B,MAAM,YAAY,GAAG,SAAS,EAC9B,OAAO,UAAU,KAChB,MAMF,CAAC;AAqJF,eAAO,MAAM,wBAAwB,YAUpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,SAAS,0BAA0B,kDAepC,CAAC;AAEF,eAAO,MAAM,cAAc,YAI1B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const filterSensitiveData: (data: unknown) => unknown;
2
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../../src/config/axios/redact.ts"],"names":[],"mappings":"AAgDA,eAAO,MAAM,mBAAmB,GAAI,MAAM,OAAO,YACK,CAAC"}
@@ -1,4 +1,5 @@
1
- import { type NotifiableError } from "@bugsnag/react-native";
2
- export declare const bugsnagErrorHandler: (err: NotifiableError, origin: string, groupingHash?: string) => void;
1
+ import { type Event, type NotifiableError } from "@bugsnag/react-native";
2
+ export declare const toNotifiableError: (error: unknown) => Error;
3
+ export declare const bugsnagErrorHandler: (err: NotifiableError, origin: string, groupingHash?: string, severity?: Event["severity"]) => void;
3
4
  export declare const configureBugsnag: () => void;
4
5
  //# sourceMappingURL=bugsnag.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bugsnag.d.ts","sourceRoot":"","sources":["../../../src/config/bugsnag.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAUtE,eAAO,MAAM,mBAAmB,GAC9B,KAAK,eAAe,EACpB,QAAQ,MAAM,EAGd,eAAe,MAAM,SAkBtB,CAAC;AAEF,eAAO,MAAM,gBAAgB,YA2B5B,CAAC"}
1
+ {"version":3,"file":"bugsnag.d.ts","sourceRoot":"","sources":["../../../src/config/bugsnag.ts"],"names":[],"mappings":"AACA,OAAgB,EACd,KAAK,KAAK,EACV,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAgD/B,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAMlD,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,KAAK,eAAe,EACpB,QAAQ,MAAM,EAGd,eAAe,MAAM,EAGrB,WAAU,KAAK,CAAC,UAAU,CAAa,SAkBxC,CAAC;AAEF,eAAO,MAAM,gBAAgB,YAuD5B,CAAC"}
@@ -3,6 +3,16 @@ export declare const STORAGE_KEYS: {
3
3
  readonly DEVICE_TOKEN: "devicetoken";
4
4
  readonly IS_VISITED: "isVisited";
5
5
  readonly NOTIFICATION_PERMISSION_REQUESTED: "notificationPermissionRequested";
6
+ /**
7
+ * Read-only fallback. Before this key existed here, every app kept its own
8
+ * "have we asked yet?" flag under this exact string -- neeto-desk,
9
+ * neeto-cal, neeto-form and neeto-invoice all wrote it verbatim from their
10
+ * own constants. An app adopting the hooks here would otherwise start with
11
+ * a null flag and re-prompt everyone who had already declined, so the
12
+ * request path reads this when its own key is unset. Never written; drop it
13
+ * once installs have rolled over.
14
+ */
15
+ readonly LEGACY_APP_NOTIFICATION_PROMPT_DONE: "notification_first_prompt_done";
6
16
  };
7
17
  export declare const DEFAULT_PAGE_SIZE = 30;
8
18
  /**
@@ -13,6 +23,7 @@ export declare const DEFAULT_PAGE_SIZE = 30;
13
23
  export declare const APP_VERSION_ERROR_REPORT_INTERVAL: number;
14
24
  /** One Bugsnag group for every app_versions failure, whatever the status. */
15
25
  export declare const APP_VERSION_ERROR_GROUPING_HASH = "app-version-check-failed";
26
+ export declare const AUTH_BUTTON_HEIGHT = 48;
16
27
  /** Design baseline: a ~5" phone, the width every scaled size was drawn against. */
17
28
  export declare const GUIDELINE_BASE_WIDTH = 350;
18
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/constants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,eAAO,MAAM,YAAY;;;;CAIf,CAAC;AAEX,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,QAAiB,CAAC;AAEhE,6EAA6E;AAC7E,eAAO,MAAM,+BAA+B,6BAA6B,CAAC;AAE1E,mFAAmF;AACnF,eAAO,MAAM,oBAAoB,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/constants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,eAAO,MAAM,YAAY;;;;IAIvB;;;;;;;;OAQG;;CAEK,CAAC;AAEX,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,QAAiB,CAAC;AAEhE,6EAA6E;AAC7E,eAAO,MAAM,+BAA+B,6BAA6B,CAAC;AAE1E,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,mFAAmF;AACnF,eAAO,MAAM,oBAAoB,MAAM,CAAC"}
@@ -2,4 +2,5 @@ export { usePushNotification } from "./usePushNotification";
2
2
  export { registerBackgroundHandlers } from "./registerBackgroundHandlers";
3
3
  export { NOTIFICATION_CHANNELS, NOTIFICATION_CHANNEL_IDS } from "./constants";
4
4
  export { getInitialFirebaseNotification } from "./firebaseMessaging";
5
+ export { hasBeenPromptedForNotifications, needsNotificationPermissionPrompt, } from "./permission";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/helpers/notifications/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/helpers/notifications/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EACL,+BAA+B,EAC/B,iCAAiC,GAClC,MAAM,cAAc,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { Platform } from "react-native";
2
+ /**
3
+ * Whether this device has a notification permission worth asking for.
4
+ *
5
+ * Android only gained a runtime notification permission in 13 (API 33);
6
+ * below that the manifest entry is the whole story and there is nothing to
7
+ * prompt for. Every iOS version needs asking.
8
+ *
9
+ * Consuming apps used to hand-roll this as
10
+ * `Platform.OS !== "android" || Platform.Version < 33`, which returns early
11
+ * on iOS as well and so never asked there at all. neeto-desk shipped that
12
+ * for months; neeto-form and neeto-invoice still carry the same line. Owning
13
+ * it here is the point of this module.
14
+ *
15
+ * Note that `Platform.Version` is a number on Android and a string on iOS,
16
+ * so the version comparison is only meaningful once the OS is known to be
17
+ * Android. Coercing keeps a string version from silently comparing as NaN.
18
+ *
19
+ * Parameters are injectable so the platform matrix is testable without
20
+ * mutating the Platform module.
21
+ */
22
+ export declare const needsNotificationPermissionPrompt: (os?: typeof Platform.OS, version?: typeof Platform.Version) => boolean;
23
+ /**
24
+ * Has this install already been through the prompt?
25
+ *
26
+ * Falls back to the app-local flag every consuming app used to keep under
27
+ * "notification_first_prompt_done". Without that read, an app adopting these
28
+ * hooks starts with a null flag, decides nobody has been asked, and
29
+ * re-prompts everyone whose status is still DENIED -- on Android 13+ that is
30
+ * every user who declined exactly once, since Android allows a second ask.
31
+ * iOS is unaffected either way: a prior denial reads BLOCKED, not DENIED.
32
+ *
33
+ * A fallback read rather than a migration, on purpose. A migration would have
34
+ * to finish before the requesting hook mounts -- it prompts on mount -- which
35
+ * means gating the gate on an async seed. Reading both keys has no ordering
36
+ * to get wrong, is idempotent, and covers every app at once because they all
37
+ * wrote the identical string.
38
+ */
39
+ export declare const hasBeenPromptedForNotifications: () => Promise<boolean>;
40
+ //# sourceMappingURL=permission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../../../src/helpers/notifications/permission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAKxC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iCAAiC,GAC5C,KAAI,OAAO,QAAQ,CAAC,EAAgB,EACpC,UAAS,OAAO,QAAQ,CAAC,OAA0B,YACP,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,+BAA+B,wBAS3C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"registerBackgroundHandlers.d.ts","sourceRoot":"","sources":["../../../../src/helpers/notifications/registerBackgroundHandlers.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAOxD,eAAO,MAAM,0BAA0B,GACrC,sBAAsB,wBAAwB,SA+C/C,CAAC"}
1
+ {"version":3,"file":"registerBackgroundHandlers.d.ts","sourceRoot":"","sources":["../../../../src/helpers/notifications/registerBackgroundHandlers.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAOxD,eAAO,MAAM,0BAA0B,GACrC,sBAAsB,wBAAwB,SA0D/C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"usePushNotification.d.ts","sourceRoot":"","sources":["../../../../src/helpers/notifications/usePushNotification.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,SAAS,CAAC;AAWjB,UAAU,uBAAuB;IAC/B,WAAW,EAAE,wBAAwB,CAAC;CACvC;AAED,eAAO,MAAM,mBAAmB,GAC9B,YAAY,OAAO,EACnB,kBAAiB,uBAAmD,YAiLrE,CAAC"}
1
+ {"version":3,"file":"usePushNotification.d.ts","sourceRoot":"","sources":["../../../../src/helpers/notifications/usePushNotification.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,SAAS,CAAC;AAWjB,UAAU,uBAAuB;IAC/B,WAAW,EAAE,wBAAwB,CAAC;CACvC;AAED,eAAO,MAAM,mBAAmB,GAC9B,YAAY,OAAO,EACnB,kBAAiB,uBAAmD,YAwLrE,CAAC"}
@@ -1,3 +1,5 @@
1
1
  export { useLocalization } from "../contexts/localization";
2
2
  export { usePushNotificationPermissions } from "./usePushNotificationPermissions";
3
+ export { useNotificationPermissionStatus } from "./useNotificationPermissionStatus";
4
+ export { useRequestNotificationPermission } from "./useRequestNotificationPermission";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../src/hooks/query/useAuth.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAUxC,OAAO,EAAE,KAAK,YAAY,EAAmB,MAAM,oBAAoB,CAAC;AASxE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAE3B,eAAO,MAAM,OAAO;4CAQf;QACD,YAAY,EAAE,YAAY,CAAC;KAC5B;;eAyIc,MAAM;4BAAsB,OAAO;;;cA1E1C,MAAM;gBACJ,MAAM;;;wBAqGyC,MAAM;;;2BAapC,MAAM;uBACV,MAAM;;;sBAYP,YAAY;;CAoBnC,CAAC"}
1
+ {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../src/hooks/query/useAuth.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAUxC,OAAO,EAAE,KAAK,YAAY,EAAmB,MAAM,oBAAoB,CAAC;AASxE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAE3B,eAAO,MAAM,OAAO;4CAQf;QACD,YAAY,EAAE,YAAY,CAAC;KAC5B;;eA8Ic,MAAM;4BAAsB,OAAO;;;cA/E1C,MAAM;gBACJ,MAAM;;;wBA0GyC,MAAM;;;2BAapC,MAAM;uBACV,MAAM;;;sBAYP,YAAY;;CAoBnC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Read-only view of the notification permission.
3
+ *
4
+ * Deliberately never requests. A screen that merely displays whether
5
+ * notifications are on -- every app's Profile -- must not be the thing that
6
+ * asks, or users who never open it are never asked at all. That is exactly
7
+ * what usePushNotificationPermissions caused by combining the two: it was
8
+ * called from Profile because Profile is where the status is shown, which
9
+ * made a settings screen the app's only requester. Use PushPermissionGate
10
+ * for the asking.
11
+ *
12
+ * Re-checks on foreground, because the only way back from a denial is the
13
+ * system Settings app, which means leaving the app and returning.
14
+ */
15
+ export declare const useNotificationPermissionStatus: () => boolean;
16
+ //# sourceMappingURL=useNotificationPermissionStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNotificationPermissionStatus.d.ts","sourceRoot":"","sources":["../../../src/hooks/useNotificationPermissionStatus.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,+BAA+B,eA4B3C,CAAC"}
@@ -1,2 +1,10 @@
1
+ /**
2
+ * @deprecated Checks and requests in one call, which is why every app that
3
+ * wanted to *display* the status ended up making its Profile screen the only
4
+ * thing that ever *asked* -- see neetozone/neeto-chat-rn#788. Prefer
5
+ * `useNotificationPermissionStatus()` to read and `<PushPermissionGate />`
6
+ * (or `useRequestNotificationPermission()`) to ask. Kept working for
7
+ * consumers that have not moved yet.
8
+ */
1
9
  export declare const usePushNotificationPermissions: () => boolean[];
2
10
  //# sourceMappingURL=usePushNotificationPermissions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usePushNotificationPermissions.d.ts","sourceRoot":"","sources":["../../../src/hooks/usePushNotificationPermissions.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,8BAA8B,iBAgG1C,CAAC"}
1
+ {"version":3,"file":"usePushNotificationPermissions.d.ts","sourceRoot":"","sources":["../../../src/hooks/usePushNotificationPermissions.ts"],"names":[],"mappings":"AAkBA;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,iBA+F1C,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Asks for notification permission once per install, and nothing else.
3
+ *
4
+ * Prefer mounting PushPermissionGate inside the signed-in branch of the
5
+ * navigator; this hook is what it wraps, exported for apps that need to
6
+ * trigger the request from somewhere other than a mount.
7
+ *
8
+ * Only asks when the status is DENIED. GRANTED needs nothing and BLOCKED
9
+ * cannot be reopened from inside the app -- sending a blocked user to
10
+ * Settings is denied-state recovery, which belongs to the UI, not here.
11
+ */
12
+ export declare const useRequestNotificationPermission: (enabled?: boolean) => void;
13
+ //# sourceMappingURL=useRequestNotificationPermission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRequestNotificationPermission.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRequestNotificationPermission.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAAI,iBAAc,SA2B9D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"NeetoCommonsRNProvider.d.ts","sourceRoot":"","sources":["../../../src/providers/NeetoCommonsRNProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAIZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC;AAIrC,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAyBlC,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,sBAAsB,CACxD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CACnC,GAAG,IAAI,CAAC,CAAC;IACV,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,eAAO,MAAM,sBAAsB,GAAI,2GAQpC,2BAA2B,sBAgC7B,CAAC"}
1
+ {"version":3,"file":"NeetoCommonsRNProvider.d.ts","sourceRoot":"","sources":["../../../src/providers/NeetoCommonsRNProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAIZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC;AAIrC,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAyBlC,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,sBAAsB,CACxD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CACnC,GAAG,IAAI,CAAC,CAAC;IACV,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,eAAO,MAAM,sBAAsB,GAAI,2GAQpC,2BAA2B,sBAmC7B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"AuthOptions.d.ts","sourceRoot":"","sources":["../../../../src/screens/authentication/AuthOptions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAiC1B,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,WAAW,GAAI,kCAGzB,gBAAgB,sBAiMlB,CAAC"}
1
+ {"version":3,"file":"AuthOptions.d.ts","sourceRoot":"","sources":["../../../../src/screens/authentication/AuthOptions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAkC1B,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,WAAW,GAAI,kCAGzB,gBAAgB,sBAkMlB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"OTPVerificationScreen.d.ts","sourceRoot":"","sources":["../../../../src/screens/authentication/OTPVerificationScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAiBxE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAKjE,eAAO,MAAM,qBAAqB,GAAI,oCAGnC,kBAAkB,CAAC,iBAAiB,CAAC,sBA2JvC,CAAC"}
1
+ {"version":3,"file":"OTPVerificationScreen.d.ts","sourceRoot":"","sources":["../../../../src/screens/authentication/OTPVerificationScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAmBxE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAKjE,eAAO,MAAM,qBAAqB,GAAI,oCAGnC,kBAAkB,CAAC,iBAAiB,CAAC,sBA6JvC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigbinary/neeto-commons-rn",
3
- "version": "2.2.0",
3
+ "version": "2.2.4",
4
4
  "license": "UNLICENSED",
5
5
  "main": "./src/index.ts",
6
6
  "react-native": "./src/index.ts",
@@ -121,6 +121,11 @@
121
121
  "react-native": "./src/components/OrganizationItem.tsx",
122
122
  "default": "./src/components/OrganizationItem.tsx"
123
123
  },
124
+ "./components/PushPermissionGate": {
125
+ "types": "./lib/typescript/components/PushPermissionGate.d.ts",
126
+ "react-native": "./src/components/PushPermissionGate.ts",
127
+ "default": "./src/components/PushPermissionGate.ts"
128
+ },
124
129
  "./components/SocialButton": {
125
130
  "types": "./lib/typescript/components/SocialButton.d.ts",
126
131
  "react-native": "./src/components/SocialButton.tsx",
@@ -181,6 +186,11 @@
181
186
  "react-native": "./src/config/axios/paramsSerializer.ts",
182
187
  "default": "./src/config/axios/paramsSerializer.ts"
183
188
  },
189
+ "./config/axios/redact": {
190
+ "types": "./lib/typescript/config/axios/redact.d.ts",
191
+ "react-native": "./src/config/axios/redact.ts",
192
+ "default": "./src/config/axios/redact.ts"
193
+ },
184
194
  "./config/axios/types": {
185
195
  "types": "./lib/typescript/config/axios/types.d.ts",
186
196
  "react-native": "./src/config/axios/types.ts",
@@ -261,6 +271,11 @@
261
271
  "react-native": "./src/helpers/notifications/index.ts",
262
272
  "default": "./src/helpers/notifications/index.ts"
263
273
  },
274
+ "./helpers/notifications/permission": {
275
+ "types": "./lib/typescript/helpers/notifications/permission.d.ts",
276
+ "react-native": "./src/helpers/notifications/permission.ts",
277
+ "default": "./src/helpers/notifications/permission.ts"
278
+ },
264
279
  "./helpers/notifications/registerBackgroundHandlers": {
265
280
  "types": "./lib/typescript/helpers/notifications/registerBackgroundHandlers.d.ts",
266
281
  "react-native": "./src/helpers/notifications/registerBackgroundHandlers.ts",
@@ -331,16 +346,31 @@
331
346
  "react-native": "./src/hooks/useAppState.ts",
332
347
  "default": "./src/hooks/useAppState.ts"
333
348
  },
349
+ "./hooks/useNotificationPermissionStatus": {
350
+ "types": "./lib/typescript/hooks/useNotificationPermissionStatus.d.ts",
351
+ "react-native": "./src/hooks/useNotificationPermissionStatus.ts",
352
+ "default": "./src/hooks/useNotificationPermissionStatus.ts"
353
+ },
334
354
  "./hooks/usePushNotificationPermissions": {
335
355
  "types": "./lib/typescript/hooks/usePushNotificationPermissions.d.ts",
336
356
  "react-native": "./src/hooks/usePushNotificationPermissions.ts",
337
357
  "default": "./src/hooks/usePushNotificationPermissions.ts"
338
358
  },
359
+ "./hooks/useRequestNotificationPermission": {
360
+ "types": "./lib/typescript/hooks/useRequestNotificationPermission.d.ts",
361
+ "react-native": "./src/hooks/useRequestNotificationPermission.ts",
362
+ "default": "./src/hooks/useRequestNotificationPermission.ts"
363
+ },
339
364
  ".": {
340
365
  "types": "./lib/typescript/index.d.ts",
341
366
  "react-native": "./src/index.ts",
342
367
  "default": "./src/index.ts"
343
368
  },
369
+ "./initializers/constants": {
370
+ "types": "./lib/typescript/initializers/constants.d.ts",
371
+ "react-native": "./src/initializers/constants.ts",
372
+ "default": "./src/initializers/constants.ts"
373
+ },
344
374
  "./initializers": {
345
375
  "types": "./lib/typescript/initializers/index.d.ts",
346
376
  "react-native": "./src/initializers/index.ts",
@@ -598,7 +628,6 @@
598
628
  },
599
629
  "peerDependencies": {
600
630
  "@babel/eslint-parser": "^7.19.1",
601
- "@bigbinary/app-version-control": "^1.1.6",
602
631
  "@bigbinary/neetoui-rn": ">=3.0.0",
603
632
  "@eslint/js": "^9.26.0",
604
633
  "@gorhom/bottom-sheet": ">=5.0.0",
@@ -652,7 +681,7 @@
652
681
  "@rozenite/redux-devtools-plugin": "2.1.0",
653
682
  "@rozenite/storage-plugin": "2.1.0",
654
683
  "@rozenite/tanstack-query-plugin": "2.1.0",
655
- "compare-versions": "^6.0.0-rc.1",
684
+ "compare-versions": "^6.1.1",
656
685
  "formik": "^2.2.9",
657
686
  "i18next": "^26.3.6",
658
687
  "qs": "^6.15.2",
@@ -670,7 +699,6 @@
670
699
  "@babel/core": "^7.29.6",
671
700
  "@babel/eslint-parser": "^7.19.1",
672
701
  "@babel/runtime": "^7.12.5",
673
- "@bigbinary/app-version-control": "^1.1.6",
674
702
  "@bigbinary/neetoui-rn": "^3.0.0",
675
703
  "@bugsnag/source-maps": "^2.3.1",
676
704
  "@eslint/js": "^9.26.0",
@@ -0,0 +1,25 @@
1
+ import { useRequestNotificationPermission } from "../hooks/useRequestNotificationPermission";
2
+
3
+ /**
4
+ * Raises the notification permission prompt, once, and renders nothing.
5
+ *
6
+ * Mount it inside the signed-in branch of the navigator:
7
+ *
8
+ * {isLoggedIn && <PushPermissionGate />}
9
+ *
10
+ * A component rather than a hook call because the request happens as soon as
11
+ * it runs and hooks cannot be called conditionally -- mounting is what gates
12
+ * it on auth, and what keeps the prompt off the login screen. Unmounting on
13
+ * sign-out means the next account on a shared device is asked on its own
14
+ * first login rather than inheriting the previous session's state.
15
+ *
16
+ * This exists because every app that put the request on its Profile screen
17
+ * shipped the same bug: Profile is a lazily mounted tab, so a user who never
18
+ * opened it was never asked, while the app went on registering a device token
19
+ * for them. See neetozone/neeto-chat-rn#788.
20
+ */
21
+ export const PushPermissionGate = () => {
22
+ useRequestNotificationPermission();
23
+
24
+ return null;
25
+ };
@@ -11,6 +11,7 @@ import {
11
11
 
12
12
  import AppleLogo from "../assets/images/apple-logo.svg";
13
13
  import GoogleLogo from "../assets/images/google-logo.svg";
14
+ import { AUTH_BUTTON_HEIGHT } from "../constants";
14
15
  import { moderateScale } from "../helpers/scale";
15
16
 
16
17
  // neetoui-rn v2 shipped this; v3 dropped it. Reproduced here against the v3
@@ -68,7 +69,7 @@ const styles = StyleSheet.create({
68
69
  borderColor: lightTheme.colors.palette.grey800,
69
70
  borderRadius: moderateScale(8),
70
71
  borderWidth: moderateScale(1),
71
- height: moderateScale(48),
72
+ height: moderateScale(AUTH_BUTTON_HEIGHT),
72
73
  width: "100%",
73
74
  },
74
75
  logo: {
@@ -11,3 +11,4 @@ export { EmptyView } from "./EmptyView";
11
11
  export { TextWithUrl } from "./TextWithUrl";
12
12
  export { OfflineScreen } from "./OfflineScreen";
13
13
  export { WorkspaceSwitcherPane } from "./WorkspaceSwitcherPane";
14
+ export { PushPermissionGate } from "./PushPermissionGate";
@@ -2,17 +2,21 @@ import {
2
2
  serializeKeysToSnakeCase,
3
3
  keysToCamelCase,
4
4
  } from "@bigbinary/neeto-cist";
5
- import Bugsnag, { type NotifiableError } from "@bugsnag/react-native";
5
+ import Bugsnag from "@bugsnag/react-native";
6
6
 
7
7
  import { shouldEnableCaseConversion } from "./constants";
8
8
  import { type CaseConversionOptions } from "./types";
9
9
 
10
+ import { toNotifiableError } from "../bugsnag";
11
+
10
12
  const logCaseConversionError = (
11
13
  error: unknown,
12
14
  context: Record<string, unknown>
13
15
  ) => {
14
16
  if (!__DEV__) {
15
- Bugsnag.notify(error as NotifiableError, event => {
17
+ // Normalized rather than cast: a non-Error throw here would otherwise
18
+ // land in the same message-less "{}" group as everything else.
19
+ Bugsnag.notify(toNotifiableError(error), event => {
16
20
  event.severity = "warning";
17
21
  event.context = "case_conversion";
18
22
  event.addMetadata("caseConversion", {
@@ -18,3 +18,22 @@ export const shouldEnableCaseConversion = (
18
18
 
19
19
  return DEFAULT_CASE_CONVERSION;
20
20
  };
21
+
22
+ // Keys whose values must never leave the device on a Bugsnag report. Matched
23
+ // case-insensitively, because axios hands back "Authorization" as readily as
24
+ // "authorization" — and an exact-match list of literals let both that and
25
+ // `auth_token`, the field the login response actually uses (slices/auth.ts),
26
+ // through untouched.
27
+ export const SENSITIVE_KEY_PATTERNS = [
28
+ /^authorization$/,
29
+ /^(set-)?cookie$/,
30
+ /^x-auth-(token|email)$/,
31
+ /password/,
32
+ /(^|[-_])api[-_]?key$/,
33
+ /(^|[-_])(auth|access|refresh|session|id)[-_]?token$/,
34
+ /^token$/,
35
+ ];
36
+
37
+ export const REDACTED = "<redacted>";
38
+
39
+ export const MAX_REDACT_DEPTH = 6;
@@ -14,6 +14,7 @@ import { convertRequestKeys, convertResponseKeys } from "./caseConversion";
14
14
  import { normalizeUrlForGrouping } from "./groupingKey";
15
15
  import { handleNetworkError } from "./networkErrorHandler";
16
16
  import setParamsSerializer from "./paramsSerializer";
17
+ import { filterSensitiveData } from "./redact";
17
18
 
18
19
  import { STORAGE_KEYS } from "../../constants";
19
20
  import { removeItem } from "../../helpers/asyncStore";
@@ -31,37 +32,6 @@ export interface ApiErrorData {
31
32
  errors?: string[];
32
33
  }
33
34
 
34
- const filterSensitiveData = (data: unknown) => {
35
- if (!data) return null;
36
-
37
- const sensitiveKeys = [
38
- "x-auth-token",
39
- "x-auth-email",
40
- "password",
41
- "password_confirmation",
42
- "current_password",
43
- "token",
44
- "access_token",
45
- "refresh_token",
46
- ];
47
-
48
- const filteredData = { ...(data as Record<string, unknown>) };
49
-
50
- const filterNestedData = (obj: Record<string, unknown>) => {
51
- Object.keys(obj).forEach(key => {
52
- if (sensitiveKeys.includes(key)) {
53
- delete obj[key];
54
- } else if (obj[key] && typeof obj[key] === "object") {
55
- filterNestedData(obj[key] as Record<string, unknown>);
56
- }
57
- });
58
- };
59
-
60
- filterNestedData(filteredData);
61
-
62
- return filteredData;
63
- };
64
-
65
35
  const isAuthEndpoint = (url: string) =>
66
36
  url.includes("/api/mobile/v1/verify_otp") ||
67
37
  url.includes("/api/mobile/v1/generate_otp") ||
@@ -194,6 +164,12 @@ const registerResponseIntercept = () => {
194
164
  message,
195
165
  name,
196
166
  code,
167
+ // Captured before `report.context` is overwritten below. The
168
+ // react-navigation plugin puts the current screen there, and
169
+ // the synthetic error's stack points at this interceptor, so
170
+ // without this the report says nothing about where the request
171
+ // came from.
172
+ screen: report.context,
197
173
  requestUrl: config?.url,
198
174
  requestMethod: config?.method?.toUpperCase(),
199
175
  baseURL: config?.baseURL,
@@ -0,0 +1,50 @@
1
+ // Redaction for anything attached to a Bugsnag report: request headers,
2
+ // query params and response bodies all pass through here on the way out.
3
+
4
+ import {
5
+ MAX_REDACT_DEPTH,
6
+ REDACTED,
7
+ SENSITIVE_KEY_PATTERNS,
8
+ } from "./constants";
9
+
10
+ const isSensitiveKey = (key: string) => {
11
+ const normalized = key.toLowerCase();
12
+
13
+ return SENSITIVE_KEY_PATTERNS.some(pattern => pattern.test(normalized));
14
+ };
15
+
16
+ // Builds a redacted deep copy. The previous version spread only the top level
17
+ // and then deleted keys in place, so every nested object it walked was still
18
+ // the caller's own — redacting for a bug report mutated the live response
19
+ // body and request config. Cycles are cut rather than followed: axios configs
20
+ // and error responses can self-reference, and the recursion used to overflow.
21
+ const redact = (
22
+ value: unknown,
23
+ depth: number,
24
+ seen: WeakSet<object>
25
+ ): unknown => {
26
+ if (value === null || typeof value !== "object") return value;
27
+
28
+ if (depth > MAX_REDACT_DEPTH) return "<truncated>";
29
+
30
+ if (seen.has(value)) return "<circular>";
31
+
32
+ seen.add(value);
33
+
34
+ const redacted = Array.isArray(value)
35
+ ? value.map(item => redact(item, depth + 1, seen))
36
+ : Object.fromEntries(
37
+ Object.entries(value as Record<string, unknown>).map(([key, item]) => [
38
+ key,
39
+ isSensitiveKey(key) ? REDACTED : redact(item, depth + 1, seen),
40
+ ])
41
+ );
42
+
43
+ // Only ancestors can form a cycle; siblings sharing a reference are fine.
44
+ seen.delete(value);
45
+
46
+ return redacted;
47
+ };
48
+
49
+ export const filterSensitiveData = (data: unknown) =>
50
+ data ? redact(data, 0, new WeakSet<object>()) : null;