@code.store/arcxp-sdk-ts 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.
@@ -0,0 +1,181 @@
1
+ import { AStory, AnImage, Tag } from '../../types/story';
2
+
3
+ type TagANS = Tag & { name: string };
4
+
5
+ type AuthorANS = {
6
+ type: 'author';
7
+ _id: string;
8
+ firstName: string;
9
+ lastName: string;
10
+ byline: string;
11
+ email: string;
12
+ affiliations: string;
13
+ education: { name: string }[];
14
+ awards: { name: string }[];
15
+ bio_page: string;
16
+ bio: string;
17
+ longBio: string;
18
+ slug: string;
19
+ native_app_rendering: boolean;
20
+ fuzzy_match: boolean;
21
+ contributor: boolean;
22
+ location: string;
23
+ role: string;
24
+ expertise: string;
25
+ personal_website: string;
26
+ twitter: string;
27
+ facebook: string;
28
+ linkedin: string;
29
+ status: boolean;
30
+ };
31
+
32
+ export type SectionReference = {
33
+ type: 'reference';
34
+ referent: {
35
+ id: string;
36
+ website: string;
37
+ type: 'section';
38
+ };
39
+ };
40
+
41
+ export type PostANSPayload = {
42
+ sourceId: string;
43
+ sourceType: string;
44
+ ANS: AStory | AnImage | AuthorANS | TagANS;
45
+ references?: unknown[];
46
+ circulations?: [
47
+ {
48
+ document_id: string;
49
+ website_id: string;
50
+ website_url?: string;
51
+ primary_section: SectionReference;
52
+ website_primary_section: SectionReference;
53
+ website_sections: SectionReference[];
54
+ }
55
+ ];
56
+ operations?: {
57
+ type: string;
58
+ story_id: string;
59
+ operation: string;
60
+ date: string;
61
+ organization_id: string;
62
+ endpoint: string;
63
+ }[];
64
+ arcAdditionalProperties?: {
65
+ importPriority?: string;
66
+ story?: {
67
+ publish: boolean;
68
+ };
69
+ video?: {
70
+ transcoding: true;
71
+ useLastUpdated: true;
72
+ importPriority: string;
73
+ thumbnailTimeInSeconds: 0;
74
+ };
75
+ };
76
+ };
77
+
78
+ export type PostANSParams = {
79
+ website: string;
80
+ groupId: string;
81
+ priority: 'historical';
82
+ };
83
+
84
+ export type GetANSParams = {
85
+ ansId: string;
86
+ ansType: string;
87
+ };
88
+
89
+ export enum ANSType {
90
+ Story = 'story',
91
+ Video = 'video',
92
+ Tag = 'tag',
93
+ Author = 'author',
94
+ Gallery = 'gallery',
95
+ Image = 'image',
96
+ Redirect = 'redirect',
97
+ }
98
+
99
+ export enum MigrationStatus {
100
+ Success = 'Success',
101
+ Queued = 'Queued',
102
+ Circulated = 'Circulated',
103
+ Published = 'Published',
104
+ Scheduled = 'Scheduled',
105
+ FailVideo = 'FailVideo',
106
+ FailImage = 'FailImage',
107
+ FailStory = 'FailStory',
108
+ FailGallery = 'FailGallery',
109
+ FailAuthor = 'FailAuthor',
110
+ FailTag = 'FailTag',
111
+ ValidationFailed = 'ValidationFailed',
112
+ }
113
+
114
+ export type Summary = SummaryRecord[];
115
+
116
+ export type SummaryRecord = {
117
+ id: number;
118
+ sourceId: string;
119
+ sourceType: string;
120
+ sourceLocation: string;
121
+ sourceAdditionalProperties: string;
122
+ ansId: string;
123
+ ansType: string;
124
+ ansLocation: string;
125
+ status: MigrationStatus;
126
+ website: string;
127
+ operations: string;
128
+ circulationLocation: string;
129
+ createDate: string;
130
+ updateDate: string;
131
+ errorMessage?: string;
132
+ priority: string;
133
+ arcAdditionalProperties: string;
134
+ groupId: string;
135
+ tags?: null;
136
+ };
137
+
138
+ export type DetailReport = SummaryRecord & { ansContent: AStory | AnImage | AuthorANS | TagANS };
139
+
140
+ export type Count = {
141
+ historical: {
142
+ total: number;
143
+ ansTypes: Partial<{
144
+ tag: ANSTypeCount;
145
+ story: ANSTypeCount;
146
+ author: ANSTypeCount;
147
+ image: ANSTypeCount;
148
+ }>;
149
+ };
150
+ };
151
+
152
+ type ANSTypeCount = Partial<Record<MigrationStatus, number>>;
153
+
154
+ export type DetailReportRequest = {
155
+ sourceId?: string;
156
+ sourceType?: string;
157
+ ansId?: string;
158
+ ansType?: string;
159
+ version?: string;
160
+ documentType?: string;
161
+ };
162
+
163
+ export type SummaryReportRequest = {
164
+ status?: MigrationStatus;
165
+ website: string;
166
+ groupId?: string;
167
+ fetchFromId?: string;
168
+ sort: SummarySortBy;
169
+ sortOrder: SummarySortOrder;
170
+ };
171
+
172
+ export enum SummarySortBy {
173
+ CreateDate = 'createDate',
174
+ UpdateDate = 'updateDate',
175
+ Id = 'id',
176
+ }
177
+
178
+ export enum SummarySortOrder {
179
+ ASC = 'ASC',
180
+ DESC = 'DESC',
181
+ }
@@ -0,0 +1,22 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { ArcAbstractAPI, ArcAPIOptions } from '../abstract-api';
3
+ import FormData from 'form-data';
4
+ import { MigrateBatchResponse } from './types';
5
+
6
+ export class ArcSales extends ArcAbstractAPI {
7
+ constructor(options: ArcAPIOptions) {
8
+ super({ ...options, apiPath: 'sales/api/v1' });
9
+ }
10
+
11
+ async migrate(filePath: string) {
12
+ const form = new FormData();
13
+ const file = await readFile(filePath);
14
+ form.append('file', file, { filename: 'subs.json' });
15
+
16
+ const { data } = await this.client.post<MigrateBatchResponse>('/migrate', form, {
17
+ headers: { ...form.getHeaders() },
18
+ });
19
+
20
+ return data;
21
+ }
22
+ }
@@ -0,0 +1,73 @@
1
+ export type BatchSubscriptionMigrate = {
2
+ subscriptions: (PaidSubscription | FreeSubscription)[];
3
+ payments: PaymentInfo[];
4
+ };
5
+
6
+ interface BaseSubscription {
7
+ type: string;
8
+ legacyID: string;
9
+ ownerClientID: string;
10
+ sku: string;
11
+ priceCode: string;
12
+ currentCycle: number;
13
+ nextEventDateUTC: string;
14
+ paymentMethod?: Record<string, any>;
15
+ }
16
+
17
+ export interface PaidSubscription extends BaseSubscription {
18
+ legacyID: string;
19
+ ownerClientID: string;
20
+ sku: string;
21
+ priceCode: string;
22
+ currentCycle: number;
23
+ nextEventDateUTC: string;
24
+ type: 'paid';
25
+ paymentMethod: {
26
+ providerID: number;
27
+ token: string;
28
+ };
29
+ billingAddress: {
30
+ line1?: string | null | undefined;
31
+ line2?: string | null | undefined;
32
+ locality?: string | null | undefined;
33
+ region?: string | null | undefined;
34
+ postal?: string | null | undefined;
35
+ country: string;
36
+ };
37
+ }
38
+
39
+ export interface FreeSubscription extends BaseSubscription {
40
+ type: 'free';
41
+ paymentMethod: {
42
+ providerID: number;
43
+ token: string;
44
+ };
45
+ }
46
+
47
+ export type PaymentInfo = {
48
+ legacySubscriptionID: string;
49
+ type: 'payment';
50
+ paymentDateUTC: string;
51
+ amount: number;
52
+ currency: string;
53
+ tax: number;
54
+ periodFromUTC?: string;
55
+ periodUntilUTC?: string;
56
+ refunds: null | Refund[];
57
+ };
58
+
59
+ export type Refund = {
60
+ /**
61
+ * 'YYYY-MM-DD HH24:mm – e.g. 2019-04-25 01:02 – refund date';
62
+ */
63
+ refundDateUTC: string;
64
+ amount: number;
65
+ currency: string;
66
+ tax: number;
67
+ providerReference?: string;
68
+ };
69
+
70
+ export type MigrateBatchResponse = {
71
+ subscriptionsInBatch?: number;
72
+ batchID?: string;
73
+ };
@@ -0,0 +1,20 @@
1
+ import { Section, SetSection } from '../../types/section';
2
+ import { ArcAbstractAPI, ArcAPIOptions } from '../abstract-api';
3
+
4
+ export class ArcSite extends ArcAbstractAPI {
5
+ constructor(options: ArcAPIOptions) {
6
+ super({ ...options, apiPath: 'site/v3' });
7
+ }
8
+
9
+ async getSection(id: string, website: string) {
10
+ const { data } = await this.client.get<Section>(`/website/${website}/section?_id=${id}`);
11
+
12
+ return data;
13
+ }
14
+
15
+ async putSection(section: SetSection) {
16
+ const { data } = await this.client.put(`/website/${section.website}/section?_id=${section._id}`, section);
17
+
18
+ return data;
19
+ }
20
+ }
File without changes
@@ -0,0 +1,73 @@
1
+ import ws from 'ws';
2
+
3
+ export type PromiseOr<T> = Promise<T> | T;
4
+ export type Callback<Args extends any[] = any[]> = (...args: Args) => PromiseOr<void>;
5
+
6
+ export default class WsClient {
7
+ private socket?: ws.WebSocket;
8
+
9
+ constructor(private address: string, private readonly options: ws.ClientOptions) {}
10
+
11
+ onOpen?: Callback<[]>;
12
+ onMessage?: Callback<[any]>;
13
+ onClose?: Callback<[ws.CloseEvent | void]>;
14
+
15
+ async connect() {
16
+ if (this.socket && this.socket.readyState === this.socket.OPEN) return;
17
+
18
+ const socket = new ws.WebSocket(this.address, this.options);
19
+ this._closing = false;
20
+ socket.onclose = (event) => this.close(event);
21
+
22
+ await new Promise((resolve, reject) => {
23
+ socket.onerror = (error) => reject(error);
24
+ socket.onopen = () => resolve(true);
25
+ });
26
+ socket.onmessage = ({ data }) => {
27
+ let message: Record<string, any>;
28
+ try {
29
+ message = JSON.parse(data.toString());
30
+ } catch (error) {
31
+ throw new Error('failed to parse message');
32
+ }
33
+
34
+ this.onMessage?.(message);
35
+ };
36
+
37
+ this.socket = socket;
38
+ this.onOpen?.();
39
+ }
40
+
41
+ private _closing = false;
42
+ close(event?: ws.CloseEvent) {
43
+ if (this._closing) return;
44
+ this._closing = true;
45
+ this.socket?.close();
46
+ this.onClose?.(event);
47
+ }
48
+
49
+ async send(data: string) {
50
+ this.throwIfNotOpen();
51
+
52
+ await new Promise((resolve, reject) => {
53
+ this.socket!.send(data, (error) => {
54
+ error ? reject(error) : resolve(undefined);
55
+ });
56
+ });
57
+ }
58
+
59
+ private throwIfNotOpen() {
60
+ if (!this.socket) {
61
+ throw new Error('Client is not connected');
62
+ }
63
+
64
+ const { readyState, OPEN } = this.socket;
65
+ if (readyState === OPEN) return;
66
+ if (readyState < OPEN) {
67
+ throw new Error('socket is still connecting');
68
+ }
69
+ if (readyState > OPEN) {
70
+ throw new Error('socket was closed');
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,113 @@
1
+ import { Alignment, AnImage } from '../types/story';
2
+
3
+ export type ContentElementType<T extends keyof typeof ContentElement> = ReturnType<(typeof ContentElement)[T]>;
4
+
5
+ const refType = 'reference' as const;
6
+
7
+ export const ContentElement = {
8
+ divider: () => {
9
+ return {
10
+ type: 'divider' as const,
11
+ };
12
+ },
13
+ text: (content: string, alignment: Alignment = 'left') => {
14
+ return {
15
+ type: 'text' as const,
16
+ content,
17
+ alignment,
18
+ };
19
+ },
20
+ quote: (content: string) => {
21
+ return {
22
+ type: 'quote' as const,
23
+ subtype: 'pullquote' as const,
24
+ citation: {
25
+ type: 'text' as const,
26
+ content: '',
27
+ },
28
+ content_elements: [
29
+ {
30
+ content,
31
+ type: 'text' as const,
32
+ },
33
+ ],
34
+ };
35
+ },
36
+ interstitial_link: (url: string, content: string) => {
37
+ return {
38
+ type: 'interstitial_link' as const,
39
+ url,
40
+ content,
41
+ };
42
+ },
43
+ header: (content: string, level: number) => {
44
+ return {
45
+ type: 'header' as const,
46
+ content,
47
+ level,
48
+ };
49
+ },
50
+ raw_html: (content: string) => {
51
+ return {
52
+ type: 'raw_html' as const,
53
+ content,
54
+ };
55
+ },
56
+ gallery: (id: string) => {
57
+ return {
58
+ type: refType,
59
+ referent: {
60
+ type: 'gallery' as const,
61
+ id,
62
+ },
63
+ };
64
+ },
65
+ list: (type: 'ordered' | 'unordered', items: string[]) => {
66
+ return {
67
+ type: 'list' as const,
68
+ list_type: type,
69
+ items: items.map((content) => {
70
+ return {
71
+ type: 'text' as const,
72
+ content,
73
+ };
74
+ }),
75
+ };
76
+ },
77
+ link_list: (title: string, links: { content: string; url: string }[]) => {
78
+ return {
79
+ type: 'link_list' as const,
80
+ title,
81
+ items: links.map(({ content, url }) => {
82
+ return {
83
+ type: 'interstitial_link' as const,
84
+ content,
85
+ url,
86
+ };
87
+ }),
88
+ };
89
+ },
90
+ image: (id: string, properties: AnImage) => {
91
+ return {
92
+ referent: {
93
+ id,
94
+ type: 'image' as const,
95
+ referent_properties: {
96
+ ...properties,
97
+ },
98
+ },
99
+ type: refType,
100
+ };
101
+ },
102
+ jwPlayer: (id: string) => {
103
+ return {
104
+ embed: {
105
+ config: {},
106
+ id,
107
+ url: 'https://cdn.jwplayer.com/players',
108
+ },
109
+ subtype: 'jw_player' as const,
110
+ type: 'custom_embed' as const,
111
+ };
112
+ },
113
+ };
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { ArcAPI } from './api';
2
+ import { ContentElement, ContentElementType } from './content-elements';
3
+ import * as ArcTypes from './types';
4
+
5
+ export { ArcAPI, ContentElement, ContentElementType, ArcTypes };
@@ -0,0 +1,17 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { ArcAPI } from '../../api';
3
+
4
+ describe('Arc API', () => {
5
+ beforeEach(() => {
6
+ vi.resetAllMocks();
7
+ vi.clearAllMocks();
8
+ });
9
+
10
+ test('should throw error (Invalid token)', async () => {
11
+ const Arc = ArcAPI({ credentials: { organizationName: 'codestore', accessToken: Date.now().toString() } });
12
+
13
+ await expect(Arc.api.Draft.generateId(Date.now().toString())).rejects.toThrowError(
14
+ 'Request failed with status code 401'
15
+ );
16
+ });
17
+ });