@addn/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/storage.d.ts +25 -0
- package/dist/auth/storage.d.ts.map +1 -0
- package/dist/auth/storage.js +72 -0
- package/dist/auth/storage.js.map +1 -0
- package/dist/auth/token-manager.d.ts +39 -0
- package/dist/auth/token-manager.d.ts.map +1 -0
- package/dist/auth/token-manager.js +98 -0
- package/dist/auth/token-manager.js.map +1 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +39 -0
- package/dist/client.js.map +1 -0
- package/dist/http/errors.d.ts +51 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/http/errors.js +92 -0
- package/dist/http/errors.js.map +1 -0
- package/dist/http/http-client.d.ts +18 -0
- package/dist/http/http-client.d.ts.map +1 -0
- package/dist/http/http-client.js +221 -0
- package/dist/http/http-client.js.map +1 -0
- package/dist/http/types.d.ts +27 -0
- package/dist/http/types.d.ts.map +1 -0
- package/dist/http/types.js +3 -0
- package/dist/http/types.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/accounts.module.d.ts +15 -0
- package/dist/modules/accounts.module.d.ts.map +1 -0
- package/dist/modules/accounts.module.js +57 -0
- package/dist/modules/accounts.module.js.map +1 -0
- package/dist/modules/analytics.module.d.ts +14 -0
- package/dist/modules/analytics.module.d.ts.map +1 -0
- package/dist/modules/analytics.module.js +24 -0
- package/dist/modules/analytics.module.js.map +1 -0
- package/dist/modules/auth.module.d.ts +18 -0
- package/dist/modules/auth.module.d.ts.map +1 -0
- package/dist/modules/auth.module.js +113 -0
- package/dist/modules/auth.module.js.map +1 -0
- package/dist/modules/content-plans.module.d.ts +58 -0
- package/dist/modules/content-plans.module.d.ts.map +1 -0
- package/dist/modules/content-plans.module.js +125 -0
- package/dist/modules/content-plans.module.js.map +1 -0
- package/dist/modules/customers.module.d.ts +12 -0
- package/dist/modules/customers.module.d.ts.map +1 -0
- package/dist/modules/customers.module.js +43 -0
- package/dist/modules/customers.module.js.map +1 -0
- package/dist/modules/notifications.module.d.ts +20 -0
- package/dist/modules/notifications.module.d.ts.map +1 -0
- package/dist/modules/notifications.module.js +37 -0
- package/dist/modules/notifications.module.js.map +1 -0
- package/dist/modules/orgs.module.d.ts +23 -0
- package/dist/modules/orgs.module.d.ts.map +1 -0
- package/dist/modules/orgs.module.js +112 -0
- package/dist/modules/orgs.module.js.map +1 -0
- package/dist/modules/projects.module.d.ts +26 -0
- package/dist/modules/projects.module.d.ts.map +1 -0
- package/dist/modules/projects.module.js +88 -0
- package/dist/modules/projects.module.js.map +1 -0
- package/dist/modules/scheduling.module.d.ts +13 -0
- package/dist/modules/scheduling.module.d.ts.map +1 -0
- package/dist/modules/scheduling.module.js +32 -0
- package/dist/modules/scheduling.module.js.map +1 -0
- package/dist/modules/storage.module.d.ts +36 -0
- package/dist/modules/storage.module.d.ts.map +1 -0
- package/dist/modules/storage.module.js +100 -0
- package/dist/modules/storage.module.js.map +1 -0
- package/dist/modules/subscriptions.module.d.ts +25 -0
- package/dist/modules/subscriptions.module.d.ts.map +1 -0
- package/dist/modules/subscriptions.module.js +62 -0
- package/dist/modules/subscriptions.module.js.map +1 -0
- package/dist/types/account.types.d.ts +17 -0
- package/dist/types/account.types.d.ts.map +1 -0
- package/dist/types/account.types.js +3 -0
- package/dist/types/account.types.js.map +1 -0
- package/dist/types/analytics.types.d.ts +15 -0
- package/dist/types/analytics.types.d.ts.map +1 -0
- package/dist/types/analytics.types.js +3 -0
- package/dist/types/analytics.types.js.map +1 -0
- package/dist/types/api.types.d.ts +20 -0
- package/dist/types/api.types.d.ts.map +1 -0
- package/dist/types/api.types.js +3 -0
- package/dist/types/api.types.js.map +1 -0
- package/dist/types/auth.types.d.ts +40 -0
- package/dist/types/auth.types.d.ts.map +1 -0
- package/dist/types/auth.types.js +3 -0
- package/dist/types/auth.types.js.map +1 -0
- package/dist/types/content-plan.types.d.ts +27 -0
- package/dist/types/content-plan.types.d.ts.map +1 -0
- package/dist/types/content-plan.types.js +3 -0
- package/dist/types/content-plan.types.js.map +1 -0
- package/dist/types/customer.types.d.ts +25 -0
- package/dist/types/customer.types.d.ts.map +1 -0
- package/dist/types/customer.types.js +3 -0
- package/dist/types/customer.types.js.map +1 -0
- package/dist/types/index.d.ts +585 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/notification.types.d.ts +11 -0
- package/dist/types/notification.types.d.ts.map +1 -0
- package/dist/types/notification.types.js +3 -0
- package/dist/types/notification.types.js.map +1 -0
- package/dist/types/org.types.d.ts +25 -0
- package/dist/types/org.types.d.ts.map +1 -0
- package/dist/types/org.types.js +3 -0
- package/dist/types/org.types.js.map +1 -0
- package/dist/types/project.types.d.ts +20 -0
- package/dist/types/project.types.d.ts.map +1 -0
- package/dist/types/project.types.js +3 -0
- package/dist/types/project.types.js.map +1 -0
- package/dist/types/scheduling.types.d.ts +31 -0
- package/dist/types/scheduling.types.d.ts.map +1 -0
- package/dist/types/scheduling.types.js +3 -0
- package/dist/types/scheduling.types.js.map +1 -0
- package/dist/types/storage.types.d.ts +8 -0
- package/dist/types/storage.types.d.ts.map +1 -0
- package/dist/types/storage.types.js +3 -0
- package/dist/types/storage.types.js.map +1 -0
- package/dist/types/subscription.types.d.ts +19 -0
- package/dist/types/subscription.types.d.ts.map +1 -0
- package/dist/types/subscription.types.js +3 -0
- package/dist/types/subscription.types.js.map +1 -0
- package/package.json +27 -0
- package/src/auth/storage.ts +75 -0
- package/src/auth/token-manager.ts +107 -0
- package/src/client.ts +51 -0
- package/src/http/errors.ts +97 -0
- package/src/http/http-client.ts +263 -0
- package/src/http/types.ts +32 -0
- package/src/index.ts +17 -0
- package/src/modules/accounts.module.ts +61 -0
- package/src/modules/analytics.module.ts +26 -0
- package/src/modules/auth.module.ts +129 -0
- package/src/modules/content-plans.module.ts +170 -0
- package/src/modules/customers.module.ts +48 -0
- package/src/modules/notifications.module.ts +41 -0
- package/src/modules/orgs.module.ts +122 -0
- package/src/modules/projects.module.ts +97 -0
- package/src/modules/scheduling.module.ts +35 -0
- package/src/modules/storage.module.ts +121 -0
- package/src/modules/subscriptions.module.ts +82 -0
- package/src/types/account.types.ts +17 -0
- package/src/types/analytics.types.ts +15 -0
- package/src/types/api.types.ts +21 -0
- package/src/types/auth.types.ts +45 -0
- package/src/types/content-plan.types.ts +28 -0
- package/src/types/customer.types.ts +25 -0
- package/src/types/index.ts +682 -0
- package/src/types/notification.types.ts +10 -0
- package/src/types/org.types.ts +27 -0
- package/src/types/project.types.ts +21 -0
- package/src/types/scheduling.types.ts +32 -0
- package/src/types/storage.types.ts +7 -0
- package/src/types/subscription.types.ts +19 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { AddnClientOptions, HttpRequestOptions } from "./types";
|
|
2
|
+
import { ApiResponse, ValidationErrorDetail } from "../types";
|
|
3
|
+
import {
|
|
4
|
+
AddnError,
|
|
5
|
+
AddnNetworkError,
|
|
6
|
+
AddnTimeoutError,
|
|
7
|
+
AddnApiError,
|
|
8
|
+
AddnAuthError,
|
|
9
|
+
AddnForbiddenError,
|
|
10
|
+
AddnNotFoundError,
|
|
11
|
+
AddnConflictError,
|
|
12
|
+
AddnValidationError,
|
|
13
|
+
AddnRateLimitError,
|
|
14
|
+
AddnServerError,
|
|
15
|
+
AddnUnexpectedError,
|
|
16
|
+
} from "./errors";
|
|
17
|
+
import { TokenManager, TokenPair } from "../auth/token-manager";
|
|
18
|
+
import { LocalStorageAdapter } from "../auth/storage";
|
|
19
|
+
|
|
20
|
+
export class HttpClient {
|
|
21
|
+
private readonly baseUrl: string;
|
|
22
|
+
private readonly timeoutMs: number;
|
|
23
|
+
public readonly tokenManager: TokenManager;
|
|
24
|
+
private readonly onRequest?: AddnClientOptions["onRequest"];
|
|
25
|
+
private readonly onError?: AddnClientOptions["onError"];
|
|
26
|
+
private readonly onUnauthenticated?: AddnClientOptions["onUnauthenticated"];
|
|
27
|
+
|
|
28
|
+
constructor(options: AddnClientOptions) {
|
|
29
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
30
|
+
this.timeoutMs = options.timeoutMs ?? 30000;
|
|
31
|
+
this.onRequest = options.onRequest;
|
|
32
|
+
this.onError = options.onError;
|
|
33
|
+
this.onUnauthenticated = options.onUnauthenticated;
|
|
34
|
+
|
|
35
|
+
const storage = options.storage ?? new LocalStorageAdapter();
|
|
36
|
+
this.tokenManager = new TokenManager(storage, this.onUnauthenticated);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async request<T = any>(options: HttpRequestOptions): Promise<T> {
|
|
40
|
+
let opts = { ...options };
|
|
41
|
+
|
|
42
|
+
if (this.onRequest) {
|
|
43
|
+
try {
|
|
44
|
+
opts = await this.onRequest(opts);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
throw new AddnUnexpectedError("Custom onRequest hook failed", err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
return await this.executeRequest<T>(opts);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof AddnAuthError && opts.requiresAuth !== false) {
|
|
54
|
+
// Attempt token refresh
|
|
55
|
+
const newTokens = await this.tokenManager.handleRefresh(async (refreshToken) => {
|
|
56
|
+
return await this.rawRefreshTokenCall(refreshToken);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (newTokens) {
|
|
60
|
+
// Retry request with new token
|
|
61
|
+
try {
|
|
62
|
+
return await this.executeRequest<T>(opts);
|
|
63
|
+
} catch (retryErr) {
|
|
64
|
+
this.handleErrorTrigger(retryErr);
|
|
65
|
+
throw retryErr;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.handleErrorTrigger(error);
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async executeRequest<T>(options: HttpRequestOptions): Promise<T> {
|
|
76
|
+
const method = options.method ?? "GET";
|
|
77
|
+
const path = options.path.startsWith("/") ? options.path : `/${options.path}`;
|
|
78
|
+
const url = this.buildUrl(path, options.query);
|
|
79
|
+
|
|
80
|
+
const headers: Record<string, string> = { ...options.headers };
|
|
81
|
+
|
|
82
|
+
if (options.requiresAuth !== false) {
|
|
83
|
+
const token = await this.tokenManager.getAccessToken();
|
|
84
|
+
if (token && !headers["Authorization"]) {
|
|
85
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let body: any = undefined;
|
|
90
|
+
if (options.formData) {
|
|
91
|
+
body = options.formData;
|
|
92
|
+
} else if (options.body !== undefined && options.body !== null) {
|
|
93
|
+
if (!headers["Content-Type"]) {
|
|
94
|
+
headers["Content-Type"] = "application/json";
|
|
95
|
+
}
|
|
96
|
+
body = JSON.stringify(options.body);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
const timeoutMs = options.timeoutMs ?? this.timeoutMs;
|
|
101
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
102
|
+
|
|
103
|
+
// Merge external signal with timeout signal if provided
|
|
104
|
+
if (options.signal) {
|
|
105
|
+
options.signal.addEventListener("abort", () => controller.abort());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let response: Response;
|
|
109
|
+
try {
|
|
110
|
+
response = await fetch(url, {
|
|
111
|
+
method,
|
|
112
|
+
headers,
|
|
113
|
+
body,
|
|
114
|
+
signal: controller.signal,
|
|
115
|
+
credentials: "include",
|
|
116
|
+
});
|
|
117
|
+
} catch (err: unknown) {
|
|
118
|
+
if (
|
|
119
|
+
(err instanceof Error && err.name === "AbortError") ||
|
|
120
|
+
(typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError") ||
|
|
121
|
+
controller.signal.aborted
|
|
122
|
+
) {
|
|
123
|
+
throw new AddnTimeoutError(timeoutMs);
|
|
124
|
+
}
|
|
125
|
+
const msg = err instanceof Error ? err.message : "Network request failed";
|
|
126
|
+
throw new AddnNetworkError(msg, err);
|
|
127
|
+
} finally {
|
|
128
|
+
clearTimeout(timeoutId);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return await this.parseResponse<T>(response);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async parseResponse<T>(response: Response): Promise<T> {
|
|
135
|
+
const contentType = response.headers.get("content-type") || "";
|
|
136
|
+
let data: any = null;
|
|
137
|
+
let rawText = "";
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
if (contentType.includes("application/json")) {
|
|
141
|
+
data = await response.json();
|
|
142
|
+
} else {
|
|
143
|
+
rawText = await response.text();
|
|
144
|
+
if (rawText) {
|
|
145
|
+
try {
|
|
146
|
+
data = JSON.parse(rawText);
|
|
147
|
+
} catch {
|
|
148
|
+
data = { message: rawText };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (parseErr) {
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
throw new AddnUnexpectedError(`HTTP ${response.status} ${response.statusText} - Failed to parse response body`, parseErr);
|
|
155
|
+
}
|
|
156
|
+
return null as unknown as T;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (response.ok) {
|
|
160
|
+
// Check if response conforms to standard ApiResponse envelope { success: true, data: T }
|
|
161
|
+
if (data && typeof data === "object" && "data" in data && "success" in data) {
|
|
162
|
+
return data.data as T;
|
|
163
|
+
}
|
|
164
|
+
return data as T;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Handle Error Statuses
|
|
168
|
+
const message = data?.message || data?.detail || response.statusText || `Request failed with status ${response.status}`;
|
|
169
|
+
const errorCode = data?.code || "API_ERROR";
|
|
170
|
+
const statusCode = response.status;
|
|
171
|
+
|
|
172
|
+
if (statusCode === 401) {
|
|
173
|
+
throw new AddnAuthError(message, statusCode, errorCode, data?.details);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (statusCode === 403) {
|
|
177
|
+
throw new AddnForbiddenError(message, statusCode, errorCode, data?.details);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (statusCode === 404) {
|
|
181
|
+
throw new AddnNotFoundError(message, statusCode, errorCode, data?.details);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (statusCode === 409) {
|
|
185
|
+
throw new AddnConflictError(message, statusCode, errorCode, data?.details);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (statusCode === 422) {
|
|
189
|
+
const details: ValidationErrorDetail[] = Array.isArray(data?.details)
|
|
190
|
+
? data.details.map((d: any) => ({
|
|
191
|
+
field: d.field || d.loc?.join(".") || "payload",
|
|
192
|
+
message: d.message || d.msg || "Invalid value",
|
|
193
|
+
type: d.type,
|
|
194
|
+
}))
|
|
195
|
+
: [];
|
|
196
|
+
throw new AddnValidationError(message, details, data);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (statusCode === 429) {
|
|
200
|
+
const retryHeader = response.headers.get("Retry-After");
|
|
201
|
+
const retrySecs = retryHeader ? parseInt(retryHeader, 10) : undefined;
|
|
202
|
+
throw new AddnRateLimitError(message, retrySecs);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (statusCode >= 500) {
|
|
206
|
+
throw new AddnServerError(message, statusCode, errorCode, data?.details);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
throw new AddnApiError(message, statusCode, errorCode, data?.details, data);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private async rawRefreshTokenCall(refreshToken?: string): Promise<TokenPair> {
|
|
213
|
+
const url = `${this.baseUrl}/api/v1/auth/refresh`;
|
|
214
|
+
const response = await fetch(url, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: { "Content-Type": "application/json" },
|
|
217
|
+
body: JSON.stringify(refreshToken ? { refresh_token: refreshToken } : {}),
|
|
218
|
+
credentials: "include",
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (!response.ok) {
|
|
222
|
+
throw new AddnAuthError("Refresh token rejected", response.status);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const data = await response.json();
|
|
226
|
+
const tokenData = data.data || data;
|
|
227
|
+
|
|
228
|
+
if (!tokenData.access_token) {
|
|
229
|
+
throw new AddnAuthError("Refresh endpoint returned invalid token payload");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
accessToken: tokenData.access_token,
|
|
234
|
+
refreshToken: tokenData.refresh_token,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private buildUrl(path: string, query?: Record<string, string | number | boolean | (string | number)[] | undefined | null>): string {
|
|
239
|
+
const fullUrl = new URL(`${this.baseUrl}${path}`);
|
|
240
|
+
if (query) {
|
|
241
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
242
|
+
if (value !== undefined && value !== null) {
|
|
243
|
+
if (Array.isArray(value)) {
|
|
244
|
+
value.forEach((v) => fullUrl.searchParams.append(key, String(v)));
|
|
245
|
+
} else {
|
|
246
|
+
fullUrl.searchParams.append(key, String(value));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return fullUrl.toString();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private handleErrorTrigger(error: unknown): void {
|
|
255
|
+
if (this.onError && error instanceof AddnError) {
|
|
256
|
+
try {
|
|
257
|
+
this.onError(error);
|
|
258
|
+
} catch {
|
|
259
|
+
// Prevent onError callback exceptions from masking original error
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AddnError } from "./errors";
|
|
2
|
+
|
|
3
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
4
|
+
|
|
5
|
+
export interface HttpRequestOptions {
|
|
6
|
+
path: string;
|
|
7
|
+
method?: HttpMethod;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
query?: Record<string, string | number | boolean | (string | number)[] | undefined | null>;
|
|
10
|
+
body?: unknown;
|
|
11
|
+
formData?: FormData;
|
|
12
|
+
requiresAuth?: boolean;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
import { ApiResponse, PaginatedMeta, PaginatedResponse } from "../types";
|
|
18
|
+
|
|
19
|
+
export interface StorageAdapter {
|
|
20
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
21
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
22
|
+
removeItem(key: string): void | Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AddnClientOptions {
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
storage?: StorageAdapter;
|
|
29
|
+
onUnauthenticated?: () => void;
|
|
30
|
+
onRequest?: (options: HttpRequestOptions) => HttpRequestOptions | Promise<HttpRequestOptions>;
|
|
31
|
+
onError?: (error: AddnError) => void;
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { AddnClient } from "./client";
|
|
2
|
+
export * from "./http/types";
|
|
3
|
+
export * from "./http/errors";
|
|
4
|
+
export * from "./auth/storage";
|
|
5
|
+
export * from "./auth/token-manager";
|
|
6
|
+
export * from "./modules/auth.module";
|
|
7
|
+
export * from "./modules/orgs.module";
|
|
8
|
+
export * from "./modules/customers.module";
|
|
9
|
+
export * from "./modules/projects.module";
|
|
10
|
+
export * from "./modules/content-plans.module";
|
|
11
|
+
export * from "./modules/scheduling.module";
|
|
12
|
+
export * from "./modules/accounts.module";
|
|
13
|
+
export * from "./modules/analytics.module";
|
|
14
|
+
export * from "./modules/notifications.module";
|
|
15
|
+
export * from "./modules/storage.module";
|
|
16
|
+
export * from "./modules/subscriptions.module";
|
|
17
|
+
export * from "./types";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { HttpClient } from "../http/http-client";
|
|
2
|
+
import type { ConnectedAccount, ConnectAccountPayload, DevConnectPayload } from "../types";
|
|
3
|
+
|
|
4
|
+
export class AccountsModule {
|
|
5
|
+
private readonly http: HttpClient;
|
|
6
|
+
|
|
7
|
+
constructor(http: HttpClient) {
|
|
8
|
+
this.http = http;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public async listConnectedAccounts(orgId?: string): Promise<ConnectedAccount[]> {
|
|
12
|
+
const res = await this.http.request<any>({
|
|
13
|
+
path: "/api/v1/accounts",
|
|
14
|
+
method: "GET",
|
|
15
|
+
query: orgId ? { org_id: orgId } : undefined,
|
|
16
|
+
});
|
|
17
|
+
if (Array.isArray(res)) return res;
|
|
18
|
+
if (res?.accounts && Array.isArray(res.accounts)) return res.accounts;
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async getConnectUrl(method: "facebook" | "instagram" = "facebook"): Promise<{ url: string }> {
|
|
23
|
+
return await this.http.request<{ url: string }>({
|
|
24
|
+
path: `/api/v1/account/connect?login_method=${method}`,
|
|
25
|
+
method: "POST",
|
|
26
|
+
body: {},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async devConnectAccount(payload: DevConnectPayload): Promise<ConnectedAccount> {
|
|
31
|
+
const res = await this.http.request<any>({
|
|
32
|
+
path: "/api/v1/account/dev-connect",
|
|
33
|
+
method: "POST",
|
|
34
|
+
body: payload,
|
|
35
|
+
});
|
|
36
|
+
return res?.account || res;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async connectAccount(payload: ConnectAccountPayload): Promise<ConnectedAccount> {
|
|
40
|
+
return await this.http.request<ConnectedAccount>({
|
|
41
|
+
path: "/api/v1/account/connect",
|
|
42
|
+
method: "POST",
|
|
43
|
+
body: payload,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async disconnectAccount(accountId: string): Promise<void> {
|
|
48
|
+
await this.http.request({
|
|
49
|
+
path: `/api/v1/account/${accountId}`,
|
|
50
|
+
method: "DELETE",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async refreshAccountToken(accountId: string): Promise<any> {
|
|
55
|
+
return await this.http.request<any>({
|
|
56
|
+
path: `/api/v1/account/${accountId}/refresh-token`,
|
|
57
|
+
method: "POST",
|
|
58
|
+
body: {},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { HttpClient } from "../http/http-client";
|
|
2
|
+
import type { AnalyticsSummary, InstagramAnalyticsData } from "../types";
|
|
3
|
+
|
|
4
|
+
export class AnalyticsModule {
|
|
5
|
+
private readonly http: HttpClient;
|
|
6
|
+
|
|
7
|
+
constructor(http: HttpClient) {
|
|
8
|
+
this.http = http;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public async getSummary(params: { org_id?: string; project_id?: string; start_date?: string; end_date?: string }): Promise<AnalyticsSummary> {
|
|
12
|
+
return await this.http.request<AnalyticsSummary>({
|
|
13
|
+
path: "/api/v1/analytics/summary",
|
|
14
|
+
method: "GET",
|
|
15
|
+
query: params,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async getInstagramAnalytics(accountId: string, timeframe: "7d" | "30d" | "90d" = "30d"): Promise<InstagramAnalyticsData> {
|
|
20
|
+
return await this.http.request<InstagramAnalyticsData>({
|
|
21
|
+
path: `/api/v1/analytics/instagram/${accountId}`,
|
|
22
|
+
method: "GET",
|
|
23
|
+
query: { timeframe },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { HttpClient } from "../http/http-client";
|
|
2
|
+
import type {
|
|
3
|
+
AuthTokenResponse,
|
|
4
|
+
LoginPayload,
|
|
5
|
+
RegisterPayload,
|
|
6
|
+
UserProfile,
|
|
7
|
+
PasswordResetRequestPayload,
|
|
8
|
+
PasswordResetConfirmPayload,
|
|
9
|
+
MetaLoginPayload,
|
|
10
|
+
} from "../types";
|
|
11
|
+
|
|
12
|
+
export class AuthModule {
|
|
13
|
+
private readonly http: HttpClient;
|
|
14
|
+
|
|
15
|
+
constructor(http: HttpClient) {
|
|
16
|
+
this.http = http;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async login(payload: LoginPayload): Promise<AuthTokenResponse> {
|
|
20
|
+
const res = await this.http.request<AuthTokenResponse>({
|
|
21
|
+
path: "/api/v1/auth/login",
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: {
|
|
24
|
+
email: payload.email || payload.username,
|
|
25
|
+
password: payload.password,
|
|
26
|
+
},
|
|
27
|
+
requiresAuth: false,
|
|
28
|
+
});
|
|
29
|
+
if (res?.access_token && res?.refresh_token) {
|
|
30
|
+
await this.http.tokenManager.setTokens(res.access_token, res.refresh_token);
|
|
31
|
+
}
|
|
32
|
+
if (res?.user) {
|
|
33
|
+
await this.http.tokenManager.setUser(res.user);
|
|
34
|
+
}
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async register(payload: RegisterPayload): Promise<AuthTokenResponse> {
|
|
39
|
+
const res = await this.http.request<AuthTokenResponse>({
|
|
40
|
+
path: "/api/v1/auth/register",
|
|
41
|
+
method: "POST",
|
|
42
|
+
body: payload,
|
|
43
|
+
requiresAuth: false,
|
|
44
|
+
});
|
|
45
|
+
if (res?.access_token && res?.refresh_token) {
|
|
46
|
+
await this.http.tokenManager.setTokens(res.access_token, res.refresh_token);
|
|
47
|
+
}
|
|
48
|
+
if (res?.user) {
|
|
49
|
+
await this.http.tokenManager.setUser(res.user);
|
|
50
|
+
}
|
|
51
|
+
return res;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async getMe(): Promise<UserProfile> {
|
|
55
|
+
const user = await this.http.request<UserProfile>({
|
|
56
|
+
path: "/api/v1/auth/me",
|
|
57
|
+
method: "GET",
|
|
58
|
+
});
|
|
59
|
+
if (user) {
|
|
60
|
+
await this.http.tokenManager.setUser(user);
|
|
61
|
+
}
|
|
62
|
+
return user;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async logout(): Promise<void> {
|
|
66
|
+
try {
|
|
67
|
+
await this.http.request({
|
|
68
|
+
path: "/api/v1/auth/logout",
|
|
69
|
+
method: "POST",
|
|
70
|
+
});
|
|
71
|
+
} finally {
|
|
72
|
+
await this.http.tokenManager.clearTokens();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public async requestPasswordReset(payload: PasswordResetRequestPayload): Promise<void> {
|
|
77
|
+
await this.http.request({
|
|
78
|
+
path: "/api/v1/auth/forgot-password",
|
|
79
|
+
method: "POST",
|
|
80
|
+
body: payload,
|
|
81
|
+
requiresAuth: false,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async confirmPasswordReset(payload: PasswordResetConfirmPayload): Promise<void> {
|
|
86
|
+
await this.http.request({
|
|
87
|
+
path: "/api/v1/auth/reset-password",
|
|
88
|
+
method: "POST",
|
|
89
|
+
body: payload,
|
|
90
|
+
requiresAuth: false,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public async completeOnboarding(): Promise<UserProfile> {
|
|
95
|
+
const user = await this.http.request<UserProfile>({
|
|
96
|
+
path: "/api/v1/auth/onboarding/complete",
|
|
97
|
+
method: "PATCH",
|
|
98
|
+
});
|
|
99
|
+
if (user) {
|
|
100
|
+
await this.http.tokenManager.setUser(user);
|
|
101
|
+
}
|
|
102
|
+
return user;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public async deleteAccount(): Promise<{ message: string }> {
|
|
106
|
+
const res = await this.http.request<{ message: string }>({
|
|
107
|
+
path: "/api/v1/auth/account",
|
|
108
|
+
method: "DELETE",
|
|
109
|
+
});
|
|
110
|
+
await this.http.tokenManager.clearTokens();
|
|
111
|
+
return res;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public async metaLogin(payload: MetaLoginPayload): Promise<AuthTokenResponse> {
|
|
115
|
+
const res = await this.http.request<AuthTokenResponse>({
|
|
116
|
+
path: "/api/v1/auth/meta/callback",
|
|
117
|
+
method: "POST",
|
|
118
|
+
body: payload,
|
|
119
|
+
requiresAuth: false,
|
|
120
|
+
});
|
|
121
|
+
if (res?.access_token && res?.refresh_token) {
|
|
122
|
+
await this.http.tokenManager.setTokens(res.access_token, res.refresh_token);
|
|
123
|
+
}
|
|
124
|
+
if (res?.user) {
|
|
125
|
+
await this.http.tokenManager.setUser(res.user);
|
|
126
|
+
}
|
|
127
|
+
return res;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { HttpClient } from "../http/http-client";
|
|
2
|
+
import type { ContentPlan, CreateContentPlanPayload, UpdateContentPlanPayload } from "../types";
|
|
3
|
+
|
|
4
|
+
export class ContentPlansModule {
|
|
5
|
+
private readonly http: HttpClient;
|
|
6
|
+
|
|
7
|
+
constructor(http: HttpClient) {
|
|
8
|
+
this.http = http;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public async listContentPlans(params?: {
|
|
12
|
+
project_id?: string;
|
|
13
|
+
status?: string;
|
|
14
|
+
instagram_account_id?: string;
|
|
15
|
+
}): Promise<ContentPlan[]> {
|
|
16
|
+
const res = await this.http.request<any>({
|
|
17
|
+
path: "/api/v1/content-plans",
|
|
18
|
+
method: "GET",
|
|
19
|
+
query: params,
|
|
20
|
+
});
|
|
21
|
+
if (res && Array.isArray(res.content_plans)) return res.content_plans;
|
|
22
|
+
if (Array.isArray(res)) return res;
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async getContentPlan(planId: string): Promise<ContentPlan> {
|
|
27
|
+
const res = await this.http.request<any>({
|
|
28
|
+
path: `/api/v1/content-plans/${planId}`,
|
|
29
|
+
method: "GET",
|
|
30
|
+
});
|
|
31
|
+
return res?.content_plan || res;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async createContentPlan(payload: CreateContentPlanPayload): Promise<ContentPlan> {
|
|
35
|
+
const res = await this.http.request<any>({
|
|
36
|
+
path: "/api/v1/content-plans",
|
|
37
|
+
method: "POST",
|
|
38
|
+
body: payload,
|
|
39
|
+
});
|
|
40
|
+
return res?.content_plan || res;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public async updateContentPlan(planId: string, payload: UpdateContentPlanPayload): Promise<ContentPlan> {
|
|
44
|
+
const res = await this.http.request<any>({
|
|
45
|
+
path: `/api/v1/content-plans/${planId}`,
|
|
46
|
+
method: "PATCH",
|
|
47
|
+
body: payload,
|
|
48
|
+
});
|
|
49
|
+
return res?.content_plan || res;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public async deleteContentPlan(planId: string): Promise<void> {
|
|
53
|
+
await this.http.request({
|
|
54
|
+
path: `/api/v1/content-plans/${planId}`,
|
|
55
|
+
method: "DELETE",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public async submitForReview(planId: string, approverIds: string[], note?: string): Promise<ContentPlan> {
|
|
60
|
+
const res = await this.http.request<any>({
|
|
61
|
+
path: `/api/v1/content-plans/${planId}/submit`,
|
|
62
|
+
method: "POST",
|
|
63
|
+
body: { approver_ids: approverIds, note },
|
|
64
|
+
});
|
|
65
|
+
return res?.content_plan || res;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public async approveDirectly(planId: string, note?: string): Promise<ContentPlan> {
|
|
69
|
+
const res = await this.http.request<any>({
|
|
70
|
+
path: `/api/v1/content-plans/${planId}/approve-directly`,
|
|
71
|
+
method: "POST",
|
|
72
|
+
body: { note },
|
|
73
|
+
});
|
|
74
|
+
return res?.content_plan || res;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public async review(planId: string, action: "approved" | "changes_requested", note?: string): Promise<ContentPlan> {
|
|
78
|
+
const res = await this.http.request<any>({
|
|
79
|
+
path: `/api/v1/content-plans/${planId}/review`,
|
|
80
|
+
method: "POST",
|
|
81
|
+
body: { action, note },
|
|
82
|
+
});
|
|
83
|
+
return res?.content_plan || res;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public async generateContent(
|
|
87
|
+
planId: string,
|
|
88
|
+
outputType: "idea" | "script" | "caption" | "hashtags" | "visual_direction",
|
|
89
|
+
instructions?: string
|
|
90
|
+
): Promise<{ output_type: string; content: string }> {
|
|
91
|
+
return await this.http.request<{ output_type: string; content: string }>({
|
|
92
|
+
path: `/api/v1/content-plans/${planId}/generate`,
|
|
93
|
+
method: "POST",
|
|
94
|
+
body: { output_type: outputType, instructions },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public async generateStandalone(payload: {
|
|
99
|
+
output_type: string;
|
|
100
|
+
instructions: string;
|
|
101
|
+
title?: string;
|
|
102
|
+
objective?: string;
|
|
103
|
+
core_idea?: string;
|
|
104
|
+
format?: string;
|
|
105
|
+
}): Promise<{ output_type: string; content: string }> {
|
|
106
|
+
return await this.http.request<{ output_type: string; content: string }>({
|
|
107
|
+
path: "/api/v1/content-plans/generate-ai-field",
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: payload,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public async generateFullPlan(payload: {
|
|
114
|
+
content_type: string;
|
|
115
|
+
prompt: string;
|
|
116
|
+
project_id?: string;
|
|
117
|
+
}): Promise<{
|
|
118
|
+
plan: {
|
|
119
|
+
title?: string;
|
|
120
|
+
objective?: string;
|
|
121
|
+
core_idea?: string;
|
|
122
|
+
hook?: string;
|
|
123
|
+
script?: string;
|
|
124
|
+
call_to_action?: string;
|
|
125
|
+
visual_direction?: string;
|
|
126
|
+
caption?: string;
|
|
127
|
+
hashtags?: string;
|
|
128
|
+
content_pillar?: string;
|
|
129
|
+
};
|
|
130
|
+
}> {
|
|
131
|
+
return await this.http.request<any>({
|
|
132
|
+
path: "/api/v1/content-plans/generate-full-plan",
|
|
133
|
+
method: "POST",
|
|
134
|
+
body: payload,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public async sharePlan(planId: string, message?: string): Promise<{ recipient_count: number }> {
|
|
139
|
+
return await this.http.request<{ recipient_count: number }>({
|
|
140
|
+
path: `/api/v1/content-plans/${planId}/share`,
|
|
141
|
+
method: "POST",
|
|
142
|
+
body: { message },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public async getComments(planId: string): Promise<any[]> {
|
|
147
|
+
const res = await this.http.request<any>({
|
|
148
|
+
path: `/api/v1/content-plans/${planId}/comments`,
|
|
149
|
+
method: "GET",
|
|
150
|
+
});
|
|
151
|
+
return res?.comments || [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public async addComment(planId: string, body: string): Promise<any> {
|
|
155
|
+
const res = await this.http.request<any>({
|
|
156
|
+
path: `/api/v1/content-plans/${planId}/comments`,
|
|
157
|
+
method: "POST",
|
|
158
|
+
body: { body },
|
|
159
|
+
});
|
|
160
|
+
return res?.comment || res;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public async getAudit(planId: string): Promise<any[]> {
|
|
164
|
+
const res = await this.http.request<any>({
|
|
165
|
+
path: `/api/v1/content-plans/${planId}/audit`,
|
|
166
|
+
method: "GET",
|
|
167
|
+
});
|
|
168
|
+
return res?.events || [];
|
|
169
|
+
}
|
|
170
|
+
}
|