dgc-core 2.0.4 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/dist/constants/revenuecat.d.ts +7 -0
- package/dist/constants/revenuecat.js +25 -0
- package/dist/enums/giveaway.d.ts +4 -2
- package/dist/enums/giveaway.js +2 -0
- package/dist/giveaways/domain.d.ts +2 -2
- package/dist/giveaways/domain.js +17 -6
- package/dist/giveaways/eligibility.d.ts +5 -0
- package/dist/giveaways/eligibility.js +24 -0
- package/dist/giveaways/giveaway-repository.d.ts +23 -6
- package/dist/giveaways/giveaway-repository.js +324 -40
- package/dist/giveaways/index.d.ts +1 -0
- package/dist/giveaways/index.js +1 -0
- package/dist/giveaways/schemas.d.ts +173 -8
- package/dist/giveaways/schemas.js +7 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/revenuecat.d.ts +16 -0
- package/dist/revenuecat.js +206 -0
- package/dist/types/giveaway.d.ts +35 -4
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/revenuecat.d.ts +19 -0
- package/dist/types/revenuecat.js +2 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GiveawayCategory, GiveawaySelectionMethod } from '../enums/giveaway';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
export declare const entryIdSchema: z.ZodString;
|
|
4
|
-
export declare const draftSchema: z.ZodObject<{
|
|
4
|
+
export declare const draftSchema: z.ZodPipe<z.ZodObject<{
|
|
5
5
|
images: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
6
6
|
title: z.ZodString;
|
|
7
7
|
description: z.ZodString;
|
|
@@ -37,8 +37,80 @@ export declare const draftSchema: z.ZodObject<{
|
|
|
37
37
|
message: z.ZodString;
|
|
38
38
|
}, z.core.$strip>;
|
|
39
39
|
}, z.core.$strip>>;
|
|
40
|
-
}, z.core.$strip
|
|
41
|
-
|
|
40
|
+
}, z.core.$strip>, z.ZodTransform<{
|
|
41
|
+
countries: string[];
|
|
42
|
+
images: string[];
|
|
43
|
+
title: string;
|
|
44
|
+
description: string;
|
|
45
|
+
image: string;
|
|
46
|
+
startsAt: string;
|
|
47
|
+
closesAt: string;
|
|
48
|
+
rules: string;
|
|
49
|
+
category: GiveawayCategory;
|
|
50
|
+
creditAmount: number;
|
|
51
|
+
claimWindowDays: number;
|
|
52
|
+
deliveryInformation: string;
|
|
53
|
+
prizes: {
|
|
54
|
+
name: string;
|
|
55
|
+
quantity: 1;
|
|
56
|
+
value: number;
|
|
57
|
+
_id?: string | undefined;
|
|
58
|
+
}[];
|
|
59
|
+
launch: {
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
title: string;
|
|
62
|
+
message: string;
|
|
63
|
+
};
|
|
64
|
+
reminders: {
|
|
65
|
+
closing: {
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
title: string;
|
|
68
|
+
message: string;
|
|
69
|
+
};
|
|
70
|
+
results: {
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
title: string;
|
|
73
|
+
message: string;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
}, {
|
|
77
|
+
images: string[];
|
|
78
|
+
title: string;
|
|
79
|
+
description: string;
|
|
80
|
+
image: string;
|
|
81
|
+
startsAt: string;
|
|
82
|
+
closesAt: string;
|
|
83
|
+
countries: string[];
|
|
84
|
+
rules: string;
|
|
85
|
+
category: GiveawayCategory;
|
|
86
|
+
creditAmount: number;
|
|
87
|
+
claimWindowDays: number;
|
|
88
|
+
deliveryInformation: string;
|
|
89
|
+
prizes: {
|
|
90
|
+
name: string;
|
|
91
|
+
quantity: 1;
|
|
92
|
+
value: number;
|
|
93
|
+
_id?: string | undefined;
|
|
94
|
+
}[];
|
|
95
|
+
launch: {
|
|
96
|
+
enabled: boolean;
|
|
97
|
+
title: string;
|
|
98
|
+
message: string;
|
|
99
|
+
};
|
|
100
|
+
reminders: {
|
|
101
|
+
closing: {
|
|
102
|
+
enabled: boolean;
|
|
103
|
+
title: string;
|
|
104
|
+
message: string;
|
|
105
|
+
};
|
|
106
|
+
results: {
|
|
107
|
+
enabled: boolean;
|
|
108
|
+
title: string;
|
|
109
|
+
message: string;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
}>>;
|
|
113
|
+
export declare const giveawaySchema: z.ZodPipe<z.ZodObject<{
|
|
42
114
|
description: z.ZodString;
|
|
43
115
|
image: z.ZodString;
|
|
44
116
|
rules: z.ZodString;
|
|
@@ -74,14 +146,85 @@ export declare const giveawaySchema: z.ZodObject<{
|
|
|
74
146
|
message: z.ZodString;
|
|
75
147
|
}, z.core.$strip>;
|
|
76
148
|
}, z.core.$strip>>;
|
|
77
|
-
}, z.core.$strip
|
|
149
|
+
}, z.core.$strip>, z.ZodTransform<{
|
|
150
|
+
countries: string[];
|
|
151
|
+
description: string;
|
|
152
|
+
image: string;
|
|
153
|
+
rules: string;
|
|
154
|
+
images: string[];
|
|
155
|
+
title: string;
|
|
156
|
+
startsAt: string;
|
|
157
|
+
closesAt: string;
|
|
158
|
+
category: GiveawayCategory;
|
|
159
|
+
creditAmount: number;
|
|
160
|
+
claimWindowDays: number;
|
|
161
|
+
deliveryInformation: string;
|
|
162
|
+
prizes: {
|
|
163
|
+
name: string;
|
|
164
|
+
quantity: 1;
|
|
165
|
+
value: number;
|
|
166
|
+
_id?: string | undefined;
|
|
167
|
+
}[];
|
|
168
|
+
launch: {
|
|
169
|
+
enabled: boolean;
|
|
170
|
+
title: string;
|
|
171
|
+
message: string;
|
|
172
|
+
};
|
|
173
|
+
reminders: {
|
|
174
|
+
closing: {
|
|
175
|
+
enabled: boolean;
|
|
176
|
+
title: string;
|
|
177
|
+
message: string;
|
|
178
|
+
};
|
|
179
|
+
results: {
|
|
180
|
+
enabled: boolean;
|
|
181
|
+
title: string;
|
|
182
|
+
message: string;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
}, {
|
|
186
|
+
description: string;
|
|
187
|
+
image: string;
|
|
188
|
+
rules: string;
|
|
189
|
+
images: string[];
|
|
190
|
+
title: string;
|
|
191
|
+
startsAt: string;
|
|
192
|
+
closesAt: string;
|
|
193
|
+
countries: string[];
|
|
194
|
+
category: GiveawayCategory;
|
|
195
|
+
creditAmount: number;
|
|
196
|
+
claimWindowDays: number;
|
|
197
|
+
deliveryInformation: string;
|
|
198
|
+
prizes: {
|
|
199
|
+
name: string;
|
|
200
|
+
quantity: 1;
|
|
201
|
+
value: number;
|
|
202
|
+
_id?: string | undefined;
|
|
203
|
+
}[];
|
|
204
|
+
launch: {
|
|
205
|
+
enabled: boolean;
|
|
206
|
+
title: string;
|
|
207
|
+
message: string;
|
|
208
|
+
};
|
|
209
|
+
reminders: {
|
|
210
|
+
closing: {
|
|
211
|
+
enabled: boolean;
|
|
212
|
+
title: string;
|
|
213
|
+
message: string;
|
|
214
|
+
};
|
|
215
|
+
results: {
|
|
216
|
+
enabled: boolean;
|
|
217
|
+
title: string;
|
|
218
|
+
message: string;
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
}>>;
|
|
78
222
|
export declare const entrySchema: z.ZodObject<{
|
|
79
|
-
country: z.ZodOptional<z.ZodString>;
|
|
80
223
|
rulesAccepted: z.ZodLiteral<true>;
|
|
81
224
|
marketingConsent: z.ZodDefault<z.ZodBoolean>;
|
|
82
225
|
}, z.core.$strip>;
|
|
83
226
|
export declare const physicalClaimSchema: z.ZodObject<{
|
|
84
|
-
fullName: z.ZodString
|
|
227
|
+
fullName: z.ZodOptional<z.ZodString>;
|
|
85
228
|
address: z.ZodObject<{
|
|
86
229
|
line1: z.ZodString;
|
|
87
230
|
line2: z.ZodDefault<z.ZodString>;
|
|
@@ -109,7 +252,7 @@ export declare const selectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
109
252
|
reason: z.ZodString;
|
|
110
253
|
revision: z.ZodNumber;
|
|
111
254
|
}, z.core.$strip>], "method">;
|
|
112
|
-
export declare const presetSchema: z.ZodObject<{
|
|
255
|
+
export declare const presetSchema: z.ZodPipe<z.ZodObject<{
|
|
113
256
|
name: z.ZodString;
|
|
114
257
|
title: z.ZodString;
|
|
115
258
|
description: z.ZodString;
|
|
@@ -120,4 +263,26 @@ export declare const presetSchema: z.ZodObject<{
|
|
|
120
263
|
countries: z.ZodArray<z.ZodString>;
|
|
121
264
|
rules: z.ZodString;
|
|
122
265
|
deliveryInformation: z.ZodDefault<z.ZodString>;
|
|
123
|
-
}, z.core.$strip
|
|
266
|
+
}, z.core.$strip>, z.ZodTransform<{
|
|
267
|
+
countries: string[];
|
|
268
|
+
name: string;
|
|
269
|
+
title: string;
|
|
270
|
+
description: string;
|
|
271
|
+
image: string;
|
|
272
|
+
images: string[];
|
|
273
|
+
category: GiveawayCategory;
|
|
274
|
+
creditAmount: number;
|
|
275
|
+
rules: string;
|
|
276
|
+
deliveryInformation: string;
|
|
277
|
+
}, {
|
|
278
|
+
name: string;
|
|
279
|
+
title: string;
|
|
280
|
+
description: string;
|
|
281
|
+
image: string;
|
|
282
|
+
images: string[];
|
|
283
|
+
category: GiveawayCategory;
|
|
284
|
+
creditAmount: number;
|
|
285
|
+
countries: string[];
|
|
286
|
+
rules: string;
|
|
287
|
+
deliveryInformation: string;
|
|
288
|
+
}>>;
|
|
@@ -67,7 +67,8 @@ exports.draftSchema = zod_1.z
|
|
|
67
67
|
.refine(v => new Date(v.closesAt) > new Date(v.startsAt), {
|
|
68
68
|
message: 'Closing time must be after start time',
|
|
69
69
|
path: ['closesAt']
|
|
70
|
-
})
|
|
70
|
+
})
|
|
71
|
+
.transform(v => ({ ...v, countries: v.category === giveaway_1.GiveawayCategory.Credits ? [] : v.countries }));
|
|
71
72
|
exports.giveawaySchema = zod_1.z
|
|
72
73
|
.object({
|
|
73
74
|
...fields,
|
|
@@ -86,14 +87,14 @@ exports.giveawaySchema = zod_1.z
|
|
|
86
87
|
for (const [name, value] of Object.entries({ launch: v.launch, ...v.reminders }))
|
|
87
88
|
if (value.enabled && (!value.title || !value.message))
|
|
88
89
|
issue([name], 'Add a push title and message');
|
|
89
|
-
})
|
|
90
|
+
})
|
|
91
|
+
.transform(v => ({ ...v, countries: v.category === giveaway_1.GiveawayCategory.Credits ? [] : v.countries }));
|
|
90
92
|
exports.entrySchema = zod_1.z.object({
|
|
91
|
-
country: country.optional(),
|
|
92
93
|
rulesAccepted: zod_1.z.literal(true, { error: 'You must agree to the giveaway rules' }),
|
|
93
94
|
marketingConsent: zod_1.z.boolean().default(false)
|
|
94
95
|
});
|
|
95
96
|
exports.physicalClaimSchema = zod_1.z.object({
|
|
96
|
-
fullName: zod_1.z.string().trim().min(2).max(120),
|
|
97
|
+
fullName: zod_1.z.string().trim().min(2).max(120).optional(),
|
|
97
98
|
address: zod_1.z.object({
|
|
98
99
|
line1: zod_1.z.string().trim().min(2).max(200),
|
|
99
100
|
line2: zod_1.z.string().trim().max(200).default(''),
|
|
@@ -139,4 +140,5 @@ exports.presetSchema = zod_1.z
|
|
|
139
140
|
.refine(v => v.category !== giveaway_1.GiveawayCategory.Credits || v.creditAmount > 0, {
|
|
140
141
|
message: 'Configure the instant credit reward amount',
|
|
141
142
|
path: ['creditAmount']
|
|
142
|
-
})
|
|
143
|
+
})
|
|
144
|
+
.transform(v => ({ ...v, countries: v.category === giveaway_1.GiveawayCategory.Credits ? [] : v.countries }));
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -36,3 +36,5 @@ __exportStar(require("./validator"), exports);
|
|
|
36
36
|
__exportStar(require("./id"), exports);
|
|
37
37
|
__exportStar(require("./http-error"), exports);
|
|
38
38
|
__exportStar(require("./giveaways"), exports);
|
|
39
|
+
__exportStar(require("./constants/revenuecat"), exports);
|
|
40
|
+
__exportStar(require("./revenuecat"), exports);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { HttpError } from './http-error';
|
|
2
|
+
import type { GiveawayCreditEligibilityEvidence, RevenueCatClient, RevenueCatEntitlementIds } from './types/revenuecat';
|
|
3
|
+
export type RevenueCatClientOptions = {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
totalTimeoutMs?: number;
|
|
8
|
+
maxPages?: number;
|
|
9
|
+
};
|
|
10
|
+
/** Deliberately excludes the original HTTP error, which can contain the API key. */
|
|
11
|
+
export declare class RevenueCatLookupError extends HttpError {
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
14
|
+
export declare function createRevenueCatClient(options: RevenueCatClientOptions): RevenueCatClient;
|
|
15
|
+
/** Resolve any eligible entitlement from one customer lookup; never infer Free from API failures. */
|
|
16
|
+
export declare function verifyCreditGiveawayEntitlement(userId: string, client: RevenueCatClient, entitlementIds: RevenueCatEntitlementIds): Promise<GiveawayCreditEligibilityEvidence | null>;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RevenueCatLookupError = void 0;
|
|
7
|
+
exports.createRevenueCatClient = createRevenueCatClient;
|
|
8
|
+
exports.verifyCreditGiveawayEntitlement = verifyCreditGiveawayEntitlement;
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const revenuecat_1 = require("./constants/revenuecat");
|
|
11
|
+
const user_1 = require("./enums/user");
|
|
12
|
+
const http_error_1 = require("./http-error");
|
|
13
|
+
/** Deliberately excludes the original HTTP error, which can contain the API key. */
|
|
14
|
+
class RevenueCatLookupError extends http_error_1.HttpError {
|
|
15
|
+
constructor() {
|
|
16
|
+
super('Unable to verify RevenueCat entitlement access. Please try again.', 503);
|
|
17
|
+
this.name = 'RevenueCatLookupError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.RevenueCatLookupError = RevenueCatLookupError;
|
|
21
|
+
function positiveInteger(value, maximum) {
|
|
22
|
+
return Number.isInteger(value) && value > 0 && value <= maximum;
|
|
23
|
+
}
|
|
24
|
+
function validUserId(value) {
|
|
25
|
+
return (typeof value === 'string' &&
|
|
26
|
+
value.length > 0 &&
|
|
27
|
+
value.length <= 128 &&
|
|
28
|
+
value.trim() === value &&
|
|
29
|
+
!/[.#$[\]/]/.test(value) &&
|
|
30
|
+
!Array.from(value).some(character => {
|
|
31
|
+
const code = character.charCodeAt(0);
|
|
32
|
+
return code < 32 || code === 127;
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function parsePage(value) {
|
|
36
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
37
|
+
throw new RevenueCatLookupError();
|
|
38
|
+
const page = value;
|
|
39
|
+
if (!Array.isArray(page.items) ||
|
|
40
|
+
(page.next_page != null && (typeof page.next_page !== 'string' || !page.next_page)))
|
|
41
|
+
throw new RevenueCatLookupError();
|
|
42
|
+
const items = page.items.map((raw) => {
|
|
43
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
44
|
+
throw new RevenueCatLookupError();
|
|
45
|
+
const entitlement = raw;
|
|
46
|
+
if (typeof entitlement.entitlement_id !== 'string' ||
|
|
47
|
+
!/^entl[a-zA-Z0-9]+$/.test(entitlement.entitlement_id) ||
|
|
48
|
+
(entitlement.expires_at !== null &&
|
|
49
|
+
(typeof entitlement.expires_at !== 'number' ||
|
|
50
|
+
!Number.isSafeInteger(entitlement.expires_at) ||
|
|
51
|
+
entitlement.expires_at < 0)))
|
|
52
|
+
throw new RevenueCatLookupError();
|
|
53
|
+
return {
|
|
54
|
+
entitlementId: entitlement.entitlement_id,
|
|
55
|
+
expiresAt: entitlement.expires_at
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
return { items, nextPage: page.next_page ?? null };
|
|
59
|
+
}
|
|
60
|
+
function retryAfterMs(value) {
|
|
61
|
+
if (typeof value === 'number')
|
|
62
|
+
return Number.isFinite(value) && value >= 0 ? value * 1000 : null;
|
|
63
|
+
if (typeof value !== 'string' || !value.trim())
|
|
64
|
+
return null;
|
|
65
|
+
const text = value.trim();
|
|
66
|
+
if (/^\d+$/.test(text)) {
|
|
67
|
+
const seconds = Number(text);
|
|
68
|
+
return Number.isFinite(seconds) ? seconds * 1000 : null;
|
|
69
|
+
}
|
|
70
|
+
const date = Date.parse(text);
|
|
71
|
+
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
|
|
72
|
+
}
|
|
73
|
+
/** Inspect only retry metadata; never retain or log the original transport error. */
|
|
74
|
+
function requestRetryDelay(error, attempt) {
|
|
75
|
+
if (!error || typeof error !== 'object')
|
|
76
|
+
return null;
|
|
77
|
+
const failure = error;
|
|
78
|
+
const response = failure.response && typeof failure.response === 'object'
|
|
79
|
+
? failure.response
|
|
80
|
+
: undefined;
|
|
81
|
+
if (response) {
|
|
82
|
+
if (typeof response.status !== 'number' || ![429, 500, 502, 503, 504].includes(response.status))
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
else if (typeof failure.code !== 'string' ||
|
|
86
|
+
![
|
|
87
|
+
'ECONNRESET',
|
|
88
|
+
'ETIMEDOUT',
|
|
89
|
+
'ECONNABORTED',
|
|
90
|
+
'EAI_AGAIN',
|
|
91
|
+
'ENOTFOUND',
|
|
92
|
+
'ERR_NETWORK',
|
|
93
|
+
'EPIPE',
|
|
94
|
+
'ECONNREFUSED'
|
|
95
|
+
].includes(failure.code))
|
|
96
|
+
return null;
|
|
97
|
+
let headerDelay = null;
|
|
98
|
+
if (response?.headers && typeof response.headers === 'object') {
|
|
99
|
+
const headers = response.headers;
|
|
100
|
+
headerDelay = retryAfterMs(typeof headers.get === 'function'
|
|
101
|
+
? headers.get('retry-after')
|
|
102
|
+
: (headers['retry-after'] ?? headers['Retry-After']));
|
|
103
|
+
}
|
|
104
|
+
const body = response?.data && typeof response.data === 'object'
|
|
105
|
+
? response.data
|
|
106
|
+
: undefined;
|
|
107
|
+
const bodyDelay = typeof body?.backoff_ms === 'number' && Number.isFinite(body.backoff_ms) && body.backoff_ms >= 0
|
|
108
|
+
? body.backoff_ms
|
|
109
|
+
: 0;
|
|
110
|
+
return Math.max(100 * 2 ** attempt, headerDelay ?? 0, bodyDelay);
|
|
111
|
+
}
|
|
112
|
+
async function requestPage(url, apiKey, timeoutMs, deadline) {
|
|
113
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
114
|
+
const remainingMs = deadline - Date.now();
|
|
115
|
+
if (remainingMs <= 0)
|
|
116
|
+
throw new RevenueCatLookupError();
|
|
117
|
+
try {
|
|
118
|
+
return await axios_1.default.get(url.href, {
|
|
119
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
120
|
+
timeout: Math.min(timeoutMs, remainingMs),
|
|
121
|
+
signal: AbortSignal.timeout(remainingMs),
|
|
122
|
+
maxRedirects: 0,
|
|
123
|
+
maxContentLength: 1024 * 1024
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const delay = attempt < 2 ? requestRetryDelay(error, attempt) : null;
|
|
128
|
+
if (delay === null || delay >= deadline - Date.now())
|
|
129
|
+
throw new RevenueCatLookupError();
|
|
130
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
throw new RevenueCatLookupError();
|
|
134
|
+
}
|
|
135
|
+
function createRevenueCatClient(options) {
|
|
136
|
+
const apiKey = options.apiKey?.trim();
|
|
137
|
+
const projectId = options.projectId?.trim();
|
|
138
|
+
const timeoutMs = options.timeoutMs ?? 15000;
|
|
139
|
+
const totalTimeoutMs = options.totalTimeoutMs ?? 15000;
|
|
140
|
+
const maxPages = options.maxPages ?? 20;
|
|
141
|
+
if (!apiKey || /[\r\n]/.test(apiKey))
|
|
142
|
+
throw new Error('Configure the RevenueCat API key');
|
|
143
|
+
if (!projectId || projectId.length > 255 || !/^[a-zA-Z0-9_-]+$/.test(projectId))
|
|
144
|
+
throw new Error('Configure a valid RevenueCat project ID');
|
|
145
|
+
if (!positiveInteger(timeoutMs, 60000) ||
|
|
146
|
+
!positiveInteger(totalTimeoutMs, 120000) ||
|
|
147
|
+
!positiveInteger(maxPages, 100))
|
|
148
|
+
throw new Error('Configure valid RevenueCat request limits');
|
|
149
|
+
return {
|
|
150
|
+
async getActiveEntitlements(userId) {
|
|
151
|
+
if (!validUserId(userId))
|
|
152
|
+
throw new http_error_1.HttpError('Invalid DGC user ID', 400);
|
|
153
|
+
const endpoint = new URL(`${revenuecat_1.REVENUECAT_API_V2}/projects/${encodeURIComponent(projectId)}/customers/${encodeURIComponent(userId)}/active_entitlements`);
|
|
154
|
+
endpoint.searchParams.set('limit', '100');
|
|
155
|
+
const deadline = Date.now() + totalTimeoutMs;
|
|
156
|
+
const visited = new Set();
|
|
157
|
+
const entitlements = [];
|
|
158
|
+
let url = endpoint;
|
|
159
|
+
try {
|
|
160
|
+
while (url) {
|
|
161
|
+
const remainingMs = deadline - Date.now();
|
|
162
|
+
if (remainingMs <= 0 ||
|
|
163
|
+
visited.size >= maxPages ||
|
|
164
|
+
url.origin !== endpoint.origin ||
|
|
165
|
+
url.pathname !== endpoint.pathname ||
|
|
166
|
+
url.username ||
|
|
167
|
+
url.password ||
|
|
168
|
+
url.hash ||
|
|
169
|
+
visited.has(url.href))
|
|
170
|
+
throw new RevenueCatLookupError();
|
|
171
|
+
visited.add(url.href);
|
|
172
|
+
const response = await requestPage(url, apiKey, timeoutMs, deadline);
|
|
173
|
+
const page = parsePage(response.data);
|
|
174
|
+
entitlements.push(...page.items);
|
|
175
|
+
url = page.nextPage ? new URL(page.nextPage, endpoint) : null;
|
|
176
|
+
}
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
if (now >= deadline)
|
|
179
|
+
throw new RevenueCatLookupError();
|
|
180
|
+
return entitlements.filter(item => item.expiresAt === null || item.expiresAt > now);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Do not attach request/config/cause or log the customer payload.
|
|
184
|
+
throw new RevenueCatLookupError();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** Resolve any eligible entitlement from one customer lookup; never infer Free from API failures. */
|
|
190
|
+
async function verifyCreditGiveawayEntitlement(userId, client, entitlementIds) {
|
|
191
|
+
const ids = (0, revenuecat_1.createRevenueCatEntitlementIds)(entitlementIds);
|
|
192
|
+
const active = await client.getActiveEntitlements(userId);
|
|
193
|
+
const checkedAt = new Date().toISOString();
|
|
194
|
+
const now = Date.parse(checkedAt);
|
|
195
|
+
const tiers = [
|
|
196
|
+
[user_1.UserCollectorType.Elite, ids.elite],
|
|
197
|
+
[user_1.UserCollectorType.Collector, ids.collector],
|
|
198
|
+
[user_1.UserCollectorType.Rookie, ids.rookie]
|
|
199
|
+
];
|
|
200
|
+
for (const [collectorType, entitlementId] of tiers) {
|
|
201
|
+
const match = active.find(item => item.entitlementId === entitlementId && (item.expiresAt === null || item.expiresAt > now));
|
|
202
|
+
if (match)
|
|
203
|
+
return { userId, checkedAt, collectorType, ...match };
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
package/dist/types/giveaway.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GiveawayStatus, GiveawayCategory, GiveawaySelectionMethod, GiveawayWinnerStatus, GiveawayNotificationKind, GiveawayNotificationAudience, GiveawayNotificationStatus, GiveawayTicketResult, GiveawayAction, GiveawayNotificationChannel } from '../enums/giveaway';
|
|
2
2
|
import type { FbUser } from './user';
|
|
3
3
|
import type { UserCollectorType } from '../enums/user';
|
|
4
|
+
import type { GiveawayCreditEligibilityEvidence } from './revenuecat';
|
|
4
5
|
/** API identities are strings. MongoDB ObjectIds serialize as 24 lowercase hexadecimal characters. */
|
|
5
6
|
export type GiveawayObjectId = string;
|
|
6
7
|
/** New tickets use DGC- followed by twelve uppercase letters or digits. Existing numeric IDs remain readable. */
|
|
@@ -16,6 +17,7 @@ export type GiveawaySelection = {
|
|
|
16
17
|
reason: string;
|
|
17
18
|
selectedBy: string;
|
|
18
19
|
selectedAt: string;
|
|
20
|
+
creditEligibility?: GiveawayCreditEligibilityEvidence;
|
|
19
21
|
};
|
|
20
22
|
export type GiveawayPrize = {
|
|
21
23
|
_id: GiveawayObjectId;
|
|
@@ -72,10 +74,13 @@ export type GiveawayEntry = {
|
|
|
72
74
|
userId: string;
|
|
73
75
|
name: string;
|
|
74
76
|
email: string;
|
|
75
|
-
/**
|
|
77
|
+
/** Product delivery details are recorded only after the winner claims. */
|
|
76
78
|
address?: GiveawayDeliveryAddress;
|
|
79
|
+
deliveryName?: string;
|
|
80
|
+
contactNumber?: string;
|
|
81
|
+
/** Retained only for historical entry records; new entries collect country at claim. */
|
|
77
82
|
country?: string;
|
|
78
|
-
/** Null
|
|
83
|
+
/** Null when the account tier is missing or unsupported; product entries remain eligible. */
|
|
79
84
|
_collectorType: UserCollectorType | null;
|
|
80
85
|
enteredAt: string;
|
|
81
86
|
rulesAcceptedAt: string;
|
|
@@ -93,6 +98,7 @@ export type GiveawayWinner = {
|
|
|
93
98
|
status: GiveawayWinnerStatus;
|
|
94
99
|
userId?: string;
|
|
95
100
|
selection?: GiveawaySelection;
|
|
101
|
+
creditEligibility?: GiveawayCreditEligibilityEvidence;
|
|
96
102
|
category: GiveawayCategory;
|
|
97
103
|
creditAmount?: number;
|
|
98
104
|
claimDeadline?: string;
|
|
@@ -124,6 +130,14 @@ export type GiveawayNotificationJob = {
|
|
|
124
130
|
/** Stable event key supports replacements without resending a prior event. */
|
|
125
131
|
eventKey?: string;
|
|
126
132
|
audience?: GiveawayNotificationAudience;
|
|
133
|
+
/** Snapshots keep targeting and expiry stable throughout a broadcast. */
|
|
134
|
+
category?: GiveawayCategory;
|
|
135
|
+
expiresAt?: string;
|
|
136
|
+
dispatchAfter?: string;
|
|
137
|
+
dispatchGeneration?: number;
|
|
138
|
+
recipientsQueued?: number;
|
|
139
|
+
recipientsSkipped?: number;
|
|
140
|
+
dispatchCompletedAt?: string;
|
|
127
141
|
status: GiveawayNotificationStatus;
|
|
128
142
|
attempts: number;
|
|
129
143
|
error?: string;
|
|
@@ -170,6 +184,12 @@ export type GiveawayWinnerRecord = GiveawayWinner & {
|
|
|
170
184
|
};
|
|
171
185
|
export type GiveawayNotificationRecord = Omit<GiveawayNotificationJob, 'claimToken'> & {
|
|
172
186
|
giveawayTitle: string;
|
|
187
|
+
/** Queue acceptance counts, not confirmation of device delivery. */
|
|
188
|
+
deliveryCounts?: {
|
|
189
|
+
queued: number;
|
|
190
|
+
skipped: number;
|
|
191
|
+
pending: number;
|
|
192
|
+
};
|
|
173
193
|
};
|
|
174
194
|
export type GiveawayOverviewData = {
|
|
175
195
|
days: {
|
|
@@ -179,14 +199,20 @@ export type GiveawayOverviewData = {
|
|
|
179
199
|
liveGiveaways: GiveawaySummary[];
|
|
180
200
|
needsSelection: Giveaway[];
|
|
181
201
|
};
|
|
182
|
-
export type GiveawayUserProfile = Pick<FbUser, 'email' | 'firstName' | 'lastName'
|
|
202
|
+
export type GiveawayUserProfile = Pick<FbUser, 'email' | 'firstName' | 'lastName'> & {
|
|
183
203
|
userId: string;
|
|
204
|
+
_collectorType?: UserCollectorType | null;
|
|
184
205
|
};
|
|
185
206
|
export type GiveawayPhysicalClaim = {
|
|
186
207
|
fullName: string;
|
|
187
208
|
address: GiveawayDeliveryAddress;
|
|
188
209
|
contactNumber: string;
|
|
189
210
|
};
|
|
211
|
+
export type GiveawayPhysicalClaimInput = {
|
|
212
|
+
fullName?: string;
|
|
213
|
+
address: Omit<GiveawayDeliveryAddress, 'line2' | 'region'> & Partial<Pick<GiveawayDeliveryAddress, 'line2' | 'region'>>;
|
|
214
|
+
contactNumber: string;
|
|
215
|
+
};
|
|
190
216
|
export type GiveawayTicket = {
|
|
191
217
|
entry: Pick<GiveawayEntry, '_id' | 'giveawayId' | 'enteredAt' | 'rulesSnapshot'> & {
|
|
192
218
|
tickets: 1;
|
|
@@ -197,6 +223,10 @@ export type GiveawayTicket = {
|
|
|
197
223
|
};
|
|
198
224
|
export type GiveawayClaim = Pick<GiveawayWinner, '_id' | 'giveawayId' | 'entryId' | 'creditAmount' | 'category' | 'status' | 'claimDeadline' | 'claimedAt' | 'claim' | 'courier' | 'tracking' | 'dispatchedAt' | 'creditedAt'> & {
|
|
199
225
|
claimExpired: boolean;
|
|
226
|
+
/** Product delivery name defaults to the entrant name and may be edited. */
|
|
227
|
+
defaults?: {
|
|
228
|
+
fullName: string;
|
|
229
|
+
};
|
|
200
230
|
};
|
|
201
231
|
export type GiveawayPreset = {
|
|
202
232
|
_id: GiveawayObjectId;
|
|
@@ -245,7 +275,6 @@ export type GiveawayTransitionInput = {
|
|
|
245
275
|
revision: number;
|
|
246
276
|
};
|
|
247
277
|
export type GiveawayEntryInput = {
|
|
248
|
-
country?: string;
|
|
249
278
|
rulesAccepted: true;
|
|
250
279
|
marketingConsent?: boolean;
|
|
251
280
|
};
|
|
@@ -268,6 +297,8 @@ export type GiveawayWinnerPreview = {
|
|
|
268
297
|
};
|
|
269
298
|
/** Public app response; excludes admin settings, prize values and personal data. */
|
|
270
299
|
export type GiveawayPublic = Pick<Giveaway, '_id' | 'title' | 'description' | 'image' | 'status' | 'startsAt' | 'closesAt' | 'countries' | 'rules' | 'category' | 'creditAmount' | 'deliveryInformation'> & {
|
|
300
|
+
/** Null permits all authenticated accounts regardless of their stored tier. */
|
|
301
|
+
eligibleCollectorTypes: UserCollectorType[] | null;
|
|
271
302
|
entryCount: number;
|
|
272
303
|
images: string[];
|
|
273
304
|
prizes: Pick<GiveawayPrize, 'name' | 'quantity'>[];
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -35,3 +35,5 @@ __exportStar(require("./user"), exports);
|
|
|
35
35
|
__exportStar(require("./x-credit"), exports);
|
|
36
36
|
__exportStar(require("./giveaway"), exports);
|
|
37
37
|
__exportStar(require("../enums/giveaway"), exports);
|
|
38
|
+
__exportStar(require("./revenuecat"), exports);
|
|
39
|
+
__exportStar(require("../constants/revenuecat"), exports);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { UserCollectorType } from '../enums/user';
|
|
2
|
+
export type RevenueCatActiveEntitlement = {
|
|
3
|
+
entitlementId: string;
|
|
4
|
+
/** RevenueCat uses milliseconds since epoch; null is non-expiring access. */
|
|
5
|
+
expiresAt: number | null;
|
|
6
|
+
};
|
|
7
|
+
export type RevenueCatEntitlementIds = Readonly<{
|
|
8
|
+
rookie: string;
|
|
9
|
+
collector: string;
|
|
10
|
+
elite: string;
|
|
11
|
+
}>;
|
|
12
|
+
export type RevenueCatClient = {
|
|
13
|
+
getActiveEntitlements(userId: string): Promise<RevenueCatActiveEntitlement[]>;
|
|
14
|
+
};
|
|
15
|
+
export type GiveawayCreditEligibilityEvidence = RevenueCatActiveEntitlement & {
|
|
16
|
+
userId: string;
|
|
17
|
+
checkedAt: string;
|
|
18
|
+
collectorType: UserCollectorType.Rookie | UserCollectorType.Collector | UserCollectorType.Elite;
|
|
19
|
+
};
|