@mysuperwall/react-native 0.0.0-stage → 1.2.0
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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +173 -2
- package/lib/MySuperwall.d.ts +67 -0
- package/lib/MySuperwall.js +261 -0
- package/lib/engine/localRuleEngine.d.ts +44 -0
- package/lib/engine/localRuleEngine.js +318 -0
- package/lib/hooks/usePaywall.d.ts +7 -0
- package/lib/hooks/usePaywall.js +25 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +27 -0
- package/lib/models/manifest.d.ts +76 -0
- package/lib/models/manifest.js +2 -0
- package/lib/models/paywall.d.ts +36 -0
- package/lib/models/paywall.js +2 -0
- package/lib/services/anonymousIdentity.d.ts +6 -0
- package/lib/services/anonymousIdentity.js +36 -0
- package/lib/services/followUpService.d.ts +9 -0
- package/lib/services/followUpService.js +26 -0
- package/lib/services/manifestService.d.ts +22 -0
- package/lib/services/manifestService.js +80 -0
- package/lib/services/offlineEventQueue.d.ts +18 -0
- package/lib/services/offlineEventQueue.js +101 -0
- package/lib/ui/PaywallModal.d.ts +8 -0
- package/lib/ui/PaywallModal.js +136 -0
- package/package.json +51 -4
- package/src/MySuperwall.ts +284 -0
- package/src/engine/localRuleEngine.ts +375 -0
- package/src/hooks/usePaywall.ts +27 -0
- package/src/index.ts +12 -0
- package/src/models/manifest.ts +81 -0
- package/src/models/paywall.ts +40 -0
- package/src/services/anonymousIdentity.ts +28 -0
- package/src/services/followUpService.ts +21 -0
- package/src/services/manifestService.ts +63 -0
- package/src/services/offlineEventQueue.ts +88 -0
- package/src/ui/PaywallModal.tsx +122 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ManifestService = void 0;
|
|
4
|
+
class ManifestService {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.scope = '';
|
|
7
|
+
this.generation = 0;
|
|
8
|
+
this.manifest = null;
|
|
9
|
+
this.asyncStorage = null;
|
|
10
|
+
try {
|
|
11
|
+
this.asyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
12
|
+
}
|
|
13
|
+
catch (_) { /* Memory-only operation remains supported. */ }
|
|
14
|
+
}
|
|
15
|
+
static scopeKey(options) {
|
|
16
|
+
return JSON.stringify([options.baseUrl.replace(/\/+$/, '') + '/', options.appKey || '',
|
|
17
|
+
options.bundleId || '', options.platform || 'ios', options.lang || 'en', JSON.stringify(options.substitutionData || {})]);
|
|
18
|
+
}
|
|
19
|
+
configure(options) {
|
|
20
|
+
const scope = ManifestService.scopeKey(options);
|
|
21
|
+
if (scope !== this.scope) {
|
|
22
|
+
this.scope = scope;
|
|
23
|
+
this.generation++;
|
|
24
|
+
this.manifest = null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
key(scope) { return '@mysuperwall_manifest_v2:' + encodeURIComponent(scope); }
|
|
28
|
+
async loadFromDisk() {
|
|
29
|
+
const scope = this.scope, generation = this.generation;
|
|
30
|
+
if (!scope || !this.asyncStorage)
|
|
31
|
+
return this.manifest;
|
|
32
|
+
try {
|
|
33
|
+
const raw = await this.asyncStorage.getItem(this.key(scope));
|
|
34
|
+
if (scope !== this.scope || generation !== this.generation)
|
|
35
|
+
return null;
|
|
36
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
37
|
+
this.manifest = parsed?.ok && parsed.paywalls && Array.isArray(parsed.triggers) ? parsed : null;
|
|
38
|
+
}
|
|
39
|
+
catch (_) { /* Never adopt a legacy or malformed cache. */ }
|
|
40
|
+
return this.manifest;
|
|
41
|
+
}
|
|
42
|
+
async saveToDisk(manifest) {
|
|
43
|
+
const scope = this.scope;
|
|
44
|
+
if (!scope)
|
|
45
|
+
return;
|
|
46
|
+
this.manifest = manifest;
|
|
47
|
+
try {
|
|
48
|
+
await this.asyncStorage?.setItem(this.key(scope), JSON.stringify(manifest));
|
|
49
|
+
}
|
|
50
|
+
catch (_) { }
|
|
51
|
+
}
|
|
52
|
+
async syncInBackground(options) {
|
|
53
|
+
this.configure(options);
|
|
54
|
+
const scope = this.scope, generation = this.generation;
|
|
55
|
+
try {
|
|
56
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
57
|
+
if (this.manifest?.hash)
|
|
58
|
+
headers['If-None-Match'] = `"${this.manifest.hash}"`;
|
|
59
|
+
const response = await fetch(options.baseUrl.replace(/\/+$/, '') + '/paywall/manifest', {
|
|
60
|
+
method: 'POST', headers, body: JSON.stringify({ platform: options.platform || 'ios',
|
|
61
|
+
appKey: options.appKey, bundleId: options.platform === 'android' ? undefined : options.bundleId,
|
|
62
|
+
packageName: options.platform === 'android' ? options.bundleId : undefined, lang: options.lang || 'en', hash: this.manifest?.hash, substitutionData: options.substitutionData || {} })
|
|
63
|
+
});
|
|
64
|
+
if (scope !== this.scope || generation !== this.generation)
|
|
65
|
+
return null;
|
|
66
|
+
if (response.status === 304)
|
|
67
|
+
return this.manifest;
|
|
68
|
+
if (response.ok) {
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
if (scope !== this.scope || generation !== this.generation)
|
|
71
|
+
return null;
|
|
72
|
+
if (!data.notModified && data.ok && data.paywalls && Array.isArray(data.triggers))
|
|
73
|
+
await this.saveToDisk(data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (_) { /* Keep this scope's cache on transient failure. */ }
|
|
77
|
+
return scope === this.scope && generation === this.generation ? this.manifest : null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.ManifestService = ManifestService;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class OfflineEventQueue {
|
|
2
|
+
private queueKey;
|
|
3
|
+
private asyncStorage;
|
|
4
|
+
private isFlushing;
|
|
5
|
+
private memoryQueue;
|
|
6
|
+
private mutation;
|
|
7
|
+
constructor(scope?: string);
|
|
8
|
+
private serialized;
|
|
9
|
+
private read;
|
|
10
|
+
private write;
|
|
11
|
+
enqueue(event: Record<string, any>): Promise<void>;
|
|
12
|
+
flush(options: {
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
appKey?: string;
|
|
15
|
+
bundleId?: string;
|
|
16
|
+
platform?: string;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OfflineEventQueue = void 0;
|
|
4
|
+
function eventId() {
|
|
5
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, char => {
|
|
6
|
+
const value = Math.floor(Math.random() * 16);
|
|
7
|
+
return (char === 'x' ? value : (value & 3) | 8).toString(16);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
class OfflineEventQueue {
|
|
11
|
+
constructor(scope) {
|
|
12
|
+
this.queueKey = '@mysuperwall_offline_event_queue_v1';
|
|
13
|
+
this.asyncStorage = null;
|
|
14
|
+
this.isFlushing = false;
|
|
15
|
+
this.memoryQueue = [];
|
|
16
|
+
this.mutation = Promise.resolve();
|
|
17
|
+
if (scope)
|
|
18
|
+
this.queueKey = '@mysuperwall_event_queue_v2:' + encodeURIComponent(scope);
|
|
19
|
+
try {
|
|
20
|
+
this.asyncStorage = require('@react-native-async-storage/async-storage').default;
|
|
21
|
+
}
|
|
22
|
+
catch (_) {
|
|
23
|
+
this.asyncStorage = null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
serialized(operation) {
|
|
27
|
+
const next = this.mutation.then(operation, operation);
|
|
28
|
+
this.mutation = next.catch(() => undefined);
|
|
29
|
+
return next;
|
|
30
|
+
}
|
|
31
|
+
async read() {
|
|
32
|
+
if (!this.asyncStorage)
|
|
33
|
+
return [...this.memoryQueue];
|
|
34
|
+
try {
|
|
35
|
+
const raw = await this.asyncStorage.getItem(this.queueKey);
|
|
36
|
+
const parsed = raw ? JSON.parse(raw) : [];
|
|
37
|
+
if (!Array.isArray(parsed))
|
|
38
|
+
return [];
|
|
39
|
+
const list = parsed.filter(item => item && typeof item === 'object').map(item => ({ ...item, eventId: item.eventId || eventId() }));
|
|
40
|
+
this.memoryQueue = list;
|
|
41
|
+
return list;
|
|
42
|
+
}
|
|
43
|
+
catch (_) {
|
|
44
|
+
this.asyncStorage = null;
|
|
45
|
+
return [...this.memoryQueue];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async write(list) {
|
|
49
|
+
this.memoryQueue = list;
|
|
50
|
+
if (this.asyncStorage) {
|
|
51
|
+
try {
|
|
52
|
+
await this.asyncStorage.setItem(this.queueKey, JSON.stringify(list));
|
|
53
|
+
}
|
|
54
|
+
catch (_) {
|
|
55
|
+
this.asyncStorage = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async enqueue(event) {
|
|
60
|
+
// IDs and context are captured before asynchronous persistence, once per event.
|
|
61
|
+
const captured = JSON.parse(JSON.stringify({ ...event, eventId: event.eventId || eventId(),
|
|
62
|
+
timestamp: event.timestamp || new Date().toISOString() }));
|
|
63
|
+
await this.serialized(async () => {
|
|
64
|
+
const list = await this.read();
|
|
65
|
+
await this.write([...list, captured].slice(-200));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async flush(options) {
|
|
69
|
+
if (this.isFlushing)
|
|
70
|
+
return;
|
|
71
|
+
this.isFlushing = true;
|
|
72
|
+
try {
|
|
73
|
+
const batch = await this.serialized(async () => {
|
|
74
|
+
const list = await this.read();
|
|
75
|
+
await this.write(list); // Persist upgraded legacy IDs before the request.
|
|
76
|
+
return list.slice(0, 100);
|
|
77
|
+
});
|
|
78
|
+
if (!batch.length)
|
|
79
|
+
return;
|
|
80
|
+
const base = options.baseUrl.endsWith('/') ? options.baseUrl : `${options.baseUrl}/`;
|
|
81
|
+
const response = await fetch(`${base}paywall/events/batch`, {
|
|
82
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
body: JSON.stringify({ platform: options.platform || 'react-native', appKey: options.appKey,
|
|
84
|
+
bundleId: options.platform === 'android' ? undefined : options.bundleId,
|
|
85
|
+
packageName: options.platform === 'android' ? options.bundleId : undefined, events: batch })
|
|
86
|
+
});
|
|
87
|
+
if (response.ok) {
|
|
88
|
+
const sent = new Set(batch.map(event => event.eventId));
|
|
89
|
+
await this.serialized(async () => {
|
|
90
|
+
const current = await this.read();
|
|
91
|
+
await this.write(current.filter(event => !sent.has(event.eventId)));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (_) { /* Retain the original IDs for retry. */ }
|
|
96
|
+
finally {
|
|
97
|
+
this.isFlushing = false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.OfflineEventQueue = OfflineEventQueue;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { PaywallData, PaywallCallbackActions } from '../models/paywall';
|
|
3
|
+
export interface PaywallModalProps extends PaywallCallbackActions {
|
|
4
|
+
visible: boolean;
|
|
5
|
+
paywall: PaywallData | null;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const PaywallModal: FC<PaywallModalProps>;
|
|
@@ -0,0 +1,136 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PaywallModal = void 0;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const MySuperwall_1 = require("../MySuperwall");
|
|
39
|
+
const react_native_1 = require("react-native");
|
|
40
|
+
const PaywallModal = props => {
|
|
41
|
+
if (!props.visible || !props.paywall)
|
|
42
|
+
return null;
|
|
43
|
+
// Remount the displayed session when HTML or attribution changes, even if the
|
|
44
|
+
// paywall ID stays the same. Freeze the payload to the HTML sent to WebView.
|
|
45
|
+
const captured = JSON.parse(JSON.stringify(props.paywall));
|
|
46
|
+
const sessionKey = JSON.stringify(captured);
|
|
47
|
+
return react_1.default.createElement(PaywallModalContent, { key: sessionKey, ...props, paywall: captured });
|
|
48
|
+
};
|
|
49
|
+
exports.PaywallModal = PaywallModal;
|
|
50
|
+
const PaywallModalContent = ({ visible, paywall, onClose, onPurchase, onRestore, onDismiss, onOpenUrl }) => {
|
|
51
|
+
const shown = (0, react_1.useRef)(false);
|
|
52
|
+
const dismissed = (0, react_1.useRef)(false);
|
|
53
|
+
if (!visible || !paywall)
|
|
54
|
+
return null;
|
|
55
|
+
const track = (type, productId) => {
|
|
56
|
+
void MySuperwall_1.MySuperwall.shared.trackPaywallEvent(type, paywall, productId);
|
|
57
|
+
};
|
|
58
|
+
const dismiss = () => {
|
|
59
|
+
if (dismissed.current)
|
|
60
|
+
return;
|
|
61
|
+
dismissed.current = true;
|
|
62
|
+
track('paywall_dismissed');
|
|
63
|
+
onClose();
|
|
64
|
+
void onDismiss?.();
|
|
65
|
+
};
|
|
66
|
+
let WebViewComponent = null;
|
|
67
|
+
try {
|
|
68
|
+
WebViewComponent = require('react-native-webview').WebView;
|
|
69
|
+
}
|
|
70
|
+
catch (_) {
|
|
71
|
+
console.warn('[MySuperwall] react-native-webview is required to render paywalls.');
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const injectedJavaScript = `
|
|
75
|
+
window.MySuperWall = {
|
|
76
|
+
postMessage: function(msg) {
|
|
77
|
+
if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
|
|
78
|
+
window.ReactNativeWebView.postMessage(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
true;
|
|
83
|
+
`;
|
|
84
|
+
const handleMessage = (event) => {
|
|
85
|
+
try {
|
|
86
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
87
|
+
const action = typeof data.action === 'string' ? data.action : data.type;
|
|
88
|
+
const type = typeof action === 'string' ? action.toLowerCase() : '';
|
|
89
|
+
switch (type) {
|
|
90
|
+
case 'purchase': {
|
|
91
|
+
const productId = data.productId || data.product_id;
|
|
92
|
+
if (typeof productId === 'string' && productId.trim()) {
|
|
93
|
+
track('purchase_started', productId);
|
|
94
|
+
if (onPurchase)
|
|
95
|
+
onPurchase(productId);
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case 'restore': {
|
|
100
|
+
track('restore_started');
|
|
101
|
+
if (onRestore)
|
|
102
|
+
onRestore();
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case 'close':
|
|
106
|
+
case 'dismiss': {
|
|
107
|
+
dismiss();
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case 'open_url':
|
|
111
|
+
case 'openurl': {
|
|
112
|
+
if (typeof data.url === 'string' && data.url.trim() && onOpenUrl)
|
|
113
|
+
onOpenUrl(data.url);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (_) { }
|
|
119
|
+
};
|
|
120
|
+
return (react_1.default.createElement(react_native_1.Modal, { visible: visible, animationType: "slide", transparent: false, onRequestClose: dismiss },
|
|
121
|
+
react_1.default.createElement(react_native_1.SafeAreaView, { style: styles.container },
|
|
122
|
+
react_1.default.createElement(WebViewComponent, { originWhitelist: ['*'], source: { html: paywall.html, baseUrl: 'https://mysuperwall.com' }, injectedJavaScriptBeforeContentLoaded: injectedJavaScript, onMessage: handleMessage, onLoad: () => { if (!shown.current) {
|
|
123
|
+
shown.current = true;
|
|
124
|
+
track('paywall_shown');
|
|
125
|
+
} }, style: styles.webview, javaScriptEnabled: true, domStorageEnabled: true, bounces: false }))));
|
|
126
|
+
};
|
|
127
|
+
const styles = react_native_1.StyleSheet.create({
|
|
128
|
+
container: {
|
|
129
|
+
flex: 1,
|
|
130
|
+
backgroundColor: '#000000'
|
|
131
|
+
},
|
|
132
|
+
webview: {
|
|
133
|
+
flex: 1,
|
|
134
|
+
backgroundColor: 'transparent'
|
|
135
|
+
}
|
|
136
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mysuperwall/react-native",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "React Native SDK for MySuperWall paywalls, targeting and offline rule evaluation",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --test test/*.test.js",
|
|
9
|
+
"build": "tsc -p tsconfig.build.json",
|
|
10
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
11
|
+
"prepack": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"react-native",
|
|
15
|
+
"mysuperwall",
|
|
16
|
+
"paywall",
|
|
17
|
+
"remote-config",
|
|
18
|
+
"in-app-purchases"
|
|
19
|
+
],
|
|
20
|
+
"author": "MySuperwall",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": ">=17.0.0",
|
|
24
|
+
"react-native": ">=0.64.0",
|
|
25
|
+
"react-native-webview": ">=11.0.0",
|
|
26
|
+
"@react-native-async-storage/async-storage": ">=1.15.0"
|
|
27
|
+
},
|
|
28
|
+
"react-native": "src/index.ts",
|
|
29
|
+
"files": [
|
|
30
|
+
"lib",
|
|
31
|
+
"src",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"CHANGELOG.md"
|
|
35
|
+
],
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"react": "18.2.0",
|
|
40
|
+
"@types/react": "^18.2.0",
|
|
41
|
+
"react-native": "0.73.4",
|
|
42
|
+
"react-native-webview": "13.8.1",
|
|
43
|
+
"@react-native-async-storage/async-storage": "1.21.0"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://mysuperwall.com",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/Unevenn/mysuperwall_react_native.git"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|