pushwave-client 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # pushwave-client (alpha)
2
+
3
+ Expo/React Native SDK for the upcoming PushWave SaaS (work in progress). Goal: fetch the Expo push token and prepare app attestation (Android Play Integrity / iOS DeviceCheck) to secure notification delivery. The SaaS/backend validation is coming soon—consider this an early-stage project.
4
+
5
+ ---
6
+
7
+ ## Quick install
8
+
9
+ 1) Install the [SDK](https://www.npmjs.com/package/pushwave-client) and [Expo notifications](https://docs.expo.dev/versions/latest/sdk/notifications/) (peer requirement):
10
+ ```bash
11
+ npm install pushwave-client
12
+ npx expo install expo-notifications
13
+ ```
14
+
15
+ 2) Add the config plugin to your app.json / app.config.*:
16
+ ```json
17
+ {
18
+ "expo": {
19
+ "plugins": ["pushwave-client"]
20
+ }
21
+ }
22
+ ```
23
+
24
+ 3) Build native (EAS or dev client). Expo Go is not supported once native code is involved:
25
+ ```bash
26
+ eas build -p android --profile development
27
+ eas build -p ios --profile development
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Minimal usage
33
+
34
+ ```tsx
35
+ import { useEffect } from "react";
36
+ import { Alert } from "react-native";
37
+ import PushWaveClient from "pushwave-client";
38
+
39
+ export default function App() {
40
+ useEffect(() => {
41
+ (async () => {
42
+ const res = await PushWaveClient.init({ apiKey: "pw_dev_xxx" });
43
+ if (!res.success) {
44
+ Alert.alert("PushWave", res.message ?? "Init failed");
45
+ }
46
+ })();
47
+ }, []);
48
+
49
+ return /* …your UI… */;
50
+ }
51
+ ```
52
+
53
+ - `PushWaveClient.init` is async and returns `{ success: boolean; message?: string }`.
54
+ - Call it **once** at app startup (e.g., in `App.tsx` or a root component `useEffect`). Recalling it later is unnecessary.
55
+ - In `__DEV__`, the SDK may log additional info/errors (e.g., failed API calls) to help debugging.
56
+
57
+ ---
58
+
59
+ ## Notifications (expo-notifications)
60
+
61
+ - The SDK retrieves the user’s Expo push token. If the user denies notification permission, no push will be delivered and the token may not be available depending on platform/permission.
62
+ - On iOS, user permission is required to obtain a push token.
63
+ - On Android, Expo handles permission/channel setup; if the user refuses, no push is delivered.
64
+
65
+ ---
66
+
67
+ ## Attestation (current status)
68
+
69
+ - Backend validation is not live yet; it will arrive with the PushWave SaaS.
70
+ - Android (Play Integrity): will require a build distributed via the Play Store (internal/closed track) with Play App Signing + Play Integrity API enabled. No support for Expo Go / sideload.
71
+ - iOS (DeviceCheck): will require a real build (dev client or TestFlight), not Expo Go.
72
+ - For now, consider attestation non-blocking (the SDK may return a `disabled` flag until the SaaS is active).
73
+
74
+ ---
75
+
76
+ ## Compatibility
77
+
78
+ - Tested on the latest Expo SDK (54). No guarantees on earlier versions.
79
+ - Requires a native build (EAS or dev client).
80
+
81
+ ---
82
+
83
+ ## Links
84
+
85
+ - NPM: https://www.npmjs.com/package/pushwave-client
86
+ - GitHub: https://github.com/luruk-hai/pushwave-client#readme
87
+
88
+ ---
89
+
90
+ ## Roadmap (with SaaS)
91
+
92
+ - Server-side validation of Play Integrity / DeviceCheck tokens.
93
+ - Full attestation docs and Play Store setup (enable Integrity API, internal track).
94
+ - Complete registration flow with the PushWave backend.
@@ -1,16 +1,23 @@
1
1
  export interface AndroidAttestationPayload {
2
+ status: "ok";
3
+ platform: "android";
2
4
  nonce: string;
3
5
  timestamp: number;
4
6
  integrityToken: string;
5
7
  }
6
8
  export interface IosAttestationPayload {
9
+ status: "ok";
10
+ platform: "ios";
7
11
  nonce: string;
8
12
  timestamp: number;
9
13
  deviceCheckToken: string;
10
14
  }
11
15
  export interface DisabledAttestation {
12
- attestationDisabled: true;
16
+ status: "disabled";
13
17
  reason: string;
14
18
  }
15
- export type ApplicationAttestation = AndroidAttestationPayload | IosAttestationPayload | DisabledAttestation | true;
19
+ export interface SkippedAttestation {
20
+ status: "skipped";
21
+ }
22
+ export type ApplicationAttestation = AndroidAttestationPayload | IosAttestationPayload | DisabledAttestation | SkippedAttestation;
16
23
  export default function getApplicationAttestation(apiKey: string): Promise<ApplicationAttestation>;
@@ -6,7 +6,7 @@ const react_native_1 = require("react-native");
6
6
  const native_1 = require("./native");
7
7
  async function getApplicationAttestation(apiKey) {
8
8
  if (!requiresAttestation(apiKey))
9
- return true;
9
+ return { status: "skipped" };
10
10
  const { nonce, timestamp } = createNonce();
11
11
  try {
12
12
  if (react_native_1.Platform.OS === "android")
@@ -16,9 +16,9 @@ async function getApplicationAttestation(apiKey) {
16
16
  }
17
17
  catch (err) {
18
18
  const reason = err?.message ?? "attestation-error";
19
- return { attestationDisabled: true, reason };
19
+ return { status: "disabled", reason };
20
20
  }
21
- return { attestationDisabled: true, reason: "platform-unsupported" };
21
+ return { status: "disabled", reason: "platform-unsupported" };
22
22
  }
23
23
  function requiresAttestation(apiKey) {
24
24
  return apiKey.startsWith("pw_pub_");
@@ -43,14 +43,14 @@ function base64UrlEncode(input) {
43
43
  async function getAndroidSignature(nonce, timestamp) {
44
44
  const integrityToken = await (0, native_1.getAndroidIntegrityToken)(nonce);
45
45
  if (!integrityToken) {
46
- return { attestationDisabled: true, reason: "play-integrity-unavailable" };
46
+ return { status: "disabled", reason: "play-integrity-unavailable" };
47
47
  }
48
- return { nonce, timestamp, integrityToken };
48
+ return { status: "ok", platform: "android", nonce, timestamp, integrityToken };
49
49
  }
50
50
  async function getIosDeviceCheck(nonce, timestamp) {
51
51
  const deviceCheckToken = await (0, native_1.getDeviceCheckToken)(nonce);
52
52
  if (!deviceCheckToken) {
53
- return { attestationDisabled: true, reason: "devicecheck-unavailable" };
53
+ return { status: "disabled", reason: "devicecheck-unavailable" };
54
54
  }
55
- return { nonce, timestamp, deviceCheckToken };
55
+ return { status: "ok", platform: "ios", nonce, timestamp, deviceCheckToken };
56
56
  }
@@ -5,10 +5,11 @@ export interface RegisterPushWaveResponse {
5
5
  success: boolean;
6
6
  message?: string;
7
7
  }
8
- export interface RegisterPushWaveOptions {
8
+ export interface RegisterPushWaveDTO {
9
9
  apiKey: string;
10
10
  expoToken: string;
11
11
  platform: string;
12
12
  appAttestation?: any;
13
+ environment: "development" | "production";
13
14
  }
14
15
  export default function registerPushWave({ apiKey }: RegisterPushWaveClient): Promise<RegisterPushWaveResponse>;
@@ -25,20 +25,15 @@ async function registerPushWave({ apiKey }) {
25
25
  };
26
26
  }
27
27
  const appAttestation = await (0, getApplicationAttestation_1.default)(apiKey);
28
- if (!appAttestation) {
29
- const message = `could not get ${react_native_1.Platform.OS} attestation.`;
30
- pwLogger_1.PWLogger.error(message);
31
- return {
32
- success: false,
33
- message: `[PushWaveClient] Error: ` + message
34
- };
35
- }
28
+ if (appAttestation.status === "disabled")
29
+ pwLogger_1.PWLogger.warn(`(${react_native_1.Platform.OS}) could not get attestation: ${appAttestation.reason}`);
36
30
  const path = "/v1/expo-tokens";
37
31
  const options = {
38
32
  apiKey: apiKey,
39
33
  expoToken: expoToken,
40
34
  platform: react_native_1.Platform.OS,
41
- appAttestation: appAttestation
35
+ appAttestation: appAttestation,
36
+ environment: __DEV__ ? "development" : "production"
42
37
  };
43
38
  try {
44
39
  const res = await (0, fetch_1.fetchApi)(path, options);
@@ -4,6 +4,7 @@ exports.fetchApi = fetchApi;
4
4
  const BASE_URL = "https://pushwave.luruk-hai.fr";
5
5
  async function fetchApi(path, data) {
6
6
  const url = BASE_URL + path;
7
+ console.log(data);
7
8
  const res = await fetch(url, {
8
9
  method: "POST",
9
10
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pushwave-client",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "PushWave Client, Expo Push Notifications SaaS SDK",
5
5
  "homepage": "https://github.com/luruk-hai/pushwave-client#readme",
6
6
  "bugs": {
@@ -0,0 +1,30 @@
1
+ export interface AndroidAttestationPayload {
2
+ status: "ok";
3
+ platform: "android";
4
+ nonce: string;
5
+ timestamp: number;
6
+ integrityToken: string;
7
+ }
8
+
9
+ export interface IosAttestationPayload {
10
+ status: "ok";
11
+ platform: "ios";
12
+ nonce: string;
13
+ timestamp: number;
14
+ deviceCheckToken: string;
15
+ }
16
+
17
+ export interface DisabledAttestation {
18
+ status: "disabled";
19
+ reason: string;
20
+ }
21
+
22
+ export interface SkippedAttestation {
23
+ status: "skipped";
24
+ }
25
+
26
+ export type ApplicationAttestation =
27
+ | AndroidAttestationPayload
28
+ | IosAttestationPayload
29
+ | DisabledAttestation
30
+ | SkippedAttestation;
@@ -1,32 +1,10 @@
1
1
  import { Buffer } from "buffer";
2
2
  import { Platform } from "react-native";
3
3
  import { getAndroidIntegrityToken, getDeviceCheckToken } from "./native";
4
+ import { ApplicationAttestation, AndroidAttestationPayload, DisabledAttestation, IosAttestationPayload } from "./index";
4
5
 
5
- export interface AndroidAttestationPayload {
6
- nonce: string;
7
- timestamp: number;
8
- integrityToken: string;
9
- }
10
-
11
- export interface IosAttestationPayload {
12
- nonce: string;
13
- timestamp: number;
14
- deviceCheckToken: string;
15
- }
16
-
17
- export interface DisabledAttestation {
18
- attestationDisabled: true;
19
- reason: string;
20
- }
21
-
22
- export type ApplicationAttestation =
23
- | AndroidAttestationPayload
24
- | IosAttestationPayload
25
- | DisabledAttestation
26
- | true;
27
-
28
- export default async function getApplicationAttestation(apiKey: string): Promise<ApplicationAttestation> {
29
- if (!requiresAttestation(apiKey)) return true;
6
+ export async function getApplicationAttestation(apiKey: string): Promise<ApplicationAttestation> {
7
+ if (!requiresAttestation(apiKey)) return { status: "skipped" };
30
8
 
31
9
  const { nonce, timestamp } = createNonce();
32
10
 
@@ -35,10 +13,10 @@ export default async function getApplicationAttestation(apiKey: string): Promise
35
13
  if (Platform.OS === "ios") return await getIosDeviceCheck(nonce, timestamp);
36
14
  } catch (err) {
37
15
  const reason = (err as Error)?.message ?? "attestation-error";
38
- return { attestationDisabled: true, reason };
16
+ return { status: "disabled", reason };
39
17
  }
40
18
 
41
- return { attestationDisabled: true, reason: "platform-unsupported" };
19
+ return { status: "disabled", reason: "platform-unsupported" };
42
20
  }
43
21
 
44
22
  function requiresAttestation(apiKey: string) {
@@ -72,10 +50,10 @@ async function getAndroidSignature(
72
50
  const integrityToken = await getAndroidIntegrityToken(nonce);
73
51
 
74
52
  if (!integrityToken) {
75
- return { attestationDisabled: true, reason: "play-integrity-unavailable" };
53
+ return { status: "disabled", reason: "play-integrity-unavailable" };
76
54
  }
77
55
 
78
- return { nonce, timestamp, integrityToken };
56
+ return { status: "ok", platform: "android", nonce, timestamp, integrityToken };
79
57
  }
80
58
 
81
59
  async function getIosDeviceCheck(
@@ -85,8 +63,8 @@ async function getIosDeviceCheck(
85
63
  const deviceCheckToken = await getDeviceCheckToken(nonce);
86
64
 
87
65
  if (!deviceCheckToken) {
88
- return { attestationDisabled: true, reason: "devicecheck-unavailable" };
66
+ return { status: "disabled", reason: "devicecheck-unavailable" };
89
67
  }
90
68
 
91
- return { nonce, timestamp, deviceCheckToken };
69
+ return { status: "ok", platform: "ios", nonce, timestamp, deviceCheckToken };
92
70
  }
@@ -0,0 +1,2 @@
1
+ export * from "./attestation.types";
2
+ export * from "./getApplicationAttestation";
package/src/index.ts CHANGED
@@ -1,7 +1,4 @@
1
- import registerPushWave, {
2
- RegisterPushWaveClient,
3
- RegisterPushWaveResponse,
4
- } from "./registerPushWave";
1
+ import { registerPushWave, RegisterPushWaveClient, RegisterPushWaveResponse } from "./register";
5
2
 
6
3
  export interface PushWaveClientType {
7
4
  init(options: RegisterPushWaveClient): Promise<RegisterPushWaveResponse>;
@@ -0,0 +1,2 @@
1
+ export * from "./registerPushWave";
2
+ export * from "./registerPushWave.dto";
@@ -0,0 +1,16 @@
1
+ export interface RegisterPushWaveClient {
2
+ apiKey: string;
3
+ }
4
+
5
+ export interface RegisterPushWaveResponse {
6
+ success: boolean;
7
+ message?: string;
8
+ }
9
+
10
+ export interface RegisterPushWaveDTO {
11
+ apiKey: string;
12
+ expoToken: string;
13
+ platform: string;
14
+ appAttestation?: any;
15
+ environment: "development" | "production"
16
+ }
@@ -1,27 +1,12 @@
1
- import { fetchApi } from "./utils/fetch";
2
- import { getExpoToken } from "./utils/expoToken";
3
- import getApplicationAttestation from "./attestation/getApplicationAttestation";
1
+ import { fetchApi } from "../utils/fetch";
2
+ import { getExpoToken } from "../utils/expoToken";
4
3
  import { Platform } from "react-native";
5
- import { PWLogger } from "./utils/pwLogger";
6
- import { isSecretKey } from "./utils/apiKeyCheck";
4
+ import { PWLogger } from "../utils/pwLogger";
5
+ import { isSecretKey } from "../utils/apiKeyCheck";
6
+ import { RegisterPushWaveClient, RegisterPushWaveDTO, RegisterPushWaveResponse } from "./registerPushWave.dto";
7
+ import { getApplicationAttestation } from "../attestation/index";
7
8
 
8
- export interface RegisterPushWaveClient {
9
- apiKey: string;
10
- }
11
-
12
- export interface RegisterPushWaveResponse {
13
- success: boolean;
14
- message?: string;
15
- }
16
-
17
- export interface RegisterPushWaveOptions {
18
- apiKey: string;
19
- expoToken: string;
20
- platform: string;
21
- appAttestation?: any;
22
- }
23
-
24
- export default async function registerPushWave(
9
+ export async function registerPushWave(
25
10
  { apiKey }: RegisterPushWaveClient
26
11
  ): Promise<RegisterPushWaveResponse> {
27
12
 
@@ -46,25 +31,17 @@ export default async function registerPushWave(
46
31
 
47
32
  const appAttestation = await getApplicationAttestation(apiKey);
48
33
 
49
- if (!appAttestation) {
50
-
51
- const message = `could not get ${Platform.OS} attestation.`;
52
-
53
- PWLogger.error(message);
54
-
55
- return {
56
- success: false,
57
- message: `[PushWaveClient] Error: ` + message
58
- }
59
- }
34
+ if (appAttestation.status === "disabled")
35
+ PWLogger.warn(`(${Platform.OS}) could not get attestation: ${appAttestation.reason}`);
60
36
 
61
37
  const path = "/v1/expo-tokens"
62
38
 
63
- const options: RegisterPushWaveOptions = {
39
+ const options: RegisterPushWaveDTO = {
64
40
  apiKey: apiKey,
65
41
  expoToken: expoToken,
66
42
  platform: Platform.OS,
67
- appAttestation: appAttestation
43
+ appAttestation: appAttestation,
44
+ environment: __DEV__ ? "development" : "production"
68
45
  }
69
46
 
70
47
  try {
@@ -7,6 +7,8 @@ export async function fetchApi<TResponse>(
7
7
 
8
8
  const url = BASE_URL + path;
9
9
 
10
+ console.log(data);
11
+
10
12
  const res = await fetch(url, {
11
13
  method: "POST",
12
14
  headers: {
File without changes