@superblocksteam/sdk 1.4.1 → 1.5.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/src/sdk.ts CHANGED
@@ -7,18 +7,18 @@ import {
7
7
  fetchApi,
8
8
  fetchApis,
9
9
  fetchApplication,
10
- fetchApplicationWithComponents,
10
+ fetchApplicationBranches,
11
11
  fetchApplications,
12
- registerComponents,
13
- uploadComponents,
12
+ fetchApplicationWithComponents,
14
13
  fetchCurrentUser,
15
- ViewMode,
14
+ pushApi,
15
+ PushApiWithCommitConfig,
16
16
  pushApplication,
17
17
  PushApplicationWithCommitConfig,
18
- fetchApplicationBranches,
19
- PushApiWithCommitConfig,
20
- pushApi,
18
+ registerComponents,
19
+ uploadComponents,
21
20
  validateGitSetup,
21
+ ViewMode,
22
22
  } from "./client";
23
23
 
24
24
  // Exporting here instead of index.ts because only sdk.ts is exposed outside in package.json
@@ -27,10 +27,16 @@ export * from "./errors";
27
27
  export class SuperblocksSdk {
28
28
  token = "";
29
29
  superblocksBaseUrl = "";
30
+ cliVersion = "";
30
31
 
31
- constructor(accessToken: string, superblocksBaseUrl: string) {
32
+ constructor(
33
+ accessToken: string,
34
+ superblocksBaseUrl: string,
35
+ cliVersion: string
36
+ ) {
32
37
  this.token = accessToken;
33
38
  this.superblocksBaseUrl = superblocksBaseUrl;
39
+ this.cliVersion = cliVersion;
34
40
  }
35
41
 
36
42
  async fetchApplication({
@@ -45,6 +51,7 @@ export class SuperblocksSdk {
45
51
  viewMode?: ViewMode;
46
52
  }) {
47
53
  return fetchApplication({
54
+ cliVersion: this.cliVersion,
48
55
  applicationId,
49
56
  branch,
50
57
  token: this.token,
@@ -56,6 +63,7 @@ export class SuperblocksSdk {
56
63
 
57
64
  async fetchApplicationBranches(applicationId: string, headers = {}) {
58
65
  return fetchApplicationBranches({
66
+ cliVersion: this.cliVersion,
59
67
  applicationId,
60
68
  token: this.token,
61
69
  superblocksBaseUrl: this.superblocksBaseUrl,
@@ -75,6 +83,7 @@ export class SuperblocksSdk {
75
83
  viewMode?: ViewMode;
76
84
  }) {
77
85
  return fetchApplicationWithComponents({
86
+ cliVersion: this.cliVersion,
78
87
  applicationId,
79
88
  branch,
80
89
  token: this.token,
@@ -85,15 +94,31 @@ export class SuperblocksSdk {
85
94
  }
86
95
 
87
96
  async fetchApplications(headers?: Record<string, string>) {
88
- return fetchApplications(this.token, this.superblocksBaseUrl, headers);
97
+ return fetchApplications(
98
+ this.cliVersion,
99
+ this.token,
100
+ this.superblocksBaseUrl,
101
+ headers
102
+ );
89
103
  }
90
104
 
91
- async fetchApi(apiId: string, viewMode: ViewMode = "export-live") {
92
- return fetchApi(apiId, this.token, this.superblocksBaseUrl, viewMode);
105
+ async fetchApi(
106
+ apiId: string,
107
+ viewMode: ViewMode = "export-live",
108
+ branch?: string
109
+ ) {
110
+ return fetchApi(
111
+ this.cliVersion,
112
+ apiId,
113
+ this.token,
114
+ this.superblocksBaseUrl,
115
+ viewMode,
116
+ branch
117
+ );
93
118
  }
94
119
 
95
120
  async fetchApis() {
96
- return fetchApis(this.token, this.superblocksBaseUrl);
121
+ return fetchApis(this.cliVersion, this.token, this.superblocksBaseUrl);
97
122
  }
98
123
 
99
124
  async validateGitSetup(
@@ -104,6 +129,7 @@ export class SuperblocksSdk {
104
129
  injectedHeaders?: Record<string, string>
105
130
  ) {
106
131
  return validateGitSetup(
132
+ this.cliVersion,
107
133
  resourceId,
108
134
  resourceType,
109
135
  event,
@@ -118,9 +144,10 @@ export class SuperblocksSdk {
118
144
  applicationId: string,
119
145
  componentConfigs: Record<string, any>,
120
146
  branch: string | null,
121
- injectedHeaders?: Record<string, string>
147
+ injectedHeaders: Record<string, string>
122
148
  ) {
123
149
  return registerComponents(
150
+ this.cliVersion,
124
151
  applicationId,
125
152
  componentConfigs,
126
153
  this.token,
@@ -137,6 +164,7 @@ export class SuperblocksSdk {
137
164
  branch: string | null
138
165
  ) {
139
166
  return uploadComponents({
167
+ cliVersion: this.cliVersion,
140
168
  applicationId,
141
169
  componentConfigs,
142
170
  files,
@@ -147,7 +175,11 @@ export class SuperblocksSdk {
147
175
  }
148
176
 
149
177
  async fetchCurrentUser() {
150
- return fetchCurrentUser(this.token, this.superblocksBaseUrl);
178
+ return fetchCurrentUser(
179
+ this.cliVersion,
180
+ this.token,
181
+ this.superblocksBaseUrl
182
+ );
151
183
  }
152
184
 
153
185
  async pushApplication({
@@ -162,6 +194,7 @@ export class SuperblocksSdk {
162
194
  headers?: Record<string, string>;
163
195
  }) {
164
196
  return pushApplication({
197
+ cliVersion: this.cliVersion,
165
198
  applicationId,
166
199
  token: this.token,
167
200
  superblocksBaseUrl: this.superblocksBaseUrl,
@@ -182,6 +215,7 @@ export class SuperblocksSdk {
182
215
  headers?: Record<string, string>;
183
216
  }) {
184
217
  return pushApi({
218
+ cliVersion: this.cliVersion,
185
219
  apiId,
186
220
  apiConfig,
187
221
  token: this.token,
@@ -0,0 +1,228 @@
1
+ import {
2
+ ApiToSign,
3
+ ApiToVerify,
4
+ AppToSign,
5
+ AppToVerify,
6
+ MethodHandlers,
7
+ RemoteCommitDto,
8
+ Signature,
9
+ } from "../types";
10
+ import { signResource, verifyResources } from "./signing";
11
+
12
+ type MethodSchema<Params, Response> = (params: Params) => Promise<Response>;
13
+
14
+ export interface ClientMethods {
15
+ v1: {
16
+ signing: {
17
+ signApplication: MethodSchema<
18
+ { branchName: string; toSign: AppToSign },
19
+ { signature: Signature }
20
+ >;
21
+ signApis: MethodSchema<
22
+ { branchName: string; toSign: ApiToSign[] },
23
+ { signatures: Signature[] }
24
+ >;
25
+ verifyApplication: MethodSchema<
26
+ { branchName: string; toVerify: AppToVerify },
27
+ { ok: boolean }
28
+ >;
29
+ verifyApi: MethodSchema<
30
+ { branchName: string; toVerify: ApiToVerify[] },
31
+ { ok: boolean }
32
+ >;
33
+ };
34
+ };
35
+ }
36
+
37
+ type ServerMethodSchema<Params, Response> = MethodSchema<
38
+ Params,
39
+ ResponseDto<Response>
40
+ >;
41
+
42
+ // This file contains the definition of the protocol used for communication between the SB server and clients
43
+
44
+ type ResponseDto<T> = {
45
+ responseMeta: ResponseMeta;
46
+ data: T;
47
+ };
48
+
49
+ export type ResponseMeta = {
50
+ status: number;
51
+ success: boolean;
52
+ error?: APIResponseError;
53
+ };
54
+
55
+ type APIResponseError = {
56
+ code: number;
57
+ message: string;
58
+ };
59
+
60
+ export interface ServerMethods {
61
+ v1: {
62
+ echo: MethodSchema<{ message: string }, { message: string }>;
63
+ public: {
64
+ application: {
65
+ component: {
66
+ register: ServerMethodSchema<
67
+ {
68
+ applicationId: string;
69
+ branchName: string;
70
+ cliVersion: string;
71
+ componentEvent: string;
72
+ components: Record<string, unknown>;
73
+ },
74
+ { success: boolean }
75
+ >;
76
+ update: ServerMethodSchema<
77
+ {
78
+ applicationId: string;
79
+ branchName?: string;
80
+ srcFiles: string[];
81
+ buildFiles: string[];
82
+ registeredComponents: Record<string, unknown>;
83
+ cliVersion: string | undefined;
84
+ componentBaseUrl: string;
85
+ signingRequired: boolean;
86
+ },
87
+ { success: boolean }
88
+ >;
89
+ };
90
+ pushCommit: ServerMethodSchema<
91
+ {
92
+ applicationId: string;
93
+ branchName: string;
94
+ commitId: string;
95
+ commitMessage: string;
96
+ application: Record<string, unknown>;
97
+ page: Record<string, unknown>;
98
+ apis: Record<string, unknown>[];
99
+ },
100
+ RemoteCommitDto
101
+ >;
102
+ };
103
+ api: {
104
+ pushCommit: ServerMethodSchema<
105
+ {
106
+ apiId: string;
107
+ branchName: string;
108
+ commitId: string;
109
+ commitMessage: string;
110
+ apiPb: Record<string, unknown>;
111
+ },
112
+ RemoteCommitDto
113
+ >;
114
+ };
115
+ };
116
+ };
117
+ }
118
+
119
+ export function createRequestHandlers({
120
+ agentUrls,
121
+ token,
122
+ }: {
123
+ token: string;
124
+ agentUrls: string[];
125
+ }) {
126
+ const requestHandlers: MethodHandlers<ClientMethods, ServerMethods, unknown> =
127
+ {
128
+ v1: {
129
+ signing: {
130
+ signApplication: [
131
+ async ({
132
+ branchName,
133
+ toSign,
134
+ }: {
135
+ branchName: string;
136
+ toSign: AppToSign;
137
+ }) => {
138
+ const signature = await signResource({
139
+ agentUrls,
140
+ token: token,
141
+ branchName,
142
+ resource: {
143
+ literal: {
144
+ data: toSign.rootHash,
145
+ },
146
+ },
147
+ });
148
+ return { signature: signature };
149
+ },
150
+ ],
151
+ signApis: [
152
+ async ({
153
+ branchName,
154
+ toSign,
155
+ }: {
156
+ branchName: string;
157
+ toSign: ApiToSign[];
158
+ }) => {
159
+ const signatures: Signature[] = [];
160
+ for (const { apiPb } of toSign) {
161
+ const signature = await signResource({
162
+ agentUrls,
163
+ token: token,
164
+ branchName,
165
+ resource: { api: apiPb },
166
+ });
167
+ signatures.push(signature);
168
+ }
169
+ return { signatures };
170
+ },
171
+ ],
172
+ verifyApplication: [
173
+ async ({
174
+ branchName,
175
+ toVerify,
176
+ }: {
177
+ branchName: string;
178
+ toVerify: AppToVerify;
179
+ }) => {
180
+ try {
181
+ await verifyResources({
182
+ agentUrls,
183
+ token,
184
+ branchName,
185
+ resources: [
186
+ {
187
+ literal: {
188
+ data: toVerify.rootHash,
189
+ signature: toVerify.signature,
190
+ },
191
+ },
192
+ ],
193
+ });
194
+ return { ok: true };
195
+ } catch {
196
+ return { ok: false };
197
+ }
198
+ },
199
+ ],
200
+
201
+ verifyApi: [
202
+ async ({
203
+ branchName,
204
+ toVerify,
205
+ }: {
206
+ branchName: string;
207
+ toVerify: ApiToVerify[];
208
+ }) => {
209
+ try {
210
+ await verifyResources({
211
+ agentUrls,
212
+ token,
213
+ branchName,
214
+ resources: toVerify.map(({ apiPb }) => ({
215
+ api: apiPb,
216
+ })),
217
+ });
218
+ return { ok: true };
219
+ } catch {
220
+ return { ok: false };
221
+ }
222
+ },
223
+ ],
224
+ },
225
+ },
226
+ };
227
+ return requestHandlers;
228
+ }
@@ -0,0 +1,164 @@
1
+ import WebSocket from "isomorphic-ws";
2
+ import {
3
+ ClientMethods,
4
+ createRequestHandlers,
5
+ ServerMethods,
6
+ } from "./handlers";
7
+ import { createISocketClient, ISocket } from "./socket";
8
+
9
+ export type StdISocketRPCClient = ISocketClient<ServerMethods>;
10
+
11
+ export async function connectToISocketRPCServer({
12
+ superblocksBaseUrl,
13
+ agentUrls,
14
+ token,
15
+ }: {
16
+ superblocksBaseUrl: string;
17
+ agentUrls: string[];
18
+ token: string;
19
+ }): Promise<StdISocketRPCClient> {
20
+ const requestHandlers = createRequestHandlers({
21
+ agentUrls,
22
+ token,
23
+ });
24
+ const authorization = `Bearer ${token}`;
25
+ const wsUrl = new URL("api/v1/rpc-ws", superblocksBaseUrl);
26
+ if (wsUrl.protocol === "http:") {
27
+ wsUrl.protocol = "ws:";
28
+ } else if (wsUrl.protocol === "https:") {
29
+ wsUrl.protocol = "wss:";
30
+ }
31
+
32
+ if (wsUrl.host === "localhost:3000") {
33
+ wsUrl.host = "127.0.0.1:8080";
34
+ } else if (wsUrl.hostname === "localhost") {
35
+ wsUrl.hostname = "127.0.0.1";
36
+ }
37
+ return await connectISocket<ServerMethods, ClientMethods, unknown>(
38
+ wsUrl.href,
39
+ authorization,
40
+ requestHandlers
41
+ );
42
+ }
43
+
44
+ // a subclass of ISocket that sends an auth token on the first request
45
+ // this is useful for client-side sockets that need to authenticate
46
+ // TODO(george): if we start using this for long-lived connections, we should add a way to refresh the token
47
+ class ISocketWithClientAuth<
48
+ ImplementedMethods,
49
+ CallableMethods,
50
+ RequestContext = void
51
+ > extends ISocket<ImplementedMethods, CallableMethods, RequestContext> {
52
+ private readonly authorization?: string;
53
+ private hasSentAuth = false;
54
+
55
+ constructor(
56
+ ws: WebSocket,
57
+ authorization: string | undefined,
58
+ requestHandlers: MethodHandlers<
59
+ ImplementedMethods,
60
+ CallableMethods,
61
+ RequestContext
62
+ >
63
+ ) {
64
+ super(ws, requestHandlers);
65
+ this.authorization = authorization;
66
+ }
67
+
68
+ // override `request` from the base class to send `authorization` when appropriate
69
+ async request<Params, Result>(
70
+ method: string,
71
+ params: Params
72
+ ): Promise<Result> {
73
+ // only send `authorization` on the first request
74
+ const authorization = this.hasSentAuth ? undefined : this.authorization;
75
+ const result = await super.request<Params, Result>(
76
+ method,
77
+ params,
78
+ authorization
79
+ );
80
+ this.hasSentAuth = true;
81
+ return result;
82
+ }
83
+ }
84
+
85
+ export async function connectISocket<
86
+ CallableMethods,
87
+ ImplementedMethods,
88
+ RequestContext = never
89
+ >(
90
+ wsUrl: string,
91
+ authorization: string | undefined,
92
+ requestHandlers: MethodHandlers<
93
+ ImplementedMethods,
94
+ CallableMethods,
95
+ RequestContext
96
+ >
97
+ ): Promise<ISocketClient<CallableMethods>> {
98
+ const ws = await connectWebSocket(wsUrl);
99
+ const isocket = new ISocketWithClientAuth(ws, authorization, requestHandlers);
100
+ return createISocketClient(isocket);
101
+ }
102
+
103
+ export function connectWebSocket(wsUrl: string): Promise<WebSocket> {
104
+ return new Promise((resolve, reject) => {
105
+ const ws = new WebSocket(wsUrl);
106
+
107
+ ws.addEventListener("open", () => {
108
+ // Resolve the promise with the WebSocket instance when the connection is open
109
+ resolve(ws);
110
+ });
111
+
112
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
113
+ // @ts-ignore
114
+ ws.addEventListener("error", (error: Error) => {
115
+ // Reject the promise if there's an error
116
+ reject(error);
117
+ });
118
+ });
119
+ }
120
+
121
+ type MethodHandler<Params, Result, PeerMethods, RequestContext> = (
122
+ params: Params,
123
+ peer: ISocketClient<PeerMethods>,
124
+ ctx: RequestContext
125
+ ) => Promise<Result>;
126
+
127
+ type MiddlewareHandler<Params, PeerMethods, RequestContext> = (
128
+ params: Params,
129
+ peerAuthorization: string | undefined,
130
+ peer: ISocketClient<PeerMethods>,
131
+ ctx: RequestContext
132
+ ) => Promise<void>;
133
+
134
+ type MethodHandlers<Methods, PeerMethods, RequestContext> = {
135
+ [Key in keyof Methods]: Methods[Key] extends (
136
+ params: infer Params
137
+ ) => Promise<infer Result>
138
+ ? [
139
+ ...middlewareHandlers: MiddlewareHandler<
140
+ Params,
141
+ PeerMethods,
142
+ RequestContext
143
+ >[],
144
+ handler: MethodHandler<Params, Result, PeerMethods, RequestContext>
145
+ ]
146
+ : Methods[Key] extends Record<string, unknown>
147
+ ? MethodHandlers<Methods[Key], PeerMethods, RequestContext>
148
+ : never;
149
+ };
150
+
151
+ type ISocketClientMethodCall<Methods> = {
152
+ [Key in keyof Methods]: Methods[Key] extends (
153
+ params: infer P
154
+ ) => Promise<infer R>
155
+ ? (params: P) => Promise<R>
156
+ : Methods[Key] extends Record<string, unknown>
157
+ ? ISocketClientMethodCall<Methods[Key]>
158
+ : never;
159
+ };
160
+
161
+ type ISocketClient<Methods> = {
162
+ close: () => void;
163
+ call: ISocketClientMethodCall<Methods>;
164
+ };
@@ -0,0 +1,113 @@
1
+ import axios, { AxiosRequestConfig, Method } from "axios";
2
+ import {
3
+ ApiResource,
4
+ GenericResource,
5
+ Signature,
6
+ SignatureResponse,
7
+ } from "../types";
8
+ import { getSanitizedApi } from "../utils";
9
+
10
+ export async function signResource({
11
+ token,
12
+ branchName,
13
+ resource,
14
+ agentUrls,
15
+ }: {
16
+ token: string;
17
+ branchName: string;
18
+ resource: ApiResource | GenericResource;
19
+ agentUrls: string[];
20
+ }): Promise<Signature> {
21
+ const requestResource: Record<string, any> = {
22
+ branchName: branchName ?? "main",
23
+ };
24
+ if ((resource as ApiResource).api) {
25
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
26
+ requestResource.api = getSanitizedApi((resource as ApiResource).api);
27
+ } else {
28
+ requestResource.literal = (resource as GenericResource).literal;
29
+ }
30
+
31
+ try {
32
+ const resp = await callAgentFallBack<SignatureResponse>({
33
+ baseUrls: agentUrls,
34
+ path: "v1/signature/sign",
35
+ method: "post",
36
+ token: token,
37
+ data: { resource: requestResource },
38
+ });
39
+ return resp.signature;
40
+ } catch (e) {
41
+ throw new Error("No agents available to sign the resource");
42
+ }
43
+ }
44
+
45
+ export async function verifyResources({
46
+ agentUrls,
47
+ token,
48
+ branchName,
49
+ resources,
50
+ }: {
51
+ resources: Array<GenericResource | ApiResource>;
52
+ token: string;
53
+ branchName: string;
54
+ agentUrls: string[];
55
+ }): Promise<void> {
56
+ try {
57
+ await callAgentFallBack<{ keyId: string }>({
58
+ baseUrls: agentUrls,
59
+ path: "v1/signature/verify",
60
+ method: "post",
61
+ token: token,
62
+ data: {
63
+ resources: resources.map((res) => {
64
+ if ((res as ApiResource).api) {
65
+ return {
66
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
67
+ api: getSanitizedApi((res as ApiResource).api),
68
+ branchName: branchName ?? "main",
69
+ };
70
+ }
71
+ return {
72
+ ...res,
73
+ branchName: branchName ?? "main",
74
+ };
75
+ }),
76
+ },
77
+ });
78
+ } catch (e) {
79
+ throw new Error("No agents available to verify the resource");
80
+ }
81
+ }
82
+
83
+ async function callAgentFallBack<T>({
84
+ baseUrls,
85
+ path,
86
+ method,
87
+ token,
88
+ data,
89
+ }: {
90
+ baseUrls: string[];
91
+ path: string;
92
+ method: Method;
93
+ token: string;
94
+ data: any;
95
+ }): Promise<T> {
96
+ for (const baseUrl of baseUrls) {
97
+ try {
98
+ const url = new URL(path, baseUrl);
99
+ const config: AxiosRequestConfig = {
100
+ url: url.toString(),
101
+ method: method,
102
+ headers: {
103
+ Authorization: "Bearer " + token,
104
+ },
105
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
106
+ data: data,
107
+ };
108
+ const resp = await axios<T>(config);
109
+ return resp.data;
110
+ } catch (e) {}
111
+ }
112
+ throw new Error("Failed to request ");
113
+ }