pushwave-client 0.3.4 → 0.3.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 +11 -1
- package/dist/register/registerPushWave.dto.d.ts +8 -0
- package/dist/register/registerPushWave.js +16 -3
- package/dist/utils/collectDeviceMetaData.d.ts +10 -0
- package/dist/utils/collectDeviceMetaData.js +75 -0
- package/dist/utils/fetch.d.ts +8 -2
- package/dist/utils/fetch.js +12 -28
- package/dist/utils/installationId.d.ts +1 -0
- package/dist/utils/installationId.js +72 -0
- package/dist/utils/pushwaveSettings.js +1 -1
- package/package.json +20 -2
- package/src/register/registerPushWave.dto.ts +9 -1
- package/src/register/registerPushWave.ts +17 -3
- package/src/utils/collectDeviceMetaData.ts +111 -0
- package/src/utils/fetch.ts +23 -43
- package/src/utils/installationId.ts +82 -0
- package/src/utils/pushwaveSettings.ts +2 -3
- package/dist/attestation/getAndroidSignature.d.ts +0 -0
- package/dist/attestation/getAndroidSignature.js +0 -1
- package/dist/attestation/getApplicationSignature.d.ts +0 -1
- package/dist/attestation/getApplicationSignature.js +0 -9
- package/dist/attestation/getIosDeviceCheck.d.ts +0 -1
- package/dist/attestation/getIosDeviceCheck.js +0 -2
- package/dist/registerPushWave.d.ts +0 -15
- package/dist/registerPushWave.js +0 -53
- package/dist/utils/validation.d.ts +0 -0
- package/dist/utils/validation.js +0 -1
package/README.md
CHANGED
|
@@ -24,6 +24,16 @@ npm install pushwave-client
|
|
|
24
24
|
npx expo install expo-notifications
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
Optional (recommended): install [expo-secure-store](https://docs.expo.dev/versions/latest/sdk/securestore/) to persist the SDK’s installationId across app restarts:
|
|
28
|
+
```bash
|
|
29
|
+
npx expo install expo-secure-store
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Optional (for richer device metadata: version/build, model, locale/timezone): install:
|
|
33
|
+
```bash
|
|
34
|
+
npx expo install expo-application expo-device expo-localization
|
|
35
|
+
```
|
|
36
|
+
|
|
27
37
|
2) Add the config plugin to your app.json / app.config.*:
|
|
28
38
|
```json
|
|
29
39
|
{
|
|
@@ -109,4 +119,4 @@ export default function App() {
|
|
|
109
119
|
|
|
110
120
|
---
|
|
111
121
|
|
|
112
|
-
*Roadmap is no longer tracked in this README; check the dashboard or docs for current feature status.*
|
|
122
|
+
*Roadmap is no longer tracked in this README; check the dashboard or docs for current feature status.*
|
|
@@ -11,4 +11,12 @@ export interface RegisterPushWaveDTO {
|
|
|
11
11
|
platform: string;
|
|
12
12
|
appAttestation?: any;
|
|
13
13
|
environment: "development" | "production";
|
|
14
|
+
appVersion: string;
|
|
15
|
+
buildNumber: string;
|
|
16
|
+
installationId: string;
|
|
17
|
+
osVersion: string;
|
|
18
|
+
deviceModel: string;
|
|
19
|
+
locale: string;
|
|
20
|
+
timezone: string;
|
|
21
|
+
countryCode: string;
|
|
14
22
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerPushWave = registerPushWave;
|
|
4
|
-
const fetch_1 = require("../utils/fetch");
|
|
5
4
|
const expoToken_1 = require("../utils/expoToken");
|
|
6
5
|
const react_native_1 = require("react-native");
|
|
7
6
|
const pwLogger_1 = require("../utils/pwLogger");
|
|
8
7
|
const apiKeyCheck_1 = require("../utils/apiKeyCheck");
|
|
9
8
|
const index_1 = require("../attestation/index");
|
|
9
|
+
const fetch_1 = require("../utils/fetch");
|
|
10
|
+
const installationId_1 = require("../utils/installationId");
|
|
11
|
+
const collectDeviceMetaData_1 = require("../utils/collectDeviceMetaData");
|
|
10
12
|
async function registerPushWave({ apiKey }) {
|
|
11
13
|
const OS = react_native_1.Platform.OS;
|
|
12
14
|
if ((0, apiKeyCheck_1.isSecretKey)(apiKey)) {
|
|
@@ -26,15 +28,26 @@ async function registerPushWave({ apiKey }) {
|
|
|
26
28
|
if (appAttestation.status === "disabled")
|
|
27
29
|
pwLogger_1.PWLogger.warn(`(${react_native_1.Platform.OS}) could not get attestation: ${appAttestation.reason}`);
|
|
28
30
|
const path = "expo-tokens";
|
|
31
|
+
const installationId = await (0, installationId_1.getInstallationId)();
|
|
32
|
+
const { appVersion, buildNumber, countryCode, deviceModel, locale, osVersion, timezone } = (0, collectDeviceMetaData_1.collectDeviceMetaData)();
|
|
29
33
|
const options = {
|
|
30
34
|
apiKey: apiKey,
|
|
31
35
|
expoToken: expoToken,
|
|
32
36
|
platform: OS,
|
|
33
37
|
appAttestation: appAttestation,
|
|
34
|
-
environment: __DEV__ ? "development" : "production"
|
|
38
|
+
environment: __DEV__ ? "development" : "production",
|
|
39
|
+
installationId,
|
|
40
|
+
appVersion,
|
|
41
|
+
buildNumber,
|
|
42
|
+
countryCode,
|
|
43
|
+
deviceModel,
|
|
44
|
+
locale,
|
|
45
|
+
osVersion,
|
|
46
|
+
timezone
|
|
35
47
|
};
|
|
48
|
+
console.log(appVersion, buildNumber, countryCode, deviceModel, locale, osVersion, timezone);
|
|
36
49
|
try {
|
|
37
|
-
const res = await (0, fetch_1.
|
|
50
|
+
const res = await (0, fetch_1.fetchApi)("PUT", path, { data: options });
|
|
38
51
|
return {
|
|
39
52
|
success: true,
|
|
40
53
|
message: res.message,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.collectDeviceMetaData = void 0;
|
|
7
|
+
const react_native_1 = require("react-native");
|
|
8
|
+
const expo_constants_1 = __importDefault(require("expo-constants"));
|
|
9
|
+
const loadApplication = () => {
|
|
10
|
+
try {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
12
|
+
return require("expo-application");
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const loadDevice = () => {
|
|
19
|
+
try {
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
21
|
+
return require("expo-device");
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const loadLocalization = () => {
|
|
28
|
+
try {
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
30
|
+
return require("expo-localization");
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const collectDeviceMetaData = () => {
|
|
37
|
+
const application = loadApplication();
|
|
38
|
+
const device = loadDevice();
|
|
39
|
+
const localization = loadLocalization();
|
|
40
|
+
const appVersion = application?.nativeApplicationVersion ??
|
|
41
|
+
(expo_constants_1.default.expoConfig?.version ?? "");
|
|
42
|
+
const buildNumber = application?.nativeBuildVersion ??
|
|
43
|
+
(expo_constants_1.default.expoConfig?.ios?.buildNumber ??
|
|
44
|
+
(expo_constants_1.default.expoConfig?.android?.versionCode
|
|
45
|
+
? String(expo_constants_1.default.expoConfig?.android?.versionCode)
|
|
46
|
+
: ""));
|
|
47
|
+
const deviceModel = device?.modelName ?? (expo_constants_1.default.deviceName ?? "");
|
|
48
|
+
const osVersion = device?.osVersion ?? (react_native_1.Platform.Version ? String(react_native_1.Platform.Version) : "");
|
|
49
|
+
const locales = localization?.getLocales?.() ?? [];
|
|
50
|
+
const primaryLocale = locales[0];
|
|
51
|
+
const locale = primaryLocale?.languageTag ??
|
|
52
|
+
(typeof Intl !== "undefined"
|
|
53
|
+
? Intl.DateTimeFormat().resolvedOptions().locale
|
|
54
|
+
: "");
|
|
55
|
+
const countryCode = primaryLocale?.regionCode ??
|
|
56
|
+
(() => {
|
|
57
|
+
const parts = locale?.split?.("-") ?? [];
|
|
58
|
+
return parts.length > 1 ? parts[parts.length - 1].toUpperCase() : "";
|
|
59
|
+
})();
|
|
60
|
+
const calendars = localization?.getCalendars?.() ?? [];
|
|
61
|
+
const timezone = calendars[0]?.timeZone ??
|
|
62
|
+
(typeof Intl !== "undefined"
|
|
63
|
+
? Intl.DateTimeFormat().resolvedOptions().timeZone ?? ""
|
|
64
|
+
: "");
|
|
65
|
+
return {
|
|
66
|
+
appVersion: appVersion ?? "",
|
|
67
|
+
buildNumber: buildNumber ?? "",
|
|
68
|
+
countryCode: countryCode ?? "",
|
|
69
|
+
deviceModel: deviceModel ?? "",
|
|
70
|
+
locale: locale ?? "",
|
|
71
|
+
osVersion: osVersion ?? "",
|
|
72
|
+
timezone: timezone ?? ""
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
exports.collectDeviceMetaData = collectDeviceMetaData;
|
package/dist/utils/fetch.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type FetchMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
2
|
+
type FetchParams = Record<string, string | number | boolean | undefined>;
|
|
3
|
+
type FetchData = Record<string, any>;
|
|
4
|
+
export declare function fetchApi<TResponse>(method: FetchMethod, path: string, { params, data }?: {
|
|
5
|
+
params?: FetchParams;
|
|
6
|
+
data?: FetchData;
|
|
7
|
+
}): Promise<TResponse>;
|
|
8
|
+
export {};
|
package/dist/utils/fetch.js
CHANGED
|
@@ -1,33 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.fetchApiGet = fetchApiGet;
|
|
3
|
+
exports.fetchApi = fetchApi;
|
|
5
4
|
const BASE_URL = "https://api.pushwave.dev/v1/public/";
|
|
6
|
-
async function
|
|
7
|
-
const url = BASE_URL + path;
|
|
8
|
-
const res = await fetch(url, {
|
|
9
|
-
method: "POST",
|
|
10
|
-
headers: {
|
|
11
|
-
"Content-Type": "application/json",
|
|
12
|
-
},
|
|
13
|
-
body: JSON.stringify(data),
|
|
14
|
-
});
|
|
15
|
-
const text = await res.text();
|
|
16
|
-
let json = null;
|
|
17
|
-
try {
|
|
18
|
-
json = text ? JSON.parse(text) : null;
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
// JSON empty/invalid
|
|
22
|
-
}
|
|
23
|
-
if (!res.ok) {
|
|
24
|
-
const apiError = (json?.error ?? json?.message ?? "");
|
|
25
|
-
const message = `(${res.status}) ${apiError}`.trim();
|
|
26
|
-
throw new Error(message);
|
|
27
|
-
}
|
|
28
|
-
return json;
|
|
29
|
-
}
|
|
30
|
-
async function fetchApiGet(path, params) {
|
|
5
|
+
async function fetchApi(method, path, { params, data } = {}) {
|
|
31
6
|
const search = params
|
|
32
7
|
? new URLSearchParams(Object.entries(params).reduce((acc, [key, value]) => {
|
|
33
8
|
if (value === undefined)
|
|
@@ -37,7 +12,16 @@ async function fetchApiGet(path, params) {
|
|
|
37
12
|
}, {})).toString()
|
|
38
13
|
: "";
|
|
39
14
|
const url = BASE_URL + path + (search ? `?${search}` : "");
|
|
40
|
-
const
|
|
15
|
+
const headers = {};
|
|
16
|
+
const body = data !== undefined && method !== "GET" ? JSON.stringify(data) : undefined;
|
|
17
|
+
if (body) {
|
|
18
|
+
headers["Content-Type"] = "application/json";
|
|
19
|
+
}
|
|
20
|
+
const res = await fetch(url, {
|
|
21
|
+
method,
|
|
22
|
+
headers,
|
|
23
|
+
body,
|
|
24
|
+
});
|
|
41
25
|
const text = await res.text();
|
|
42
26
|
let json = null;
|
|
43
27
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getInstallationId: () => Promise<string>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getInstallationId = void 0;
|
|
4
|
+
const pwLogger_1 = require("./pwLogger");
|
|
5
|
+
const STORAGE_KEY = "pushwave-installation-id";
|
|
6
|
+
let cachedId = null;
|
|
7
|
+
let warnedMissingSecureStore = false;
|
|
8
|
+
const generateId = () => {
|
|
9
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
10
|
+
return crypto.randomUUID();
|
|
11
|
+
}
|
|
12
|
+
// Fallback UUID-ish generator
|
|
13
|
+
const bytes = new Uint8Array(16);
|
|
14
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
15
|
+
crypto.getRandomValues(bytes);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
19
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
23
|
+
return [
|
|
24
|
+
hex.slice(0, 8),
|
|
25
|
+
hex.slice(8, 12),
|
|
26
|
+
hex.slice(12, 16),
|
|
27
|
+
hex.slice(16, 20),
|
|
28
|
+
hex.slice(20)
|
|
29
|
+
].join("-");
|
|
30
|
+
};
|
|
31
|
+
const loadSecureStore = () => {
|
|
32
|
+
try {
|
|
33
|
+
return require("expo-secure-store");
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const getInstallationId = async () => {
|
|
40
|
+
if (cachedId)
|
|
41
|
+
return cachedId;
|
|
42
|
+
const SecureStore = loadSecureStore();
|
|
43
|
+
if (SecureStore) {
|
|
44
|
+
try {
|
|
45
|
+
const existing = await SecureStore.getItemAsync(STORAGE_KEY);
|
|
46
|
+
if (existing) {
|
|
47
|
+
cachedId = existing;
|
|
48
|
+
return existing;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
pwLogger_1.PWLogger.warn("Failed reading installationId from SecureStore:", err);
|
|
53
|
+
}
|
|
54
|
+
const id = generateId();
|
|
55
|
+
try {
|
|
56
|
+
await SecureStore.setItemAsync(STORAGE_KEY, id);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
pwLogger_1.PWLogger.warn("Failed persisting installationId to SecureStore:", err);
|
|
60
|
+
}
|
|
61
|
+
cachedId = id;
|
|
62
|
+
return id;
|
|
63
|
+
}
|
|
64
|
+
if (!warnedMissingSecureStore) {
|
|
65
|
+
warnedMissingSecureStore = true;
|
|
66
|
+
pwLogger_1.PWLogger.warn("expo-secure-store not installed; installationId will not persist across app restarts. Install expo-secure-store for better targeting.");
|
|
67
|
+
}
|
|
68
|
+
// Non-persistent fallback
|
|
69
|
+
cachedId = generateId();
|
|
70
|
+
return cachedId;
|
|
71
|
+
};
|
|
72
|
+
exports.getInstallationId = getInstallationId;
|
|
@@ -6,7 +6,7 @@ const fetch_1 = require("./fetch");
|
|
|
6
6
|
const pwLogger_1 = require("./pwLogger");
|
|
7
7
|
const pushwaveSettingsPromise = (async () => {
|
|
8
8
|
try {
|
|
9
|
-
return await (0, fetch_1.
|
|
9
|
+
return await (0, fetch_1.fetchApi)("GET", "pushwave-config", { params: { platform: react_native_1.Platform.OS } });
|
|
10
10
|
}
|
|
11
11
|
catch (e) {
|
|
12
12
|
// log si besoin
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pushwave-client",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -25,7 +25,25 @@
|
|
|
25
25
|
"expo-constants": "*",
|
|
26
26
|
"expo-notifications": "*",
|
|
27
27
|
"react-native": "*",
|
|
28
|
-
"expo": "*"
|
|
28
|
+
"expo": "*",
|
|
29
|
+
"expo-secure-store": "*",
|
|
30
|
+
"expo-application": "*",
|
|
31
|
+
"expo-device": "*",
|
|
32
|
+
"expo-localization": "*"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"expo-secure-store": {
|
|
36
|
+
"optional": true
|
|
37
|
+
},
|
|
38
|
+
"expo-application": {
|
|
39
|
+
"optional": true
|
|
40
|
+
},
|
|
41
|
+
"expo-device": {
|
|
42
|
+
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"expo-localization": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
29
47
|
},
|
|
30
48
|
"dependencies": {
|
|
31
49
|
"cross-fetch": "^4.1.0",
|
|
@@ -12,5 +12,13 @@ export interface RegisterPushWaveDTO {
|
|
|
12
12
|
expoToken: string;
|
|
13
13
|
platform: string;
|
|
14
14
|
appAttestation?: any;
|
|
15
|
-
environment: "development" | "production"
|
|
15
|
+
environment: "development" | "production",
|
|
16
|
+
appVersion: string;
|
|
17
|
+
buildNumber: string;
|
|
18
|
+
installationId: string;
|
|
19
|
+
osVersion: string;
|
|
20
|
+
deviceModel: string;
|
|
21
|
+
locale: string;
|
|
22
|
+
timezone: string;
|
|
23
|
+
countryCode: string;
|
|
16
24
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { fetchApiPost } from "../utils/fetch";
|
|
2
1
|
import { getExpoToken } from "../utils/expoToken";
|
|
3
2
|
import { Platform } from "react-native";
|
|
4
3
|
import { PWLogger } from "../utils/pwLogger";
|
|
5
4
|
import { isSecretKey } from "../utils/apiKeyCheck";
|
|
6
5
|
import { RegisterPushWaveClient, RegisterPushWaveDTO, RegisterPushWaveResponse } from "./registerPushWave.dto";
|
|
7
6
|
import { getApplicationAttestation } from "../attestation/index";
|
|
7
|
+
import { fetchApi } from "../utils/fetch";
|
|
8
|
+
import { getInstallationId } from "../utils/installationId";
|
|
9
|
+
import { collectDeviceMetaData } from "../utils/collectDeviceMetaData";
|
|
8
10
|
|
|
9
11
|
export async function registerPushWave(
|
|
10
12
|
{ apiKey }: RegisterPushWaveClient
|
|
@@ -37,16 +39,28 @@ export async function registerPushWave(
|
|
|
37
39
|
|
|
38
40
|
const path = "expo-tokens"
|
|
39
41
|
|
|
42
|
+
const installationId = await getInstallationId();
|
|
43
|
+
|
|
44
|
+
const { appVersion, buildNumber, countryCode, deviceModel, locale, osVersion, timezone } = collectDeviceMetaData();
|
|
45
|
+
|
|
40
46
|
const options: RegisterPushWaveDTO = {
|
|
41
47
|
apiKey: apiKey,
|
|
42
48
|
expoToken: expoToken,
|
|
43
49
|
platform: OS,
|
|
44
50
|
appAttestation: appAttestation,
|
|
45
|
-
environment: __DEV__ ? "development" : "production"
|
|
51
|
+
environment: __DEV__ ? "development" : "production",
|
|
52
|
+
installationId,
|
|
53
|
+
appVersion,
|
|
54
|
+
buildNumber,
|
|
55
|
+
countryCode,
|
|
56
|
+
deviceModel,
|
|
57
|
+
locale,
|
|
58
|
+
osVersion,
|
|
59
|
+
timezone
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
try {
|
|
49
|
-
const res: RegisterPushWaveResponse = await
|
|
63
|
+
const res: RegisterPushWaveResponse = await fetchApi("PUT", path, { data: options })
|
|
50
64
|
|
|
51
65
|
return {
|
|
52
66
|
success: true,
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
import Constants from "expo-constants";
|
|
3
|
+
|
|
4
|
+
export type DeviceMetaData = {
|
|
5
|
+
appVersion: string;
|
|
6
|
+
buildNumber: string;
|
|
7
|
+
countryCode: string;
|
|
8
|
+
deviceModel: string;
|
|
9
|
+
locale: string;
|
|
10
|
+
osVersion: string;
|
|
11
|
+
timezone: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type ApplicationModule = {
|
|
15
|
+
nativeApplicationVersion?: string | null;
|
|
16
|
+
nativeBuildVersion?: string | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type DeviceModule = {
|
|
20
|
+
modelName?: string | null;
|
|
21
|
+
osVersion?: string | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type LocalizationModule = {
|
|
25
|
+
getLocales?: () => Array<{
|
|
26
|
+
languageTag?: string;
|
|
27
|
+
regionCode?: string;
|
|
28
|
+
}>;
|
|
29
|
+
getCalendars?: () => Array<{
|
|
30
|
+
timeZone?: string;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const loadApplication = (): ApplicationModule | null => {
|
|
35
|
+
try {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
37
|
+
return require("expo-application") as ApplicationModule;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const loadDevice = (): DeviceModule | null => {
|
|
44
|
+
try {
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
46
|
+
return require("expo-device") as DeviceModule;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const loadLocalization = (): LocalizationModule | null => {
|
|
53
|
+
try {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
55
|
+
return require("expo-localization") as LocalizationModule;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const collectDeviceMetaData = (): DeviceMetaData => {
|
|
62
|
+
const application = loadApplication();
|
|
63
|
+
const device = loadDevice();
|
|
64
|
+
const localization = loadLocalization();
|
|
65
|
+
|
|
66
|
+
const appVersion =
|
|
67
|
+
application?.nativeApplicationVersion ??
|
|
68
|
+
(Constants.expoConfig?.version ?? "");
|
|
69
|
+
|
|
70
|
+
const buildNumber =
|
|
71
|
+
application?.nativeBuildVersion ??
|
|
72
|
+
(Constants.expoConfig?.ios?.buildNumber ??
|
|
73
|
+
(Constants.expoConfig?.android?.versionCode
|
|
74
|
+
? String(Constants.expoConfig?.android?.versionCode)
|
|
75
|
+
: ""));
|
|
76
|
+
|
|
77
|
+
const deviceModel = device?.modelName ?? (Constants.deviceName ?? "");
|
|
78
|
+
const osVersion = device?.osVersion ?? (Platform.Version ? String(Platform.Version) : "");
|
|
79
|
+
|
|
80
|
+
const locales = localization?.getLocales?.() ?? [];
|
|
81
|
+
const primaryLocale = locales[0];
|
|
82
|
+
const locale =
|
|
83
|
+
primaryLocale?.languageTag ??
|
|
84
|
+
(typeof Intl !== "undefined"
|
|
85
|
+
? Intl.DateTimeFormat().resolvedOptions().locale
|
|
86
|
+
: "");
|
|
87
|
+
|
|
88
|
+
const countryCode =
|
|
89
|
+
primaryLocale?.regionCode ??
|
|
90
|
+
(() => {
|
|
91
|
+
const parts = locale?.split?.("-") ?? [];
|
|
92
|
+
return parts.length > 1 ? parts[parts.length - 1].toUpperCase() : "";
|
|
93
|
+
})();
|
|
94
|
+
|
|
95
|
+
const calendars = localization?.getCalendars?.() ?? [];
|
|
96
|
+
const timezone =
|
|
97
|
+
calendars[0]?.timeZone ??
|
|
98
|
+
(typeof Intl !== "undefined"
|
|
99
|
+
? Intl.DateTimeFormat().resolvedOptions().timeZone ?? ""
|
|
100
|
+
: "");
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
appVersion: appVersion ?? "",
|
|
104
|
+
buildNumber: buildNumber ?? "",
|
|
105
|
+
countryCode: countryCode ?? "",
|
|
106
|
+
deviceModel: deviceModel ?? "",
|
|
107
|
+
locale: locale ?? "",
|
|
108
|
+
osVersion: osVersion ?? "",
|
|
109
|
+
timezone: timezone ?? ""
|
|
110
|
+
};
|
|
111
|
+
};
|
package/src/utils/fetch.ts
CHANGED
|
@@ -1,57 +1,37 @@
|
|
|
1
1
|
const BASE_URL = "https://api.pushwave.dev/v1/public/";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
): Promise<TResponse> {
|
|
7
|
-
|
|
8
|
-
const url = BASE_URL + path;
|
|
9
|
-
|
|
10
|
-
const res = await fetch(url, {
|
|
11
|
-
method: "POST",
|
|
12
|
-
headers: {
|
|
13
|
-
"Content-Type": "application/json",
|
|
14
|
-
},
|
|
15
|
-
body: JSON.stringify(data),
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const text = await res.text();
|
|
19
|
-
let json: any = null;
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
json = text ? JSON.parse(text) : null;
|
|
23
|
-
} catch (err) {
|
|
24
|
-
// JSON empty/invalid
|
|
25
|
-
}
|
|
3
|
+
type FetchMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
4
|
+
type FetchParams = Record<string, string | number | boolean | undefined>;
|
|
5
|
+
type FetchData = Record<string, any>;
|
|
26
6
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const message = `(${res.status}) ${apiError}`.trim();
|
|
31
|
-
|
|
32
|
-
throw new Error(message);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return json as TResponse;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function fetchApiGet<TResponse>(
|
|
7
|
+
export async function fetchApi<TResponse>(
|
|
8
|
+
method: FetchMethod,
|
|
39
9
|
path: string,
|
|
40
|
-
params
|
|
10
|
+
{ params, data }: { params?: FetchParams; data?: FetchData } = {}
|
|
41
11
|
): Promise<TResponse> {
|
|
42
12
|
const search = params
|
|
43
13
|
? new URLSearchParams(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
14
|
+
Object.entries(params).reduce<Record<string, string>>((acc, [key, value]) => {
|
|
15
|
+
if (value === undefined) return acc;
|
|
16
|
+
acc[key] = String(value);
|
|
17
|
+
return acc;
|
|
18
|
+
}, {})
|
|
19
|
+
).toString()
|
|
50
20
|
: "";
|
|
51
21
|
|
|
52
22
|
const url = BASE_URL + path + (search ? `?${search}` : "");
|
|
53
23
|
|
|
54
|
-
const
|
|
24
|
+
const headers: Record<string, string> = {};
|
|
25
|
+
const body = data !== undefined && method !== "GET" ? JSON.stringify(data) : undefined;
|
|
26
|
+
if (body) {
|
|
27
|
+
headers["Content-Type"] = "application/json";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const res = await fetch(url, {
|
|
31
|
+
method,
|
|
32
|
+
headers,
|
|
33
|
+
body,
|
|
34
|
+
});
|
|
55
35
|
|
|
56
36
|
const text = await res.text();
|
|
57
37
|
let json: any = null;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { PWLogger } from "./pwLogger";
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = "pushwave-installation-id";
|
|
4
|
+
|
|
5
|
+
let cachedId: string | null = null;
|
|
6
|
+
let warnedMissingSecureStore = false;
|
|
7
|
+
|
|
8
|
+
type SecureStoreModule = {
|
|
9
|
+
getItemAsync(key: string): Promise<string | null>;
|
|
10
|
+
setItemAsync(key: string, value: string): Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const generateId = (): string => {
|
|
14
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
15
|
+
return crypto.randomUUID();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Fallback UUID-ish generator
|
|
19
|
+
const bytes = new Uint8Array(16);
|
|
20
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
21
|
+
crypto.getRandomValues(bytes);
|
|
22
|
+
} else {
|
|
23
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
24
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
29
|
+
return [
|
|
30
|
+
hex.slice(0, 8),
|
|
31
|
+
hex.slice(8, 12),
|
|
32
|
+
hex.slice(12, 16),
|
|
33
|
+
hex.slice(16, 20),
|
|
34
|
+
hex.slice(20)
|
|
35
|
+
].join("-");
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const loadSecureStore = (): SecureStoreModule | null => {
|
|
39
|
+
try {
|
|
40
|
+
return require("expo-secure-store") as SecureStoreModule;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const getInstallationId = async (): Promise<string> => {
|
|
47
|
+
if (cachedId) return cachedId;
|
|
48
|
+
|
|
49
|
+
const SecureStore = loadSecureStore();
|
|
50
|
+
|
|
51
|
+
if (SecureStore) {
|
|
52
|
+
try {
|
|
53
|
+
const existing = await SecureStore.getItemAsync(STORAGE_KEY);
|
|
54
|
+
if (existing) {
|
|
55
|
+
cachedId = existing;
|
|
56
|
+
return existing;
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
PWLogger.warn("Failed reading installationId from SecureStore:", err);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const id = generateId();
|
|
63
|
+
try {
|
|
64
|
+
await SecureStore.setItemAsync(STORAGE_KEY, id);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
PWLogger.warn("Failed persisting installationId to SecureStore:", err);
|
|
67
|
+
}
|
|
68
|
+
cachedId = id;
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!warnedMissingSecureStore) {
|
|
73
|
+
warnedMissingSecureStore = true;
|
|
74
|
+
PWLogger.warn(
|
|
75
|
+
"expo-secure-store not installed; installationId will not persist across app restarts. Install expo-secure-store for better targeting."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Non-persistent fallback
|
|
80
|
+
cachedId = generateId();
|
|
81
|
+
return cachedId;
|
|
82
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Platform } from "react-native";
|
|
2
|
-
import {
|
|
2
|
+
import { fetchApi } from "./fetch";
|
|
3
3
|
import { PWLogger } from "./pwLogger";
|
|
4
4
|
|
|
5
5
|
export type PushwaveSettings = {
|
|
@@ -9,9 +9,8 @@ export type PushwaveSettings = {
|
|
|
9
9
|
|
|
10
10
|
const pushwaveSettingsPromise: Promise<PushwaveSettings> = (async () => {
|
|
11
11
|
try {
|
|
12
|
-
return await
|
|
12
|
+
return await fetchApi("GET", "pushwave-config", { params: { platform: Platform.OS } });
|
|
13
13
|
} catch (e) {
|
|
14
|
-
// log si besoin
|
|
15
14
|
PWLogger.warn(`Unable to load PushWave configuration: ${e}`)
|
|
16
15
|
return {}
|
|
17
16
|
}
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function getApplicationSignature(): void;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function (): any;
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
}
|
|
15
|
-
export default function registerPushWave({ apiKey }: RegisterPushWaveClient): Promise<RegisterPushWaveResponse>;
|
package/dist/registerPushWave.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.default = registerPushWave;
|
|
7
|
-
const fetch_1 = require("./utils/fetch");
|
|
8
|
-
const expoToken_1 = require("./utils/expoToken");
|
|
9
|
-
const getApplicationAttestation_1 = __importDefault(require("./attestation/getApplicationAttestation"));
|
|
10
|
-
const react_native_1 = require("react-native");
|
|
11
|
-
const pwLogger_1 = require("./utils/pwLogger");
|
|
12
|
-
const apiKeyCheck_1 = require("./utils/apiKeyCheck");
|
|
13
|
-
async function registerPushWave({ apiKey }) {
|
|
14
|
-
if ((0, apiKeyCheck_1.isSecretKey)(apiKey)) {
|
|
15
|
-
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.`;
|
|
16
|
-
pwLogger_1.PWLogger.warn(warn);
|
|
17
|
-
}
|
|
18
|
-
const expoToken = await (0, expoToken_1.getExpoToken)();
|
|
19
|
-
if (!expoToken) {
|
|
20
|
-
const message = "could not get ExpoToken";
|
|
21
|
-
pwLogger_1.PWLogger.error(message);
|
|
22
|
-
return {
|
|
23
|
-
success: false,
|
|
24
|
-
message: "[PushWaveClient] Error: " + message
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
const appAttestation = await (0, getApplicationAttestation_1.default)(apiKey);
|
|
28
|
-
if (appAttestation.status === "disabled")
|
|
29
|
-
pwLogger_1.PWLogger.warn(`(${react_native_1.Platform.OS}) could not get attestation: ${appAttestation.reason}`);
|
|
30
|
-
const path = "/v1/expo-tokens";
|
|
31
|
-
const options = {
|
|
32
|
-
apiKey: apiKey,
|
|
33
|
-
expoToken: expoToken,
|
|
34
|
-
platform: react_native_1.Platform.OS,
|
|
35
|
-
appAttestation: appAttestation,
|
|
36
|
-
environment: __DEV__ ? "development" : "production"
|
|
37
|
-
};
|
|
38
|
-
try {
|
|
39
|
-
const res = await (0, fetch_1.fetchApi)(path, options);
|
|
40
|
-
return {
|
|
41
|
-
success: true,
|
|
42
|
-
message: res.message,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
const e = err;
|
|
47
|
-
pwLogger_1.PWLogger.error(e.message);
|
|
48
|
-
return {
|
|
49
|
-
success: false,
|
|
50
|
-
message: e.message,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
File without changes
|
package/dist/utils/validation.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|