pushwave-client 0.2.6 → 0.2.7
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/android/src/main/java/com/pushwaveclient/PushwaveAttestationModule.kt +7 -1
- package/dist/attestation/getApplicationAttestation.js +0 -5
- package/dist/attestation/native.js +13 -1
- package/dist/register/registerPushWave.js +4 -3
- package/dist/utils/fetch.d.ts +2 -1
- package/dist/utils/fetch.js +30 -3
- package/dist/utils/pushwaveSettings.d.ts +5 -0
- package/dist/utils/pushwaveSettings.js +19 -0
- package/package.json +1 -1
- package/src/attestation/getApplicationAttestation.ts +0 -6
- package/src/attestation/native.ts +20 -1
- package/src/register/registerPushWave.ts +6 -4
- package/src/utils/fetch.ts +41 -3
- package/src/utils/pushwaveSettings.ts +22 -0
|
@@ -16,11 +16,17 @@ class PushwaveAttestationModule(private val context: ReactApplicationContext) :
|
|
|
16
16
|
override fun getName(): String = "PushwaveAttestation"
|
|
17
17
|
|
|
18
18
|
@ReactMethod
|
|
19
|
-
fun getIntegrityToken(nonce: String, promise: Promise) {
|
|
19
|
+
fun getIntegrityToken(nonce: String, cloudProjectNumber: Double?, promise: Promise) {
|
|
20
20
|
try {
|
|
21
|
+
if (cloudProjectNumber == null) {
|
|
22
|
+
promise.reject("PLAY_INTEGRITY_ERROR", "cloudProjectNumber is required")
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
val manager: IntegrityManager = IntegrityManagerFactory.create(context)
|
|
22
27
|
val request = IntegrityTokenRequest.builder()
|
|
23
28
|
.setNonce(nonce)
|
|
29
|
+
.setCloudProjectNumber(cloudProjectNumber.toLong())
|
|
24
30
|
.build()
|
|
25
31
|
|
|
26
32
|
manager.requestIntegrityToken(request)
|
|
@@ -5,8 +5,6 @@ const buffer_1 = require("buffer");
|
|
|
5
5
|
const react_native_1 = require("react-native");
|
|
6
6
|
const native_1 = require("./native");
|
|
7
7
|
async function getApplicationAttestation(apiKey) {
|
|
8
|
-
if (!requiresAttestation(apiKey))
|
|
9
|
-
return { status: "skipped" };
|
|
10
8
|
const { nonce, timestamp } = createNonce();
|
|
11
9
|
try {
|
|
12
10
|
if (react_native_1.Platform.OS === "android")
|
|
@@ -20,9 +18,6 @@ async function getApplicationAttestation(apiKey) {
|
|
|
20
18
|
}
|
|
21
19
|
return { status: "disabled", reason: "platform-unsupported" };
|
|
22
20
|
}
|
|
23
|
-
function requiresAttestation(apiKey) {
|
|
24
|
-
return apiKey.startsWith("pw_pub_");
|
|
25
|
-
}
|
|
26
21
|
function createNonce() {
|
|
27
22
|
const timestamp = Date.now();
|
|
28
23
|
const random = Math.random().toString(36).slice(2);
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getAndroidIntegrityToken = getAndroidIntegrityToken;
|
|
4
4
|
exports.getDeviceCheckToken = getDeviceCheckToken;
|
|
5
5
|
const react_native_1 = require("react-native");
|
|
6
|
+
const pushwaveSettings_1 = require("../utils/pushwaveSettings");
|
|
6
7
|
const MODULE_NAME = "PushwaveAttestation";
|
|
7
8
|
const NativeAttestation = react_native_1.NativeModules?.[MODULE_NAME];
|
|
8
9
|
function assertLinked() {
|
|
@@ -13,8 +14,10 @@ function assertLinked() {
|
|
|
13
14
|
async function getAndroidIntegrityToken(nonce) {
|
|
14
15
|
if (react_native_1.Platform.OS !== "android")
|
|
15
16
|
return;
|
|
17
|
+
const pushwaveSettings = await (0, pushwaveSettings_1.getPushwaveSettings)();
|
|
18
|
+
const cloudProjectNumber = parseCloudProjectNumber(pushwaveSettings);
|
|
16
19
|
assertLinked();
|
|
17
|
-
return NativeAttestation.getIntegrityToken(nonce);
|
|
20
|
+
return NativeAttestation.getIntegrityToken(nonce, cloudProjectNumber);
|
|
18
21
|
}
|
|
19
22
|
async function getDeviceCheckToken(nonce) {
|
|
20
23
|
if (react_native_1.Platform.OS !== "ios")
|
|
@@ -22,3 +25,12 @@ async function getDeviceCheckToken(nonce) {
|
|
|
22
25
|
assertLinked();
|
|
23
26
|
return NativeAttestation.getDeviceCheckToken(nonce);
|
|
24
27
|
}
|
|
28
|
+
function parseCloudProjectNumber(pushwaveSettings) {
|
|
29
|
+
const projectNumber = typeof pushwaveSettings?.cloudProjectNumber === "string"
|
|
30
|
+
? Number(pushwaveSettings.cloudProjectNumber)
|
|
31
|
+
: pushwaveSettings?.cloudProjectNumber;
|
|
32
|
+
if (typeof projectNumber !== "number" || !Number.isFinite(projectNumber)) {
|
|
33
|
+
throw new Error("Google Cloud Project Number missing.");
|
|
34
|
+
}
|
|
35
|
+
return projectNumber;
|
|
36
|
+
}
|
|
@@ -8,6 +8,7 @@ const pwLogger_1 = require("../utils/pwLogger");
|
|
|
8
8
|
const apiKeyCheck_1 = require("../utils/apiKeyCheck");
|
|
9
9
|
const index_1 = require("../attestation/index");
|
|
10
10
|
async function registerPushWave({ apiKey }) {
|
|
11
|
+
const OS = react_native_1.Platform.OS;
|
|
11
12
|
if ((0, apiKeyCheck_1.isSecretKey)(apiKey)) {
|
|
12
13
|
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
14
|
pwLogger_1.PWLogger.warn(warn);
|
|
@@ -24,16 +25,16 @@ async function registerPushWave({ apiKey }) {
|
|
|
24
25
|
const appAttestation = await (0, index_1.getApplicationAttestation)(apiKey);
|
|
25
26
|
if (appAttestation.status === "disabled")
|
|
26
27
|
pwLogger_1.PWLogger.warn(`(${react_native_1.Platform.OS}) could not get attestation: ${appAttestation.reason}`);
|
|
27
|
-
const path = "
|
|
28
|
+
const path = "expo-tokens";
|
|
28
29
|
const options = {
|
|
29
30
|
apiKey: apiKey,
|
|
30
31
|
expoToken: expoToken,
|
|
31
|
-
platform:
|
|
32
|
+
platform: OS,
|
|
32
33
|
appAttestation: appAttestation,
|
|
33
34
|
environment: __DEV__ ? "development" : "production"
|
|
34
35
|
};
|
|
35
36
|
try {
|
|
36
|
-
const res = await (0, fetch_1.
|
|
37
|
+
const res = await (0, fetch_1.fetchApiPost)(path, options);
|
|
37
38
|
return {
|
|
38
39
|
success: true,
|
|
39
40
|
message: res.message,
|
package/dist/utils/fetch.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function fetchApiPost<TResponse>(path: string, data?: Record<string, any>): Promise<TResponse>;
|
|
2
|
+
export declare function fetchApiGet<TResponse>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<TResponse>;
|
package/dist/utils/fetch.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
exports.fetchApiPost = fetchApiPost;
|
|
4
|
+
exports.fetchApiGet = fetchApiGet;
|
|
5
|
+
const BASE_URL = "https://pushwave.luruk-hai.fr/v1/";
|
|
6
|
+
async function fetchApiPost(path, data = {}) {
|
|
6
7
|
const url = BASE_URL + path;
|
|
7
8
|
const res = await fetch(url, {
|
|
8
9
|
method: "POST",
|
|
@@ -26,3 +27,29 @@ async function fetchApi(path, data) {
|
|
|
26
27
|
}
|
|
27
28
|
return json;
|
|
28
29
|
}
|
|
30
|
+
async function fetchApiGet(path, params) {
|
|
31
|
+
const search = params
|
|
32
|
+
? new URLSearchParams(Object.entries(params).reduce((acc, [key, value]) => {
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
return acc;
|
|
35
|
+
acc[key] = String(value);
|
|
36
|
+
return acc;
|
|
37
|
+
}, {})).toString()
|
|
38
|
+
: "";
|
|
39
|
+
const url = BASE_URL + path + (search ? `?${search}` : "");
|
|
40
|
+
const res = await fetch(url);
|
|
41
|
+
const text = await res.text();
|
|
42
|
+
let json = null;
|
|
43
|
+
try {
|
|
44
|
+
json = text ? JSON.parse(text) : null;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
// JSON empty/invalid
|
|
48
|
+
}
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const apiError = (json?.error ?? json?.message ?? "");
|
|
51
|
+
const message = `(${res.status}) ${apiError}`.trim();
|
|
52
|
+
throw new Error(message);
|
|
53
|
+
}
|
|
54
|
+
return json;
|
|
55
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPushwaveSettings = getPushwaveSettings;
|
|
4
|
+
const react_native_1 = require("react-native");
|
|
5
|
+
const fetch_1 = require("./fetch");
|
|
6
|
+
const pwLogger_1 = require("./pwLogger");
|
|
7
|
+
const pushwaveSettingsPromise = (async () => {
|
|
8
|
+
try {
|
|
9
|
+
return await (0, fetch_1.fetchApiGet)("pushwave-config", { platform: react_native_1.Platform.OS });
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
// log si besoin
|
|
13
|
+
pwLogger_1.PWLogger.warn(`Unable to load PushWave configuration: ${e}`);
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
function getPushwaveSettings() {
|
|
18
|
+
return pushwaveSettingsPromise;
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -4,8 +4,6 @@ import { getAndroidIntegrityToken, getDeviceCheckToken } from "./native";
|
|
|
4
4
|
import { ApplicationAttestation, AndroidAttestationPayload, DisabledAttestation, IosAttestationPayload } from "./index";
|
|
5
5
|
|
|
6
6
|
export async function getApplicationAttestation(apiKey: string): Promise<ApplicationAttestation> {
|
|
7
|
-
if (!requiresAttestation(apiKey)) return { status: "skipped" };
|
|
8
|
-
|
|
9
7
|
const { nonce, timestamp } = createNonce();
|
|
10
8
|
|
|
11
9
|
try {
|
|
@@ -19,10 +17,6 @@ export async function getApplicationAttestation(apiKey: string): Promise<Applica
|
|
|
19
17
|
return { status: "disabled", reason: "platform-unsupported" };
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
function requiresAttestation(apiKey: string) {
|
|
23
|
-
return apiKey.startsWith("pw_pub_");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
20
|
function createNonce() {
|
|
27
21
|
const timestamp = Date.now();
|
|
28
22
|
const random = Math.random().toString(36).slice(2);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NativeModules, Platform } from "react-native";
|
|
2
|
+
import { getPushwaveSettings, PushwaveSettings } from "../utils/pushwaveSettings";
|
|
2
3
|
|
|
3
4
|
const MODULE_NAME = "PushwaveAttestation";
|
|
4
5
|
const NativeAttestation = NativeModules?.[MODULE_NAME];
|
|
@@ -11,8 +12,13 @@ function assertLinked() {
|
|
|
11
12
|
|
|
12
13
|
export async function getAndroidIntegrityToken(nonce: string): Promise<string | undefined> {
|
|
13
14
|
if (Platform.OS !== "android") return;
|
|
15
|
+
|
|
16
|
+
const pushwaveSettings = await getPushwaveSettings();
|
|
17
|
+
|
|
18
|
+
const cloudProjectNumber = parseCloudProjectNumber(pushwaveSettings);
|
|
19
|
+
|
|
14
20
|
assertLinked();
|
|
15
|
-
return NativeAttestation.getIntegrityToken(nonce);
|
|
21
|
+
return NativeAttestation.getIntegrityToken(nonce, cloudProjectNumber);
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
export async function getDeviceCheckToken(nonce: string): Promise<string | undefined> {
|
|
@@ -20,3 +26,16 @@ export async function getDeviceCheckToken(nonce: string): Promise<string | undef
|
|
|
20
26
|
assertLinked();
|
|
21
27
|
return NativeAttestation.getDeviceCheckToken(nonce);
|
|
22
28
|
}
|
|
29
|
+
|
|
30
|
+
function parseCloudProjectNumber(pushwaveSettings: PushwaveSettings): number {
|
|
31
|
+
const projectNumber =
|
|
32
|
+
typeof pushwaveSettings?.cloudProjectNumber === "string"
|
|
33
|
+
? Number(pushwaveSettings.cloudProjectNumber)
|
|
34
|
+
: pushwaveSettings?.cloudProjectNumber;
|
|
35
|
+
|
|
36
|
+
if (typeof projectNumber !== "number" || !Number.isFinite(projectNumber)) {
|
|
37
|
+
throw new Error("Google Cloud Project Number missing.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return projectNumber;
|
|
41
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchApiPost, fetchApiGet } from "../utils/fetch";
|
|
2
2
|
import { getExpoToken } from "../utils/expoToken";
|
|
3
3
|
import { Platform } from "react-native";
|
|
4
4
|
import { PWLogger } from "../utils/pwLogger";
|
|
5
5
|
import { isSecretKey } from "../utils/apiKeyCheck";
|
|
6
6
|
import { RegisterPushWaveClient, RegisterPushWaveDTO, RegisterPushWaveResponse } from "./registerPushWave.dto";
|
|
7
7
|
import { getApplicationAttestation } from "../attestation/index";
|
|
8
|
+
import { platform } from "os";
|
|
8
9
|
|
|
9
10
|
export async function registerPushWave(
|
|
10
11
|
{ apiKey }: RegisterPushWaveClient
|
|
11
12
|
): Promise<RegisterPushWaveResponse> {
|
|
13
|
+
const OS = Platform.OS;
|
|
12
14
|
|
|
13
15
|
if (isSecretKey(apiKey)) {
|
|
14
16
|
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.`;
|
|
@@ -34,18 +36,18 @@ export async function registerPushWave(
|
|
|
34
36
|
if (appAttestation.status === "disabled")
|
|
35
37
|
PWLogger.warn(`(${Platform.OS}) could not get attestation: ${appAttestation.reason}`);
|
|
36
38
|
|
|
37
|
-
const path = "
|
|
39
|
+
const path = "expo-tokens"
|
|
38
40
|
|
|
39
41
|
const options: RegisterPushWaveDTO = {
|
|
40
42
|
apiKey: apiKey,
|
|
41
43
|
expoToken: expoToken,
|
|
42
|
-
platform:
|
|
44
|
+
platform: OS,
|
|
43
45
|
appAttestation: appAttestation,
|
|
44
46
|
environment: __DEV__ ? "development" : "production"
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
try {
|
|
48
|
-
const res: RegisterPushWaveResponse = await
|
|
50
|
+
const res: RegisterPushWaveResponse = await fetchApiPost(path, options)
|
|
49
51
|
|
|
50
52
|
return {
|
|
51
53
|
success: true,
|
package/src/utils/fetch.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const BASE_URL = "https://pushwave.luruk-hai.fr";
|
|
1
|
+
const BASE_URL = "https://pushwave.luruk-hai.fr/v1/";
|
|
2
2
|
|
|
3
|
-
export async function
|
|
3
|
+
export async function fetchApiPost<TResponse>(
|
|
4
4
|
path: string,
|
|
5
|
-
data: Record<string, any>
|
|
5
|
+
data: Record<string, any> = {}
|
|
6
6
|
): Promise<TResponse> {
|
|
7
7
|
|
|
8
8
|
const url = BASE_URL + path;
|
|
@@ -34,3 +34,41 @@ export async function fetchApi<TResponse>(
|
|
|
34
34
|
|
|
35
35
|
return json as TResponse;
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
export async function fetchApiGet<TResponse>(
|
|
39
|
+
path: string,
|
|
40
|
+
params?: Record<string, string | number | boolean | undefined>
|
|
41
|
+
): Promise<TResponse> {
|
|
42
|
+
const search = params
|
|
43
|
+
? new URLSearchParams(
|
|
44
|
+
Object.entries(params).reduce<Record<string, string>>((acc, [key, value]) => {
|
|
45
|
+
if (value === undefined) return acc;
|
|
46
|
+
acc[key] = String(value);
|
|
47
|
+
return acc;
|
|
48
|
+
}, {})
|
|
49
|
+
).toString()
|
|
50
|
+
: "";
|
|
51
|
+
|
|
52
|
+
const url = BASE_URL + path + (search ? `?${search}` : "");
|
|
53
|
+
|
|
54
|
+
const res = await fetch(url);
|
|
55
|
+
|
|
56
|
+
const text = await res.text();
|
|
57
|
+
let json: any = null;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
json = text ? JSON.parse(text) : null;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// JSON empty/invalid
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const apiError = (json?.error ?? json?.message ?? "") as string;
|
|
67
|
+
|
|
68
|
+
const message = `(${res.status}) ${apiError}`.trim();
|
|
69
|
+
|
|
70
|
+
throw new Error(message);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return json as TResponse;
|
|
74
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
import { fetchApiGet } from "./fetch";
|
|
3
|
+
import { PWLogger } from "./pwLogger";
|
|
4
|
+
|
|
5
|
+
export type PushwaveSettings = {
|
|
6
|
+
cloudProjectNumber?: string | number;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const pushwaveSettingsPromise: Promise<PushwaveSettings> = (async () => {
|
|
11
|
+
try {
|
|
12
|
+
return await fetchApiGet("pushwave-config", { platform: Platform.OS });
|
|
13
|
+
} catch (e) {
|
|
14
|
+
// log si besoin
|
|
15
|
+
PWLogger.warn(`Unable to load PushWave configuration: ${e}`)
|
|
16
|
+
return {}
|
|
17
|
+
}
|
|
18
|
+
})();
|
|
19
|
+
|
|
20
|
+
export function getPushwaveSettings() {
|
|
21
|
+
return pushwaveSettingsPromise;
|
|
22
|
+
}
|