pulse-updates 1.1.1 → 1.2.1
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 +53 -10
- package/SECURITY.md +42 -0
- package/android/src/main/java/app/pulse/updates/PulseController.kt +6 -2
- package/android/src/main/java/app/pulse/updates/PulseUpdatesModule.kt +25 -0
- package/app.plugin.js +47 -0
- package/ios/PulseUpdates/PulseController.swift +7 -2
- package/ios/PulseUpdates/PulseUpdates.m +7 -0
- package/ios/PulseUpdates/PulseUpdates.swift +22 -0
- package/lib/commonjs/NativePulseUpdates.js.map +1 -1
- package/lib/commonjs/PulseUpdates.js +36 -0
- package/lib/commonjs/PulseUpdates.js.map +1 -1
- package/lib/commonjs/config.js +175 -21
- package/lib/commonjs/config.js.map +1 -1
- package/lib/commonjs/decisions.js +128 -0
- package/lib/commonjs/decisions.js.map +1 -0
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/init.js +153 -13
- package/lib/commonjs/init.js.map +1 -1
- package/lib/commonjs/track.js +154 -12
- package/lib/commonjs/track.js.map +1 -1
- package/lib/commonjs/usePulseUpdates.js +21 -16
- package/lib/commonjs/usePulseUpdates.js.map +1 -1
- package/lib/module/NativePulseUpdates.js.map +1 -1
- package/lib/module/PulseUpdates.js +33 -0
- package/lib/module/PulseUpdates.js.map +1 -1
- package/lib/module/config.js +174 -21
- package/lib/module/config.js.map +1 -1
- package/lib/module/decisions.js +122 -0
- package/lib/module/decisions.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/init.js +154 -15
- package/lib/module/init.js.map +1 -1
- package/lib/module/track.js +152 -12
- package/lib/module/track.js.map +1 -1
- package/lib/module/usePulseUpdates.js +21 -16
- package/lib/module/usePulseUpdates.js.map +1 -1
- package/lib/typescript/NativePulseUpdates.d.ts +1 -0
- package/lib/typescript/NativePulseUpdates.d.ts.map +1 -1
- package/lib/typescript/PulseUpdates.d.ts +20 -0
- package/lib/typescript/PulseUpdates.d.ts.map +1 -1
- package/lib/typescript/config.d.ts +22 -0
- package/lib/typescript/config.d.ts.map +1 -1
- package/lib/typescript/decisions.d.ts +55 -0
- package/lib/typescript/decisions.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/init.d.ts +51 -4
- package/lib/typescript/init.d.ts.map +1 -1
- package/lib/typescript/track.d.ts +29 -1
- package/lib/typescript/track.d.ts.map +1 -1
- package/lib/typescript/usePulseUpdates.d.ts.map +1 -1
- package/logo.png +0 -0
- package/package.json +14 -3
- package/scripts/publish.mjs +125 -7
- package/src/NativePulseUpdates.ts +1 -0
- package/src/PulseUpdates.ts +54 -0
- package/src/config.ts +234 -21
- package/src/decisions.ts +179 -0
- package/src/index.ts +1 -0
- package/src/init.ts +227 -12
- package/src/track.ts +191 -13
- package/src/usePulseUpdates.ts +21 -16
package/README.md
CHANGED
|
@@ -11,31 +11,70 @@
|
|
|
11
11
|
## Everything wired, in one call
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import { initPulse
|
|
14
|
+
import { initPulse } from 'pulse-updates';
|
|
15
15
|
import { AppState } from 'react-native';
|
|
16
16
|
|
|
17
|
-
initPulse({
|
|
17
|
+
export const pulse = initPulse({
|
|
18
18
|
apiUrl: 'https://pulse.example.com',
|
|
19
19
|
appSlug: 'my-app',
|
|
20
|
-
|
|
21
|
-
storage: mmkv, // { getString, set } — MMKV, AsyncStorage wrapper, anything
|
|
20
|
+
storage: mmkv, // { getString, set }; app version/locale are auto-detected
|
|
22
21
|
defaults: { newPaywall: false },
|
|
23
|
-
appState: AppState,
|
|
22
|
+
appState: AppState,
|
|
23
|
+
signingKeyId: 'production-1',
|
|
24
|
+
signingPublicKey: PULSE_PUBLIC_KEY,
|
|
25
|
+
requireConfigSignature: true,
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
// remote config, with the default when the server has never been reached
|
|
27
|
-
if (
|
|
29
|
+
if (pulse.config.boolean('newPaywall')) showNewPaywall();
|
|
28
30
|
|
|
29
31
|
// your own events — declare `purchase` in the registry first
|
|
30
|
-
track('purchase', { revenue: 4.99, currency: 'EUR' });
|
|
32
|
+
pulse.track('purchase', { revenue: 4.99, currency: 'EUR' });
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
That is the whole integration for config, experiments and metrics: the two urls are
|
|
34
36
|
built from the slug (so the halves cannot address different apps), the install gets a
|
|
35
37
|
stable id — minted and kept if the app has none of its own — and events are queued and
|
|
36
|
-
flushed
|
|
38
|
+
flushed from a persistent outbox in batches. `pulse.health()` exposes config freshness,
|
|
39
|
+
signature state, and event backlog. Retention needs nothing further: Pulse counts the install from the
|
|
37
40
|
config request it already makes.
|
|
38
41
|
|
|
42
|
+
Identity and consent stay explicit and reversible:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
pulse.setUser(account.id, { plan: account.plan });
|
|
46
|
+
pulse.setAnalyticsConsent(true);
|
|
47
|
+
// on logout
|
|
48
|
+
pulse.clearUser();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Expo
|
|
52
|
+
|
|
53
|
+
Add one plugin entry; it writes both native manifests during prebuild:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"expo": {
|
|
58
|
+
"plugins": [["pulse-updates", {
|
|
59
|
+
"apiUrl": "https://pulse.example.com",
|
|
60
|
+
"signingKeyId": "production-1",
|
|
61
|
+
"signingPublicKey": "BASE64_PUBLIC_KEY"
|
|
62
|
+
}]]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Typed contracts
|
|
68
|
+
|
|
69
|
+
Keep a reviewable `pulse.schema.json` with `config`, `events`, and `actions`, then run:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx pulse-updates generate
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This writes `src/pulse.generated.ts`; it never declares server events automatically.
|
|
76
|
+
Unknown events remain visible and must be approved in the PULSE dashboard.
|
|
77
|
+
|
|
39
78
|
OTA updates are the other half of the package and set up separately — see below.
|
|
40
79
|
|
|
41
80
|
## Features
|
|
@@ -351,8 +390,12 @@ For capability-based crash-prediction, record what native modules the shipped bi
|
|
|
351
390
|
npx pulse-updates register-capabilities --platform <ios|android> [--api-key <key>]
|
|
352
391
|
```
|
|
353
392
|
|
|
354
|
-
Run it at build time
|
|
355
|
-
|
|
393
|
+
Run it at build time so the server can warn when a future JS-only update references native modules
|
|
394
|
+
the installed binary doesn't have. When the release pipeline already has the exact embedded bundle,
|
|
395
|
+
pass it with `--bundle <path>`. On Android the command automatically reuses the newest Gradle packager
|
|
396
|
+
source map (the exact pre-Hermes graph retained under `android/app/build/intermediates/sourcemaps`), so
|
|
397
|
+
a normal Gradle release build needs no extra path. If no embedded or generated source exists, it builds
|
|
398
|
+
an unminified, scan-only Metro graph; that fallback is never uploaded or used as the app's launch bundle.
|
|
356
399
|
|
|
357
400
|
### Configuration File
|
|
358
401
|
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Security model — pulse-updates
|
|
2
|
+
|
|
3
|
+
`pulse-updates` ships JavaScript/asset bundles to production apps over the air. That makes the
|
|
4
|
+
update channel a remote-code-execution surface: the security of every install depends on the
|
|
5
|
+
guarantees below being configured correctly.
|
|
6
|
+
|
|
7
|
+
## Trust chain
|
|
8
|
+
|
|
9
|
+
1. **Publish** — the CLI (`scripts/publish.mjs`) authenticates to the server with a per-app
|
|
10
|
+
`X-API-Key`. This key can publish arbitrary bundles, so it MUST be treated as a deploy secret:
|
|
11
|
+
keep it in CI secrets / `PULSE_API_KEY`, never commit it (`pulse.config.json` should be gitignored).
|
|
12
|
+
2. **Sign** — the server signs each manifest with an Ed25519 private key (canonicalized JSON,
|
|
13
|
+
signature detached) and returns `signature.{alg,keyId,sig}`.
|
|
14
|
+
3. **Verify on device** — the SDK verifies the Ed25519 signature against the configured
|
|
15
|
+
`PulseUpdatesSigningPublicKey` (matching `PulseUpdatesSigningKeyId`) and checks each asset's
|
|
16
|
+
SHA-256 against the (now-trusted) manifest hashes before applying an update.
|
|
17
|
+
|
|
18
|
+
## Fail-closed signing (required in release)
|
|
19
|
+
|
|
20
|
+
`PulseUpdatesRequireSignature` defaults to **`true` in release builds** (`false` in debug). When on:
|
|
21
|
+
|
|
22
|
+
- If `PulseUpdatesSigningPublicKey` / `PulseUpdatesSigningKeyId` are **not configured**, or the
|
|
23
|
+
manifest **carries no signature**, or the signature **does not verify**, the remote update is
|
|
24
|
+
**refused** and the app keeps running the embedded / last-known-good bundle.
|
|
25
|
+
- Set `PulseUpdatesRequireSignature=false` **only** to intentionally allow unsigned updates
|
|
26
|
+
(e.g. a local dev server). Never do this in a production build.
|
|
27
|
+
|
|
28
|
+
This prevents a misconfiguration (a stripped plist key, a new app onboarded without keys, an A/B
|
|
29
|
+
build) from silently downgrading the channel to "accept any unsigned bundle".
|
|
30
|
+
|
|
31
|
+
## Key management & rotation
|
|
32
|
+
|
|
33
|
+
- Generate a per-product Ed25519 keypair; configure the **public** key in the app and keep the
|
|
34
|
+
**private** key only on the signing server.
|
|
35
|
+
- Prefer a distinct `keyId` per product (e.g. `myapp-prod-1`, `web-prod-1`) so trust roots are
|
|
36
|
+
independent and separately rotatable.
|
|
37
|
+
- To rotate: ship a build that trusts both the old and new `keyId`/public key, cut over server-side
|
|
38
|
+
signing to the new key, then drop the old key in a subsequent build.
|
|
39
|
+
|
|
40
|
+
## Reporting
|
|
41
|
+
|
|
42
|
+
Report vulnerabilities privately to **security@pulseupdates.dev** — do not open public issues for security reports.
|
|
@@ -177,8 +177,12 @@ class PulseController private constructor() {
|
|
|
177
177
|
|
|
178
178
|
val enabled = metadata.getBoolean("PulseUpdatesEnabled", true)
|
|
179
179
|
|
|
180
|
-
//
|
|
181
|
-
|
|
180
|
+
// Explicit native config > embedded manifest > versionName.
|
|
181
|
+
// The Expo plugin writes the first value, so its runtimeVersion option
|
|
182
|
+
// must not be decorative.
|
|
183
|
+
val runtimeVersion = metadata.getString("PulseUpdatesRuntimeVersion")
|
|
184
|
+
?.takeIf { it.isNotBlank() }
|
|
185
|
+
?: embeddedManifest?.runtimeVersion
|
|
182
186
|
?: try {
|
|
183
187
|
val packageInfo = ctx.packageManager.getPackageInfo(ctx.packageName, 0)
|
|
184
188
|
packageInfo.versionName ?: packageInfo.versionCode.toString()
|
|
@@ -2,6 +2,7 @@ package app.pulse.updates
|
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.*
|
|
4
4
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
5
|
+
import java.security.MessageDigest
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* PulseUpdates React Native Module
|
|
@@ -72,6 +73,30 @@ class PulseUpdatesModule(private val reactContext: ReactApplicationContext) :
|
|
|
72
73
|
promise.resolve(buildState())
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
@ReactMethod
|
|
77
|
+
fun verifySignatureAsync(canonicalPayload: String, alg: String, keyId: String, signature: String, promise: Promise) {
|
|
78
|
+
val config = PulseController.getInstance().config
|
|
79
|
+
if (alg.lowercase() != "ed25519" || config?.signingKeyId != keyId || config.signingPublicKey == null) {
|
|
80
|
+
promise.resolve(false)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
val publicKeyBytes = android.util.Base64.decode(config.signingPublicKey, android.util.Base64.DEFAULT)
|
|
85
|
+
val signatureBytes = android.util.Base64.decode(signature, android.util.Base64.DEFAULT)
|
|
86
|
+
val spec = net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.getByName("Ed25519")
|
|
87
|
+
val publicKey = net.i2p.crypto.eddsa.EdDSAPublicKey(
|
|
88
|
+
net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec(publicKeyBytes, spec)
|
|
89
|
+
)
|
|
90
|
+
val verifier = net.i2p.crypto.eddsa.EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm))
|
|
91
|
+
verifier.initVerify(publicKey)
|
|
92
|
+
verifier.update(canonicalPayload.toByteArray(Charsets.UTF_8))
|
|
93
|
+
promise.resolve(verifier.verify(signatureBytes))
|
|
94
|
+
} catch (error: Exception) {
|
|
95
|
+
pulseLogWarn(TAG, "verifySignatureAsync failed: ${error.message}")
|
|
96
|
+
promise.resolve(false)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
private fun buildState(): WritableMap {
|
|
76
101
|
val controller = PulseController.getInstance()
|
|
77
102
|
val state = Arguments.createMap()
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { withAndroidManifest, withInfoPlist } = require('@expo/config-plugins');
|
|
2
|
+
|
|
3
|
+
/** Expo config plugin: one entry in app.json, no manual plist/manifest editing. */
|
|
4
|
+
module.exports = function withPulseUpdates(config, options = {}) {
|
|
5
|
+
const enabled = options.enabled !== false;
|
|
6
|
+
const apiUrl = options.apiUrl || '';
|
|
7
|
+
const runtimeVersion = options.runtimeVersion || config.version || '';
|
|
8
|
+
const channel = options.channel || 'production';
|
|
9
|
+
|
|
10
|
+
config = withInfoPlist(config, (result) => {
|
|
11
|
+
Object.assign(result.modResults, {
|
|
12
|
+
PulseUpdatesEnabled: enabled,
|
|
13
|
+
PulseUpdatesURL: apiUrl,
|
|
14
|
+
PulseUpdatesRuntimeVersion: runtimeVersion,
|
|
15
|
+
PulseUpdatesChannel: channel,
|
|
16
|
+
PulseUpdatesCheckOnLaunch: options.checkOnLaunch || 'ALWAYS',
|
|
17
|
+
PulseUpdatesLaunchWaitMs: options.launchWaitMs || 0,
|
|
18
|
+
PulseUpdatesRequireSignature: options.requireSignature !== false,
|
|
19
|
+
...(options.signingKeyId ? { PulseUpdatesSigningKeyId: options.signingKeyId } : {}),
|
|
20
|
+
...(options.signingPublicKey ? { PulseUpdatesSigningPublicKey: options.signingPublicKey } : {}),
|
|
21
|
+
});
|
|
22
|
+
return result;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return withAndroidManifest(config, (result) => {
|
|
26
|
+
const application = result.modResults.manifest.application?.[0];
|
|
27
|
+
if (!application) throw new Error('Pulse Updates: Android application entry not found');
|
|
28
|
+
const values = {
|
|
29
|
+
PulseUpdatesEnabled: String(enabled),
|
|
30
|
+
PulseUpdatesURL: apiUrl,
|
|
31
|
+
PulseUpdatesRuntimeVersion: runtimeVersion,
|
|
32
|
+
PulseUpdatesChannel: channel,
|
|
33
|
+
PulseUpdatesCheckOnLaunch: options.checkOnLaunch || 'ALWAYS',
|
|
34
|
+
PulseUpdatesLaunchWaitMs: String(options.launchWaitMs || 0),
|
|
35
|
+
PulseUpdatesRequireSignature: String(options.requireSignature !== false),
|
|
36
|
+
...(options.signingKeyId ? { PulseUpdatesSigningKeyId: options.signingKeyId } : {}),
|
|
37
|
+
...(options.signingPublicKey ? { PulseUpdatesSigningPublicKey: options.signingPublicKey } : {}),
|
|
38
|
+
};
|
|
39
|
+
application['meta-data'] = application['meta-data'] || [];
|
|
40
|
+
for (const [name, value] of Object.entries(values)) {
|
|
41
|
+
const existing = application['meta-data'].find((item) => item.$?.['android:name'] === name);
|
|
42
|
+
if (existing) existing.$['android:value'] = value;
|
|
43
|
+
else application['meta-data'].push({ $: { 'android:name': name, 'android:value': value } });
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
});
|
|
47
|
+
};
|
|
@@ -165,9 +165,14 @@ public final class PulseController {
|
|
|
165
165
|
|
|
166
166
|
let enabled = (info?["PulseUpdatesEnabled"] as? Bool) ?? true
|
|
167
167
|
|
|
168
|
-
//
|
|
168
|
+
// Explicit native config > embedded manifest > CFBundleShortVersionString.
|
|
169
|
+
// The Expo plugin writes the first value, so its runtimeVersion option
|
|
170
|
+
// must not be decorative.
|
|
169
171
|
let runtimeVersion: String
|
|
170
|
-
if let
|
|
172
|
+
if let configured = info?["PulseUpdatesRuntimeVersion"] as? String,
|
|
173
|
+
!configured.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
174
|
+
runtimeVersion = configured
|
|
175
|
+
} else if let embedded = embeddedManifest {
|
|
171
176
|
runtimeVersion = embedded.runtimeVersion
|
|
172
177
|
} else {
|
|
173
178
|
runtimeVersion = (info?["CFBundleShortVersionString"] as? String)
|
|
@@ -18,6 +18,13 @@ RCT_EXTERN_METHOD(configure:(BOOL)enabled
|
|
|
18
18
|
RCT_EXTERN_METHOD(getStateAsync:(RCTPromiseResolveBlock)resolve
|
|
19
19
|
reject:(RCTPromiseRejectBlock)reject)
|
|
20
20
|
|
|
21
|
+
RCT_EXTERN_METHOD(verifySignatureAsync:(NSString *)canonicalPayload
|
|
22
|
+
alg:(NSString *)alg
|
|
23
|
+
keyId:(NSString *)keyId
|
|
24
|
+
signature:(NSString *)signature
|
|
25
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
26
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
27
|
+
|
|
21
28
|
RCT_EXTERN_METHOD(checkForUpdateAsync:(RCTPromiseResolveBlock)resolve
|
|
22
29
|
reject:(RCTPromiseRejectBlock)reject)
|
|
23
30
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import Foundation
|
|
5
5
|
import React
|
|
6
|
+
import CryptoKit
|
|
6
7
|
|
|
7
8
|
@objc(PulseUpdates)
|
|
8
9
|
public class PulseUpdates: RCTEventEmitter {
|
|
@@ -61,6 +62,27 @@ public class PulseUpdates: RCTEventEmitter {
|
|
|
61
62
|
resolve(PulseUpdates.buildState())
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
@objc
|
|
66
|
+
func verifySignatureAsync(_ canonicalPayload: String,
|
|
67
|
+
alg: String,
|
|
68
|
+
keyId: String,
|
|
69
|
+
signature: String,
|
|
70
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
71
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
72
|
+
guard alg.lowercased() == "ed25519",
|
|
73
|
+
let config = PulseController.shared.config,
|
|
74
|
+
config.signingKeyId == keyId,
|
|
75
|
+
let keyBase64 = config.signingPublicKey,
|
|
76
|
+
let keyData = Data(base64Encoded: keyBase64),
|
|
77
|
+
let signatureData = Data(base64Encoded: signature),
|
|
78
|
+
let payloadData = canonicalPayload.data(using: .utf8),
|
|
79
|
+
let publicKey = try? Curve25519.Signing.PublicKey(rawRepresentation: keyData) else {
|
|
80
|
+
resolve(false)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
resolve(publicKey.isValidSignature(signatureData, for: payloadData))
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
private static func buildState() -> [String: Any] {
|
|
65
87
|
let controller = PulseController.shared
|
|
66
88
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativePulseUpdates.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativePulseUpdates.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAmDpCC,gCAAmB,CAACC,GAAG,CAAO,cAAc,CAAC","ignoreList":[]}
|
|
@@ -14,13 +14,16 @@ exports.getIsEnabled = getIsEnabled;
|
|
|
14
14
|
exports.getLocalAssetPath = getLocalAssetPath;
|
|
15
15
|
exports.getLocalAssets = getLocalAssets;
|
|
16
16
|
exports.getUpdateBuild = getUpdateBuild;
|
|
17
|
+
exports.getUpdatesState = getUpdatesState;
|
|
17
18
|
exports.isConfigured = isConfigured;
|
|
18
19
|
exports.manifest = exports.localAssets = exports.isUsingEmbeddedAssets = exports.isEnabled = exports.isEmbeddedLaunch = void 0;
|
|
19
20
|
exports.markAppReady = markAppReady;
|
|
21
|
+
exports.onUpdatesStateChange = onUpdatesStateChange;
|
|
20
22
|
exports.refreshStateAsync = refreshStateAsync;
|
|
21
23
|
exports.reloadAsync = reloadAsync;
|
|
22
24
|
exports.reportLaunchFailure = reportLaunchFailure;
|
|
23
25
|
exports.updateId = exports.runtimeVersion = void 0;
|
|
26
|
+
exports.verifyConfigSignatureAsync = verifyConfigSignatureAsync;
|
|
24
27
|
var _reactNative = require("react-native");
|
|
25
28
|
var _NativePulseUpdates = _interopRequireDefault(require("./NativePulseUpdates"));
|
|
26
29
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -38,6 +41,29 @@ let _isEmbeddedLaunch = true;
|
|
|
38
41
|
let _manifest = null;
|
|
39
42
|
let _createdAt = null;
|
|
40
43
|
let _localAssets = null;
|
|
44
|
+
const stateListeners = new Set();
|
|
45
|
+
/** A stable read surface for React and for diagnostics. */
|
|
46
|
+
function getUpdatesState() {
|
|
47
|
+
return {
|
|
48
|
+
isEnabled: _isEnabled,
|
|
49
|
+
updateId: _updateId,
|
|
50
|
+
runtimeVersion: _runtimeVersion,
|
|
51
|
+
channel: _channel,
|
|
52
|
+
isEmbeddedLaunch: _isEmbeddedLaunch,
|
|
53
|
+
manifest: _manifest,
|
|
54
|
+
createdAt: _createdAt,
|
|
55
|
+
localAssets: _localAssets
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Subscribe to native-state refreshes without depending on mutable ES exports. */
|
|
60
|
+
function onUpdatesStateChange(listener) {
|
|
61
|
+
stateListeners.add(listener);
|
|
62
|
+
return () => stateListeners.delete(listener);
|
|
63
|
+
}
|
|
64
|
+
function notifyStateListeners() {
|
|
65
|
+
for (const listener of stateListeners) listener();
|
|
66
|
+
}
|
|
41
67
|
|
|
42
68
|
/**
|
|
43
69
|
* Configure pulse-updates with the given options.
|
|
@@ -61,6 +87,9 @@ function configure(config) {
|
|
|
61
87
|
_isConfigured = true;
|
|
62
88
|
_isEnabled = config.enabled;
|
|
63
89
|
_runtimeVersion = config.runtimeVersion;
|
|
90
|
+
exports.isEnabled = isEnabled = _isEnabled;
|
|
91
|
+
exports.runtimeVersion = runtimeVersion = _runtimeVersion;
|
|
92
|
+
notifyStateListeners();
|
|
64
93
|
}
|
|
65
94
|
|
|
66
95
|
/**
|
|
@@ -140,6 +169,7 @@ async function refreshStateAsync() {
|
|
|
140
169
|
// Read localAssets map for asset resolution
|
|
141
170
|
_localAssets = state.localAssets ?? null;
|
|
142
171
|
exports.localAssets = localAssets = _localAssets;
|
|
172
|
+
notifyStateListeners();
|
|
143
173
|
} catch (error) {
|
|
144
174
|
console.warn('[PulseUpdates] Failed to refresh state:', error);
|
|
145
175
|
// Keep current values on error
|
|
@@ -239,6 +269,12 @@ async function reportLaunchFailure(reason) {
|
|
|
239
269
|
await PulseUpdatesModule.reportLaunchFailure(reason);
|
|
240
270
|
}
|
|
241
271
|
|
|
272
|
+
/** Verify config/decision payloads with the public key already pinned in native config. */
|
|
273
|
+
async function verifyConfigSignatureAsync(canonicalPayload, signature) {
|
|
274
|
+
if (!PulseUpdatesModule?.verifySignatureAsync) return false;
|
|
275
|
+
return PulseUpdatesModule.verifySignatureAsync(canonicalPayload, signature.alg, signature.keyId, signature.sig);
|
|
276
|
+
}
|
|
277
|
+
|
|
242
278
|
/**
|
|
243
279
|
* Get the build number from the current update's metadata.
|
|
244
280
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_NativePulseUpdates","_interopRequireDefault","e","__esModule","default","PulseUpdatesModule","NativePulseUpdates","_isConfigured","_isEnabled","_updateId","_runtimeVersion","_channel","_isEmbeddedLaunch","_manifest","_createdAt","_localAssets","
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_NativePulseUpdates","_interopRequireDefault","e","__esModule","default","PulseUpdatesModule","NativePulseUpdates","_isConfigured","_isEnabled","_updateId","_runtimeVersion","_channel","_isEmbeddedLaunch","_manifest","_createdAt","_localAssets","stateListeners","Set","getUpdatesState","isEnabled","updateId","runtimeVersion","channel","isEmbeddedLaunch","manifest","createdAt","localAssets","onUpdatesStateChange","listener","add","delete","notifyStateListeners","configure","config","console","warn","enabled","updateUrl","checkOnLaunch","launchWaitMs","signingKeyId","signingPublicKey","exports","isConfigured","getIsEnabled","isUsingEmbeddedAssets","refreshStateAsync","__DEV__","state","getStateAsync","manifestString","JSON","parse","commitTime","Date","error","checkForUpdateAsync","isAvailable","reason","Error","result","isRollback","failedReason","fetchUpdateAsync","isNew","reloadAsync","log","markAppReady","reportLaunchFailure","verifyConfigSignatureAsync","canonicalPayload","signature","verifySignatureAsync","alg","keyId","sig","getUpdateBuild","metadata","build","getDisplayVersion","appVersion","getLocalAssetPath","assetKey","getLocalAssets","eventEmitter","getEventEmitter","NativeEventEmitter","addUpdateListener","emitter","subscription","addListener","remove"],"sourceRoot":"../../src","sources":["PulseUpdates.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AACA,IAAAC,mBAAA,GAAAC,sBAAA,CAAAF,OAAA;AAAsD,SAAAE,uBAAAC,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAGtD,MAAMG,kBAAkB,GAAGC,2BAAkB;;AAE7C;AACA,IAAIC,aAAa,GAAG,KAAK;;AAEzB;AACA,IAAIC,UAAU,GAAG,KAAK;AACtB,IAAIC,SAAwB,GAAG,IAAI;AACnC,IAAIC,eAA8B,GAAG,IAAI;AACzC,IAAIC,QAAuB,GAAG,IAAI;AAClC,IAAIC,iBAAiB,GAAG,IAAI;AAC5B,IAAIC,SAA+B,GAAG,IAAI;AAC1C,IAAIC,UAAuB,GAAG,IAAI;AAClC,IAAIC,YAA2C,GAAG,IAAI;AACtD,MAAMC,cAAc,GAAG,IAAIC,GAAG,CAAa,CAAC;AAa5C;AACO,SAASC,eAAeA,CAAA,EAAqB;EAClD,OAAO;IACLC,SAAS,EAAEX,UAAU;IACrBY,QAAQ,EAAEX,SAAS;IACnBY,cAAc,EAAEX,eAAe;IAC/BY,OAAO,EAAEX,QAAQ;IACjBY,gBAAgB,EAAEX,iBAAiB;IACnCY,QAAQ,EAAEX,SAAS;IACnBY,SAAS,EAAEX,UAAU;IACrBY,WAAW,EAAEX;EACf,CAAC;AACH;;AAEA;AACO,SAASY,oBAAoBA,CAACC,QAAoB,EAAc;EACrEZ,cAAc,CAACa,GAAG,CAACD,QAAQ,CAAC;EAC5B,OAAO,MAAMZ,cAAc,CAACc,MAAM,CAACF,QAAQ,CAAC;AAC9C;AAEA,SAASG,oBAAoBA,CAAA,EAAS;EACpC,KAAK,MAAMH,QAAQ,IAAIZ,cAAc,EAAEY,QAAQ,CAAC,CAAC;AACnD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASI,SAASA,CAACC,MAA0B,EAAQ;EAC1D,IAAI,CAAC5B,kBAAkB,EAAE;IACvB6B,OAAO,CAACC,IAAI,CAAC,sCAAsC,CAAC;IACpD;EACF;EAEA,IAAI5B,aAAa,EAAE;IACjB2B,OAAO,CAACC,IAAI,CAAC,kDAAkD,CAAC;IAChE;EACF;EAEA9B,kBAAkB,CAAC2B,SAAS,CAC1BC,MAAM,CAACG,OAAO,EACdH,MAAM,CAACI,SAAS,EAChBJ,MAAM,CAACZ,cAAc,EACrBY,MAAM,CAACK,aAAa,EACpBL,MAAM,CAACM,YAAY,EACnBN,MAAM,CAACX,OAAO,IAAI,IAAI,EACtBW,MAAM,CAACO,YAAY,IAAI,IAAI,EAC3BP,MAAM,CAACQ,gBAAgB,IAAI,IAC7B,CAAC;EAEDlC,aAAa,GAAG,IAAI;EACpBC,UAAU,GAAGyB,MAAM,CAACG,OAAO;EAC3B1B,eAAe,GAAGuB,MAAM,CAACZ,cAAc;EACvCqB,OAAA,CAAAvB,SAAA,GAAAA,SAAS,GAAGX,UAAU;EACtBkC,OAAA,CAAArB,cAAA,GAAAA,cAAc,GAAGX,eAAe;EAChCqB,oBAAoB,CAAC,CAAC;AACxB;;AAEA;AACA;AACA;AACA;AACO,SAASY,YAAYA,CAAA,EAAY;EACtC,OAAOpC,aAAa;AACtB;;AAEA;AACA;AACA;AACO,SAASqC,YAAYA,CAAA,EAAY;EACtC,OAAOpC,UAAU;AACnB;;AAEA;AACO,IAAIW,SAAS,GAAAuB,OAAA,CAAAvB,SAAA,GAAG,KAAK;AACrB,IAAIC,QAAuB,GAAAsB,OAAA,CAAAtB,QAAA,GAAG,IAAI;AAClC,IAAIC,cAA6B,GAAAqB,OAAA,CAAArB,cAAA,GAAG,IAAI;AACxC,IAAIC,OAAsB,GAAAoB,OAAA,CAAApB,OAAA,GAAG,IAAI;AACjC,IAAIC,gBAAgB,GAAAmB,OAAA,CAAAnB,gBAAA,GAAG,IAAI;AAClC;AACO,IAAIsB,qBAAqB,GAAAH,OAAA,CAAAG,qBAAA,GAAG,IAAI;AAChC,IAAIrB,QAA8B,GAAAkB,OAAA,CAAAlB,QAAA,GAAG,IAAI;AACzC,IAAIC,SAAsB,GAAAiB,OAAA,CAAAjB,SAAA,GAAG,IAAI;;AAExC;AACA;AACA;AACA;AACA;AACA;AACO,IAAIC,WAA0C,GAAAgB,OAAA,CAAAhB,WAAA,GAAG,IAAI;;AAE5D;AACA;AACA;AACA;AACA;AACO,eAAeoB,iBAAiBA,CAAA,EAAkB;EACvD,IAAIC,OAAO,IAAI,CAAC1C,kBAAkB,EAAE;EAEpC,IAAI;IACF,MAAM2C,KAAK,GAAG,MAAM3C,kBAAkB,CAAC4C,aAAa,CAAC,CAAC;;IAEtD;IACAzC,UAAU,GAAGwC,KAAK,CAAC7B,SAAS,IAAI,KAAK;IACrCV,SAAS,GAAGuC,KAAK,CAAC5B,QAAQ,IAAI,IAAI;IAClCV,eAAe,GAAGsC,KAAK,CAAC3B,cAAc,IAAI,IAAI;IAC9CV,QAAQ,GAAGqC,KAAK,CAAC1B,OAAO,IAAI,IAAI;IAChCV,iBAAiB,GAAGoC,KAAK,CAACzB,gBAAgB,IAAI,IAAI;;IAElD;IACAmB,OAAA,CAAAvB,SAAA,GAAAA,SAAS,GAAGX,UAAU;IACtBkC,OAAA,CAAAtB,QAAA,GAAAA,QAAQ,GAAGX,SAAS;IACpBiC,OAAA,CAAArB,cAAA,GAAAA,cAAc,GAAGX,eAAe;IAChCgC,OAAA,CAAApB,OAAA,GAAAA,OAAO,GAAGX,QAAQ;IAClB+B,OAAA,CAAAnB,gBAAA,GAAAA,gBAAgB,GAAGX,iBAAiB;IACpC;IACA8B,OAAA,CAAAG,qBAAA,GAAAA,qBAAqB,GAAGjC,iBAAiB;IAEzC,IAAIoC,KAAK,CAACE,cAAc,EAAE;MACxB,IAAI;QACFrC,SAAS,GAAGsC,IAAI,CAACC,KAAK,CAACJ,KAAK,CAACE,cAAc,CAAC;QAC5CR,OAAA,CAAAlB,QAAA,GAAAA,QAAQ,GAAGX,SAAS;MACtB,CAAC,CAAC,MAAM;QACNA,SAAS,GAAG,IAAI;QAChB6B,OAAA,CAAAlB,QAAA,GAAAA,QAAQ,GAAG,IAAI;MACjB;IACF,CAAC,MAAM;MACLX,SAAS,GAAG,IAAI;MAChB6B,OAAA,CAAAlB,QAAA,GAAAA,QAAQ,GAAG,IAAI;IACjB;IAEAV,UAAU,GAAGkC,KAAK,CAACK,UAAU,GAAG,IAAIC,IAAI,CAACN,KAAK,CAACK,UAAU,CAAC,GAAG,IAAI;IACjEX,OAAA,CAAAjB,SAAA,GAAAA,SAAS,GAAGX,UAAU;;IAEtB;IACAC,YAAY,GAAGiC,KAAK,CAACtB,WAAW,IAAI,IAAI;IACxCgB,OAAA,CAAAhB,WAAA,GAAAA,WAAW,GAAGX,YAAY;IAC1BgB,oBAAoB,CAAC,CAAC;EACxB,CAAC,CAAC,OAAOwB,KAAK,EAAE;IACdrB,OAAO,CAACC,IAAI,CAAC,yCAAyC,EAAEoB,KAAK,CAAC;IAC9D;EACF;AACF;;AAEA;AACA;AACA;AACO,eAAeC,mBAAmBA,CAAA,EAA+B;EACtE,IAAIT,OAAO,EAAE;IACX,OAAO;MAAEU,WAAW,EAAE,KAAK;MAAEC,MAAM,EAAE;IAAuC,CAAC;EAC/E;EAEA,IAAI,CAACrD,kBAAkB,EAAE;IACvB,MAAM,IAAIsD,KAAK,CAAC,sCAAsC,CAAC;EACzD;EAEA,MAAMC,MAAM,GAAG,MAAMvD,kBAAkB,CAACmD,mBAAmB,CAAC,CAAC;EAE7D,IAAII,MAAM,CAACV,cAAc,EAAE;IACzB,OAAO;MACLO,WAAW,EAAEG,MAAM,CAACH,WAAW;MAC/BjC,QAAQ,EAAE2B,IAAI,CAACC,KAAK,CAACQ,MAAM,CAACV,cAAc,CAAC;MAC3CW,UAAU,EAAED,MAAM,CAACC,UAAU;MAC7BC,YAAY,EAAEF,MAAM,CAACE;IACvB,CAAC;EACH;EAEA,OAAO;IACLL,WAAW,EAAEG,MAAM,CAACH,WAAW;IAC/BI,UAAU,EAAED,MAAM,CAACC,UAAU;IAC7BC,YAAY,EAAEF,MAAM,CAACE;EACvB,CAAC;AACH;;AAEA;AACA;AACA;AACO,eAAeC,gBAAgBA,CAAA,EAA+B;EACnE,IAAIhB,OAAO,EAAE;IACX,OAAO;MAAEiB,KAAK,EAAE;IAAM,CAAC;EACzB;EAEA,IAAI,CAAC3D,kBAAkB,EAAE;IACvB,MAAM,IAAIsD,KAAK,CAAC,sCAAsC,CAAC;EACzD;EAEA,MAAMC,MAAM,GAAG,MAAMvD,kBAAkB,CAAC0D,gBAAgB,CAAC,CAAC;EAE1D,IAAIH,MAAM,CAACV,cAAc,EAAE;IACzB,OAAO;MACLc,KAAK,EAAEJ,MAAM,CAACI,KAAK;MACnBxC,QAAQ,EAAE2B,IAAI,CAACC,KAAK,CAACQ,MAAM,CAACV,cAAc;IAC5C,CAAC;EACH;EAEA,OAAO;IACLc,KAAK,EAAEJ,MAAM,CAACI;EAChB,CAAC;AACH;;AAEA;AACA;AACA;AACO,eAAeC,WAAWA,CAAA,EAAkB;EACjD,IAAIlB,OAAO,EAAE;IACXb,OAAO,CAACgC,GAAG,CAAC,mDAAmD,CAAC;IAChE;EACF;EAEA,IAAI,CAAC7D,kBAAkB,EAAE;IACvB,MAAM,IAAIsD,KAAK,CAAC,sCAAsC,CAAC;EACzD;EAEA,MAAMtD,kBAAkB,CAAC4D,WAAW,CAAC,CAAC;AACxC;;AAEA;AACA;AACA;AACO,eAAeE,YAAYA,CAAA,EAAkB;EAClD,IAAI,CAAC3D,UAAU,EAAE;IACf;EACF;EAEA,IAAI,CAACH,kBAAkB,EAAE;IACvB;EACF;EAEA,MAAMA,kBAAkB,CAAC8D,YAAY,CAAC,CAAC;AACzC;;AAEA;AACA;AACA;AACO,eAAeC,mBAAmBA,CAACV,MAAc,EAAiB;EACvE,IAAI,CAAClD,UAAU,EAAE;IACf;EACF;EAEA,IAAI,CAACH,kBAAkB,EAAE;IACvB;EACF;EAEA,MAAMA,kBAAkB,CAAC+D,mBAAmB,CAACV,MAAM,CAAC;AACtD;;AAEA;AACO,eAAeW,0BAA0BA,CAC9CC,gBAAwB,EACxBC,SAAsD,EACpC;EAClB,IAAI,CAAClE,kBAAkB,EAAEmE,oBAAoB,EAAE,OAAO,KAAK;EAC3D,OAAOnE,kBAAkB,CAACmE,oBAAoB,CAC5CF,gBAAgB,EAChBC,SAAS,CAACE,GAAG,EACbF,SAAS,CAACG,KAAK,EACfH,SAAS,CAACI,GACZ,CAAC;AACH;;AAEA;AACA;AACA;AACO,SAASC,cAAcA,CAAA,EAAkB;EAC9C,IAAI,CAACpE,UAAU,IAAII,iBAAiB,IAAI,CAACC,SAAS,EAAE;IAClD,OAAO,IAAI;EACb;EACA,OAAQA,SAAS,CAACgE,QAAQ,EAAEC,KAAK,IAAe,IAAI;AACtD;;AAEA;AACA;AACA;AACO,SAASC,iBAAiBA,CAACC,UAAkB,EAAU;EAC5D,MAAMF,KAAK,GAAGF,cAAc,CAAC,CAAC;EAC9B,IAAIE,KAAK,EAAE;IACT,OAAO,GAAGE,UAAU,IAAIF,KAAK,EAAE;EACjC;EACA,OAAOE,UAAU;AACnB;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASC,iBAAiBA,CAACC,QAAgB,EAAiB;EACjE,IAAI,CAACnE,YAAY,EAAE;IACjB,OAAO,IAAI;EACb;EACA,OAAOA,YAAY,CAACmE,QAAQ,CAAC,IAAI,IAAI;AACvC;;AAEA;AACA;AACA;AACA;AACO,SAASC,cAAcA,CAAA,EAA2B;EACvD,OAAOpE,YAAY,GAAG;IAAE,GAAGA;EAAa,CAAC,GAAG,CAAC,CAAC;AAChD;;AAEA;AACA,IAAIqE,YAAuC,GAAG,IAAI;AAElD,SAASC,eAAeA,CAAA,EAAuB;EAC7C,IAAI,CAACD,YAAY,IAAI/E,kBAAkB,EAAE;IACvC+E,YAAY,GAAG,IAAIE,+BAAkB,CAACjF,kBAAyB,CAAC;EAClE;EACA,OAAO+E,YAAY;AACrB;AAcA;AACA;AACA;AACO,SAASG,iBAAiBA,CAC/B3D,QAAsC,EACd;EACxB,MAAM4D,OAAO,GAAGH,eAAe,CAAC,CAAC;EACjC,MAAMI,YAAY,GAAGD,OAAO,CAACE,WAAW,CAAC,mBAAmB,EAAE9D,QAAQ,CAAC;EACvE,OAAO;IACL+D,MAAM,EAAEA,CAAA,KAAMF,YAAY,CAACE,MAAM,CAAC;EACpC,CAAC;AACH","ignoreList":[]}
|