adapty 0.8.4 → 0.8.5
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 +1 -1
- package/bin/dev.js +2 -2
- package/bin/run.js +2 -2
- package/dist/commands/access-levels/create.js +1 -1
- package/dist/commands/access-levels/list.d.ts +1 -1
- package/dist/commands/apps/create.js +9 -6
- package/dist/commands/apps/list.d.ts +1 -1
- package/dist/commands/apps/update.js +7 -4
- package/dist/commands/asa/ad-groups/create.js +9 -7
- package/dist/commands/asa/ad-groups/get.js +3 -2
- package/dist/commands/asa/ad-groups/list.d.ts +1 -1
- package/dist/commands/asa/ad-groups/list.js +1 -1
- package/dist/commands/asa/ad-groups/update.js +22 -12
- package/dist/commands/asa/ads/create.js +7 -5
- package/dist/commands/asa/ads/get.js +3 -2
- package/dist/commands/asa/ads/list.d.ts +1 -1
- package/dist/commands/asa/ads/list.js +1 -1
- package/dist/commands/asa/ads/update.js +9 -5
- package/dist/commands/asa/apps/list.d.ts +1 -1
- package/dist/commands/asa/apps/list.js +1 -1
- package/dist/commands/asa/automations/create.js +9 -6
- package/dist/commands/asa/automations/get.js +3 -2
- package/dist/commands/asa/automations/list.d.ts +1 -1
- package/dist/commands/asa/automations/list.js +1 -1
- package/dist/commands/asa/automations/run.js +5 -3
- package/dist/commands/asa/automations/runs.d.ts +1 -1
- package/dist/commands/asa/automations/runs.js +4 -4
- package/dist/commands/asa/automations/update.js +17 -9
- package/dist/commands/asa/campaigns/bulk-create.js +31 -18
- package/dist/commands/asa/campaigns/bulk-list.js +7 -5
- package/dist/commands/asa/campaigns/bulk-status.js +3 -2
- package/dist/commands/asa/campaigns/create.js +10 -8
- package/dist/commands/asa/campaigns/get.js +3 -2
- package/dist/commands/asa/campaigns/list.d.ts +1 -1
- package/dist/commands/asa/campaigns/list.js +1 -1
- package/dist/commands/asa/campaigns/update.js +25 -15
- package/dist/commands/asa/competitors/summary.js +5 -5
- package/dist/commands/asa/connect.js +5 -3
- package/dist/commands/asa/creatives/list.d.ts +1 -1
- package/dist/commands/asa/creatives/list.js +1 -1
- package/dist/commands/asa/keywords/add.js +13 -10
- package/dist/commands/asa/keywords/list.d.ts +1 -1
- package/dist/commands/asa/keywords/list.js +1 -1
- package/dist/commands/asa/keywords/update.js +15 -10
- package/dist/commands/asa/metrics/index.d.ts +2 -2
- package/dist/commands/asa/metrics/index.js +22 -8
- package/dist/commands/asa/metrics/overview.js +3 -3
- package/dist/commands/asa/negative-keywords/add.js +10 -7
- package/dist/commands/asa/negative-keywords/list.d.ts +1 -1
- package/dist/commands/asa/negative-keywords/list.js +1 -1
- package/dist/commands/asa/orgs/list.d.ts +1 -1
- package/dist/commands/asa/orgs/list.js +1 -1
- package/dist/commands/asa/product-pages/list.d.ts +1 -1
- package/dist/commands/asa/product-pages/list.js +1 -1
- package/dist/commands/asa/product-pages/sync.js +1 -1
- package/dist/commands/asa/search-terms/list.d.ts +1 -1
- package/dist/commands/asa/search-terms/list.js +1 -1
- package/dist/commands/asa/whoami.js +7 -2
- package/dist/commands/auth/login.js +3 -1
- package/dist/commands/flows/config/get.js +2 -2
- package/dist/commands/flows/config/preview.d.ts +2 -2
- package/dist/commands/flows/config/preview.js +6 -4
- package/dist/commands/flows/config/update.js +5 -3
- package/dist/commands/flows/config/validate.js +5 -3
- package/dist/commands/flows/list.d.ts +1 -1
- package/dist/commands/flows/media/upload.js +1 -1
- package/dist/commands/flows/publish.js +5 -5
- package/dist/commands/paywalls/create.js +1 -1
- package/dist/commands/paywalls/list.d.ts +1 -1
- package/dist/commands/paywalls/placements.d.ts +2 -2
- package/dist/commands/paywalls/update.js +1 -1
- package/dist/commands/placements/create.js +17 -12
- package/dist/commands/placements/list.d.ts +1 -1
- package/dist/commands/placements/update.js +20 -15
- package/dist/commands/products/create.js +20 -13
- package/dist/commands/products/list.d.ts +1 -1
- package/dist/commands/products/update.js +1 -1
- package/dist/commands/segments/list.d.ts +1 -1
- package/dist/lib/api-client.d.ts +10 -6
- package/dist/lib/api-client.js +56 -35
- package/dist/lib/api-schemas.d.ts +56 -56
- package/dist/lib/app-url.js +2 -1
- package/dist/lib/asa-client.d.ts +13 -8
- package/dist/lib/asa-client.js +9 -4
- package/dist/lib/asa-flags.d.ts +19 -19
- package/dist/lib/asa-flags.js +38 -25
- package/dist/lib/asa-keyword-action.d.ts +10 -10
- package/dist/lib/asa-keyword-action.js +40 -25
- package/dist/lib/asa-schemas.d.ts +100 -80
- package/dist/lib/auth.js +2 -1
- package/dist/lib/client-from-config.d.ts +1 -1
- package/dist/lib/client-from-config.js +2 -1
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/confirm.d.ts +4 -4
- package/dist/lib/confirm.js +12 -7
- package/dist/lib/errors.d.ts +18 -18
- package/dist/lib/errors.js +12 -9
- package/dist/lib/flags.d.ts +3 -3
- package/dist/lib/flags.js +2 -1
- package/dist/lib/flow-help.js +11 -6
- package/dist/lib/output.js +23 -12
- package/dist/lib/preview.d.ts +5 -5
- package/dist/lib/preview.js +2 -1
- package/oclif.manifest.json +131 -131
- package/package.json +10 -8
|
@@ -17,11 +17,11 @@ export default class PlacementsUpdate extends Command {
|
|
|
17
17
|
];
|
|
18
18
|
static flags = {
|
|
19
19
|
...appFlag,
|
|
20
|
-
audiences: Flags.string({
|
|
21
|
-
description: 'JSON array of audience entries. Every entry needs an explicit content_type. '
|
|
22
|
-
'Paywall: {content_type:"paywall", segment_ids, paywall_id, priority}. '
|
|
23
|
-
'Flow: {content_type:"flow", segment_ids, flow_id, priority}. '
|
|
24
|
-
'A flow must be published first (flows publish) — attaching a draft flow returns 400.',
|
|
20
|
+
'audiences': Flags.string({
|
|
21
|
+
description: 'JSON array of audience entries. Every entry needs an explicit content_type. '
|
|
22
|
+
+ 'Paywall: {content_type:"paywall", segment_ids, paywall_id, priority}. '
|
|
23
|
+
+ 'Flow: {content_type:"flow", segment_ids, flow_id, priority}. '
|
|
24
|
+
+ 'A flow must be published first (flows publish) — attaching a draft flow returns 400.',
|
|
25
25
|
exactlyOne: ['paywall-id', 'audiences'],
|
|
26
26
|
}),
|
|
27
27
|
'developer-id': Flags.string({ description: 'Developer ID for the placement', required: true }),
|
|
@@ -29,7 +29,7 @@ export default class PlacementsUpdate extends Command {
|
|
|
29
29
|
description: 'Paywall ID (UUID). DEPRECATED: use --audiences.',
|
|
30
30
|
exactlyOne: ['paywall-id', 'audiences'],
|
|
31
31
|
}),
|
|
32
|
-
title: Flags.string({ description: 'Placement title', required: true }),
|
|
32
|
+
'title': Flags.string({ description: 'Placement title', required: true }),
|
|
33
33
|
};
|
|
34
34
|
async run() {
|
|
35
35
|
const { args, flags } = await this.parse(PlacementsUpdate);
|
|
@@ -43,16 +43,18 @@ export default class PlacementsUpdate extends Command {
|
|
|
43
43
|
title: flags.title,
|
|
44
44
|
};
|
|
45
45
|
if (flags['paywall-id']) {
|
|
46
|
-
process.stderr.write('⚠️ --paywall-id is deprecated. Use --audiences instead.\n'
|
|
47
|
-
' `paywall_id` will be removed from the API in a future release.\n');
|
|
48
|
-
process.stderr.write('⚠️ --paywall-id will rewrite all audiences on this placement.\n'
|
|
49
|
-
' If the placement has segment-specific paywalls, they will be replaced\n'
|
|
50
|
-
' by a single default audience. Use --audiences to preserve them.\n');
|
|
46
|
+
process.stderr.write('⚠️ --paywall-id is deprecated. Use --audiences instead.\n'
|
|
47
|
+
+ ' `paywall_id` will be removed from the API in a future release.\n');
|
|
48
|
+
process.stderr.write('⚠️ --paywall-id will rewrite all audiences on this placement.\n'
|
|
49
|
+
+ ' If the placement has segment-specific paywalls, they will be replaced\n'
|
|
50
|
+
+ ' by a single default audience. Use --audiences to preserve them.\n');
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- FIXME if you see this
|
|
51
52
|
body.paywall_id = flags['paywall-id'];
|
|
52
53
|
}
|
|
53
54
|
else {
|
|
54
55
|
let parsed;
|
|
55
56
|
try {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- FIXME if you see this
|
|
56
58
|
parsed = JSON.parse(flags.audiences);
|
|
57
59
|
}
|
|
58
60
|
catch (error) {
|
|
@@ -61,12 +63,14 @@ export default class PlacementsUpdate extends Command {
|
|
|
61
63
|
if (!Array.isArray(parsed)) {
|
|
62
64
|
this.error('--audiences must be a JSON array of audience entries.', { exit: 2 });
|
|
63
65
|
}
|
|
64
|
-
|
|
66
|
+
const entries = parsed;
|
|
67
|
+
for (const [index, entry] of entries.entries()) {
|
|
65
68
|
const problem = audienceEntryProblem(entry);
|
|
66
|
-
if (problem)
|
|
69
|
+
if (problem) {
|
|
67
70
|
this.error(`--audiences[${index}]: ${problem}`, { exit: 2 });
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
|
-
body.audiences =
|
|
73
|
+
body.audiences = entries;
|
|
70
74
|
}
|
|
71
75
|
const client = await createAuthenticatedClient(this.config);
|
|
72
76
|
let result;
|
|
@@ -75,8 +79,9 @@ export default class PlacementsUpdate extends Command {
|
|
|
75
79
|
}
|
|
76
80
|
catch (error) {
|
|
77
81
|
const message = draftFlowError(error, flags.app, body.audiences);
|
|
78
|
-
if (message)
|
|
82
|
+
if (message) {
|
|
79
83
|
this.error(message, { exit: 2 });
|
|
84
|
+
}
|
|
80
85
|
throw error;
|
|
81
86
|
}
|
|
82
87
|
this.log('Placement updated!');
|
|
@@ -26,13 +26,13 @@ export default class ProductsCreate extends Command {
|
|
|
26
26
|
'ios-product-id': Flags.string({ description: 'iOS product ID', helpGroup: 'STORE BINDINGS' }),
|
|
27
27
|
'paddle-price-id': Flags.string({ description: 'Paddle price ID (requires --paddle-product-id)', helpGroup: 'STORE BINDINGS' }),
|
|
28
28
|
'paddle-product-id': Flags.string({ description: 'Paddle product ID (requires --paddle-price-id)', helpGroup: 'STORE BINDINGS' }),
|
|
29
|
-
period: Flags.string({
|
|
29
|
+
'period': Flags.string({
|
|
30
30
|
description: 'Subscription period (weekly, monthly, two_months, trimonthly, semiannual, annual, lifetime)',
|
|
31
31
|
required: true,
|
|
32
32
|
}),
|
|
33
33
|
'stripe-price-id': Flags.string({ description: 'Stripe price ID (requires --stripe-product-id)', helpGroup: 'STORE BINDINGS' }),
|
|
34
34
|
'stripe-product-id': Flags.string({ description: 'Stripe product ID (requires --stripe-price-id)', helpGroup: 'STORE BINDINGS' }),
|
|
35
|
-
title: Flags.string({ description: 'Product title', required: true }),
|
|
35
|
+
'title': Flags.string({ description: 'Product title', required: true }),
|
|
36
36
|
};
|
|
37
37
|
async run() {
|
|
38
38
|
const { flags } = await this.parse(ProductsCreate);
|
|
@@ -45,10 +45,10 @@ export default class ProductsCreate extends Command {
|
|
|
45
45
|
if (xor(flags['paddle-product-id'], flags['paddle-price-id'])) {
|
|
46
46
|
this.error('--paddle-product-id and --paddle-price-id are required together', { exit: 2 });
|
|
47
47
|
}
|
|
48
|
-
if (!flags['ios-product-id']
|
|
49
|
-
!flags['android-product-id']
|
|
50
|
-
!flags['stripe-product-id']
|
|
51
|
-
!flags['paddle-product-id']) {
|
|
48
|
+
if (!flags['ios-product-id']
|
|
49
|
+
&& !flags['android-product-id']
|
|
50
|
+
&& !flags['stripe-product-id']
|
|
51
|
+
&& !flags['paddle-product-id']) {
|
|
52
52
|
this.error('At least one store binding is required (ios/android/stripe/paddle)', { exit: 2 });
|
|
53
53
|
}
|
|
54
54
|
if (flags['android-product-id'] && !flags['android-base-plan-id'] && flags.period !== 'lifetime') {
|
|
@@ -60,20 +60,27 @@ export default class ProductsCreate extends Command {
|
|
|
60
60
|
period: flags.period,
|
|
61
61
|
title: flags.title,
|
|
62
62
|
};
|
|
63
|
-
if (flags['ios-product-id'])
|
|
63
|
+
if (flags['ios-product-id']) {
|
|
64
64
|
body.ios_product_id = flags['ios-product-id'];
|
|
65
|
-
|
|
65
|
+
}
|
|
66
|
+
if (flags['android-product-id']) {
|
|
66
67
|
body.android_product_id = flags['android-product-id'];
|
|
67
|
-
|
|
68
|
+
}
|
|
69
|
+
if (flags['android-base-plan-id']) {
|
|
68
70
|
body.android_base_plan_id = flags['android-base-plan-id'];
|
|
69
|
-
|
|
71
|
+
}
|
|
72
|
+
if (flags['stripe-product-id']) {
|
|
70
73
|
body.stripe_product_id = flags['stripe-product-id'];
|
|
71
|
-
|
|
74
|
+
}
|
|
75
|
+
if (flags['stripe-price-id']) {
|
|
72
76
|
body.stripe_price_id = flags['stripe-price-id'];
|
|
73
|
-
|
|
77
|
+
}
|
|
78
|
+
if (flags['paddle-product-id']) {
|
|
74
79
|
body.paddle_product_id = flags['paddle-product-id'];
|
|
75
|
-
|
|
80
|
+
}
|
|
81
|
+
if (flags['paddle-price-id']) {
|
|
76
82
|
body.paddle_price_id = flags['paddle-price-id'];
|
|
83
|
+
}
|
|
77
84
|
const result = await client.post(`/apps/${flags.app}/products`, body);
|
|
78
85
|
this.log('Product created!');
|
|
79
86
|
printResponse(result, this.log.bind(this));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import type { ProductDTO } from '../../lib/api-schemas.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { PaginatedResponse } from '../../lib/flags.js';
|
|
4
4
|
export default class ProductsList extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static enableJsonFlag: boolean;
|
|
@@ -14,7 +14,7 @@ export default class ProductsUpdate extends Command {
|
|
|
14
14
|
static flags = {
|
|
15
15
|
...appFlag,
|
|
16
16
|
'access-level-id': Flags.string({ description: 'Access level ID (UUID)', required: true }),
|
|
17
|
-
title: Flags.string({ description: 'Product title', required: true }),
|
|
17
|
+
'title': Flags.string({ description: 'Product title', required: true }),
|
|
18
18
|
};
|
|
19
19
|
async run() {
|
|
20
20
|
const { args, flags } = await this.parse(ProductsUpdate);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import type { SegmentDTO } from '../../lib/api-schemas.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { PaginatedResponse } from '../../lib/flags.js';
|
|
4
4
|
export default class SegmentsList extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static enableJsonFlag: boolean;
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ApiErrorFormat } from './errors.js';
|
|
2
2
|
export type QueryParams = Record<string, string | string[] | undefined>;
|
|
3
|
-
export
|
|
3
|
+
export type RequestOptions = {
|
|
4
4
|
headers?: Record<string, string>;
|
|
5
5
|
onResponse?: (headers: Headers) => void;
|
|
6
|
-
}
|
|
7
|
-
export
|
|
6
|
+
};
|
|
7
|
+
export type ApiClientOptions = {
|
|
8
8
|
baseUrl?: string;
|
|
9
9
|
defaultBaseUrl?: string;
|
|
10
10
|
errorFormat?: ApiErrorFormat;
|
|
11
|
+
quiet?: boolean;
|
|
11
12
|
token?: null | string;
|
|
12
13
|
urlEnvVar?: string;
|
|
13
14
|
userAgent?: string;
|
|
14
|
-
}
|
|
15
|
+
};
|
|
15
16
|
export declare class ApiClient {
|
|
16
17
|
private baseUrl;
|
|
17
18
|
private errorFormat;
|
|
19
|
+
private quiet;
|
|
18
20
|
private token;
|
|
19
21
|
private userAgent;
|
|
20
22
|
constructor(opts?: ApiClientOptions);
|
|
@@ -22,7 +24,9 @@ export declare class ApiClient {
|
|
|
22
24
|
post<T = unknown>(path: string, body?: unknown, params?: QueryParams, opts?: RequestOptions): Promise<T>;
|
|
23
25
|
postForm<T = unknown>(path: string, form: FormData, params?: QueryParams, opts?: RequestOptions): Promise<T>;
|
|
24
26
|
put<T = unknown>(path: string, body?: unknown, params?: QueryParams, opts?: RequestOptions): Promise<T>;
|
|
27
|
+
private buildHeaders;
|
|
25
28
|
private buildUrl;
|
|
26
|
-
private
|
|
29
|
+
private isRetryableRefusal;
|
|
30
|
+
private readBody;
|
|
27
31
|
private request;
|
|
28
32
|
}
|
package/dist/lib/api-client.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { ApiError, NetworkError, parseApiError } from './errors.js';
|
|
2
2
|
const DEFAULT_API_URL = 'https://api-admin.adapty.io/api/v1/developer';
|
|
3
3
|
const MAX_RETRY_AFTER_SECONDS = 60;
|
|
4
|
+
// 429 is the caller going too fast; 503 is a dependency being down. Both name a wait and clear up on their own.
|
|
5
|
+
const RETRYABLE_STATUSES = new Set([429, 503]);
|
|
4
6
|
function ensureTrailingSlash(path) {
|
|
5
7
|
return path.endsWith('/') ? path : `${path}/`;
|
|
6
8
|
}
|
|
9
|
+
const NO_RETRIES = { network: false, refused: false };
|
|
7
10
|
export class ApiClient {
|
|
8
11
|
baseUrl;
|
|
9
12
|
errorFormat;
|
|
13
|
+
quiet;
|
|
10
14
|
token;
|
|
11
15
|
userAgent;
|
|
12
16
|
constructor(opts = {}) {
|
|
@@ -17,6 +21,7 @@ export class ApiClient {
|
|
|
17
21
|
process.stderr.write(`Warning: using non-default API URL: ${this.baseUrl}\n`);
|
|
18
22
|
}
|
|
19
23
|
this.errorFormat = opts.errorFormat ?? 'developer';
|
|
24
|
+
this.quiet = opts.quiet ?? false;
|
|
20
25
|
this.token = opts.token ?? null;
|
|
21
26
|
this.userAgent = opts.userAgent ?? 'adapty-cli';
|
|
22
27
|
}
|
|
@@ -25,7 +30,7 @@ export class ApiClient {
|
|
|
25
30
|
}
|
|
26
31
|
async post(path, body, params, opts) {
|
|
27
32
|
return this.request(this.buildUrl(path, params), {
|
|
28
|
-
body: body ? JSON.stringify(body) :
|
|
33
|
+
body: body ? JSON.stringify(body) : null,
|
|
29
34
|
method: 'POST',
|
|
30
35
|
}, opts);
|
|
31
36
|
}
|
|
@@ -34,48 +39,70 @@ export class ApiClient {
|
|
|
34
39
|
}
|
|
35
40
|
async put(path, body, params, opts) {
|
|
36
41
|
return this.request(this.buildUrl(path, params), {
|
|
37
|
-
body: body ? JSON.stringify(body) :
|
|
42
|
+
body: body ? JSON.stringify(body) : null,
|
|
38
43
|
method: 'PUT',
|
|
39
44
|
}, opts);
|
|
40
45
|
}
|
|
46
|
+
buildHeaders(init, opts) {
|
|
47
|
+
const headers = { 'User-Agent': this.userAgent };
|
|
48
|
+
if (init.body && !(init.body instanceof FormData)) {
|
|
49
|
+
headers['Content-Type'] = 'application/json';
|
|
50
|
+
}
|
|
51
|
+
if (this.token) {
|
|
52
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
53
|
+
}
|
|
54
|
+
return { ...headers, ...opts.headers };
|
|
55
|
+
}
|
|
41
56
|
buildUrl(path, params) {
|
|
42
57
|
const url = `${this.baseUrl}${ensureTrailingSlash(path)}`;
|
|
43
|
-
if (!params)
|
|
58
|
+
if (!params) {
|
|
44
59
|
return url;
|
|
60
|
+
}
|
|
45
61
|
const search = new URLSearchParams();
|
|
46
62
|
for (const [key, value] of Object.entries(params)) {
|
|
47
|
-
if (value === undefined)
|
|
63
|
+
if (value === undefined) {
|
|
48
64
|
continue;
|
|
49
|
-
|
|
65
|
+
}
|
|
66
|
+
for (const item of Array.isArray(value) ? value : [value]) {
|
|
50
67
|
search.append(key, item);
|
|
68
|
+
}
|
|
51
69
|
}
|
|
52
70
|
return search.size === 0 ? url : `${url}?${search.toString()}`;
|
|
53
71
|
}
|
|
54
|
-
|
|
55
|
-
return (this.errorFormat === 'asa'
|
|
56
|
-
error.statusCode
|
|
57
|
-
error.errorCode !== 'cli_cooldown_active'
|
|
58
|
-
error.retryAfterSeconds !== undefined
|
|
59
|
-
error.retryAfterSeconds <= MAX_RETRY_AFTER_SECONDS);
|
|
72
|
+
isRetryableRefusal(error) {
|
|
73
|
+
return (this.errorFormat === 'asa'
|
|
74
|
+
&& RETRYABLE_STATUSES.has(error.statusCode)
|
|
75
|
+
&& error.errorCode !== 'cli_cooldown_active'
|
|
76
|
+
&& error.retryAfterSeconds !== undefined
|
|
77
|
+
&& error.retryAfterSeconds <= MAX_RETRY_AFTER_SECONDS);
|
|
60
78
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
'User-Agent': this.userAgent,
|
|
65
|
-
};
|
|
66
|
-
if (init.body && !(init.body instanceof FormData)) {
|
|
67
|
-
headers['Content-Type'] = 'application/json';
|
|
79
|
+
async readBody(response, errorOptions) {
|
|
80
|
+
try {
|
|
81
|
+
return await response.json();
|
|
68
82
|
}
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
catch {
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
throw new ApiError(response.status, `http_${response.status}`, {}, errorOptions);
|
|
86
|
+
}
|
|
87
|
+
throw new ApiError(response.status, 'malformed_response', {}, {
|
|
88
|
+
...errorOptions,
|
|
89
|
+
detail: `The server answered ${response.status} with a body that is not JSON, so the response was cut short `
|
|
90
|
+
+ 'rather than refused. Nothing was read; retry the request.',
|
|
91
|
+
});
|
|
71
92
|
}
|
|
72
|
-
|
|
93
|
+
}
|
|
94
|
+
async request(url, init, opts = {}, retried = NO_RETRIES) {
|
|
95
|
+
const headers = this.buildHeaders(init, opts);
|
|
73
96
|
let response;
|
|
74
97
|
try {
|
|
75
98
|
response = await fetch(url, { ...init, headers });
|
|
76
99
|
}
|
|
77
100
|
catch (error) {
|
|
78
|
-
|
|
101
|
+
const failure = new NetworkError(error instanceof Error ? error.message : 'Connection failed');
|
|
102
|
+
if (retried.network || init.method !== 'GET') {
|
|
103
|
+
throw failure;
|
|
104
|
+
}
|
|
105
|
+
return this.request(url, init, opts, { ...retried, network: true });
|
|
79
106
|
}
|
|
80
107
|
opts.onResponse?.(response.headers);
|
|
81
108
|
if (response.status === 204) {
|
|
@@ -83,28 +110,22 @@ export class ApiClient {
|
|
|
83
110
|
}
|
|
84
111
|
const retryAfter = Number.parseInt(response.headers.get('Retry-After') ?? '', 10);
|
|
85
112
|
const errorOptions = this.errorFormat === 'asa' && !Number.isNaN(retryAfter) ? { retryAfterSeconds: retryAfter } : {};
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
body = await response.json();
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
throw new ApiError(response.status, `http_${response.status}`, {}, errorOptions);
|
|
93
|
-
}
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
113
|
+
const body = await this.readBody(response, errorOptions);
|
|
96
114
|
if (!response.ok) {
|
|
97
115
|
const error = parseApiError(response.status, body, errorOptions, this.errorFormat);
|
|
98
116
|
if (response.status === 401) {
|
|
99
117
|
error.message = 'Token expired or invalid. Run `adapty auth login`.';
|
|
100
118
|
}
|
|
101
|
-
if (!retried && this.
|
|
119
|
+
if (!retried.refused && this.isRetryableRefusal(error)) {
|
|
102
120
|
const seconds = error.retryAfterSeconds ?? 0;
|
|
103
|
-
|
|
121
|
+
if (!this.quiet) {
|
|
122
|
+
const reason = error.statusCode === 429 ? 'Rate limited' : 'Temporarily unavailable';
|
|
123
|
+
process.stderr.write(`${reason} (${error.errorCode}); waiting ${seconds}s per Retry-After, then retrying once.\n`);
|
|
124
|
+
}
|
|
104
125
|
await new Promise((resolve) => {
|
|
105
126
|
setTimeout(resolve, seconds * 1000);
|
|
106
127
|
});
|
|
107
|
-
return this.request(url, init, opts, true);
|
|
128
|
+
return this.request(url, init, opts, { ...retried, refused: true });
|
|
108
129
|
}
|
|
109
130
|
throw error;
|
|
110
131
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export type ProductPeriod = 'annual' | 'consumable' | 'lifetime' | 'monthly' | 'nonsubscriptions' | 'semiannual' | 'trimonthly' | 'two_months' | 'uncategorised' | 'weekly';
|
|
2
|
-
export
|
|
2
|
+
export type VendorProductDTO = {
|
|
3
3
|
base_plan_id: null | string;
|
|
4
4
|
id: null | string;
|
|
5
5
|
product_id: null | string;
|
|
6
|
-
}
|
|
7
|
-
export
|
|
6
|
+
};
|
|
7
|
+
export type AppSummaryDTO = {
|
|
8
8
|
id: string;
|
|
9
9
|
sdk_key: null | string;
|
|
10
10
|
title: string;
|
|
11
|
-
}
|
|
12
|
-
export
|
|
11
|
+
};
|
|
12
|
+
export type AppDetailDTO = {
|
|
13
13
|
apple_bundle_id: null | string;
|
|
14
14
|
google_bundle_id: null | string;
|
|
15
15
|
id: string;
|
|
@@ -17,37 +17,37 @@ export interface AppDetailDTO {
|
|
|
17
17
|
sdk_key: null | string;
|
|
18
18
|
secret_key: null | string;
|
|
19
19
|
title: string;
|
|
20
|
-
}
|
|
21
|
-
export
|
|
20
|
+
};
|
|
21
|
+
export type AppCreateRequestDTO = {
|
|
22
22
|
apple_bundle_id?: null | string;
|
|
23
23
|
google_bundle_id?: null | string;
|
|
24
24
|
title: string;
|
|
25
|
-
}
|
|
26
|
-
export
|
|
25
|
+
};
|
|
26
|
+
export type AppUpdateRequestDTO = {
|
|
27
27
|
apple_bundle_id?: null | string;
|
|
28
28
|
google_bundle_id?: null | string;
|
|
29
29
|
title?: null | string;
|
|
30
|
-
}
|
|
31
|
-
export
|
|
30
|
+
};
|
|
31
|
+
export type AccessLevelDTO = {
|
|
32
32
|
id: string;
|
|
33
33
|
sdk_id: string;
|
|
34
34
|
title: null | string;
|
|
35
|
-
}
|
|
36
|
-
export
|
|
35
|
+
};
|
|
36
|
+
export type AccessLevelCreateRequestDTO = {
|
|
37
37
|
sdk_id: string;
|
|
38
38
|
title?: null | string;
|
|
39
|
-
}
|
|
40
|
-
export
|
|
39
|
+
};
|
|
40
|
+
export type AccessLevelUpdateRequestDTO = {
|
|
41
41
|
title?: null | string;
|
|
42
|
-
}
|
|
43
|
-
export
|
|
42
|
+
};
|
|
43
|
+
export type ProductDTO = {
|
|
44
44
|
access_level_id: string;
|
|
45
45
|
id: string;
|
|
46
46
|
period: ProductPeriod;
|
|
47
47
|
title: string;
|
|
48
48
|
vendor_products: Record<string, VendorProductDTO>;
|
|
49
|
-
}
|
|
50
|
-
export
|
|
49
|
+
};
|
|
50
|
+
export type ProductCreateRequestDTO = {
|
|
51
51
|
access_level_id: string;
|
|
52
52
|
android_base_plan_id?: null | string;
|
|
53
53
|
android_product_id?: null | string;
|
|
@@ -59,33 +59,33 @@ export interface ProductCreateRequestDTO {
|
|
|
59
59
|
stripe_price_id?: null | string;
|
|
60
60
|
stripe_product_id?: null | string;
|
|
61
61
|
title: string;
|
|
62
|
-
}
|
|
63
|
-
export
|
|
62
|
+
};
|
|
63
|
+
export type ProductUpdateRequestDTO = {
|
|
64
64
|
access_level_id: string;
|
|
65
65
|
title: string;
|
|
66
|
-
}
|
|
67
|
-
export
|
|
66
|
+
};
|
|
67
|
+
export type PaywallDTO = {
|
|
68
68
|
id: string;
|
|
69
69
|
product_ids: string[];
|
|
70
70
|
title: string;
|
|
71
|
-
}
|
|
72
|
-
export
|
|
71
|
+
};
|
|
72
|
+
export type PaywallWriteRequestDTO = {
|
|
73
73
|
product_ids?: string[];
|
|
74
74
|
title: string;
|
|
75
|
-
}
|
|
75
|
+
};
|
|
76
76
|
/** Flow lifecycle status. Values come from the server-side `FlowStatus` enum (e.g. draft, dirty, published). */
|
|
77
77
|
export type FlowStatus = string;
|
|
78
|
-
export
|
|
78
|
+
export type FlowDTO = {
|
|
79
79
|
id: string;
|
|
80
80
|
name: string;
|
|
81
81
|
status: FlowStatus;
|
|
82
82
|
updated_at: string;
|
|
83
|
-
}
|
|
84
|
-
export
|
|
83
|
+
};
|
|
84
|
+
export type FlowRemoteConfigDTO = {
|
|
85
85
|
data: string;
|
|
86
86
|
locale: string;
|
|
87
|
-
}
|
|
88
|
-
export
|
|
87
|
+
};
|
|
88
|
+
export type FlowConfigDTO = {
|
|
89
89
|
config: Record<string, unknown>;
|
|
90
90
|
/** Human message for a failed publication (raw passthrough from the flow version). */
|
|
91
91
|
publication_error?: null | string;
|
|
@@ -97,77 +97,77 @@ export interface FlowConfigDTO {
|
|
|
97
97
|
transform_error?: null | string;
|
|
98
98
|
/** Millisecond timestamp of the last content change; the value `expected_updated_at` is compared against on write. */
|
|
99
99
|
updated_at: number;
|
|
100
|
-
}
|
|
101
|
-
export
|
|
100
|
+
};
|
|
101
|
+
export type FlowWriteRequestDTO = {
|
|
102
102
|
name: string;
|
|
103
|
-
}
|
|
104
|
-
export
|
|
103
|
+
};
|
|
104
|
+
export type FlowConfigWriteRequestDTO = {
|
|
105
105
|
config: Record<string, unknown>;
|
|
106
106
|
/** Optimistic lock: the `updated_at` from a prior config read. Omit for last-write-wins. */
|
|
107
107
|
expected_updated_at?: null | number;
|
|
108
108
|
remote_configs?: FlowRemoteConfigDTO[];
|
|
109
|
-
}
|
|
110
|
-
export
|
|
109
|
+
};
|
|
110
|
+
export type FlowConfigValidateRequestDTO = {
|
|
111
111
|
config: Record<string, unknown>;
|
|
112
|
-
}
|
|
113
|
-
export
|
|
112
|
+
};
|
|
113
|
+
export type FlowConfigIssueDTO = {
|
|
114
114
|
/** Machine code, relayed from the transformer; absent until it reports path-level diagnostics. */
|
|
115
115
|
code?: null | string;
|
|
116
116
|
message: string;
|
|
117
117
|
/** Location of the issue; absent until the transformer reports path-level diagnostics. */
|
|
118
118
|
path?: null | string;
|
|
119
119
|
severity: string;
|
|
120
|
-
}
|
|
121
|
-
export
|
|
120
|
+
};
|
|
121
|
+
export type FlowConfigValidationDTO = {
|
|
122
122
|
issues: FlowConfigIssueDTO[];
|
|
123
123
|
valid: boolean;
|
|
124
|
-
}
|
|
125
|
-
export
|
|
124
|
+
};
|
|
125
|
+
export type MediaDTO = {
|
|
126
126
|
id: number;
|
|
127
127
|
name: string;
|
|
128
128
|
/** Base64-encoded preview thumbnail; absent when no preview was generated. */
|
|
129
129
|
preview_base64?: null | string;
|
|
130
130
|
/** CDN URL to reference from a flow config. */
|
|
131
131
|
url: string;
|
|
132
|
-
}
|
|
133
|
-
export
|
|
132
|
+
};
|
|
133
|
+
export type SegmentDTO = {
|
|
134
134
|
description: null | string;
|
|
135
135
|
id: string;
|
|
136
136
|
title: string;
|
|
137
|
-
}
|
|
138
|
-
export
|
|
137
|
+
};
|
|
138
|
+
export type PlacementPaywallAudienceEntryDTO = {
|
|
139
139
|
content_type: 'paywall';
|
|
140
140
|
paywall_id: string;
|
|
141
141
|
priority: number;
|
|
142
142
|
segment_ids?: string[];
|
|
143
|
-
}
|
|
144
|
-
export
|
|
143
|
+
};
|
|
144
|
+
export type PlacementFlowAudienceEntryDTO = {
|
|
145
145
|
content_type: 'flow';
|
|
146
146
|
/** The flow must be `published` (not draft) — the backend returns 400 otherwise. */
|
|
147
147
|
flow_id: string;
|
|
148
148
|
priority: number;
|
|
149
149
|
segment_ids?: string[];
|
|
150
|
-
}
|
|
150
|
+
};
|
|
151
151
|
export type PlacementAudienceEntryDTO = PlacementFlowAudienceEntryDTO | PlacementPaywallAudienceEntryDTO;
|
|
152
|
-
export
|
|
152
|
+
export type PlacementSummaryDTO = {
|
|
153
153
|
developer_id: string;
|
|
154
154
|
id: string;
|
|
155
155
|
/** Placement activation state: true = Live, false = Inactive. */
|
|
156
156
|
is_active: boolean;
|
|
157
157
|
title: string;
|
|
158
|
-
}
|
|
159
|
-
export
|
|
158
|
+
};
|
|
159
|
+
export type PlacementDetailDTO = {
|
|
160
160
|
audiences?: PlacementAudienceEntryDTO[];
|
|
161
161
|
developer_id: string;
|
|
162
162
|
id: string;
|
|
163
163
|
/** Placement activation state: true = Live, false = Inactive. */
|
|
164
164
|
is_active: boolean;
|
|
165
165
|
title: string;
|
|
166
|
-
}
|
|
167
|
-
export
|
|
166
|
+
};
|
|
167
|
+
export type PlacementWriteRequestDTO = {
|
|
168
168
|
audiences: null | PlacementAudienceEntryDTO[];
|
|
169
169
|
developer_id: string;
|
|
170
170
|
/** @deprecated use `audiences` */
|
|
171
171
|
paywall_id: null | string;
|
|
172
172
|
title: string;
|
|
173
|
-
}
|
|
173
|
+
};
|
package/dist/lib/app-url.js
CHANGED
|
@@ -20,8 +20,9 @@ export function appUrl(path) {
|
|
|
20
20
|
* ADAPTY_APP_URL the link is left exactly as issued — the API is free to serve it from any host.
|
|
21
21
|
*/
|
|
22
22
|
export function onAppHost(issuedUrl) {
|
|
23
|
-
if (!process.env[APP_URL_ENV_VAR])
|
|
23
|
+
if (!process.env[APP_URL_ENV_VAR]) {
|
|
24
24
|
return issuedUrl;
|
|
25
|
+
}
|
|
25
26
|
let issued;
|
|
26
27
|
try {
|
|
27
28
|
issued = new URL(issuedUrl);
|
package/dist/lib/asa-client.d.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
+
import { ApiClient } from './api-client.js';
|
|
2
|
+
import type { QueryParams } from './api-client.js';
|
|
1
3
|
import type { Config } from '@oclif/core';
|
|
2
|
-
import { ApiClient, type QueryParams } from './api-client.js';
|
|
3
4
|
export declare const ASA_API_URL = "https://api-asa-admin.adapty.io/api/v1/cli";
|
|
4
5
|
export declare const ASA_API_URL_ENV_VAR = "ADAPTY_ASA_API_URL";
|
|
5
|
-
export
|
|
6
|
-
|
|
6
|
+
export type AsaCommandContext = {
|
|
7
|
+
config: Config;
|
|
8
|
+
jsonEnabled: () => boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function createAsaClient(command: AsaCommandContext): Promise<ApiClient>;
|
|
11
|
+
export type AsaWriteOptions = {
|
|
7
12
|
body?: unknown;
|
|
8
|
-
idempotencyKey?: string;
|
|
9
|
-
params?: QueryParams;
|
|
10
|
-
}
|
|
11
|
-
export
|
|
13
|
+
idempotencyKey?: string | undefined;
|
|
14
|
+
params?: QueryParams | undefined;
|
|
15
|
+
};
|
|
16
|
+
export type AsaWriteOutcome<T> = {
|
|
12
17
|
replayed: boolean;
|
|
13
18
|
result: T;
|
|
14
|
-
}
|
|
19
|
+
};
|
|
15
20
|
export declare function asaWrite<T>(client: ApiClient, method: 'post' | 'put', path: string, opts?: AsaWriteOptions): Promise<AsaWriteOutcome<T>>;
|
|
16
21
|
export declare function noteReplay(replayed: boolean, log: (msg: string) => void): void;
|