@nibgate/sdk 0.2.0 → 0.2.2

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.
@@ -0,0 +1,168 @@
1
+ import { normalizeResource } from '../core/resource.js';
2
+ import { browserWindow } from './env.js';
3
+ import { emit } from './events.js';
4
+ import { getPaymentProof, storePaymentProof } from './storage.js';
5
+ import { stringifyJson } from './json.js';
6
+ import { createGate } from './gate.js';
7
+ import { trackResourcePage } from './track.js';
8
+
9
+ export async function checkResourceAccess(resource, options = {}) {
10
+ const item = createGate(resource, options.gateOptions || {});
11
+ const accessPath = options.accessPath || item.resource.accessPath || '/api/nibgate/access';
12
+ const status = typeof options.onStatus === 'function' ? options.onStatus : () => {};
13
+
14
+ status(options.checkingMessage || 'Checking access route...');
15
+ item.unlockStarted({ source: options.source, paymentProvider: options.paymentProvider || 'nibgate-access-route' });
16
+
17
+ const response = await fetch(accessPath, {
18
+ method: options.method || 'GET',
19
+ headers: {
20
+ accept: 'application/json',
21
+ ...(getPaymentProof(item.resource) ? { 'x-nibgate-payment-proof': getPaymentProof(item.resource) } : {}),
22
+ ...(options.headers || {})
23
+ },
24
+ body: options.body
25
+ });
26
+ const payload = await response.json().catch(() => ({}));
27
+
28
+ if (response.status === 402) {
29
+ item.track('payment_challenge_returned', { source: options.source, challenge: payload, resource: item.resource });
30
+ status(options.challengeMessage || 'Payment challenge returned. Continue with checkout.');
31
+ if (typeof options.createPaymentSignature === 'function' || typeof options.checkout === 'function') {
32
+ return payWithPaymentSignature(resource, {
33
+ ...options, challenge: payload,
34
+ paymentRequiredHeader: response.headers.get('PAYMENT-REQUIRED') || response.headers.get('payment-required') || ''
35
+ });
36
+ }
37
+ if (options.autoPay && options.payPath) {
38
+ const paymentResult = await payAndUnlockResource(resource, options);
39
+ if (paymentResult.ok && options.retryAfterPay !== false) {
40
+ return checkResourceAccess(resource, { ...options, autoPay: false });
41
+ }
42
+ return paymentResult;
43
+ }
44
+ return { ok: false, status: response.status, challenge: payload, resource: item.resource, response };
45
+ }
46
+
47
+ if (!response.ok) {
48
+ status(payload.error || options.errorMessage || 'Access check failed');
49
+ return { ok: false, status: response.status, error: payload.error || 'Access check failed', payload, resource: item.resource, response };
50
+ }
51
+
52
+ const payment = options.payment || payload.payment || null;
53
+ if (payment) {
54
+ item.unlockCompleted(payment);
55
+ item.paymentCompleted(payment);
56
+ }
57
+ status(options.successMessage || 'Access allowed and Nibgate events emitted.');
58
+ return { ok: true, status: response.status, payload, payment, resource: item.resource, response };
59
+ }
60
+
61
+ export async function payWithPaymentSignature(resource, options = {}) {
62
+ const item = createGate(resource, options.gateOptions || {});
63
+ const accessPath = options.accessPath || item.resource.accessPath || '/api/nibgate/access';
64
+ const status = typeof options.onStatus === 'function' ? options.onStatus : () => {};
65
+
66
+ status(options.paymentMessage || 'Waiting for wallet payment approval...');
67
+ item.unlockStarted({ source: options.source, paymentProvider: options.paymentProvider || 'wallet-gateway' });
68
+
69
+ let paymentSignature = options.paymentSignature || '';
70
+ let paymentMemo = options.memo || '';
71
+ let paymentMetadata = options.payment || {};
72
+
73
+ if (!paymentSignature) {
74
+ const paymentRequiredHeader = options.paymentRequiredHeader || '';
75
+ const challenge = options.challenge || null;
76
+ const checkout = options.createPaymentSignature || options.checkout;
77
+ const result = await checkout({
78
+ resource: item.resource, challenge, paymentRequiredHeader, accessPath
79
+ });
80
+ paymentSignature = result?.paymentSignature || result?.signature || result?.payment || '';
81
+ paymentMemo = result?.memo || result?.paymentMemo || '';
82
+ paymentMetadata = result?.metadata || result?.paymentMetadata || result || {};
83
+ }
84
+
85
+ if (!paymentSignature) {
86
+ const error = 'Wallet did not return a payment signature.';
87
+ item.track('payment_failed', { source: options.source, error });
88
+ status(error);
89
+ return { ok: false, status: 400, error, resource: item.resource };
90
+ }
91
+
92
+ const response = await fetch(accessPath, {
93
+ method: options.method || 'GET',
94
+ headers: {
95
+ accept: 'application/json',
96
+ 'payment-signature': paymentSignature,
97
+ ...(paymentMemo ? { 'payment-memo': paymentMemo } : {}),
98
+ ...(options.headers || {})
99
+ }
100
+ });
101
+ const responseText = await response.text();
102
+ let payload = {};
103
+ try {
104
+ payload = responseText ? JSON.parse(responseText) : {};
105
+ } catch (_error) {
106
+ payload = { error: responseText || 'Payment verification failed' };
107
+ }
108
+
109
+ if (!response.ok) {
110
+ const detail = payload.detail || payload.reason || payload.invalidReason || payload.error || responseText || 'Payment verification failed';
111
+ const error = typeof detail === 'string' ? detail : stringifyJson(detail);
112
+ item.track('payment_failed', { source: options.source, status: response.status, error, ...paymentMetadata });
113
+ status(options.paymentErrorMessage || error);
114
+ return { ok: false, status: response.status, error, payload, resource: item.resource, response };
115
+ }
116
+
117
+ const payment = payload.payment || {
118
+ paymentProvider: options.paymentProvider || 'wallet-gateway',
119
+ paymentId: paymentSignature,
120
+ memo: paymentMemo,
121
+ amount: Number(item.resource.price || 0),
122
+ revenue: Number(item.resource.price || 0),
123
+ currency: item.resource.currency || 'USDC',
124
+ ...paymentMetadata
125
+ };
126
+ storePaymentProof(item.resource, payload.unlockProof);
127
+ item.markUnlocked(payment);
128
+ status(options.paymentSuccessMessage || 'Payment verified. Content unlocked.');
129
+ return { ok: true, status: response.status, payload, payment, resource: item.resource, response };
130
+ }
131
+
132
+ export async function payAndUnlockResource(resource, options = {}) {
133
+ const item = createGate(resource, options.gateOptions || {});
134
+ const payPath = options.payPath || item.resource.payPath || '/api/nibgate/pay';
135
+ const status = typeof options.onStatus === 'function' ? options.onStatus : () => {};
136
+
137
+ status(options.paymentMessage || 'Starting payment...');
138
+ item.unlockStarted({ source: options.source, paymentProvider: options.paymentProvider || 'circle-gateway' });
139
+
140
+ const response = await fetch(payPath, {
141
+ method: options.payMethod || 'POST',
142
+ headers: {
143
+ accept: 'application/json',
144
+ 'content-type': 'application/json',
145
+ ...(options.payHeaders || {})
146
+ },
147
+ body: JSON.stringify({ resource: item.resource, ...(options.payPayload || {}) })
148
+ });
149
+ const payload = await response.json().catch(() => ({}));
150
+
151
+ if (!response.ok || !payload.ok) {
152
+ item.track('payment_failed', { source: options.source, status: response.status, error: payload.error || 'Payment failed', detail: payload.detail || '' });
153
+ status(payload.detail || payload.error || options.paymentErrorMessage || 'Payment failed.');
154
+ return { ok: false, status: response.status, payload, resource: item.resource, response };
155
+ }
156
+
157
+ const payment = payload.payment || {
158
+ paymentProvider: options.paymentProvider || 'circle-gateway',
159
+ paymentId: payload.paymentId || `nibgate_payment_${Date.now()}`,
160
+ amount: Number(item.resource.price || 0),
161
+ revenue: Number(item.resource.price || 0),
162
+ currency: item.resource.currency || 'USDC'
163
+ };
164
+ storePaymentProof(item.resource, payload.unlockProof);
165
+ item.markUnlocked(payment);
166
+ status(options.paymentSuccessMessage || 'Payment verified. Content unlocked.');
167
+ return { ok: true, status: response.status, payload, payment, resource: item.resource, response };
168
+ }
@@ -0,0 +1,53 @@
1
+ import { normalizeResource } from '../core/resource.js';
2
+ import { browserWindow } from './env.js';
3
+ import { checkResourceAccess } from './access.js';
4
+
5
+ function setElementText(target, message) {
6
+ const win = browserWindow();
7
+ if (!target || !win) return;
8
+ const element = typeof target === 'string' ? win.document.querySelector(target) : target;
9
+ if (element) element.textContent = message || '';
10
+ }
11
+
12
+ function setElementDisabled(target, disabled) {
13
+ const win = browserWindow();
14
+ if (!target || !win) return;
15
+ const element = typeof target === 'string' ? win.document.querySelector(target) : target;
16
+ if (element && 'disabled' in element) element.disabled = Boolean(disabled);
17
+ }
18
+
19
+ export function createWalletCheckout(resource, options = {}) {
20
+ const normalized = normalizeResource(resource);
21
+ const accessPath = options.accessPath || normalized.accessPath || '/api/nibgate/access';
22
+ const button = options.button || null;
23
+ const statusTarget = options.status || null;
24
+ const status = typeof options.onStatus === 'function'
25
+ ? options.onStatus
26
+ : (message) => setElementText(statusTarget, message);
27
+ const checkout = options.checkout || options.createPaymentSignature || options.pay;
28
+
29
+ if (typeof checkout !== 'function') {
30
+ throw new Error('createWalletCheckout requires checkout/createPaymentSignature/pay callback for the active wallet or Gateway adapter.');
31
+ }
32
+
33
+ async function unlock(extra = {}) {
34
+ setElementDisabled(button, true);
35
+ try {
36
+ return await checkResourceAccess(normalized, {
37
+ ...options, ...extra, accessPath, createPaymentSignature: checkout, onStatus: status
38
+ });
39
+ } finally {
40
+ setElementDisabled(button, false);
41
+ }
42
+ }
43
+
44
+ function mount() {
45
+ const win = browserWindow();
46
+ if (!win || !button) return { unlock };
47
+ const element = typeof button === 'string' ? win.document.querySelector(button) : button;
48
+ if (element) element.addEventListener('click', () => unlock().catch((error) => status(error.message || 'Checkout failed.')));
49
+ return { unlock };
50
+ }
51
+
52
+ return { resource: normalized, unlock, mount };
53
+ }
@@ -0,0 +1,97 @@
1
+ import { normalizeResource, normalizeContentType } from '../core/resource.js';
2
+ import { ratingMessage } from '../core/rating.js';
3
+ import { emit, flushQueue, payloadWithResource } from './events.js';
4
+ import { setDefaultClient } from './gate.js';
5
+ import { createGate } from './gate.js';
6
+ import { checkResourceAccess, payWithPaymentSignature, payAndUnlockResource } from './access.js';
7
+ import { createWalletCheckout } from './checkout.js';
8
+ import { createEvmGatewayUnlock, createCircleGatewayBrowserAdapter } from './evm-gateway.js';
9
+ import { rateResource, createOnchainRating, mountRatingUI } from './rating-ui.js';
10
+ import { trackResourcePage, setupResourcePage } from './track.js';
11
+ import { createTransferCheckout, payWithTransfer } from './transfer.js';
12
+
13
+ export function createNibgate(defaults = {}) {
14
+ const defaultResource = defaults.resource ? normalizeResource(defaults.resource) : null;
15
+
16
+ function resourceWithDefaults(resource = {}) {
17
+ return normalizeResource({
18
+ ...(defaultResource || {}),
19
+ ...(typeof resource === 'string' ? { id: resource } : resource)
20
+ });
21
+ }
22
+
23
+ return {
24
+ content(resource, extra = {}) {
25
+ return emit('content_registered', payloadWithResource(resourceWithDefaults(resource), extra));
26
+ },
27
+ registerContent(resource, extra = {}) {
28
+ return emit('content_registered', payloadWithResource(resourceWithDefaults(resource), extra));
29
+ },
30
+ view(resource, extra = {}) {
31
+ return emit('resource_view', payloadWithResource(resourceWithDefaults(resource), extra));
32
+ },
33
+ track(eventName, payload = {}) {
34
+ return emit(eventName || 'custom', payload);
35
+ },
36
+ unlockStarted(resource, extra = {}) {
37
+ return emit('unlock_started', payloadWithResource(resourceWithDefaults(resource), extra));
38
+ },
39
+ unlockCompleted(resource, payment = {}) {
40
+ return emit('unlock_completed', payloadWithResource(resourceWithDefaults(resource), payment));
41
+ },
42
+ paymentCompleted(resource, payment = {}) {
43
+ return emit('payment_completed', payloadWithResource(resourceWithDefaults(resource), payment));
44
+ },
45
+ rateResource(resource, rating = {}, extra = {}) {
46
+ return rateResource(resourceWithDefaults(resource), rating, extra);
47
+ },
48
+ ratingMessage(resource, rating = {}, messageOptions = {}) {
49
+ return ratingMessage(resourceWithDefaults(resource), rating, messageOptions);
50
+ },
51
+ gate(resource, options = {}) {
52
+ return createGate(resourceWithDefaults(resource), { ...options, client: this });
53
+ },
54
+ trackResourcePage(resource, options = {}) {
55
+ return trackResourcePage(resourceWithDefaults(resource), options);
56
+ },
57
+ checkResourceAccess(resource, options = {}) {
58
+ return checkResourceAccess(resourceWithDefaults(resource), options);
59
+ },
60
+ payWithPaymentSignature(resource, options = {}) {
61
+ return payWithPaymentSignature(resourceWithDefaults(resource), options);
62
+ },
63
+ createWalletCheckout(resource, options = {}) {
64
+ return createWalletCheckout(resourceWithDefaults(resource), options);
65
+ },
66
+ createCircleGatewayBrowserAdapter(options = {}) {
67
+ return createCircleGatewayBrowserAdapter(options);
68
+ },
69
+ createTransferCheckout(resource, options = {}) {
70
+ return createTransferCheckout(resourceWithDefaults(resource), options);
71
+ },
72
+ payWithTransfer(resource, options = {}) {
73
+ return payWithTransfer(resourceWithDefaults(resource), options);
74
+ },
75
+ createEvmGatewayUnlock(resource, options = {}) {
76
+ return createEvmGatewayUnlock(resourceWithDefaults(resource), options);
77
+ },
78
+ createOnchainRating(resource, options = {}) {
79
+ return createOnchainRating(resourceWithDefaults(resource), options);
80
+ },
81
+ mountRatingUI(resource, options = {}) {
82
+ return mountRatingUI(resourceWithDefaults(resource), options);
83
+ },
84
+ payAndUnlockResource(resource, options = {}) {
85
+ return payAndUnlockResource(resourceWithDefaults(resource), options);
86
+ },
87
+ setupResourcePage(resource, options = {}) {
88
+ return setupResourcePage(resourceWithDefaults(resource), options);
89
+ },
90
+ normalizeResource: resourceWithDefaults,
91
+ normalizeContentType,
92
+ flush: flushQueue
93
+ };
94
+ }
95
+
96
+ export const nibgate = createNibgate();
97
+ setDefaultClient(nibgate);
@@ -0,0 +1,180 @@
1
+ import { normalizeResource } from '../core/resource.js';
2
+ import { browserWindow } from './env.js';
3
+ import { clearPaymentProof } from './storage.js';
4
+ import { stringifyJson } from './json.js';
5
+ import { createGate } from './gate.js';
6
+ import { checkResourceAccess } from './access.js';
7
+ import { trackResourcePage } from './track.js';
8
+
9
+ export async function createCircleGatewayBrowserAdapter(options = {}) {
10
+ const gateway = await import('./gateway.js');
11
+ return gateway.createCircleGatewayBrowserAdapter(options);
12
+ }
13
+
14
+ export function createEvmGatewayUnlock(resource, options = {}) {
15
+ const item = createGate(resource, options.gateOptions || {});
16
+ const win = browserWindow();
17
+ const accessPath = options.accessPath || item.resource.accessPath || '/api/nibgate/access';
18
+ const source = options.source || 'nibgate-evm-gateway';
19
+ const network = options.network || 'eip155:5042002';
20
+ const statusTarget = typeof options.status === 'string' ? win?.document.querySelector(options.status) : options.status;
21
+ const connectButton = typeof options.connectButton === 'string' ? win?.document.querySelector(options.connectButton) : options.connectButton;
22
+ const disconnectButton = typeof options.disconnectButton === 'string' ? win?.document.querySelector(options.disconnectButton) : options.disconnectButton;
23
+ const unlockButton = typeof options.unlockButton === 'string' ? win?.document.querySelector(options.unlockButton) : options.unlockButton;
24
+ const clearButton = typeof options.clearButton === 'string' ? win?.document.querySelector(options.clearButton) : options.clearButton;
25
+ const walletLabel = typeof options.walletLabel === 'string' ? win?.document.querySelector(options.walletLabel) : options.walletLabel;
26
+ const unlockedTarget = typeof options.unlockedTarget === 'string' ? win?.document.querySelector(options.unlockedTarget) : options.unlockedTarget;
27
+
28
+ let walletAddress = '';
29
+ let busy = false;
30
+
31
+ function setStatus(message) {
32
+ if (typeof options.onStatus === 'function') options.onStatus(message);
33
+ if (statusTarget) statusTarget.textContent = message || '';
34
+ }
35
+
36
+ function shortAddress(address) {
37
+ return address ? `${address.slice(0, 6)}...${address.slice(-4)}` : '';
38
+ }
39
+
40
+ function provider() {
41
+ return win?.ethereum || options.provider || null;
42
+ }
43
+
44
+ function setBusy(value) {
45
+ busy = Boolean(value);
46
+ [connectButton, disconnectButton, unlockButton, clearButton].forEach((button) => {
47
+ if (button && 'disabled' in button) {
48
+ button.disabled = busy || (button === connectButton && !provider()) || (button === disconnectButton && !walletAddress);
49
+ }
50
+ });
51
+ }
52
+
53
+ function renderWallet() {
54
+ const hasProvider = Boolean(provider());
55
+ if (walletLabel) walletLabel.textContent = walletAddress ? shortAddress(walletAddress) : hasProvider ? 'Ready to connect' : 'No wallet detected';
56
+ if (connectButton) connectButton.textContent = walletAddress ? 'Connected' : 'Connect wallet';
57
+ if (disconnectButton) disconnectButton.textContent = 'Disconnect';
58
+ if (connectButton && 'disabled' in connectButton) connectButton.disabled = busy || !hasProvider;
59
+ if (disconnectButton && 'disabled' in disconnectButton) disconnectButton.disabled = busy || !walletAddress;
60
+ }
61
+
62
+ function setUnlocked(isUnlocked, payment = {}) {
63
+ if (unlockButton) unlockButton.textContent = isUnlocked ? 'Unlocked' : `Unlock for ${item.resource.price} ${item.resource.currency || 'USDC'}`;
64
+ if (unlockedTarget) {
65
+ if ('hidden' in unlockedTarget) unlockedTarget.hidden = !isUnlocked;
66
+ unlockedTarget.setAttribute('aria-hidden', isUnlocked ? 'false' : 'true');
67
+ }
68
+ if (isUnlocked) item.markUnlocked(payment);
69
+ }
70
+
71
+ async function connect() {
72
+ setBusy(true);
73
+ setStatus('Opening wallet connection...');
74
+ try {
75
+ const evm = provider();
76
+ if (!evm) throw new Error(options.noWalletMessage || 'Install or open an EVM wallet to continue.');
77
+ const accounts = await evm.request({ method: 'eth_requestAccounts' });
78
+ walletAddress = Array.isArray(accounts) ? accounts[0] || '' : '';
79
+ if (!walletAddress) throw new Error('No wallet account selected.');
80
+ renderWallet();
81
+ setStatus('Wallet connected. You can unlock now.');
82
+ return walletAddress;
83
+ } finally {
84
+ setBusy(false);
85
+ }
86
+ }
87
+
88
+ async function disconnect() {
89
+ setBusy(true);
90
+ try {
91
+ const evm = provider();
92
+ if (evm?.request && walletAddress) {
93
+ try {
94
+ await evm.request({ method: 'wallet_revokePermissions', params: [{ eth_accounts: {} }] });
95
+ } catch (_error) { }
96
+ }
97
+ walletAddress = '';
98
+ renderWallet();
99
+ setStatus(options.disconnectMessage || 'Wallet disconnected for this page.');
100
+ return true;
101
+ } finally {
102
+ setBusy(false);
103
+ }
104
+ }
105
+
106
+ async function checkout(input) {
107
+ const evm = provider();
108
+ if (!evm) throw new Error(options.noWalletMessage || 'Install or open an EVM wallet to continue.');
109
+ if (!walletAddress) await connect();
110
+ const gatewayWallet = await createCircleGatewayBrowserAdapter({
111
+ network,
112
+ signer: {
113
+ address: walletAddress,
114
+ signTypedData: (typedData) => evm.request({ method: 'eth_signTypedData_v4', params: [walletAddress, stringifyJson(typedData)] })
115
+ },
116
+ clientModule: options.circleClientModule,
117
+ clientModuleUrl: options.circleClientModuleUrl
118
+ });
119
+ return gatewayWallet.pay(input);
120
+ }
121
+
122
+ async function unlock() {
123
+ setBusy(true);
124
+ try {
125
+ if (!walletAddress) await connect();
126
+ setBusy(true);
127
+ setStatus('Requesting Gateway unlock...');
128
+ const result = await checkResourceAccess(item.resource, {
129
+ accessPath, source,
130
+ paymentProvider: options.paymentProvider || 'circle-gateway-browser',
131
+ challengeMessage: options.challengeMessage || 'Gateway payment required. Connect your wallet to continue...',
132
+ paymentMessage: options.paymentMessage || 'Approve the Gateway payment proof in your wallet...',
133
+ successMessage: options.successMessage || `Unlocked ${item.resource.title || 'content'}.`,
134
+ checkout, onStatus: setStatus
135
+ });
136
+ if (result.ok) {
137
+ setUnlocked(true, result.payment || {});
138
+ if (typeof options.onUnlock === 'function') options.onUnlock(result);
139
+ }
140
+ return result;
141
+ } catch (error) {
142
+ const message = error?.message || 'Unlock failed. Please try again.';
143
+ setStatus(message);
144
+ return { ok: false, status: 0, error: message, resource: item.resource };
145
+ } finally {
146
+ setBusy(false);
147
+ renderWallet();
148
+ }
149
+ }
150
+
151
+ function clear() {
152
+ clearPaymentProof(item.resource);
153
+ setUnlocked(false);
154
+ setStatus('Local payment proof cleared. The next unlock will require Gateway payment again.');
155
+ }
156
+
157
+ async function hydrate() {
158
+ const evm = provider();
159
+ try {
160
+ const accounts = evm ? await evm.request({ method: 'eth_accounts' }) : [];
161
+ walletAddress = Array.isArray(accounts) ? accounts[0] || '' : '';
162
+ } catch { }
163
+ renderWallet();
164
+ setUnlocked(false);
165
+ }
166
+
167
+ function mount() {
168
+ connectButton?.addEventListener?.('click', () => connect().catch((error) => setStatus(error?.message || 'Could not connect wallet.')));
169
+ disconnectButton?.addEventListener?.('click', () => disconnect().catch((error) => setStatus(error?.message || 'Could not disconnect wallet.')));
170
+ unlockButton?.addEventListener?.('click', () => unlock());
171
+ clearButton?.addEventListener?.('click', clear);
172
+ hydrate();
173
+ trackResourcePage(item.resource, { source });
174
+ return controller;
175
+ }
176
+
177
+ const controller = { resource: item.resource, connect, disconnect, unlock, clear, hydrate, mount, getWalletAddress: () => walletAddress };
178
+ if (options.autoMount !== false) mount();
179
+ return controller;
180
+ }
@@ -0,0 +1,63 @@
1
+ import { normalizeResource } from '../core/resource.js';
2
+ import { emit, payloadWithResource } from './events.js';
3
+ import { hasUnlock, markUnlocked } from './storage.js';
4
+
5
+ let defaultClient = null;
6
+
7
+ export function setDefaultClient(client) {
8
+ defaultClient = client;
9
+ }
10
+
11
+ export function getDefaultClient() {
12
+ return defaultClient;
13
+ }
14
+
15
+ export function createGate(resource, options = {}) {
16
+ const normalized = normalizeResource(resource);
17
+ const client = options.client || defaultClient;
18
+
19
+ return {
20
+ resource: normalized,
21
+ content(extra = {}) {
22
+ return client.content(normalized, extra);
23
+ },
24
+ view(extra = {}) {
25
+ return client.view(normalized, extra);
26
+ },
27
+ track(eventName, payload = {}) {
28
+ return client.track(eventName, payloadWithResource(normalized, payload));
29
+ },
30
+ unlockStarted(extra = {}) {
31
+ return client.unlockStarted(normalized, extra);
32
+ },
33
+ unlockCompleted(payment = {}) {
34
+ markUnlocked(normalized, payment);
35
+ return client.unlockCompleted(normalized, payment);
36
+ },
37
+ paymentCompleted(payment = {}) {
38
+ return client.paymentCompleted(normalized, payment);
39
+ },
40
+ isUnlocked() {
41
+ return hasUnlock(normalized);
42
+ },
43
+ markUnlocked(payment = {}) {
44
+ markUnlocked(normalized, payment);
45
+ client.unlockCompleted(normalized, payment);
46
+ client.paymentCompleted(normalized, payment);
47
+ return true;
48
+ },
49
+ async unlock(handlerOrPayment = {}) {
50
+ client.unlockStarted(normalized);
51
+ const payment = typeof handlerOrPayment === 'function'
52
+ ? await handlerOrPayment(normalized)
53
+ : handlerOrPayment;
54
+ markUnlocked(normalized, payment || {});
55
+ client.unlockCompleted(normalized, payment || {});
56
+ client.paymentCompleted(normalized, payment || {});
57
+ return { unlocked: true, resource: normalized, payment: payment || {} };
58
+ },
59
+ rate(rating = {}, extra = {}) {
60
+ return client.rateResource(normalized, rating, extra);
61
+ }
62
+ };
63
+ }