pushwave-client 0.2.4 → 0.2.6

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,107 @@
1
+ # pushwave-client (alpha)
2
+
3
+ PushWave is a lightweight Expo-first SDK to get push notifications running without building your own backend or wrestling with native setup. It fetches the Expo push token and prepares app attestation (Android Play Integrity / iOS DeviceCheck) so you can secure delivery. The SaaS dashboard (scheduling, targeting, templates, cron-like sends) is coming soon. **Consider this an early-stage project**.
4
+
5
+ ---
6
+
7
+ ## Why PushWave?
8
+
9
+ - **No backend needed**: token storage, targeting logic, scheduling, and sending are all handled by PushWave’s cloud. Forget cron jobs and custom endpoints.
10
+ - **Expo-first design**: auto-linking, config plugin, no manual Gradle/Pod edits. Works seamlessly with EAS, dev clients, and Expo Router.
11
+ - **Native attestation (roadmap)**: Play Integrity on Android + DeviceCheck on iOS to reduce spoofed APKs, fake tokens, and leaked API keys.
12
+ - **One-line setup**: `PushWaveClient.init({ apiKey })` retrieves the Expo token, performs attestation when required, and logs enhanced debug info under `__DEV__`.
13
+ - **Dashboard-first workflow (roadmap)**: audiences, groups, segments, templates, one-off or recurring pushes without touching Firebase or APNs directly.
14
+ - **Minimal external config**: for Android you still upload your FCM credentials to Expo (required by the platform), but PushWave handles the rest.
15
+
16
+ ---
17
+
18
+ ## Quick install
19
+
20
+ 1) Install the [SDK](https://www.npmjs.com/package/pushwave-client) and [Expo notifications](https://docs.expo.dev/versions/latest/sdk/notifications/) (peer requirement):
21
+ ```bash
22
+ npm install pushwave-client
23
+ npx expo install expo-notifications
24
+ ```
25
+
26
+ 2) Add the config plugin to your app.json / app.config.*:
27
+ ```json
28
+ {
29
+ "expo": {
30
+ "plugins": ["pushwave-client"]
31
+ }
32
+ }
33
+ ```
34
+
35
+ 3) Build native (EAS or dev client). Expo Go is not supported once native code is involved:
36
+ ```bash
37
+ eas build -p android --profile development
38
+ eas build -p ios --profile development
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Minimal usage
44
+
45
+ ```tsx
46
+ import { useEffect } from "react";
47
+ import { Alert } from "react-native";
48
+ import PushWaveClient from "pushwave-client";
49
+
50
+ export default function App() {
51
+ useEffect(() => {
52
+ (async () => {
53
+ const res = await PushWaveClient.init({ apiKey: "pw_dev_xxx" });
54
+ if (!res.success) {
55
+ Alert.alert("PushWave", res.message ?? "Init failed");
56
+ }
57
+ })();
58
+ }, []);
59
+
60
+ return /* …your UI… */;
61
+ }
62
+ ```
63
+
64
+ - `PushWaveClient.init` is async and returns `{ success: boolean; message?: string }`.
65
+ - Call it **once** at app startup (e.g., in `App.tsx` or a root component `useEffect`). Recalling it later is unnecessary.
66
+ - In `__DEV__`, the SDK may log additional info/errors (e.g., failed API calls) to help debugging.
67
+
68
+ ---
69
+
70
+ ## Notifications (expo-notifications)
71
+
72
+ - 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.
73
+ - On iOS, user permission is required to obtain a push token.
74
+ - On Android, Expo handles permission/channel setup; if the user refuses, no push is delivered.
75
+ - You still need to provide FCM credentials to Expo for Android push (standard Expo requirement).
76
+
77
+ ---
78
+
79
+ ## Attestation (current status)
80
+
81
+ - Backend validation is not live yet; it will arrive with the PushWave SaaS.
82
+ - 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.
83
+ - iOS (DeviceCheck): will require a real build (dev client or TestFlight), not Expo Go.
84
+ - For now, consider attestation non-blocking (the SDK may return a `disabled` flag until the SaaS is active).
85
+
86
+ ---
87
+
88
+ ## Compatibility
89
+
90
+ - Tested on the latest Expo SDK (54). No guarantees on earlier versions.
91
+ - Requires a native build (EAS or dev client).
92
+
93
+ ---
94
+
95
+ ## Links
96
+
97
+ - NPM: https://www.npmjs.com/package/pushwave-client
98
+ - GitHub: https://github.com/luruk-hai/pushwave-client#readme
99
+
100
+ ---
101
+
102
+ ## Roadmap (with SaaS)
103
+
104
+ - Server-side validation of Play Integrity / DeviceCheck tokens.
105
+ - Full attestation docs and Play Store setup (enable Integrity API, internal track).
106
+ - Dashboard for targeting, templates, scheduling (one-off and cron-like).
107
+ - Complete registration flow with the PushWave backend.
@@ -0,0 +1,22 @@
1
+ export interface AndroidAttestationPayload {
2
+ status: "ok";
3
+ platform: "android";
4
+ nonce: string;
5
+ timestamp: number;
6
+ integrityToken: string;
7
+ }
8
+ export interface IosAttestationPayload {
9
+ status: "ok";
10
+ platform: "ios";
11
+ nonce: string;
12
+ timestamp: number;
13
+ deviceCheckToken: string;
14
+ }
15
+ export interface DisabledAttestation {
16
+ status: "disabled";
17
+ reason: string;
18
+ }
19
+ export interface SkippedAttestation {
20
+ status: "skipped";
21
+ }
22
+ export type ApplicationAttestation = AndroidAttestationPayload | IosAttestationPayload | DisabledAttestation | SkippedAttestation;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,23 +1,2 @@
1
- export interface AndroidAttestationPayload {
2
- status: "ok";
3
- platform: "android";
4
- nonce: string;
5
- timestamp: number;
6
- integrityToken: string;
7
- }
8
- export interface IosAttestationPayload {
9
- status: "ok";
10
- platform: "ios";
11
- nonce: string;
12
- timestamp: number;
13
- deviceCheckToken: string;
14
- }
15
- export interface DisabledAttestation {
16
- status: "disabled";
17
- reason: string;
18
- }
19
- export interface SkippedAttestation {
20
- status: "skipped";
21
- }
22
- export type ApplicationAttestation = AndroidAttestationPayload | IosAttestationPayload | DisabledAttestation | SkippedAttestation;
23
- export default function getApplicationAttestation(apiKey: string): Promise<ApplicationAttestation>;
1
+ import { ApplicationAttestation } from "./index";
2
+ export declare function getApplicationAttestation(apiKey: string): Promise<ApplicationAttestation>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = getApplicationAttestation;
3
+ exports.getApplicationAttestation = getApplicationAttestation;
4
4
  const buffer_1 = require("buffer");
5
5
  const react_native_1 = require("react-native");
6
6
  const native_1 = require("./native");
@@ -0,0 +1,2 @@
1
+ export * from "./attestation.types";
2
+ export * from "./getApplicationAttestation";
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./attestation.types"), exports);
18
+ __exportStar(require("./getApplicationAttestation"), exports);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RegisterPushWaveClient, RegisterPushWaveResponse } from "./registerPushWave";
1
+ import { RegisterPushWaveClient, RegisterPushWaveResponse } from "./register";
2
2
  export interface PushWaveClientType {
3
3
  init(options: RegisterPushWaveClient): Promise<RegisterPushWaveResponse>;
4
4
  }
package/dist/index.js CHANGED
@@ -1,12 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const registerPushWave_1 = __importDefault(require("./registerPushWave"));
3
+ const register_1 = require("./register");
7
4
  const PushWaveClient = {
8
5
  init(options) {
9
- return (0, registerPushWave_1.default)(options);
6
+ return (0, register_1.registerPushWave)(options);
10
7
  },
11
8
  };
12
9
  exports.default = PushWaveClient;
@@ -0,0 +1,2 @@
1
+ export * from "./registerPushWave";
2
+ export * from "./registerPushWave.dto";
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./registerPushWave"), exports);
18
+ __exportStar(require("./registerPushWave.dto"), exports);
@@ -0,0 +1,2 @@
1
+ import { RegisterPushWaveClient, RegisterPushWaveResponse } from "./registerPushWave.dto";
2
+ export declare function registerPushWave({ apiKey }: RegisterPushWaveClient): Promise<RegisterPushWaveResponse>;
@@ -0,0 +1,14 @@
1
+ export interface RegisterPushWaveClient {
2
+ apiKey: string;
3
+ }
4
+ export interface RegisterPushWaveResponse {
5
+ success: boolean;
6
+ message?: string;
7
+ }
8
+ export interface RegisterPushWaveDTO {
9
+ apiKey: string;
10
+ expoToken: string;
11
+ platform: string;
12
+ appAttestation?: any;
13
+ environment: "development" | "production";
14
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerPushWave = registerPushWave;
4
+ const fetch_1 = require("../utils/fetch");
5
+ const expoToken_1 = require("../utils/expoToken");
6
+ const react_native_1 = require("react-native");
7
+ const pwLogger_1 = require("../utils/pwLogger");
8
+ const apiKeyCheck_1 = require("../utils/apiKeyCheck");
9
+ const index_1 = require("../attestation/index");
10
+ async function registerPushWave({ apiKey }) {
11
+ if ((0, apiKeyCheck_1.isSecretKey)(apiKey)) {
12
+ const warn = `\x1b[0m You are using your SECRET API key in a client environment. This key must NEVER be embedded in a mobile app.`;
13
+ pwLogger_1.PWLogger.warn(warn);
14
+ }
15
+ const expoToken = await (0, expoToken_1.getExpoToken)();
16
+ if (!expoToken) {
17
+ const message = "could not get ExpoToken";
18
+ pwLogger_1.PWLogger.error(message);
19
+ return {
20
+ success: false,
21
+ message: "[PushWaveClient] Error: " + message
22
+ };
23
+ }
24
+ const appAttestation = await (0, index_1.getApplicationAttestation)(apiKey);
25
+ if (appAttestation.status === "disabled")
26
+ pwLogger_1.PWLogger.warn(`(${react_native_1.Platform.OS}) could not get attestation: ${appAttestation.reason}`);
27
+ const path = "/v1/expo-tokens";
28
+ const options = {
29
+ apiKey: apiKey,
30
+ expoToken: expoToken,
31
+ platform: react_native_1.Platform.OS,
32
+ appAttestation: appAttestation,
33
+ environment: __DEV__ ? "development" : "production"
34
+ };
35
+ try {
36
+ const res = await (0, fetch_1.fetchApi)(path, options);
37
+ return {
38
+ success: true,
39
+ message: res.message,
40
+ };
41
+ }
42
+ catch (err) {
43
+ const e = err;
44
+ pwLogger_1.PWLogger.error(e.message);
45
+ return {
46
+ success: false,
47
+ message: e.message,
48
+ };
49
+ }
50
+ }
@@ -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>;
@@ -32,7 +32,8 @@ async function registerPushWave({ apiKey }) {
32
32
  apiKey: apiKey,
33
33
  expoToken: expoToken,
34
34
  platform: react_native_1.Platform.OS,
35
- appAttestation: appAttestation
35
+ appAttestation: appAttestation,
36
+ environment: __DEV__ ? "development" : "production"
36
37
  };
37
38
  try {
38
39
  const res = await (0, fetch_1.fetchApi)(path, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pushwave-client",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
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,39 +1,9 @@
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
- status: "ok";
7
- platform: "android";
8
- nonce: string;
9
- timestamp: number;
10
- integrityToken: string;
11
- }
12
-
13
- export interface IosAttestationPayload {
14
- status: "ok";
15
- platform: "ios";
16
- nonce: string;
17
- timestamp: number;
18
- deviceCheckToken: string;
19
- }
20
-
21
- export interface DisabledAttestation {
22
- status: "disabled";
23
- reason: string;
24
- }
25
-
26
- export interface SkippedAttestation {
27
- status: "skipped";
28
- }
29
-
30
- export type ApplicationAttestation =
31
- | AndroidAttestationPayload
32
- | IosAttestationPayload
33
- | DisabledAttestation
34
- | SkippedAttestation;
35
-
36
- export default async function getApplicationAttestation(apiKey: string): Promise<ApplicationAttestation> {
6
+ export async function getApplicationAttestation(apiKey: string): Promise<ApplicationAttestation> {
37
7
  if (!requiresAttestation(apiKey)) return { status: "skipped" };
38
8
 
39
9
  const { nonce, timestamp } = createNonce();
@@ -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,28 +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
- environment: "development" | "production"
23
- }
24
-
25
- export default async function registerPushWave(
9
+ export async function registerPushWave(
26
10
  { apiKey }: RegisterPushWaveClient
27
11
  ): Promise<RegisterPushWaveResponse> {
28
12
 
@@ -52,7 +36,7 @@ export default async function registerPushWave(
52
36
 
53
37
  const path = "/v1/expo-tokens"
54
38
 
55
- const options: RegisterPushWaveOptions = {
39
+ const options: RegisterPushWaveDTO = {
56
40
  apiKey: apiKey,
57
41
  expoToken: expoToken,
58
42
  platform: Platform.OS,
@@ -33,4 +33,4 @@ export async function fetchApi<TResponse>(
33
33
  }
34
34
 
35
35
  return json as TResponse;
36
- }
36
+ }
File without changes