@galva-io/galva-admin-node 1.0.0-dev
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 +218 -0
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +344 -0
- package/dist/index.d.ts +344 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Galva Admin Node SDK
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for integrating with Galva Admin API. This SDK provides a type-safe way to sync billing events and manage user data.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install galva-admin-node
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import galvaAdmin from 'galva-admin-node';
|
|
15
|
+
|
|
16
|
+
const galva = galvaAdmin({
|
|
17
|
+
apiKey: 'your-api-key-here',
|
|
18
|
+
baseUrl: 'https://api.galva.io', // optional
|
|
19
|
+
debug: true // optional, enables logging
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage Examples
|
|
24
|
+
|
|
25
|
+
### Track a Purchase
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
await galva.trackPurchase({
|
|
29
|
+
id: 'evt_123',
|
|
30
|
+
endUserId: 'user_456',
|
|
31
|
+
appIdentifier: 'com.example.app',
|
|
32
|
+
productIdentifier: 'premium_monthly',
|
|
33
|
+
planIdentifier: 'monthly',
|
|
34
|
+
platform: 'ios',
|
|
35
|
+
eventTimestamp: new Date().toISOString(),
|
|
36
|
+
purchasedAt: new Date().toISOString(),
|
|
37
|
+
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
38
|
+
isTrial: false
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Track a Renewal
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
await galva.trackRenewal({
|
|
46
|
+
id: 'evt_124',
|
|
47
|
+
endUserId: 'user_456',
|
|
48
|
+
appIdentifier: 'com.example.app',
|
|
49
|
+
productIdentifier: 'premium_monthly',
|
|
50
|
+
planIdentifier: 'monthly',
|
|
51
|
+
platform: 'ios',
|
|
52
|
+
eventTimestamp: new Date().toISOString(),
|
|
53
|
+
purchasedAt: new Date().toISOString(),
|
|
54
|
+
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Track a Cancellation
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
await galva.trackCancellation({
|
|
62
|
+
id: 'evt_125',
|
|
63
|
+
endUserId: 'user_456',
|
|
64
|
+
appIdentifier: 'com.example.app',
|
|
65
|
+
productIdentifier: 'premium_monthly',
|
|
66
|
+
planIdentifier: 'monthly',
|
|
67
|
+
platform: 'ios',
|
|
68
|
+
eventTimestamp: new Date().toISOString(),
|
|
69
|
+
cancelReason: 'too_expensive',
|
|
70
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Identify a User
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
await galva.identify({
|
|
78
|
+
userId: 'user_456',
|
|
79
|
+
email: 'user@example.com',
|
|
80
|
+
name: 'John Doe',
|
|
81
|
+
attributes: {
|
|
82
|
+
plan: 'premium',
|
|
83
|
+
signupDate: '2024-01-01',
|
|
84
|
+
referralSource: 'organic'
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Sync Multiple Events (Batch)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const events = [
|
|
93
|
+
{
|
|
94
|
+
id: 'evt_126',
|
|
95
|
+
type: 'initial-purchase' as const,
|
|
96
|
+
endUserId: 'user_789',
|
|
97
|
+
appIdentifier: 'com.example.app',
|
|
98
|
+
productIdentifier: 'premium_yearly',
|
|
99
|
+
planIdentifier: 'yearly',
|
|
100
|
+
platform: 'android' as const,
|
|
101
|
+
eventTimestamp: new Date().toISOString(),
|
|
102
|
+
purchasedAt: new Date().toISOString(),
|
|
103
|
+
expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString()
|
|
104
|
+
},
|
|
105
|
+
// ... more events
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
await galva.syncEvents(events);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Queue Events for Batch Processing
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Queue events (they'll be automatically batched)
|
|
115
|
+
galva.queueEvent({
|
|
116
|
+
id: 'evt_127',
|
|
117
|
+
type: 'renewal',
|
|
118
|
+
endUserId: 'user_123',
|
|
119
|
+
appIdentifier: 'com.example.app',
|
|
120
|
+
productIdentifier: 'premium_monthly',
|
|
121
|
+
planIdentifier: 'monthly',
|
|
122
|
+
platform: 'stripe',
|
|
123
|
+
eventTimestamp: new Date().toISOString(),
|
|
124
|
+
purchasedAt: new Date().toISOString()
|
|
125
|
+
}, {
|
|
126
|
+
maxBatchSize: 50, // Flush after 50 events
|
|
127
|
+
flushInterval: 10000 // Flush every 10 seconds
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Manually flush queued events
|
|
131
|
+
await galva.flush();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## API Reference
|
|
135
|
+
|
|
136
|
+
### Configuration
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
interface GalvaConfig {
|
|
140
|
+
apiKey: string; // Required: Your API key
|
|
141
|
+
baseUrl?: string; // Optional: API base URL (default: https://api.galva.io)
|
|
142
|
+
timeout?: number; // Optional: Request timeout in ms (default: 30000)
|
|
143
|
+
debug?: boolean; // Optional: Enable debug logging (default: false)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Methods
|
|
148
|
+
|
|
149
|
+
#### Event Tracking
|
|
150
|
+
|
|
151
|
+
- `trackPurchase(event)` - Track an initial purchase event
|
|
152
|
+
- `trackRenewal(event)` - Track a subscription renewal
|
|
153
|
+
- `trackCancellation(event)` - Track a subscription cancellation
|
|
154
|
+
- `trackBillingIssue(event)` - Track a billing issue
|
|
155
|
+
- `trackExpiration(event)` - Track a subscription expiration
|
|
156
|
+
- `trackUpgrade(event)` - Track a plan upgrade
|
|
157
|
+
- `trackRefund(event)` - Track a refund
|
|
158
|
+
|
|
159
|
+
#### Generic Event Methods
|
|
160
|
+
|
|
161
|
+
- `syncEvent(event)` - Sync a single billing event
|
|
162
|
+
- `syncEvents(events)` - Batch sync multiple events
|
|
163
|
+
- `queueEvent(event, options?)` - Queue an event for batch processing
|
|
164
|
+
- `flush()` - Manually flush the event queue
|
|
165
|
+
|
|
166
|
+
#### User Management
|
|
167
|
+
|
|
168
|
+
- `identify(userData)` - Identify a user and sync their attributes
|
|
169
|
+
- `getUserStatus(userId)` - Get a user's subscription status
|
|
170
|
+
|
|
171
|
+
#### Utility
|
|
172
|
+
|
|
173
|
+
- `healthCheck()` - Check API health status
|
|
174
|
+
|
|
175
|
+
### Event Types
|
|
176
|
+
|
|
177
|
+
The SDK supports the following billing event types:
|
|
178
|
+
|
|
179
|
+
- `initial-purchase` - New subscription or trial
|
|
180
|
+
- `renewal` - Subscription renewal
|
|
181
|
+
- `cancellation` - Subscription cancellation
|
|
182
|
+
- `billing-issue` - Payment failure or issue
|
|
183
|
+
- `expiration` - Subscription expiration
|
|
184
|
+
- `upgraded` - Plan upgrade
|
|
185
|
+
- `refund` - Refund processed
|
|
186
|
+
- `resubscribe` - User resubscribed
|
|
187
|
+
- `transfer` - Subscription transferred between users
|
|
188
|
+
- `grace_period_start` - Grace period started
|
|
189
|
+
|
|
190
|
+
### Error Handling
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { GalvaError } from 'galva-admin-node';
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
await galva.trackPurchase(eventData);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (error instanceof GalvaError) {
|
|
199
|
+
console.error('Galva API Error:', {
|
|
200
|
+
code: error.code,
|
|
201
|
+
message: error.message,
|
|
202
|
+
statusCode: error.statusCode,
|
|
203
|
+
details: error.details
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
206
|
+
console.error('Unexpected error:', error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Requirements
|
|
212
|
+
|
|
213
|
+
- Node.js 18.0 or higher (for native fetch support)
|
|
214
|
+
- TypeScript 5.0 or higher (for development)
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
'use strict';var googleapis=require('googleapis'),paddleNodeSdk=require('@paddle/paddle-node-sdk');var T=n=>{let e=new googleapis.google.auth.JWT({email:n.email,key:n.key.replace(/\\n/g,`
|
|
2
|
+
`),scopes:["https://www.googleapis.com/auth/androidpublisher"]});return googleapis.google.androidpublisher({version:"v3",auth:e})},p=class{static async getSubscription(e,t,a){return await T({email:a.email,key:a.key}).purchases.subscriptionsv2.get({token:e,packageName:t})}};var u=(o=>(o.TIMEZONE="$gv_timezone",o.LANGUAGE_CODE="$gv_languageCode",o.EMAIL="$gv_email",o.FULL_NAME="$gv_fullName",o.FIRST_NAME="$gv_firstName",o.LAST_NAME="$gv_lastName",o.COUNTRY="$gv_country",o.TOTAL_LIFETIME_VALUE="$gv_totalLifetimeValue",o))(u||{}),N={$gv_timezone:"timezone",$gv_languageCode:"languageCode",$gv_email:"email",$gv_fullName:"fullName",$gv_firstName:"firstName",$gv_lastName:"lastName",$gv_country:"country",$gv_totalLifetimeValue:"totalLifetimeValue"},m={timezone:"$gv_timezone",languageCode:"$gv_languageCode",email:"$gv_email",fullName:"$gv_fullName",firstName:"$gv_firstName",lastName:"$gv_lastName",country:"$gv_country",totalLifetimeValue:"$gv_totalLifetimeValue"};var s=class n extends Error{code;constructor(e){super(e.message),this.name="GalvaError",this.code=e.code,Error.captureStackTrace&&Error.captureStackTrace(this,n);}static from(e){if(e instanceof n)return e;let t=e instanceof Error?e.message:"An unknown error occurred";return new n({code:"UNKNOWN_ERROR",message:t})}};var A="https://api.galva.io",U="https://api.galva.dev",c=class{config;baseUrl;constructor(e){let t=e?.apiKey||process.env.GALVA_API_KEY;if(!t)throw new s({code:"MISSING_API_KEY",message:"API key is required. Provide it in options or set GALVA_API_KEY env variable."});this.config={apiKey:t,environment:e?.environment||(process.env.NODE_ENV==="development"?"development":"production"),timeout:e?.timeout||1e4},this.baseUrl=this.config.environment==="development"?U:A;}async sendRequest(e,t,a){let r=`${this.baseUrl}${t}`,i=new AbortController,l=setTimeout(()=>i.abort(),this.config.timeout);try{let d=await fetch(r,{method:e,headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:a?JSON.stringify(a):void 0,signal:i.signal});if(!d.ok){let o=await d.json();throw new s(o)}}catch(d){throw d instanceof s?d:d instanceof Error&&d.name==="AbortError"?new s({code:"TIMEOUT",message:`Request timed out after ${this.config.timeout}ms`}):s.from(d)}finally{clearTimeout(l);}}endUser={identify:async(e,t)=>{await this.sendRequest("POST","/endUsers:identify",{endUserId:e,timestamp:t?.timestamp,context:t?.context,traits:t?.traits});},updateDefaultInfo:async(e,t)=>{let a={};for(let[r,i]of Object.entries(t))if(i!==void 0){let l=m[r];a[l]=i;}await this.sendRequest("POST","/endUsers:identify",{endUserId:e,traits:a});}}},E=class extends c{credentials;constructor(e,t){super(e),this.credentials=t;}billingEvent={appstore:async(e,t,a)=>{if(!this.credentials.appstore)throw new s({code:"MISSING_CREDENTIALS",message:"App Store credentials are required. Provide them in withCredentials()."});await this.sendRequest("POST","/endUsers/billingEvents",{platform:"appstore",endUserId:e,payload:{signedPayload:t,appAppleId:this.credentials.appstore.appAppleId,bundleId:this.credentials.appstore.bundleId,env:this.config.environment},options:a});},playstore:async(e,t)=>{if(!this.credentials.playstore)throw new s({code:"MISSING_CREDENTIALS",message:"Play Store credentials are required. Provide them in withCredentials()."});let a=Buffer.from(t,"base64").toString(),r=null;try{r=JSON.parse(a);}catch{throw new s({code:"INVALID_PAYLOAD",message:"Invalid base64 payload: unable to parse JSON."})}if(!r||typeof r!="object")throw new s({code:"INVALID_PAYLOAD",message:"Decoded payload is not a valid JSON object."});if(!("subscriptionNotification"in r))throw new s({code:"INVALID_PAYLOAD",message:"Decoded payload does not contain subscriptionNotification field."});let i=await p.getSubscription(r.subscriptionNotification.purchaseToken,r.packageName,this.credentials.playstore),l={...r,subscriptionNotification:{...r.subscriptionNotification,subscriptionPurchase:i.data}};await this.sendRequest("POST","/endUsers/billingEvents",{platform:"playstore",endUserId:e,payload:l});},paddle:async(e,t,a)=>{if(!this.credentials.paddle)throw new s({code:"MISSING_CREDENTIALS",message:"Paddle credentials are required. Provide them in withCredentials()."});let r=new paddleNodeSdk.Paddle(this.credentials.paddle.apiKey),i;try{i=await r.webhooks.unmarshal(t,this.credentials.paddle.secretKey,a);}catch(l){throw new s({code:"INVALID_WEBHOOK",message:`Invalid Paddle webhook data: ${l.message}`})}await this.sendRequest("POST","/endUsers/billingEvents",{platform:"paddle",endUserId:e,payload:i});}}},g=class extends c{constructor(e){super(e);}billingEvent={appstore:async(e,t,a)=>{let{signedPayload:r,bundleId:i,appAppleId:l}=t;await this.sendRequest("POST","/endUsers/billingEvents",{platform:"appstore",endUserId:e,payload:{signedPayload:r,appAppleId:l,bundleId:i,env:this.config.environment},options:a});},playstore:async(e,t)=>{await this.sendRequest("POST","/endUsers/billingEvents",{platform:"playstore",endUserId:e,payload:t});},paddle:async(e,t)=>{await this.sendRequest("POST","/endUsers/billingEvents",{platform:"paddle",endUserId:e,payload:t});}};withCredentials(e){return new E(this.config,e)}};
|
|
3
|
+
exports.END_USER_DEFAULT_INFO_TO_TRAIT_MAP=m;exports.END_USER_DEFAULT_TRAIT_MAP=N;exports.EndUserDefaultTraitName=u;exports.Galva=g;exports.GalvaError=s;//# sourceMappingURL=index.cjs.map
|
|
4
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/playstore.ts","../src/types/endUser.ts","../src/types/error.ts","../src/galva.ts"],"names":["authentication","payload","jwtClient","google","PlaystoreService","token","packageName","cred","EndUserDefaultTraitName","END_USER_DEFAULT_TRAIT_MAP","END_USER_DEFAULT_INFO_TO_TRAIT_MAP","GalvaError","_GalvaError","response","error","message","PRODUCTION_API_URL","DEVELOPMENT_API_URL","GalvaBase","options","apiKey","method","endpoint","data","url","controller","timeoutId","errorResponse","endUserId","defaultInfo","traits","key","value","traitName","GalvaWithCreds","config","credentials","signedPayload","base64Payload","decodedPayloadString","decodedPayload","subscription","finalPayload","rawBody","signature","paddle","Paddle","eventData","Galva","bundleId","appAppleId","eventEntity"],"mappings":"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","file":"index.cjs","sourcesContent":["import { PlaystoreCredentials } from '@/galva';\nimport { google } from 'googleapis';\n\nconst authentication = (payload: { email: string; key: string }) => {\n const jwtClient = new google.auth.JWT({\n email: payload.email,\n key: payload.key.replace(/\\\\n/g, '\\n'),\n scopes: ['https://www.googleapis.com/auth/androidpublisher'],\n });\n return google.androidpublisher({\n version: 'v3',\n auth: jwtClient,\n });\n};\n\nexport class PlaystoreService {\n static async getSubscription(\n token: string,\n packageName: string,\n cred: PlaystoreCredentials,\n ): Promise<any> {\n const client = authentication({\n email: cred.email,\n key: cred.key,\n });\n\n const result = await client.purchases.subscriptionsv2.get({\n token,\n packageName,\n });\n\n return result;\n }\n}\n","/**\n * Default trait names for end user profiles.\n * Use these constants for type-safe access to built-in traits.\n */\nexport enum EndUserDefaultTraitName {\n TIMEZONE = \"$gv_timezone\",\n LANGUAGE_CODE = \"$gv_languageCode\",\n EMAIL = \"$gv_email\",\n FULL_NAME = \"$gv_fullName\",\n FIRST_NAME = \"$gv_firstName\",\n LAST_NAME = \"$gv_lastName\",\n COUNTRY = \"$gv_country\",\n TOTAL_LIFETIME_VALUE = \"$gv_totalLifetimeValue\",\n}\n\n/**\n * Built-in trait types with their expected value types.\n */\nexport interface EndUserDefaultTraits {\n [EndUserDefaultTraitName.TIMEZONE]?: string;\n [EndUserDefaultTraitName.LANGUAGE_CODE]?: string;\n [EndUserDefaultTraitName.EMAIL]?: string;\n [EndUserDefaultTraitName.FULL_NAME]?: string;\n [EndUserDefaultTraitName.FIRST_NAME]?: string;\n [EndUserDefaultTraitName.LAST_NAME]?: string;\n [EndUserDefaultTraitName.COUNTRY]?: string;\n [EndUserDefaultTraitName.TOTAL_LIFETIME_VALUE]?: number;\n}\n\n/**\n * End user traits combining default traits with arbitrary custom traits.\n */\nexport type EndUserTraits = EndUserDefaultTraits & Record<string, any>;\n\n/**\n * Friendly parameter names for updating end user default info.\n * Maps to the underlying $gv_ prefixed trait names.\n */\nexport interface EndUserDefaultInfo {\n timezone?: string;\n languageCode?: string;\n email?: string;\n fullName?: string;\n firstName?: string;\n lastName?: string;\n country?: string;\n totalLifetimeValue?: number;\n}\n\n/**\n * Mapping from friendly parameter names to original trait names.\n * TypeScript ensures this mapping contains all keys from EndUserDefaultTraitName.\n */\nexport const END_USER_DEFAULT_TRAIT_MAP: Record<\n EndUserDefaultTraitName,\n keyof EndUserDefaultInfo\n> = {\n [EndUserDefaultTraitName.TIMEZONE]: \"timezone\",\n [EndUserDefaultTraitName.LANGUAGE_CODE]: \"languageCode\",\n [EndUserDefaultTraitName.EMAIL]: \"email\",\n [EndUserDefaultTraitName.FULL_NAME]: \"fullName\",\n [EndUserDefaultTraitName.FIRST_NAME]: \"firstName\",\n [EndUserDefaultTraitName.LAST_NAME]: \"lastName\",\n [EndUserDefaultTraitName.COUNTRY]: \"country\",\n [EndUserDefaultTraitName.TOTAL_LIFETIME_VALUE]: \"totalLifetimeValue\",\n};\n\n/**\n * Reverse mapping from friendly parameter names to trait names.\n * TypeScript ensures this mapping contains all keys from EndUserDefaultInfo.\n */\nexport const END_USER_DEFAULT_INFO_TO_TRAIT_MAP: Record<\n keyof EndUserDefaultInfo,\n EndUserDefaultTraitName\n> = {\n timezone: EndUserDefaultTraitName.TIMEZONE,\n languageCode: EndUserDefaultTraitName.LANGUAGE_CODE,\n email: EndUserDefaultTraitName.EMAIL,\n fullName: EndUserDefaultTraitName.FULL_NAME,\n firstName: EndUserDefaultTraitName.FIRST_NAME,\n lastName: EndUserDefaultTraitName.LAST_NAME,\n country: EndUserDefaultTraitName.COUNTRY,\n totalLifetimeValue: EndUserDefaultTraitName.TOTAL_LIFETIME_VALUE,\n};\n","/**\n * Error response structure from Galva API.\n */\nexport interface GalvaErrorResponse {\n code: string;\n message: string;\n}\n\n/**\n * Custom error class for Galva SDK errors.\n * Extends Error with additional properties from API error responses.\n */\nexport class GalvaError extends Error {\n /** Error code from the API (e.g., 'BAD_REQUEST', 'UNAUTHORIZED') */\n readonly code: string;\n\n constructor(response: GalvaErrorResponse) {\n super(response.message);\n this.name = \"GalvaError\";\n this.code = response.code;\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, GalvaError);\n }\n }\n\n /**\n * Creates a GalvaError from an unknown error.\n * If the error is already a GalvaError, returns it as-is.\n * Otherwise, wraps it in a GalvaError with UNKNOWN_ERROR code.\n */\n static from(error: unknown): GalvaError {\n if (error instanceof GalvaError) {\n return error;\n }\n\n const message =\n error instanceof Error ? error.message : \"An unknown error occurred\";\n\n return new GalvaError({\n code: \"UNKNOWN_ERROR\",\n message,\n });\n }\n}\n","import { PlaystoreDeveloperNotification } from './types/playstore';\nimport { PlaystoreService } from './services/playstore';\nimport { EventEntity, Paddle } from '@paddle/paddle-node-sdk';\nimport type { EndUserTraits, EndUserDefaultInfo } from './types/endUser';\nimport { END_USER_DEFAULT_INFO_TO_TRAIT_MAP } from './types/endUser';\nimport { GalvaError } from './types/error';\n\nexport type {\n EndUserDefaultTraits,\n EndUserTraits,\n EndUserDefaultInfo,\n} from './types/endUser';\nexport {\n EndUserDefaultTraitName,\n END_USER_DEFAULT_TRAIT_MAP,\n END_USER_DEFAULT_INFO_TO_TRAIT_MAP,\n} from './types/endUser';\nexport { GalvaError } from './types/error';\nexport type { GalvaErrorResponse } from './types/error';\n\nexport interface GalvaOptions {\n /**\n * Defaults to 'production'. Falls back to NODE_ENV.\n * @type {('production' | 'development')}\n */\n environment?: 'production' | 'development';\n\n /**\n * API key from Galva dashboard. Falls back to GALVA_API_KEY env var.\n * @type {string}\n */\n apiKey?: string;\n\n /**\n * Request timeout in ms. Defaults to 10000.\n * @type {number}\n */\n timeout?: number;\n}\n\n/**\n * Credentials for authenticating with the Google Play Store API.\n *\n * @interface PlaystoreCredentials\n */\nexport interface PlaystoreCredentials {\n /** Service account email address */\n email: string;\n /** Service account private key */\n key: string;\n}\n\n/**\n * Credentials for authenticating and verifying Paddle webhooks.\n *\n * @interface PaddleCredentials\n */\nexport interface PaddleCredentials {\n /** Paddle API key */\n apiKey: string;\n /** Webhook secret key for signature verification */\n secretKey: string;\n}\n\n/**\n * Credentials for Apple App Store.\n *\n * @interface AppstoreCredentials\n */\nexport interface AppstoreCredentials {\n /** The app's bundle identifier (e.g., 'com.example.app') */\n bundleId: string;\n /** The app's Apple ID from App Store Connect */\n appAppleId: number;\n}\n\n/**\n * Credentials configuration for withCredentials method.\n *\n * @interface CredentialsConfig\n */\nexport interface CredentialsConfig {\n /** App Store credentials */\n appstore?: AppstoreCredentials;\n /** Play Store service account credentials */\n playstore?: PlaystoreCredentials;\n /** Paddle API credentials */\n paddle?: PaddleCredentials;\n}\n\nconst PRODUCTION_API_URL = 'https://api.galva.io';\nconst DEVELOPMENT_API_URL = 'https://api.galva.dev';\n\n/**\n * Base Galva SDK client with end user management methods.\n * @class GalvaBase\n */\nclass GalvaBase {\n protected config: GalvaOptions;\n protected readonly baseUrl: string;\n\n /**\n * @param {GalvaOptions} [options] - Configuration options\n * @throws {GalvaError} If API key is not provided\n */\n constructor(options?: GalvaOptions) {\n const apiKey = options?.apiKey || process.env.GALVA_API_KEY;\n if (!apiKey) {\n throw new GalvaError({\n code: 'MISSING_API_KEY',\n message:\n 'API key is required. Provide it in options or set GALVA_API_KEY env variable.',\n });\n }\n\n this.config = {\n apiKey: apiKey,\n environment:\n options?.environment ||\n (process.env.NODE_ENV === 'development' ? 'development' : 'production'),\n timeout: options?.timeout || 10000,\n };\n this.baseUrl =\n this.config.environment === 'development'\n ? DEVELOPMENT_API_URL\n : PRODUCTION_API_URL;\n }\n\n protected async sendRequest(\n method: 'POST' | 'GET',\n endpoint: string,\n data?: unknown,\n ): Promise<void> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.config.apiKey!,\n },\n body: data ? JSON.stringify(data) : undefined,\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const errorResponse = await response.json();\n throw new GalvaError(errorResponse);\n }\n } catch (error) {\n if (error instanceof GalvaError) {\n throw error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new GalvaError({\n code: 'TIMEOUT',\n message: `Request timed out after ${this.config.timeout}ms`,\n });\n }\n throw GalvaError.from(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** End user management methods. */\n public endUser = {\n /**\n * Identifies an end user with optional traits and context.\n * @param {string} endUserId - End user ID in your system\n * @param {Object} [options] - Identification data\n * @param {string} [options.timestamp] - ISO 8601 timestamp\n * @param {Record<string, any>} [options.context] - Additional context\n * @param {EndUserTraits} [options.traits] - User traits. Use EndUserDefaultTraitName for built-in traits.\n * @throws {GalvaError} If API request fails\n * @example\n * ```typescript\n * await galva.endUser.identify('user-123', {\n * traits: { [EndUserDefaultTraitName.EMAIL]: 'user@example.com' },\n * });\n * ```\n */\n identify: async (\n endUserId: string,\n options?: {\n timestamp?: string;\n context?: Record<string, any>;\n traits?: EndUserTraits;\n },\n ): Promise<void> => {\n await this.sendRequest('POST', '/endUsers:identify', {\n endUserId,\n timestamp: options?.timestamp,\n context: options?.context,\n traits: options?.traits,\n });\n },\n\n /**\n * Updates an end user's default profile info.\n * @param {string} endUserId - End user ID in your system\n * @param {EndUserDefaultInfo} defaultInfo - Profile info to update\n * @throws {GalvaError} If API request fails\n * @example\n * ```typescript\n * await galva.endUser.updateDefaultInfo('user-123', {\n * email: 'user@example.com',\n * fullName: 'John Doe',\n * });\n * ```\n */\n updateDefaultInfo: async (\n endUserId: string,\n defaultInfo: EndUserDefaultInfo,\n ): Promise<void> => {\n const traits: EndUserTraits = {};\n\n for (const [key, value] of Object.entries(defaultInfo)) {\n if (value !== undefined) {\n const traitName =\n END_USER_DEFAULT_INFO_TO_TRAIT_MAP[key as keyof EndUserDefaultInfo];\n traits[traitName] = value;\n }\n }\n\n await this.sendRequest('POST', '/endUsers:identify', {\n endUserId,\n traits,\n });\n },\n };\n}\n\nclass GalvaWithCreds extends GalvaBase {\n // private config: GalvaOptions;\n // private readonly baseUrl: string;\n private credentials: CredentialsConfig;\n\n /**\n * @internal Use `galva.withCredentials(...)` instead.\n */\n constructor(config: GalvaOptions, credentials: CredentialsConfig) {\n super(config);\n // this.config = config;\n // this.baseUrl = baseUrl;\n this.credentials = credentials;\n }\n\n /**\n * Billing event handlers with credential support.\n * @property {Function} appstore - App Store events (signed payload only)\n * @property {Function} playstore - Play Store events (raw base64 payload)\n * @property {Function} paddle - Paddle events (raw body with verification)\n */\n public billingEvent = {\n /**\n * Tracks an App Store billing event. Uses credentials from withCredentials().\n * @param {string} endUserId - End user ID\n * @param {string} signedPayload - Signed payload from Apple\n * @param {Record<string, any>} [options] - Additional options\n * @returns {Promise<void>}\n * @throws {GalvaError} If credentials missing or API fails\n * @example\n * ```typescript\n * await galvaWithCreds.billingEvent.appstore('user-123', signedPayload);\n * ```\n */\n appstore: async (\n endUserId: string,\n signedPayload: string,\n options?: Record<string, any>,\n ): Promise<void> => {\n if (!this.credentials.appstore) {\n throw new GalvaError({\n code: 'MISSING_CREDENTIALS',\n message:\n 'App Store credentials are required. Provide them in withCredentials().',\n });\n }\n\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'appstore',\n endUserId,\n payload: {\n signedPayload,\n appAppleId: this.credentials.appstore.appAppleId,\n bundleId: this.credentials.appstore.bundleId,\n env: this.config.environment,\n },\n options,\n });\n },\n\n /**\n * Tracks a Play Store billing event. Decodes payload and fetches subscription details.\n * @param {string} endUserId - End user ID\n * @param {string} base64Payload - Base64 payload from Pub/Sub\n * @returns {Promise<void>}\n * @throws {GalvaError} If credentials missing or API fails\n * @example\n * ```typescript\n * await galvaWithCreds.billingEvent.playstore('user-123', base64Payload);\n * ```\n */\n playstore: async (\n endUserId: string,\n base64Payload: string,\n ): Promise<void> => {\n if (!this.credentials.playstore) {\n throw new GalvaError({\n code: 'MISSING_CREDENTIALS',\n message:\n 'Play Store credentials are required. Provide them in withCredentials().',\n });\n }\n\n const decodedPayloadString = Buffer.from(\n base64Payload,\n 'base64',\n ).toString();\n let decodedPayload: PlaystoreDeveloperNotification | null = null;\n try {\n decodedPayload = JSON.parse(decodedPayloadString);\n } catch (error) {\n throw new GalvaError({\n code: 'INVALID_PAYLOAD',\n message: 'Invalid base64 payload: unable to parse JSON.',\n });\n }\n\n if (!decodedPayload || typeof decodedPayload !== 'object') {\n throw new GalvaError({\n code: 'INVALID_PAYLOAD',\n message: 'Decoded payload is not a valid JSON object.',\n });\n }\n\n if (!('subscriptionNotification' in decodedPayload)) {\n throw new GalvaError({\n code: 'INVALID_PAYLOAD',\n message:\n 'Decoded payload does not contain subscriptionNotification field.',\n });\n }\n\n const subscription = await PlaystoreService.getSubscription(\n decodedPayload.subscriptionNotification.purchaseToken,\n decodedPayload.packageName,\n this.credentials.playstore,\n );\n\n const finalPayload: PlaystoreDeveloperNotification = {\n ...decodedPayload,\n subscriptionNotification: {\n ...decodedPayload.subscriptionNotification,\n subscriptionPurchase: subscription.data,\n },\n };\n\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'playstore',\n endUserId,\n payload: finalPayload,\n });\n },\n\n /**\n * Tracks a Paddle billing event. Verifies signature and unmarshals the event.\n * @param {string} endUserId - End user ID\n * @param {string} rawBody - Raw body from Paddle webhook\n * @param {string} signature - Paddle-Signature header value\n * @returns {Promise<void>}\n * @throws {GalvaError} If credentials missing or API fails\n * @example\n * ```typescript\n * await galvaWithCreds.billingEvent.paddle('user-123', rawBody, signature);\n * ```\n */\n paddle: async (\n endUserId: string,\n rawBody: string,\n signature: string,\n ): Promise<void> => {\n if (!this.credentials.paddle) {\n throw new GalvaError({\n code: 'MISSING_CREDENTIALS',\n message:\n 'Paddle credentials are required. Provide them in withCredentials().',\n });\n }\n\n const paddle = new Paddle(this.credentials.paddle.apiKey);\n let eventData: EventEntity;\n\n try {\n eventData = await paddle.webhooks.unmarshal(\n rawBody,\n this.credentials.paddle.secretKey,\n signature,\n );\n } catch (error) {\n throw new GalvaError({\n code: 'INVALID_WEBHOOK',\n message: `Invalid Paddle webhook data: ${(error as Error).message}`,\n });\n }\n\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'paddle',\n endUserId,\n payload: eventData,\n });\n },\n };\n}\n\nexport class Galva extends GalvaBase {\n constructor(options?: GalvaOptions) {\n super(options);\n }\n\n /**\n * Billing event handlers for different payment platforms.\n * @property {Function} appstore - App Store events\n * @property {Function} playstore - Play Store events (decoded payload)\n * @property {Function} paddle - Paddle events (verified event)\n */\n public billingEvent = {\n /**\n * Tracks an App Store billing event.\n * @param {string} endUserId - End user ID\n * @param {Object} payload - App Store notification payload\n * @param {string} payload.signedPayload - Signed payload from Apple\n * @param {string} payload.bundleId - App bundle identifier\n * @param {number} payload.appAppleId - App Apple ID\n * @param {Record<string, any>} [options] - Additional options\n * @throws {GalvaError} If API request fails\n * @example\n * ```typescript\n * await galva.billingEvent.appstore('user-123', {\n * signedPayload,\n * bundleId: 'com.example.app',\n * appAppleId: 123456789\n * });\n * ```\n */\n appstore: async (\n endUserId: string,\n payload: {\n signedPayload: string;\n bundleId: string;\n appAppleId: number;\n },\n options?: Record<string, any>,\n ): Promise<void> => {\n const { signedPayload, bundleId, appAppleId } = payload;\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'appstore',\n endUserId,\n payload: {\n signedPayload,\n appAppleId: appAppleId,\n bundleId: bundleId,\n env: this.config.environment,\n },\n options,\n });\n },\n\n /**\n * Tracks a Play Store billing event with decoded notification.\n * @param {string} endUserId - End user ID\n * @param {PlaystoreDeveloperNotification} payload - Decoded developer notification\n * @returns {Promise<void>}\n * @example\n * ```typescript\n * await galva.billingEvent.playstore('user-123', decodedNotification);\n * ```\n */\n playstore: async (\n endUserId: string,\n payload: PlaystoreDeveloperNotification,\n ): Promise<void> => {\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'playstore',\n endUserId,\n payload,\n });\n },\n\n /**\n * Tracks a Paddle billing event with verified event entity.\n * @param {string} endUserId - End user ID\n * @param {EventEntity} eventEntity - Verified Paddle event\n * @returns {Promise<void>}\n * @example\n * ```typescript\n * await galva.billingEvent.paddle('user-123', verifiedEvent);\n * ```\n */\n paddle: async (\n endUserId: string,\n eventEntity: EventEntity,\n ): Promise<void> => {\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'paddle',\n endUserId,\n payload: eventEntity,\n });\n },\n };\n\n withCredentials(credentials: CredentialsConfig) {\n return new GalvaWithCreds(this.config, credentials);\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { androidpublisher_v3 } from 'googleapis';
|
|
2
|
+
import { EventEntity } from '@paddle/paddle-node-sdk';
|
|
3
|
+
|
|
4
|
+
interface PlaystoreOneTimeProductNotification {
|
|
5
|
+
version: string;
|
|
6
|
+
notificationType: number;
|
|
7
|
+
purchaseToken: string;
|
|
8
|
+
sku: string;
|
|
9
|
+
}
|
|
10
|
+
interface PlaystoreVoidedPurchaseNotification {
|
|
11
|
+
purchaseToken: string;
|
|
12
|
+
orderId: string;
|
|
13
|
+
productType: number;
|
|
14
|
+
refundType: number;
|
|
15
|
+
}
|
|
16
|
+
interface PlaystoreSubscriptionNotification {
|
|
17
|
+
version: string;
|
|
18
|
+
notificationType: number;
|
|
19
|
+
purchaseToken: string;
|
|
20
|
+
subscriptionPurchase: androidpublisher_v3.Schema$SubscriptionPurchaseV2;
|
|
21
|
+
}
|
|
22
|
+
interface PlaystoreTestNotification {
|
|
23
|
+
version: string;
|
|
24
|
+
}
|
|
25
|
+
interface PlaystoreDeveloperNotificationBase {
|
|
26
|
+
version: string;
|
|
27
|
+
packageName: string;
|
|
28
|
+
eventTimeMillis: number;
|
|
29
|
+
}
|
|
30
|
+
type PlaystoreDeveloperNotification = (PlaystoreDeveloperNotificationBase & {
|
|
31
|
+
subscriptionNotification: PlaystoreSubscriptionNotification;
|
|
32
|
+
}) | (PlaystoreDeveloperNotificationBase & {
|
|
33
|
+
oneTimeProductNotification: PlaystoreOneTimeProductNotification;
|
|
34
|
+
}) | (PlaystoreDeveloperNotificationBase & {
|
|
35
|
+
voidedPurchaseNotification: PlaystoreVoidedPurchaseNotification;
|
|
36
|
+
}) | (PlaystoreDeveloperNotificationBase & {
|
|
37
|
+
testNotification: PlaystoreTestNotification;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default trait names for end user profiles.
|
|
42
|
+
* Use these constants for type-safe access to built-in traits.
|
|
43
|
+
*/
|
|
44
|
+
declare enum EndUserDefaultTraitName {
|
|
45
|
+
TIMEZONE = "$gv_timezone",
|
|
46
|
+
LANGUAGE_CODE = "$gv_languageCode",
|
|
47
|
+
EMAIL = "$gv_email",
|
|
48
|
+
FULL_NAME = "$gv_fullName",
|
|
49
|
+
FIRST_NAME = "$gv_firstName",
|
|
50
|
+
LAST_NAME = "$gv_lastName",
|
|
51
|
+
COUNTRY = "$gv_country",
|
|
52
|
+
TOTAL_LIFETIME_VALUE = "$gv_totalLifetimeValue"
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Built-in trait types with their expected value types.
|
|
56
|
+
*/
|
|
57
|
+
interface EndUserDefaultTraits {
|
|
58
|
+
[EndUserDefaultTraitName.TIMEZONE]?: string;
|
|
59
|
+
[EndUserDefaultTraitName.LANGUAGE_CODE]?: string;
|
|
60
|
+
[EndUserDefaultTraitName.EMAIL]?: string;
|
|
61
|
+
[EndUserDefaultTraitName.FULL_NAME]?: string;
|
|
62
|
+
[EndUserDefaultTraitName.FIRST_NAME]?: string;
|
|
63
|
+
[EndUserDefaultTraitName.LAST_NAME]?: string;
|
|
64
|
+
[EndUserDefaultTraitName.COUNTRY]?: string;
|
|
65
|
+
[EndUserDefaultTraitName.TOTAL_LIFETIME_VALUE]?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* End user traits combining default traits with arbitrary custom traits.
|
|
69
|
+
*/
|
|
70
|
+
type EndUserTraits = EndUserDefaultTraits & Record<string, any>;
|
|
71
|
+
/**
|
|
72
|
+
* Friendly parameter names for updating end user default info.
|
|
73
|
+
* Maps to the underlying $gv_ prefixed trait names.
|
|
74
|
+
*/
|
|
75
|
+
interface EndUserDefaultInfo {
|
|
76
|
+
timezone?: string;
|
|
77
|
+
languageCode?: string;
|
|
78
|
+
email?: string;
|
|
79
|
+
fullName?: string;
|
|
80
|
+
firstName?: string;
|
|
81
|
+
lastName?: string;
|
|
82
|
+
country?: string;
|
|
83
|
+
totalLifetimeValue?: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Mapping from friendly parameter names to original trait names.
|
|
87
|
+
* TypeScript ensures this mapping contains all keys from EndUserDefaultTraitName.
|
|
88
|
+
*/
|
|
89
|
+
declare const END_USER_DEFAULT_TRAIT_MAP: Record<EndUserDefaultTraitName, keyof EndUserDefaultInfo>;
|
|
90
|
+
/**
|
|
91
|
+
* Reverse mapping from friendly parameter names to trait names.
|
|
92
|
+
* TypeScript ensures this mapping contains all keys from EndUserDefaultInfo.
|
|
93
|
+
*/
|
|
94
|
+
declare const END_USER_DEFAULT_INFO_TO_TRAIT_MAP: Record<keyof EndUserDefaultInfo, EndUserDefaultTraitName>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Error response structure from Galva API.
|
|
98
|
+
*/
|
|
99
|
+
interface GalvaErrorResponse {
|
|
100
|
+
code: string;
|
|
101
|
+
message: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Custom error class for Galva SDK errors.
|
|
105
|
+
* Extends Error with additional properties from API error responses.
|
|
106
|
+
*/
|
|
107
|
+
declare class GalvaError extends Error {
|
|
108
|
+
/** Error code from the API (e.g., 'BAD_REQUEST', 'UNAUTHORIZED') */
|
|
109
|
+
readonly code: string;
|
|
110
|
+
constructor(response: GalvaErrorResponse);
|
|
111
|
+
/**
|
|
112
|
+
* Creates a GalvaError from an unknown error.
|
|
113
|
+
* If the error is already a GalvaError, returns it as-is.
|
|
114
|
+
* Otherwise, wraps it in a GalvaError with UNKNOWN_ERROR code.
|
|
115
|
+
*/
|
|
116
|
+
static from(error: unknown): GalvaError;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface GalvaOptions {
|
|
120
|
+
/**
|
|
121
|
+
* Defaults to 'production'. Falls back to NODE_ENV.
|
|
122
|
+
* @type {('production' | 'development')}
|
|
123
|
+
*/
|
|
124
|
+
environment?: 'production' | 'development';
|
|
125
|
+
/**
|
|
126
|
+
* API key from Galva dashboard. Falls back to GALVA_API_KEY env var.
|
|
127
|
+
* @type {string}
|
|
128
|
+
*/
|
|
129
|
+
apiKey?: string;
|
|
130
|
+
/**
|
|
131
|
+
* Request timeout in ms. Defaults to 10000.
|
|
132
|
+
* @type {number}
|
|
133
|
+
*/
|
|
134
|
+
timeout?: number;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Credentials for authenticating with the Google Play Store API.
|
|
138
|
+
*
|
|
139
|
+
* @interface PlaystoreCredentials
|
|
140
|
+
*/
|
|
141
|
+
interface PlaystoreCredentials {
|
|
142
|
+
/** Service account email address */
|
|
143
|
+
email: string;
|
|
144
|
+
/** Service account private key */
|
|
145
|
+
key: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Credentials for authenticating and verifying Paddle webhooks.
|
|
149
|
+
*
|
|
150
|
+
* @interface PaddleCredentials
|
|
151
|
+
*/
|
|
152
|
+
interface PaddleCredentials {
|
|
153
|
+
/** Paddle API key */
|
|
154
|
+
apiKey: string;
|
|
155
|
+
/** Webhook secret key for signature verification */
|
|
156
|
+
secretKey: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Credentials for Apple App Store.
|
|
160
|
+
*
|
|
161
|
+
* @interface AppstoreCredentials
|
|
162
|
+
*/
|
|
163
|
+
interface AppstoreCredentials {
|
|
164
|
+
/** The app's bundle identifier (e.g., 'com.example.app') */
|
|
165
|
+
bundleId: string;
|
|
166
|
+
/** The app's Apple ID from App Store Connect */
|
|
167
|
+
appAppleId: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Credentials configuration for withCredentials method.
|
|
171
|
+
*
|
|
172
|
+
* @interface CredentialsConfig
|
|
173
|
+
*/
|
|
174
|
+
interface CredentialsConfig {
|
|
175
|
+
/** App Store credentials */
|
|
176
|
+
appstore?: AppstoreCredentials;
|
|
177
|
+
/** Play Store service account credentials */
|
|
178
|
+
playstore?: PlaystoreCredentials;
|
|
179
|
+
/** Paddle API credentials */
|
|
180
|
+
paddle?: PaddleCredentials;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Base Galva SDK client with end user management methods.
|
|
184
|
+
* @class GalvaBase
|
|
185
|
+
*/
|
|
186
|
+
declare class GalvaBase {
|
|
187
|
+
protected config: GalvaOptions;
|
|
188
|
+
protected readonly baseUrl: string;
|
|
189
|
+
/**
|
|
190
|
+
* @param {GalvaOptions} [options] - Configuration options
|
|
191
|
+
* @throws {GalvaError} If API key is not provided
|
|
192
|
+
*/
|
|
193
|
+
constructor(options?: GalvaOptions);
|
|
194
|
+
protected sendRequest(method: 'POST' | 'GET', endpoint: string, data?: unknown): Promise<void>;
|
|
195
|
+
/** End user management methods. */
|
|
196
|
+
endUser: {
|
|
197
|
+
/**
|
|
198
|
+
* Identifies an end user with optional traits and context.
|
|
199
|
+
* @param {string} endUserId - End user ID in your system
|
|
200
|
+
* @param {Object} [options] - Identification data
|
|
201
|
+
* @param {string} [options.timestamp] - ISO 8601 timestamp
|
|
202
|
+
* @param {Record<string, any>} [options.context] - Additional context
|
|
203
|
+
* @param {EndUserTraits} [options.traits] - User traits. Use EndUserDefaultTraitName for built-in traits.
|
|
204
|
+
* @throws {GalvaError} If API request fails
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* await galva.endUser.identify('user-123', {
|
|
208
|
+
* traits: { [EndUserDefaultTraitName.EMAIL]: 'user@example.com' },
|
|
209
|
+
* });
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
identify: (endUserId: string, options?: {
|
|
213
|
+
timestamp?: string;
|
|
214
|
+
context?: Record<string, any>;
|
|
215
|
+
traits?: EndUserTraits;
|
|
216
|
+
}) => Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* Updates an end user's default profile info.
|
|
219
|
+
* @param {string} endUserId - End user ID in your system
|
|
220
|
+
* @param {EndUserDefaultInfo} defaultInfo - Profile info to update
|
|
221
|
+
* @throws {GalvaError} If API request fails
|
|
222
|
+
* @example
|
|
223
|
+
* ```typescript
|
|
224
|
+
* await galva.endUser.updateDefaultInfo('user-123', {
|
|
225
|
+
* email: 'user@example.com',
|
|
226
|
+
* fullName: 'John Doe',
|
|
227
|
+
* });
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
updateDefaultInfo: (endUserId: string, defaultInfo: EndUserDefaultInfo) => Promise<void>;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
declare class GalvaWithCreds extends GalvaBase {
|
|
234
|
+
private credentials;
|
|
235
|
+
/**
|
|
236
|
+
* @internal Use `galva.withCredentials(...)` instead.
|
|
237
|
+
*/
|
|
238
|
+
constructor(config: GalvaOptions, credentials: CredentialsConfig);
|
|
239
|
+
/**
|
|
240
|
+
* Billing event handlers with credential support.
|
|
241
|
+
* @property {Function} appstore - App Store events (signed payload only)
|
|
242
|
+
* @property {Function} playstore - Play Store events (raw base64 payload)
|
|
243
|
+
* @property {Function} paddle - Paddle events (raw body with verification)
|
|
244
|
+
*/
|
|
245
|
+
billingEvent: {
|
|
246
|
+
/**
|
|
247
|
+
* Tracks an App Store billing event. Uses credentials from withCredentials().
|
|
248
|
+
* @param {string} endUserId - End user ID
|
|
249
|
+
* @param {string} signedPayload - Signed payload from Apple
|
|
250
|
+
* @param {Record<string, any>} [options] - Additional options
|
|
251
|
+
* @returns {Promise<void>}
|
|
252
|
+
* @throws {GalvaError} If credentials missing or API fails
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* await galvaWithCreds.billingEvent.appstore('user-123', signedPayload);
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
appstore: (endUserId: string, signedPayload: string, options?: Record<string, any>) => Promise<void>;
|
|
259
|
+
/**
|
|
260
|
+
* Tracks a Play Store billing event. Decodes payload and fetches subscription details.
|
|
261
|
+
* @param {string} endUserId - End user ID
|
|
262
|
+
* @param {string} base64Payload - Base64 payload from Pub/Sub
|
|
263
|
+
* @returns {Promise<void>}
|
|
264
|
+
* @throws {GalvaError} If credentials missing or API fails
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* await galvaWithCreds.billingEvent.playstore('user-123', base64Payload);
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
playstore: (endUserId: string, base64Payload: string) => Promise<void>;
|
|
271
|
+
/**
|
|
272
|
+
* Tracks a Paddle billing event. Verifies signature and unmarshals the event.
|
|
273
|
+
* @param {string} endUserId - End user ID
|
|
274
|
+
* @param {string} rawBody - Raw body from Paddle webhook
|
|
275
|
+
* @param {string} signature - Paddle-Signature header value
|
|
276
|
+
* @returns {Promise<void>}
|
|
277
|
+
* @throws {GalvaError} If credentials missing or API fails
|
|
278
|
+
* @example
|
|
279
|
+
* ```typescript
|
|
280
|
+
* await galvaWithCreds.billingEvent.paddle('user-123', rawBody, signature);
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
paddle: (endUserId: string, rawBody: string, signature: string) => Promise<void>;
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
declare class Galva extends GalvaBase {
|
|
287
|
+
constructor(options?: GalvaOptions);
|
|
288
|
+
/**
|
|
289
|
+
* Billing event handlers for different payment platforms.
|
|
290
|
+
* @property {Function} appstore - App Store events
|
|
291
|
+
* @property {Function} playstore - Play Store events (decoded payload)
|
|
292
|
+
* @property {Function} paddle - Paddle events (verified event)
|
|
293
|
+
*/
|
|
294
|
+
billingEvent: {
|
|
295
|
+
/**
|
|
296
|
+
* Tracks an App Store billing event.
|
|
297
|
+
* @param {string} endUserId - End user ID
|
|
298
|
+
* @param {Object} payload - App Store notification payload
|
|
299
|
+
* @param {string} payload.signedPayload - Signed payload from Apple
|
|
300
|
+
* @param {string} payload.bundleId - App bundle identifier
|
|
301
|
+
* @param {number} payload.appAppleId - App Apple ID
|
|
302
|
+
* @param {Record<string, any>} [options] - Additional options
|
|
303
|
+
* @throws {GalvaError} If API request fails
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* await galva.billingEvent.appstore('user-123', {
|
|
307
|
+
* signedPayload,
|
|
308
|
+
* bundleId: 'com.example.app',
|
|
309
|
+
* appAppleId: 123456789
|
|
310
|
+
* });
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
appstore: (endUserId: string, payload: {
|
|
314
|
+
signedPayload: string;
|
|
315
|
+
bundleId: string;
|
|
316
|
+
appAppleId: number;
|
|
317
|
+
}, options?: Record<string, any>) => Promise<void>;
|
|
318
|
+
/**
|
|
319
|
+
* Tracks a Play Store billing event with decoded notification.
|
|
320
|
+
* @param {string} endUserId - End user ID
|
|
321
|
+
* @param {PlaystoreDeveloperNotification} payload - Decoded developer notification
|
|
322
|
+
* @returns {Promise<void>}
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* await galva.billingEvent.playstore('user-123', decodedNotification);
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
playstore: (endUserId: string, payload: PlaystoreDeveloperNotification) => Promise<void>;
|
|
329
|
+
/**
|
|
330
|
+
* Tracks a Paddle billing event with verified event entity.
|
|
331
|
+
* @param {string} endUserId - End user ID
|
|
332
|
+
* @param {EventEntity} eventEntity - Verified Paddle event
|
|
333
|
+
* @returns {Promise<void>}
|
|
334
|
+
* @example
|
|
335
|
+
* ```typescript
|
|
336
|
+
* await galva.billingEvent.paddle('user-123', verifiedEvent);
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
paddle: (endUserId: string, eventEntity: EventEntity) => Promise<void>;
|
|
340
|
+
};
|
|
341
|
+
withCredentials(credentials: CredentialsConfig): GalvaWithCreds;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export { type AppstoreCredentials, type CredentialsConfig, END_USER_DEFAULT_INFO_TO_TRAIT_MAP, END_USER_DEFAULT_TRAIT_MAP, type EndUserDefaultInfo, EndUserDefaultTraitName, type EndUserDefaultTraits, type EndUserTraits, Galva, GalvaError, type GalvaErrorResponse, type GalvaOptions, type PaddleCredentials, type PlaystoreCredentials };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { androidpublisher_v3 } from 'googleapis';
|
|
2
|
+
import { EventEntity } from '@paddle/paddle-node-sdk';
|
|
3
|
+
|
|
4
|
+
interface PlaystoreOneTimeProductNotification {
|
|
5
|
+
version: string;
|
|
6
|
+
notificationType: number;
|
|
7
|
+
purchaseToken: string;
|
|
8
|
+
sku: string;
|
|
9
|
+
}
|
|
10
|
+
interface PlaystoreVoidedPurchaseNotification {
|
|
11
|
+
purchaseToken: string;
|
|
12
|
+
orderId: string;
|
|
13
|
+
productType: number;
|
|
14
|
+
refundType: number;
|
|
15
|
+
}
|
|
16
|
+
interface PlaystoreSubscriptionNotification {
|
|
17
|
+
version: string;
|
|
18
|
+
notificationType: number;
|
|
19
|
+
purchaseToken: string;
|
|
20
|
+
subscriptionPurchase: androidpublisher_v3.Schema$SubscriptionPurchaseV2;
|
|
21
|
+
}
|
|
22
|
+
interface PlaystoreTestNotification {
|
|
23
|
+
version: string;
|
|
24
|
+
}
|
|
25
|
+
interface PlaystoreDeveloperNotificationBase {
|
|
26
|
+
version: string;
|
|
27
|
+
packageName: string;
|
|
28
|
+
eventTimeMillis: number;
|
|
29
|
+
}
|
|
30
|
+
type PlaystoreDeveloperNotification = (PlaystoreDeveloperNotificationBase & {
|
|
31
|
+
subscriptionNotification: PlaystoreSubscriptionNotification;
|
|
32
|
+
}) | (PlaystoreDeveloperNotificationBase & {
|
|
33
|
+
oneTimeProductNotification: PlaystoreOneTimeProductNotification;
|
|
34
|
+
}) | (PlaystoreDeveloperNotificationBase & {
|
|
35
|
+
voidedPurchaseNotification: PlaystoreVoidedPurchaseNotification;
|
|
36
|
+
}) | (PlaystoreDeveloperNotificationBase & {
|
|
37
|
+
testNotification: PlaystoreTestNotification;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default trait names for end user profiles.
|
|
42
|
+
* Use these constants for type-safe access to built-in traits.
|
|
43
|
+
*/
|
|
44
|
+
declare enum EndUserDefaultTraitName {
|
|
45
|
+
TIMEZONE = "$gv_timezone",
|
|
46
|
+
LANGUAGE_CODE = "$gv_languageCode",
|
|
47
|
+
EMAIL = "$gv_email",
|
|
48
|
+
FULL_NAME = "$gv_fullName",
|
|
49
|
+
FIRST_NAME = "$gv_firstName",
|
|
50
|
+
LAST_NAME = "$gv_lastName",
|
|
51
|
+
COUNTRY = "$gv_country",
|
|
52
|
+
TOTAL_LIFETIME_VALUE = "$gv_totalLifetimeValue"
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Built-in trait types with their expected value types.
|
|
56
|
+
*/
|
|
57
|
+
interface EndUserDefaultTraits {
|
|
58
|
+
[EndUserDefaultTraitName.TIMEZONE]?: string;
|
|
59
|
+
[EndUserDefaultTraitName.LANGUAGE_CODE]?: string;
|
|
60
|
+
[EndUserDefaultTraitName.EMAIL]?: string;
|
|
61
|
+
[EndUserDefaultTraitName.FULL_NAME]?: string;
|
|
62
|
+
[EndUserDefaultTraitName.FIRST_NAME]?: string;
|
|
63
|
+
[EndUserDefaultTraitName.LAST_NAME]?: string;
|
|
64
|
+
[EndUserDefaultTraitName.COUNTRY]?: string;
|
|
65
|
+
[EndUserDefaultTraitName.TOTAL_LIFETIME_VALUE]?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* End user traits combining default traits with arbitrary custom traits.
|
|
69
|
+
*/
|
|
70
|
+
type EndUserTraits = EndUserDefaultTraits & Record<string, any>;
|
|
71
|
+
/**
|
|
72
|
+
* Friendly parameter names for updating end user default info.
|
|
73
|
+
* Maps to the underlying $gv_ prefixed trait names.
|
|
74
|
+
*/
|
|
75
|
+
interface EndUserDefaultInfo {
|
|
76
|
+
timezone?: string;
|
|
77
|
+
languageCode?: string;
|
|
78
|
+
email?: string;
|
|
79
|
+
fullName?: string;
|
|
80
|
+
firstName?: string;
|
|
81
|
+
lastName?: string;
|
|
82
|
+
country?: string;
|
|
83
|
+
totalLifetimeValue?: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Mapping from friendly parameter names to original trait names.
|
|
87
|
+
* TypeScript ensures this mapping contains all keys from EndUserDefaultTraitName.
|
|
88
|
+
*/
|
|
89
|
+
declare const END_USER_DEFAULT_TRAIT_MAP: Record<EndUserDefaultTraitName, keyof EndUserDefaultInfo>;
|
|
90
|
+
/**
|
|
91
|
+
* Reverse mapping from friendly parameter names to trait names.
|
|
92
|
+
* TypeScript ensures this mapping contains all keys from EndUserDefaultInfo.
|
|
93
|
+
*/
|
|
94
|
+
declare const END_USER_DEFAULT_INFO_TO_TRAIT_MAP: Record<keyof EndUserDefaultInfo, EndUserDefaultTraitName>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Error response structure from Galva API.
|
|
98
|
+
*/
|
|
99
|
+
interface GalvaErrorResponse {
|
|
100
|
+
code: string;
|
|
101
|
+
message: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Custom error class for Galva SDK errors.
|
|
105
|
+
* Extends Error with additional properties from API error responses.
|
|
106
|
+
*/
|
|
107
|
+
declare class GalvaError extends Error {
|
|
108
|
+
/** Error code from the API (e.g., 'BAD_REQUEST', 'UNAUTHORIZED') */
|
|
109
|
+
readonly code: string;
|
|
110
|
+
constructor(response: GalvaErrorResponse);
|
|
111
|
+
/**
|
|
112
|
+
* Creates a GalvaError from an unknown error.
|
|
113
|
+
* If the error is already a GalvaError, returns it as-is.
|
|
114
|
+
* Otherwise, wraps it in a GalvaError with UNKNOWN_ERROR code.
|
|
115
|
+
*/
|
|
116
|
+
static from(error: unknown): GalvaError;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface GalvaOptions {
|
|
120
|
+
/**
|
|
121
|
+
* Defaults to 'production'. Falls back to NODE_ENV.
|
|
122
|
+
* @type {('production' | 'development')}
|
|
123
|
+
*/
|
|
124
|
+
environment?: 'production' | 'development';
|
|
125
|
+
/**
|
|
126
|
+
* API key from Galva dashboard. Falls back to GALVA_API_KEY env var.
|
|
127
|
+
* @type {string}
|
|
128
|
+
*/
|
|
129
|
+
apiKey?: string;
|
|
130
|
+
/**
|
|
131
|
+
* Request timeout in ms. Defaults to 10000.
|
|
132
|
+
* @type {number}
|
|
133
|
+
*/
|
|
134
|
+
timeout?: number;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Credentials for authenticating with the Google Play Store API.
|
|
138
|
+
*
|
|
139
|
+
* @interface PlaystoreCredentials
|
|
140
|
+
*/
|
|
141
|
+
interface PlaystoreCredentials {
|
|
142
|
+
/** Service account email address */
|
|
143
|
+
email: string;
|
|
144
|
+
/** Service account private key */
|
|
145
|
+
key: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Credentials for authenticating and verifying Paddle webhooks.
|
|
149
|
+
*
|
|
150
|
+
* @interface PaddleCredentials
|
|
151
|
+
*/
|
|
152
|
+
interface PaddleCredentials {
|
|
153
|
+
/** Paddle API key */
|
|
154
|
+
apiKey: string;
|
|
155
|
+
/** Webhook secret key for signature verification */
|
|
156
|
+
secretKey: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Credentials for Apple App Store.
|
|
160
|
+
*
|
|
161
|
+
* @interface AppstoreCredentials
|
|
162
|
+
*/
|
|
163
|
+
interface AppstoreCredentials {
|
|
164
|
+
/** The app's bundle identifier (e.g., 'com.example.app') */
|
|
165
|
+
bundleId: string;
|
|
166
|
+
/** The app's Apple ID from App Store Connect */
|
|
167
|
+
appAppleId: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Credentials configuration for withCredentials method.
|
|
171
|
+
*
|
|
172
|
+
* @interface CredentialsConfig
|
|
173
|
+
*/
|
|
174
|
+
interface CredentialsConfig {
|
|
175
|
+
/** App Store credentials */
|
|
176
|
+
appstore?: AppstoreCredentials;
|
|
177
|
+
/** Play Store service account credentials */
|
|
178
|
+
playstore?: PlaystoreCredentials;
|
|
179
|
+
/** Paddle API credentials */
|
|
180
|
+
paddle?: PaddleCredentials;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Base Galva SDK client with end user management methods.
|
|
184
|
+
* @class GalvaBase
|
|
185
|
+
*/
|
|
186
|
+
declare class GalvaBase {
|
|
187
|
+
protected config: GalvaOptions;
|
|
188
|
+
protected readonly baseUrl: string;
|
|
189
|
+
/**
|
|
190
|
+
* @param {GalvaOptions} [options] - Configuration options
|
|
191
|
+
* @throws {GalvaError} If API key is not provided
|
|
192
|
+
*/
|
|
193
|
+
constructor(options?: GalvaOptions);
|
|
194
|
+
protected sendRequest(method: 'POST' | 'GET', endpoint: string, data?: unknown): Promise<void>;
|
|
195
|
+
/** End user management methods. */
|
|
196
|
+
endUser: {
|
|
197
|
+
/**
|
|
198
|
+
* Identifies an end user with optional traits and context.
|
|
199
|
+
* @param {string} endUserId - End user ID in your system
|
|
200
|
+
* @param {Object} [options] - Identification data
|
|
201
|
+
* @param {string} [options.timestamp] - ISO 8601 timestamp
|
|
202
|
+
* @param {Record<string, any>} [options.context] - Additional context
|
|
203
|
+
* @param {EndUserTraits} [options.traits] - User traits. Use EndUserDefaultTraitName for built-in traits.
|
|
204
|
+
* @throws {GalvaError} If API request fails
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* await galva.endUser.identify('user-123', {
|
|
208
|
+
* traits: { [EndUserDefaultTraitName.EMAIL]: 'user@example.com' },
|
|
209
|
+
* });
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
identify: (endUserId: string, options?: {
|
|
213
|
+
timestamp?: string;
|
|
214
|
+
context?: Record<string, any>;
|
|
215
|
+
traits?: EndUserTraits;
|
|
216
|
+
}) => Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* Updates an end user's default profile info.
|
|
219
|
+
* @param {string} endUserId - End user ID in your system
|
|
220
|
+
* @param {EndUserDefaultInfo} defaultInfo - Profile info to update
|
|
221
|
+
* @throws {GalvaError} If API request fails
|
|
222
|
+
* @example
|
|
223
|
+
* ```typescript
|
|
224
|
+
* await galva.endUser.updateDefaultInfo('user-123', {
|
|
225
|
+
* email: 'user@example.com',
|
|
226
|
+
* fullName: 'John Doe',
|
|
227
|
+
* });
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
updateDefaultInfo: (endUserId: string, defaultInfo: EndUserDefaultInfo) => Promise<void>;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
declare class GalvaWithCreds extends GalvaBase {
|
|
234
|
+
private credentials;
|
|
235
|
+
/**
|
|
236
|
+
* @internal Use `galva.withCredentials(...)` instead.
|
|
237
|
+
*/
|
|
238
|
+
constructor(config: GalvaOptions, credentials: CredentialsConfig);
|
|
239
|
+
/**
|
|
240
|
+
* Billing event handlers with credential support.
|
|
241
|
+
* @property {Function} appstore - App Store events (signed payload only)
|
|
242
|
+
* @property {Function} playstore - Play Store events (raw base64 payload)
|
|
243
|
+
* @property {Function} paddle - Paddle events (raw body with verification)
|
|
244
|
+
*/
|
|
245
|
+
billingEvent: {
|
|
246
|
+
/**
|
|
247
|
+
* Tracks an App Store billing event. Uses credentials from withCredentials().
|
|
248
|
+
* @param {string} endUserId - End user ID
|
|
249
|
+
* @param {string} signedPayload - Signed payload from Apple
|
|
250
|
+
* @param {Record<string, any>} [options] - Additional options
|
|
251
|
+
* @returns {Promise<void>}
|
|
252
|
+
* @throws {GalvaError} If credentials missing or API fails
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* await galvaWithCreds.billingEvent.appstore('user-123', signedPayload);
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
appstore: (endUserId: string, signedPayload: string, options?: Record<string, any>) => Promise<void>;
|
|
259
|
+
/**
|
|
260
|
+
* Tracks a Play Store billing event. Decodes payload and fetches subscription details.
|
|
261
|
+
* @param {string} endUserId - End user ID
|
|
262
|
+
* @param {string} base64Payload - Base64 payload from Pub/Sub
|
|
263
|
+
* @returns {Promise<void>}
|
|
264
|
+
* @throws {GalvaError} If credentials missing or API fails
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* await galvaWithCreds.billingEvent.playstore('user-123', base64Payload);
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
playstore: (endUserId: string, base64Payload: string) => Promise<void>;
|
|
271
|
+
/**
|
|
272
|
+
* Tracks a Paddle billing event. Verifies signature and unmarshals the event.
|
|
273
|
+
* @param {string} endUserId - End user ID
|
|
274
|
+
* @param {string} rawBody - Raw body from Paddle webhook
|
|
275
|
+
* @param {string} signature - Paddle-Signature header value
|
|
276
|
+
* @returns {Promise<void>}
|
|
277
|
+
* @throws {GalvaError} If credentials missing or API fails
|
|
278
|
+
* @example
|
|
279
|
+
* ```typescript
|
|
280
|
+
* await galvaWithCreds.billingEvent.paddle('user-123', rawBody, signature);
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
paddle: (endUserId: string, rawBody: string, signature: string) => Promise<void>;
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
declare class Galva extends GalvaBase {
|
|
287
|
+
constructor(options?: GalvaOptions);
|
|
288
|
+
/**
|
|
289
|
+
* Billing event handlers for different payment platforms.
|
|
290
|
+
* @property {Function} appstore - App Store events
|
|
291
|
+
* @property {Function} playstore - Play Store events (decoded payload)
|
|
292
|
+
* @property {Function} paddle - Paddle events (verified event)
|
|
293
|
+
*/
|
|
294
|
+
billingEvent: {
|
|
295
|
+
/**
|
|
296
|
+
* Tracks an App Store billing event.
|
|
297
|
+
* @param {string} endUserId - End user ID
|
|
298
|
+
* @param {Object} payload - App Store notification payload
|
|
299
|
+
* @param {string} payload.signedPayload - Signed payload from Apple
|
|
300
|
+
* @param {string} payload.bundleId - App bundle identifier
|
|
301
|
+
* @param {number} payload.appAppleId - App Apple ID
|
|
302
|
+
* @param {Record<string, any>} [options] - Additional options
|
|
303
|
+
* @throws {GalvaError} If API request fails
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* await galva.billingEvent.appstore('user-123', {
|
|
307
|
+
* signedPayload,
|
|
308
|
+
* bundleId: 'com.example.app',
|
|
309
|
+
* appAppleId: 123456789
|
|
310
|
+
* });
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
appstore: (endUserId: string, payload: {
|
|
314
|
+
signedPayload: string;
|
|
315
|
+
bundleId: string;
|
|
316
|
+
appAppleId: number;
|
|
317
|
+
}, options?: Record<string, any>) => Promise<void>;
|
|
318
|
+
/**
|
|
319
|
+
* Tracks a Play Store billing event with decoded notification.
|
|
320
|
+
* @param {string} endUserId - End user ID
|
|
321
|
+
* @param {PlaystoreDeveloperNotification} payload - Decoded developer notification
|
|
322
|
+
* @returns {Promise<void>}
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* await galva.billingEvent.playstore('user-123', decodedNotification);
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
playstore: (endUserId: string, payload: PlaystoreDeveloperNotification) => Promise<void>;
|
|
329
|
+
/**
|
|
330
|
+
* Tracks a Paddle billing event with verified event entity.
|
|
331
|
+
* @param {string} endUserId - End user ID
|
|
332
|
+
* @param {EventEntity} eventEntity - Verified Paddle event
|
|
333
|
+
* @returns {Promise<void>}
|
|
334
|
+
* @example
|
|
335
|
+
* ```typescript
|
|
336
|
+
* await galva.billingEvent.paddle('user-123', verifiedEvent);
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
paddle: (endUserId: string, eventEntity: EventEntity) => Promise<void>;
|
|
340
|
+
};
|
|
341
|
+
withCredentials(credentials: CredentialsConfig): GalvaWithCreds;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export { type AppstoreCredentials, type CredentialsConfig, END_USER_DEFAULT_INFO_TO_TRAIT_MAP, END_USER_DEFAULT_TRAIT_MAP, type EndUserDefaultInfo, EndUserDefaultTraitName, type EndUserDefaultTraits, type EndUserTraits, Galva, GalvaError, type GalvaErrorResponse, type GalvaOptions, type PaddleCredentials, type PlaystoreCredentials };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import {google}from'googleapis';import {Paddle}from'@paddle/paddle-node-sdk';var T=n=>{let e=new google.auth.JWT({email:n.email,key:n.key.replace(/\\n/g,`
|
|
2
|
+
`),scopes:["https://www.googleapis.com/auth/androidpublisher"]});return google.androidpublisher({version:"v3",auth:e})},p=class{static async getSubscription(e,t,a){return await T({email:a.email,key:a.key}).purchases.subscriptionsv2.get({token:e,packageName:t})}};var u=(o=>(o.TIMEZONE="$gv_timezone",o.LANGUAGE_CODE="$gv_languageCode",o.EMAIL="$gv_email",o.FULL_NAME="$gv_fullName",o.FIRST_NAME="$gv_firstName",o.LAST_NAME="$gv_lastName",o.COUNTRY="$gv_country",o.TOTAL_LIFETIME_VALUE="$gv_totalLifetimeValue",o))(u||{}),N={$gv_timezone:"timezone",$gv_languageCode:"languageCode",$gv_email:"email",$gv_fullName:"fullName",$gv_firstName:"firstName",$gv_lastName:"lastName",$gv_country:"country",$gv_totalLifetimeValue:"totalLifetimeValue"},m={timezone:"$gv_timezone",languageCode:"$gv_languageCode",email:"$gv_email",fullName:"$gv_fullName",firstName:"$gv_firstName",lastName:"$gv_lastName",country:"$gv_country",totalLifetimeValue:"$gv_totalLifetimeValue"};var s=class n extends Error{code;constructor(e){super(e.message),this.name="GalvaError",this.code=e.code,Error.captureStackTrace&&Error.captureStackTrace(this,n);}static from(e){if(e instanceof n)return e;let t=e instanceof Error?e.message:"An unknown error occurred";return new n({code:"UNKNOWN_ERROR",message:t})}};var A="https://api.galva.io",U="https://api.galva.dev",c=class{config;baseUrl;constructor(e){let t=e?.apiKey||process.env.GALVA_API_KEY;if(!t)throw new s({code:"MISSING_API_KEY",message:"API key is required. Provide it in options or set GALVA_API_KEY env variable."});this.config={apiKey:t,environment:e?.environment||(process.env.NODE_ENV==="development"?"development":"production"),timeout:e?.timeout||1e4},this.baseUrl=this.config.environment==="development"?U:A;}async sendRequest(e,t,a){let r=`${this.baseUrl}${t}`,i=new AbortController,l=setTimeout(()=>i.abort(),this.config.timeout);try{let d=await fetch(r,{method:e,headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:a?JSON.stringify(a):void 0,signal:i.signal});if(!d.ok){let o=await d.json();throw new s(o)}}catch(d){throw d instanceof s?d:d instanceof Error&&d.name==="AbortError"?new s({code:"TIMEOUT",message:`Request timed out after ${this.config.timeout}ms`}):s.from(d)}finally{clearTimeout(l);}}endUser={identify:async(e,t)=>{await this.sendRequest("POST","/endUsers:identify",{endUserId:e,timestamp:t?.timestamp,context:t?.context,traits:t?.traits});},updateDefaultInfo:async(e,t)=>{let a={};for(let[r,i]of Object.entries(t))if(i!==void 0){let l=m[r];a[l]=i;}await this.sendRequest("POST","/endUsers:identify",{endUserId:e,traits:a});}}},E=class extends c{credentials;constructor(e,t){super(e),this.credentials=t;}billingEvent={appstore:async(e,t,a)=>{if(!this.credentials.appstore)throw new s({code:"MISSING_CREDENTIALS",message:"App Store credentials are required. Provide them in withCredentials()."});await this.sendRequest("POST","/endUsers/billingEvents",{platform:"appstore",endUserId:e,payload:{signedPayload:t,appAppleId:this.credentials.appstore.appAppleId,bundleId:this.credentials.appstore.bundleId,env:this.config.environment},options:a});},playstore:async(e,t)=>{if(!this.credentials.playstore)throw new s({code:"MISSING_CREDENTIALS",message:"Play Store credentials are required. Provide them in withCredentials()."});let a=Buffer.from(t,"base64").toString(),r=null;try{r=JSON.parse(a);}catch{throw new s({code:"INVALID_PAYLOAD",message:"Invalid base64 payload: unable to parse JSON."})}if(!r||typeof r!="object")throw new s({code:"INVALID_PAYLOAD",message:"Decoded payload is not a valid JSON object."});if(!("subscriptionNotification"in r))throw new s({code:"INVALID_PAYLOAD",message:"Decoded payload does not contain subscriptionNotification field."});let i=await p.getSubscription(r.subscriptionNotification.purchaseToken,r.packageName,this.credentials.playstore),l={...r,subscriptionNotification:{...r.subscriptionNotification,subscriptionPurchase:i.data}};await this.sendRequest("POST","/endUsers/billingEvents",{platform:"playstore",endUserId:e,payload:l});},paddle:async(e,t,a)=>{if(!this.credentials.paddle)throw new s({code:"MISSING_CREDENTIALS",message:"Paddle credentials are required. Provide them in withCredentials()."});let r=new Paddle(this.credentials.paddle.apiKey),i;try{i=await r.webhooks.unmarshal(t,this.credentials.paddle.secretKey,a);}catch(l){throw new s({code:"INVALID_WEBHOOK",message:`Invalid Paddle webhook data: ${l.message}`})}await this.sendRequest("POST","/endUsers/billingEvents",{platform:"paddle",endUserId:e,payload:i});}}},g=class extends c{constructor(e){super(e);}billingEvent={appstore:async(e,t,a)=>{let{signedPayload:r,bundleId:i,appAppleId:l}=t;await this.sendRequest("POST","/endUsers/billingEvents",{platform:"appstore",endUserId:e,payload:{signedPayload:r,appAppleId:l,bundleId:i,env:this.config.environment},options:a});},playstore:async(e,t)=>{await this.sendRequest("POST","/endUsers/billingEvents",{platform:"playstore",endUserId:e,payload:t});},paddle:async(e,t)=>{await this.sendRequest("POST","/endUsers/billingEvents",{platform:"paddle",endUserId:e,payload:t});}};withCredentials(e){return new E(this.config,e)}};
|
|
3
|
+
export{m as END_USER_DEFAULT_INFO_TO_TRAIT_MAP,N as END_USER_DEFAULT_TRAIT_MAP,u as EndUserDefaultTraitName,g as Galva,s as GalvaError};//# sourceMappingURL=index.js.map
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/playstore.ts","../src/types/endUser.ts","../src/types/error.ts","../src/galva.ts"],"names":["authentication","payload","jwtClient","google","PlaystoreService","token","packageName","cred","EndUserDefaultTraitName","END_USER_DEFAULT_TRAIT_MAP","END_USER_DEFAULT_INFO_TO_TRAIT_MAP","GalvaError","_GalvaError","response","error","message","PRODUCTION_API_URL","DEVELOPMENT_API_URL","GalvaBase","options","apiKey","method","endpoint","data","url","controller","timeoutId","errorResponse","endUserId","defaultInfo","traits","key","value","traitName","GalvaWithCreds","config","credentials","signedPayload","base64Payload","decodedPayloadString","decodedPayload","subscription","finalPayload","rawBody","signature","paddle","Paddle","eventData","Galva","bundleId","appAppleId","eventEntity"],"mappings":"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","file":"index.js","sourcesContent":["import { PlaystoreCredentials } from '@/galva';\nimport { google } from 'googleapis';\n\nconst authentication = (payload: { email: string; key: string }) => {\n const jwtClient = new google.auth.JWT({\n email: payload.email,\n key: payload.key.replace(/\\\\n/g, '\\n'),\n scopes: ['https://www.googleapis.com/auth/androidpublisher'],\n });\n return google.androidpublisher({\n version: 'v3',\n auth: jwtClient,\n });\n};\n\nexport class PlaystoreService {\n static async getSubscription(\n token: string,\n packageName: string,\n cred: PlaystoreCredentials,\n ): Promise<any> {\n const client = authentication({\n email: cred.email,\n key: cred.key,\n });\n\n const result = await client.purchases.subscriptionsv2.get({\n token,\n packageName,\n });\n\n return result;\n }\n}\n","/**\n * Default trait names for end user profiles.\n * Use these constants for type-safe access to built-in traits.\n */\nexport enum EndUserDefaultTraitName {\n TIMEZONE = \"$gv_timezone\",\n LANGUAGE_CODE = \"$gv_languageCode\",\n EMAIL = \"$gv_email\",\n FULL_NAME = \"$gv_fullName\",\n FIRST_NAME = \"$gv_firstName\",\n LAST_NAME = \"$gv_lastName\",\n COUNTRY = \"$gv_country\",\n TOTAL_LIFETIME_VALUE = \"$gv_totalLifetimeValue\",\n}\n\n/**\n * Built-in trait types with their expected value types.\n */\nexport interface EndUserDefaultTraits {\n [EndUserDefaultTraitName.TIMEZONE]?: string;\n [EndUserDefaultTraitName.LANGUAGE_CODE]?: string;\n [EndUserDefaultTraitName.EMAIL]?: string;\n [EndUserDefaultTraitName.FULL_NAME]?: string;\n [EndUserDefaultTraitName.FIRST_NAME]?: string;\n [EndUserDefaultTraitName.LAST_NAME]?: string;\n [EndUserDefaultTraitName.COUNTRY]?: string;\n [EndUserDefaultTraitName.TOTAL_LIFETIME_VALUE]?: number;\n}\n\n/**\n * End user traits combining default traits with arbitrary custom traits.\n */\nexport type EndUserTraits = EndUserDefaultTraits & Record<string, any>;\n\n/**\n * Friendly parameter names for updating end user default info.\n * Maps to the underlying $gv_ prefixed trait names.\n */\nexport interface EndUserDefaultInfo {\n timezone?: string;\n languageCode?: string;\n email?: string;\n fullName?: string;\n firstName?: string;\n lastName?: string;\n country?: string;\n totalLifetimeValue?: number;\n}\n\n/**\n * Mapping from friendly parameter names to original trait names.\n * TypeScript ensures this mapping contains all keys from EndUserDefaultTraitName.\n */\nexport const END_USER_DEFAULT_TRAIT_MAP: Record<\n EndUserDefaultTraitName,\n keyof EndUserDefaultInfo\n> = {\n [EndUserDefaultTraitName.TIMEZONE]: \"timezone\",\n [EndUserDefaultTraitName.LANGUAGE_CODE]: \"languageCode\",\n [EndUserDefaultTraitName.EMAIL]: \"email\",\n [EndUserDefaultTraitName.FULL_NAME]: \"fullName\",\n [EndUserDefaultTraitName.FIRST_NAME]: \"firstName\",\n [EndUserDefaultTraitName.LAST_NAME]: \"lastName\",\n [EndUserDefaultTraitName.COUNTRY]: \"country\",\n [EndUserDefaultTraitName.TOTAL_LIFETIME_VALUE]: \"totalLifetimeValue\",\n};\n\n/**\n * Reverse mapping from friendly parameter names to trait names.\n * TypeScript ensures this mapping contains all keys from EndUserDefaultInfo.\n */\nexport const END_USER_DEFAULT_INFO_TO_TRAIT_MAP: Record<\n keyof EndUserDefaultInfo,\n EndUserDefaultTraitName\n> = {\n timezone: EndUserDefaultTraitName.TIMEZONE,\n languageCode: EndUserDefaultTraitName.LANGUAGE_CODE,\n email: EndUserDefaultTraitName.EMAIL,\n fullName: EndUserDefaultTraitName.FULL_NAME,\n firstName: EndUserDefaultTraitName.FIRST_NAME,\n lastName: EndUserDefaultTraitName.LAST_NAME,\n country: EndUserDefaultTraitName.COUNTRY,\n totalLifetimeValue: EndUserDefaultTraitName.TOTAL_LIFETIME_VALUE,\n};\n","/**\n * Error response structure from Galva API.\n */\nexport interface GalvaErrorResponse {\n code: string;\n message: string;\n}\n\n/**\n * Custom error class for Galva SDK errors.\n * Extends Error with additional properties from API error responses.\n */\nexport class GalvaError extends Error {\n /** Error code from the API (e.g., 'BAD_REQUEST', 'UNAUTHORIZED') */\n readonly code: string;\n\n constructor(response: GalvaErrorResponse) {\n super(response.message);\n this.name = \"GalvaError\";\n this.code = response.code;\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, GalvaError);\n }\n }\n\n /**\n * Creates a GalvaError from an unknown error.\n * If the error is already a GalvaError, returns it as-is.\n * Otherwise, wraps it in a GalvaError with UNKNOWN_ERROR code.\n */\n static from(error: unknown): GalvaError {\n if (error instanceof GalvaError) {\n return error;\n }\n\n const message =\n error instanceof Error ? error.message : \"An unknown error occurred\";\n\n return new GalvaError({\n code: \"UNKNOWN_ERROR\",\n message,\n });\n }\n}\n","import { PlaystoreDeveloperNotification } from './types/playstore';\nimport { PlaystoreService } from './services/playstore';\nimport { EventEntity, Paddle } from '@paddle/paddle-node-sdk';\nimport type { EndUserTraits, EndUserDefaultInfo } from './types/endUser';\nimport { END_USER_DEFAULT_INFO_TO_TRAIT_MAP } from './types/endUser';\nimport { GalvaError } from './types/error';\n\nexport type {\n EndUserDefaultTraits,\n EndUserTraits,\n EndUserDefaultInfo,\n} from './types/endUser';\nexport {\n EndUserDefaultTraitName,\n END_USER_DEFAULT_TRAIT_MAP,\n END_USER_DEFAULT_INFO_TO_TRAIT_MAP,\n} from './types/endUser';\nexport { GalvaError } from './types/error';\nexport type { GalvaErrorResponse } from './types/error';\n\nexport interface GalvaOptions {\n /**\n * Defaults to 'production'. Falls back to NODE_ENV.\n * @type {('production' | 'development')}\n */\n environment?: 'production' | 'development';\n\n /**\n * API key from Galva dashboard. Falls back to GALVA_API_KEY env var.\n * @type {string}\n */\n apiKey?: string;\n\n /**\n * Request timeout in ms. Defaults to 10000.\n * @type {number}\n */\n timeout?: number;\n}\n\n/**\n * Credentials for authenticating with the Google Play Store API.\n *\n * @interface PlaystoreCredentials\n */\nexport interface PlaystoreCredentials {\n /** Service account email address */\n email: string;\n /** Service account private key */\n key: string;\n}\n\n/**\n * Credentials for authenticating and verifying Paddle webhooks.\n *\n * @interface PaddleCredentials\n */\nexport interface PaddleCredentials {\n /** Paddle API key */\n apiKey: string;\n /** Webhook secret key for signature verification */\n secretKey: string;\n}\n\n/**\n * Credentials for Apple App Store.\n *\n * @interface AppstoreCredentials\n */\nexport interface AppstoreCredentials {\n /** The app's bundle identifier (e.g., 'com.example.app') */\n bundleId: string;\n /** The app's Apple ID from App Store Connect */\n appAppleId: number;\n}\n\n/**\n * Credentials configuration for withCredentials method.\n *\n * @interface CredentialsConfig\n */\nexport interface CredentialsConfig {\n /** App Store credentials */\n appstore?: AppstoreCredentials;\n /** Play Store service account credentials */\n playstore?: PlaystoreCredentials;\n /** Paddle API credentials */\n paddle?: PaddleCredentials;\n}\n\nconst PRODUCTION_API_URL = 'https://api.galva.io';\nconst DEVELOPMENT_API_URL = 'https://api.galva.dev';\n\n/**\n * Base Galva SDK client with end user management methods.\n * @class GalvaBase\n */\nclass GalvaBase {\n protected config: GalvaOptions;\n protected readonly baseUrl: string;\n\n /**\n * @param {GalvaOptions} [options] - Configuration options\n * @throws {GalvaError} If API key is not provided\n */\n constructor(options?: GalvaOptions) {\n const apiKey = options?.apiKey || process.env.GALVA_API_KEY;\n if (!apiKey) {\n throw new GalvaError({\n code: 'MISSING_API_KEY',\n message:\n 'API key is required. Provide it in options or set GALVA_API_KEY env variable.',\n });\n }\n\n this.config = {\n apiKey: apiKey,\n environment:\n options?.environment ||\n (process.env.NODE_ENV === 'development' ? 'development' : 'production'),\n timeout: options?.timeout || 10000,\n };\n this.baseUrl =\n this.config.environment === 'development'\n ? DEVELOPMENT_API_URL\n : PRODUCTION_API_URL;\n }\n\n protected async sendRequest(\n method: 'POST' | 'GET',\n endpoint: string,\n data?: unknown,\n ): Promise<void> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.config.apiKey!,\n },\n body: data ? JSON.stringify(data) : undefined,\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const errorResponse = await response.json();\n throw new GalvaError(errorResponse);\n }\n } catch (error) {\n if (error instanceof GalvaError) {\n throw error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new GalvaError({\n code: 'TIMEOUT',\n message: `Request timed out after ${this.config.timeout}ms`,\n });\n }\n throw GalvaError.from(error);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** End user management methods. */\n public endUser = {\n /**\n * Identifies an end user with optional traits and context.\n * @param {string} endUserId - End user ID in your system\n * @param {Object} [options] - Identification data\n * @param {string} [options.timestamp] - ISO 8601 timestamp\n * @param {Record<string, any>} [options.context] - Additional context\n * @param {EndUserTraits} [options.traits] - User traits. Use EndUserDefaultTraitName for built-in traits.\n * @throws {GalvaError} If API request fails\n * @example\n * ```typescript\n * await galva.endUser.identify('user-123', {\n * traits: { [EndUserDefaultTraitName.EMAIL]: 'user@example.com' },\n * });\n * ```\n */\n identify: async (\n endUserId: string,\n options?: {\n timestamp?: string;\n context?: Record<string, any>;\n traits?: EndUserTraits;\n },\n ): Promise<void> => {\n await this.sendRequest('POST', '/endUsers:identify', {\n endUserId,\n timestamp: options?.timestamp,\n context: options?.context,\n traits: options?.traits,\n });\n },\n\n /**\n * Updates an end user's default profile info.\n * @param {string} endUserId - End user ID in your system\n * @param {EndUserDefaultInfo} defaultInfo - Profile info to update\n * @throws {GalvaError} If API request fails\n * @example\n * ```typescript\n * await galva.endUser.updateDefaultInfo('user-123', {\n * email: 'user@example.com',\n * fullName: 'John Doe',\n * });\n * ```\n */\n updateDefaultInfo: async (\n endUserId: string,\n defaultInfo: EndUserDefaultInfo,\n ): Promise<void> => {\n const traits: EndUserTraits = {};\n\n for (const [key, value] of Object.entries(defaultInfo)) {\n if (value !== undefined) {\n const traitName =\n END_USER_DEFAULT_INFO_TO_TRAIT_MAP[key as keyof EndUserDefaultInfo];\n traits[traitName] = value;\n }\n }\n\n await this.sendRequest('POST', '/endUsers:identify', {\n endUserId,\n traits,\n });\n },\n };\n}\n\nclass GalvaWithCreds extends GalvaBase {\n // private config: GalvaOptions;\n // private readonly baseUrl: string;\n private credentials: CredentialsConfig;\n\n /**\n * @internal Use `galva.withCredentials(...)` instead.\n */\n constructor(config: GalvaOptions, credentials: CredentialsConfig) {\n super(config);\n // this.config = config;\n // this.baseUrl = baseUrl;\n this.credentials = credentials;\n }\n\n /**\n * Billing event handlers with credential support.\n * @property {Function} appstore - App Store events (signed payload only)\n * @property {Function} playstore - Play Store events (raw base64 payload)\n * @property {Function} paddle - Paddle events (raw body with verification)\n */\n public billingEvent = {\n /**\n * Tracks an App Store billing event. Uses credentials from withCredentials().\n * @param {string} endUserId - End user ID\n * @param {string} signedPayload - Signed payload from Apple\n * @param {Record<string, any>} [options] - Additional options\n * @returns {Promise<void>}\n * @throws {GalvaError} If credentials missing or API fails\n * @example\n * ```typescript\n * await galvaWithCreds.billingEvent.appstore('user-123', signedPayload);\n * ```\n */\n appstore: async (\n endUserId: string,\n signedPayload: string,\n options?: Record<string, any>,\n ): Promise<void> => {\n if (!this.credentials.appstore) {\n throw new GalvaError({\n code: 'MISSING_CREDENTIALS',\n message:\n 'App Store credentials are required. Provide them in withCredentials().',\n });\n }\n\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'appstore',\n endUserId,\n payload: {\n signedPayload,\n appAppleId: this.credentials.appstore.appAppleId,\n bundleId: this.credentials.appstore.bundleId,\n env: this.config.environment,\n },\n options,\n });\n },\n\n /**\n * Tracks a Play Store billing event. Decodes payload and fetches subscription details.\n * @param {string} endUserId - End user ID\n * @param {string} base64Payload - Base64 payload from Pub/Sub\n * @returns {Promise<void>}\n * @throws {GalvaError} If credentials missing or API fails\n * @example\n * ```typescript\n * await galvaWithCreds.billingEvent.playstore('user-123', base64Payload);\n * ```\n */\n playstore: async (\n endUserId: string,\n base64Payload: string,\n ): Promise<void> => {\n if (!this.credentials.playstore) {\n throw new GalvaError({\n code: 'MISSING_CREDENTIALS',\n message:\n 'Play Store credentials are required. Provide them in withCredentials().',\n });\n }\n\n const decodedPayloadString = Buffer.from(\n base64Payload,\n 'base64',\n ).toString();\n let decodedPayload: PlaystoreDeveloperNotification | null = null;\n try {\n decodedPayload = JSON.parse(decodedPayloadString);\n } catch (error) {\n throw new GalvaError({\n code: 'INVALID_PAYLOAD',\n message: 'Invalid base64 payload: unable to parse JSON.',\n });\n }\n\n if (!decodedPayload || typeof decodedPayload !== 'object') {\n throw new GalvaError({\n code: 'INVALID_PAYLOAD',\n message: 'Decoded payload is not a valid JSON object.',\n });\n }\n\n if (!('subscriptionNotification' in decodedPayload)) {\n throw new GalvaError({\n code: 'INVALID_PAYLOAD',\n message:\n 'Decoded payload does not contain subscriptionNotification field.',\n });\n }\n\n const subscription = await PlaystoreService.getSubscription(\n decodedPayload.subscriptionNotification.purchaseToken,\n decodedPayload.packageName,\n this.credentials.playstore,\n );\n\n const finalPayload: PlaystoreDeveloperNotification = {\n ...decodedPayload,\n subscriptionNotification: {\n ...decodedPayload.subscriptionNotification,\n subscriptionPurchase: subscription.data,\n },\n };\n\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'playstore',\n endUserId,\n payload: finalPayload,\n });\n },\n\n /**\n * Tracks a Paddle billing event. Verifies signature and unmarshals the event.\n * @param {string} endUserId - End user ID\n * @param {string} rawBody - Raw body from Paddle webhook\n * @param {string} signature - Paddle-Signature header value\n * @returns {Promise<void>}\n * @throws {GalvaError} If credentials missing or API fails\n * @example\n * ```typescript\n * await galvaWithCreds.billingEvent.paddle('user-123', rawBody, signature);\n * ```\n */\n paddle: async (\n endUserId: string,\n rawBody: string,\n signature: string,\n ): Promise<void> => {\n if (!this.credentials.paddle) {\n throw new GalvaError({\n code: 'MISSING_CREDENTIALS',\n message:\n 'Paddle credentials are required. Provide them in withCredentials().',\n });\n }\n\n const paddle = new Paddle(this.credentials.paddle.apiKey);\n let eventData: EventEntity;\n\n try {\n eventData = await paddle.webhooks.unmarshal(\n rawBody,\n this.credentials.paddle.secretKey,\n signature,\n );\n } catch (error) {\n throw new GalvaError({\n code: 'INVALID_WEBHOOK',\n message: `Invalid Paddle webhook data: ${(error as Error).message}`,\n });\n }\n\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'paddle',\n endUserId,\n payload: eventData,\n });\n },\n };\n}\n\nexport class Galva extends GalvaBase {\n constructor(options?: GalvaOptions) {\n super(options);\n }\n\n /**\n * Billing event handlers for different payment platforms.\n * @property {Function} appstore - App Store events\n * @property {Function} playstore - Play Store events (decoded payload)\n * @property {Function} paddle - Paddle events (verified event)\n */\n public billingEvent = {\n /**\n * Tracks an App Store billing event.\n * @param {string} endUserId - End user ID\n * @param {Object} payload - App Store notification payload\n * @param {string} payload.signedPayload - Signed payload from Apple\n * @param {string} payload.bundleId - App bundle identifier\n * @param {number} payload.appAppleId - App Apple ID\n * @param {Record<string, any>} [options] - Additional options\n * @throws {GalvaError} If API request fails\n * @example\n * ```typescript\n * await galva.billingEvent.appstore('user-123', {\n * signedPayload,\n * bundleId: 'com.example.app',\n * appAppleId: 123456789\n * });\n * ```\n */\n appstore: async (\n endUserId: string,\n payload: {\n signedPayload: string;\n bundleId: string;\n appAppleId: number;\n },\n options?: Record<string, any>,\n ): Promise<void> => {\n const { signedPayload, bundleId, appAppleId } = payload;\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'appstore',\n endUserId,\n payload: {\n signedPayload,\n appAppleId: appAppleId,\n bundleId: bundleId,\n env: this.config.environment,\n },\n options,\n });\n },\n\n /**\n * Tracks a Play Store billing event with decoded notification.\n * @param {string} endUserId - End user ID\n * @param {PlaystoreDeveloperNotification} payload - Decoded developer notification\n * @returns {Promise<void>}\n * @example\n * ```typescript\n * await galva.billingEvent.playstore('user-123', decodedNotification);\n * ```\n */\n playstore: async (\n endUserId: string,\n payload: PlaystoreDeveloperNotification,\n ): Promise<void> => {\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'playstore',\n endUserId,\n payload,\n });\n },\n\n /**\n * Tracks a Paddle billing event with verified event entity.\n * @param {string} endUserId - End user ID\n * @param {EventEntity} eventEntity - Verified Paddle event\n * @returns {Promise<void>}\n * @example\n * ```typescript\n * await galva.billingEvent.paddle('user-123', verifiedEvent);\n * ```\n */\n paddle: async (\n endUserId: string,\n eventEntity: EventEntity,\n ): Promise<void> => {\n await this.sendRequest('POST', '/endUsers/billingEvents', {\n platform: 'paddle',\n endUserId,\n payload: eventEntity,\n });\n },\n };\n\n withCredentials(credentials: CredentialsConfig) {\n return new GalvaWithCreds(this.config, credentials);\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@galva-io/galva-admin-node",
|
|
3
|
+
"version": "1.0.0-dev",
|
|
4
|
+
"description": "Node.js SDK for Galva Admin API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"dev": "tsup --watch"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"galva",
|
|
22
|
+
"admin",
|
|
23
|
+
"sdk",
|
|
24
|
+
"billing",
|
|
25
|
+
"subscription",
|
|
26
|
+
"api"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@paddle/paddle-node-sdk": "^3.6.0",
|
|
32
|
+
"@types/node": "^20.19.33",
|
|
33
|
+
"googleapis": "^171.4.0",
|
|
34
|
+
"tsup": "^8.5.1",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@paddle/paddle-node-sdk": "^3.6.0",
|
|
46
|
+
"googleapis": "^171.4.0"
|
|
47
|
+
}
|
|
48
|
+
}
|