krawlet-js 2.1.2 → 2.2.1

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/index.cjs CHANGED
@@ -31,7 +31,9 @@ __export(index_exports, {
31
31
  ReportsResource: () => ReportsResource,
32
32
  ShopsResource: () => ShopsResource,
33
33
  StorageResource: () => StorageResource,
34
- TransfersResource: () => TransfersResource
34
+ TransfersResource: () => TransfersResource,
35
+ WebsocketProtocolError: () => WebsocketProtocolError,
36
+ WebsocketsResource: () => WebsocketsResource
35
37
  });
36
38
  module.exports = __toCommonJS(index_exports);
37
39
 
@@ -88,6 +90,12 @@ var HttpClient = class {
88
90
  getRateLimit() {
89
91
  return this.lastRateLimit;
90
92
  }
93
+ /**
94
+ * Get configured API base URL
95
+ */
96
+ getBaseUrl() {
97
+ return this.config.baseUrl;
98
+ }
91
99
  /**
92
100
  * Make an HTTP request
93
101
  */
@@ -828,6 +836,308 @@ var TransfersResource = class {
828
836
  }
829
837
  };
830
838
 
839
+ // src/resources/websockets.ts
840
+ var WebsocketProtocolError = class extends Error {
841
+ constructor(message, code, requestId) {
842
+ super(message);
843
+ this.name = "WebsocketProtocolError";
844
+ this.code = code;
845
+ this.requestId = requestId;
846
+ }
847
+ };
848
+ var WebsocketsResource = class {
849
+ constructor(client, options = {}) {
850
+ this.client = client;
851
+ this.ws = null;
852
+ this.connectPromise = null;
853
+ this.pending = /* @__PURE__ */ new Map();
854
+ this.transferUpdateListeners = /* @__PURE__ */ new Set();
855
+ this.nextRequestId = 1;
856
+ this.connectTimeoutMs = options.connectTimeoutMs ?? 1e4;
857
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 15e3;
858
+ this.webSocketFactory = options.webSocketFactory ?? this.getDefaultWebSocketFactory();
859
+ }
860
+ /**
861
+ * Open websocket connection and wait for initial hello
862
+ */
863
+ async start() {
864
+ if (this.connectPromise) {
865
+ return this.connectPromise;
866
+ }
867
+ const url = this.buildWebSocketUrl();
868
+ this.connectPromise = new Promise((resolve, reject) => {
869
+ const ws = this.webSocketFactory(url);
870
+ this.ws = ws;
871
+ const timeout = setTimeout(() => {
872
+ this.connectPromise = null;
873
+ this.ws = null;
874
+ try {
875
+ ws.close();
876
+ } catch {
877
+ }
878
+ reject(new Error(`WebSocket connection timeout after ${this.connectTimeoutMs}ms`));
879
+ }, this.connectTimeoutMs);
880
+ ws.onopen = () => {
881
+ };
882
+ ws.onmessage = (event) => {
883
+ this.handleMessageEvent(event.data, resolve, reject, timeout);
884
+ };
885
+ ws.onerror = () => {
886
+ clearTimeout(timeout);
887
+ this.connectPromise = null;
888
+ this.ws = null;
889
+ reject(new Error("WebSocket connection failed"));
890
+ };
891
+ ws.onclose = (event) => {
892
+ clearTimeout(timeout);
893
+ this.connectPromise = null;
894
+ this.ws = null;
895
+ this.rejectAllPending(
896
+ new Error(`WebSocket closed (${event.code}${event.reason ? `: ${event.reason}` : ""})`)
897
+ );
898
+ };
899
+ });
900
+ return this.connectPromise;
901
+ }
902
+ /**
903
+ * Authenticate this connection as a client key
904
+ */
905
+ async authenticate(token, id = this.createRequestId()) {
906
+ const response = await this.sendAndWait("auth", {
907
+ type: "auth",
908
+ id,
909
+ token
910
+ });
911
+ if (response.type !== "auth_ok") {
912
+ throw new Error(`Unexpected auth response type: ${response.type}`);
913
+ }
914
+ return response.payload;
915
+ }
916
+ /**
917
+ * Ping the websocket server
918
+ */
919
+ async ping(id = this.createRequestId()) {
920
+ const response = await this.sendAndWait("ping", {
921
+ type: "ping",
922
+ id
923
+ });
924
+ if (response.type !== "pong") {
925
+ throw new Error(`Unexpected ping response type: ${response.type}`);
926
+ }
927
+ return response.payload;
928
+ }
929
+ /**
930
+ * Create a transfer over websocket
931
+ */
932
+ async createTransfer(payload, id = this.createRequestId()) {
933
+ const response = await this.sendAndWait("create_transfer", {
934
+ type: "create_transfer",
935
+ id,
936
+ payload
937
+ });
938
+ if (response.type !== "create_transfer_ok") {
939
+ throw new Error(`Unexpected create_transfer response type: ${response.type}`);
940
+ }
941
+ return response.payload;
942
+ }
943
+ /**
944
+ * Fetch transfer by id
945
+ */
946
+ async getTransfer(transferId, id = this.createRequestId()) {
947
+ const response = await this.sendAndWait("get_transfer", {
948
+ type: "get_transfer",
949
+ id,
950
+ payload: { transferId }
951
+ });
952
+ if (response.type !== "get_transfer_ok") {
953
+ throw new Error(`Unexpected get_transfer response type: ${response.type}`);
954
+ }
955
+ return response.payload;
956
+ }
957
+ /**
958
+ * Cancel transfer by id
959
+ */
960
+ async cancelTransfer(transferId, id = this.createRequestId()) {
961
+ const response = await this.sendAndWait("cancel_transfer", {
962
+ type: "cancel_transfer",
963
+ id,
964
+ payload: { transferId }
965
+ });
966
+ if (response.type !== "cancel_transfer_ok") {
967
+ throw new Error(`Unexpected cancel_transfer response type: ${response.type}`);
968
+ }
969
+ return response.payload;
970
+ }
971
+ /**
972
+ * List transfers for this client
973
+ */
974
+ async listTransfers(id = this.createRequestId()) {
975
+ const response = await this.sendAndWait("list_transfers", {
976
+ type: "list_transfers",
977
+ id,
978
+ payload: {}
979
+ });
980
+ if (response.type !== "list_transfers_ok") {
981
+ throw new Error(`Unexpected list_transfers response type: ${response.type}`);
982
+ }
983
+ return response.payload.transfers;
984
+ }
985
+ /**
986
+ * List targets available to this client
987
+ */
988
+ async listTargets(id = this.createRequestId()) {
989
+ const response = await this.sendAndWait("list_targets", {
990
+ type: "list_targets",
991
+ id,
992
+ payload: {}
993
+ });
994
+ if (response.type !== "list_targets_ok") {
995
+ throw new Error(`Unexpected list_targets response type: ${response.type}`);
996
+ }
997
+ return response.payload.targets;
998
+ }
999
+ /**
1000
+ * Subscribe to unsolicited transfer updates.
1001
+ * Returns an unsubscribe function.
1002
+ */
1003
+ onTransferUpdate(listener) {
1004
+ this.transferUpdateListeners.add(listener);
1005
+ return () => {
1006
+ this.transferUpdateListeners.delete(listener);
1007
+ };
1008
+ }
1009
+ /**
1010
+ * Close the websocket connection
1011
+ */
1012
+ close(code, reason) {
1013
+ if (!this.ws) {
1014
+ return;
1015
+ }
1016
+ try {
1017
+ this.ws.close(code, reason);
1018
+ } finally {
1019
+ this.ws = null;
1020
+ this.connectPromise = null;
1021
+ this.rejectAllPending(new Error("WebSocket connection closed"));
1022
+ }
1023
+ }
1024
+ /**
1025
+ * True if the websocket is currently open
1026
+ */
1027
+ isConnected() {
1028
+ return Boolean(this.ws && this.ws.readyState === 1);
1029
+ }
1030
+ async sendAndWait(messageType, message) {
1031
+ if (!this.ws || this.ws.readyState !== 1) {
1032
+ await this.start();
1033
+ }
1034
+ if (!this.ws || this.ws.readyState !== 1) {
1035
+ throw new Error("WebSocket is not connected");
1036
+ }
1037
+ const requestId = message.id;
1038
+ const responsePromise = new Promise((resolve, reject) => {
1039
+ const timeout = setTimeout(() => {
1040
+ this.pending.delete(requestId);
1041
+ reject(new Error(`WebSocket ${messageType} request timed out after ${this.requestTimeoutMs}ms`));
1042
+ }, this.requestTimeoutMs);
1043
+ this.pending.set(requestId, {
1044
+ resolve: (value) => resolve(value),
1045
+ reject,
1046
+ timeout
1047
+ });
1048
+ });
1049
+ this.ws.send(JSON.stringify(message));
1050
+ return responsePromise;
1051
+ }
1052
+ handleMessageEvent(rawData, connectResolve, connectReject, connectTimeout) {
1053
+ const parsed = this.safeParse(rawData);
1054
+ if (!parsed) {
1055
+ return;
1056
+ }
1057
+ if (parsed.type === "hello") {
1058
+ clearTimeout(connectTimeout);
1059
+ connectResolve(parsed);
1060
+ return;
1061
+ }
1062
+ if (parsed.type === "transfer_update") {
1063
+ this.emitTransferUpdate(parsed.payload);
1064
+ return;
1065
+ }
1066
+ if (typeof parsed.id === "undefined") {
1067
+ if (parsed.type === "error") {
1068
+ const payload = parsed.payload;
1069
+ connectReject(new WebsocketProtocolError(payload.message, payload.code));
1070
+ }
1071
+ return;
1072
+ }
1073
+ const pending = this.pending.get(parsed.id);
1074
+ if (!pending) {
1075
+ return;
1076
+ }
1077
+ clearTimeout(pending.timeout);
1078
+ this.pending.delete(parsed.id);
1079
+ if (parsed.type === "error") {
1080
+ const payload = parsed.payload;
1081
+ pending.reject(new WebsocketProtocolError(payload.message, payload.code, parsed.id));
1082
+ return;
1083
+ }
1084
+ pending.resolve(parsed);
1085
+ }
1086
+ emitTransferUpdate(transfer) {
1087
+ for (const listener of this.transferUpdateListeners) {
1088
+ listener(transfer);
1089
+ }
1090
+ }
1091
+ safeParse(rawData) {
1092
+ try {
1093
+ const parsed = JSON.parse(rawData);
1094
+ if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
1095
+ return null;
1096
+ }
1097
+ return parsed;
1098
+ } catch {
1099
+ return null;
1100
+ }
1101
+ }
1102
+ createRequestId() {
1103
+ const id = this.nextRequestId;
1104
+ this.nextRequestId += 1;
1105
+ return id;
1106
+ }
1107
+ rejectAllPending(error) {
1108
+ for (const entry of this.pending.values()) {
1109
+ clearTimeout(entry.timeout);
1110
+ entry.reject(error);
1111
+ }
1112
+ this.pending.clear();
1113
+ }
1114
+ buildWebSocketUrl() {
1115
+ const base = new URL(this.client.getBaseUrl());
1116
+ base.protocol = base.protocol === "https:" ? "wss:" : "ws:";
1117
+ const trimmedPath = base.pathname.replace(/\/+$/, "");
1118
+ if (trimmedPath.endsWith("/api/v1")) {
1119
+ base.pathname = `${trimmedPath}/ws`;
1120
+ } else if (trimmedPath.endsWith("/api")) {
1121
+ base.pathname = `${trimmedPath}/v1/ws`;
1122
+ } else if (trimmedPath.endsWith("/v1")) {
1123
+ base.pathname = `${trimmedPath}/ws`;
1124
+ } else {
1125
+ base.pathname = "/v1/ws";
1126
+ }
1127
+ base.search = "";
1128
+ base.hash = "";
1129
+ return base.toString();
1130
+ }
1131
+ getDefaultWebSocketFactory() {
1132
+ return (url) => {
1133
+ if (typeof globalThis.WebSocket === "undefined") {
1134
+ throw new Error("WebSocket is not available. Provide a webSocketFactory in WebsocketsResource options.");
1135
+ }
1136
+ return new globalThis.WebSocket(url);
1137
+ };
1138
+ }
1139
+ };
1140
+
831
1141
  // src/client.ts
832
1142
  var KrawletClient = class {
833
1143
  /**
@@ -854,6 +1164,7 @@ var KrawletClient = class {
854
1164
  this.reports = new ReportsResource(this.httpClient);
855
1165
  this.apiKey = new ApiKeyResource(this.httpClient);
856
1166
  this.transfers = new TransfersResource(this.httpClient);
1167
+ this.websockets = new WebsocketsResource(this.httpClient);
857
1168
  }
858
1169
  /**
859
1170
  * Get the last known rate limit information
@@ -894,5 +1205,7 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
894
1205
  ReportsResource,
895
1206
  ShopsResource,
896
1207
  StorageResource,
897
- TransfersResource
1208
+ TransfersResource,
1209
+ WebsocketProtocolError,
1210
+ WebsocketsResource
898
1211
  });
package/dist/index.d.cts CHANGED
@@ -458,6 +458,31 @@ interface PublicStorageTransferResponse {
458
458
  transfer: Transfer;
459
459
  sourceEntity: PublicStorageSourceEntity;
460
460
  }
461
+ type WebsocketMessageId = string | number;
462
+ interface WebsocketEnvelope<TPayload = unknown> {
463
+ type: string;
464
+ id?: WebsocketMessageId;
465
+ payload: TPayload;
466
+ }
467
+ interface WebsocketErrorPayload {
468
+ code: string;
469
+ message: string;
470
+ }
471
+ interface WebsocketHelloPayload {
472
+ supportedClientMessages?: string[];
473
+ [key: string]: unknown;
474
+ }
475
+ interface WebsocketAuthOk {
476
+ tier: ApiKeyTier;
477
+ name: string;
478
+ role: 'client';
479
+ }
480
+ interface WebsocketTransferListPayload {
481
+ transfers: Transfer[];
482
+ }
483
+ interface WebsocketTargetListPayload {
484
+ targets: TransferTarget[];
485
+ }
461
486
  interface ReportRecords<TRecord = unknown> {
462
487
  count: number;
463
488
  records: TRecord[];
@@ -522,6 +547,7 @@ declare class HttpClient {
522
547
  private lastRateLimit?;
523
548
  constructor(config: HttpClientConfig);
524
549
  getRateLimit(): RateLimit | undefined;
550
+ getBaseUrl(): string;
525
551
  request<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
526
552
  private buildUrl;
527
553
  private buildHeaders;
@@ -632,6 +658,62 @@ declare class TransfersResource {
632
658
  requestPublicStorage(data: PublicStorageTransferRequest): Promise<PublicStorageTransferResponse>;
633
659
  }
634
660
 
661
+ interface WebSocketLike {
662
+ readyState: number;
663
+ send(data: string): void;
664
+ close(code?: number, reason?: string): void;
665
+ onopen: (() => void) | null;
666
+ onmessage: ((event: {
667
+ data: string;
668
+ }) => void) | null;
669
+ onerror: (() => void) | null;
670
+ onclose: ((event: {
671
+ code: number;
672
+ reason: string;
673
+ }) => void) | null;
674
+ }
675
+ interface WebsocketClientOptions {
676
+ connectTimeoutMs?: number;
677
+ requestTimeoutMs?: number;
678
+ webSocketFactory?: (url: string) => WebSocketLike;
679
+ }
680
+ declare class WebsocketProtocolError extends Error {
681
+ readonly code: string;
682
+ readonly requestId?: WebsocketMessageId;
683
+ constructor(message: string, code: string, requestId?: WebsocketMessageId);
684
+ }
685
+ declare class WebsocketsResource {
686
+ private client;
687
+ private ws;
688
+ private connectPromise;
689
+ private pending;
690
+ private transferUpdateListeners;
691
+ private nextRequestId;
692
+ private readonly connectTimeoutMs;
693
+ private readonly requestTimeoutMs;
694
+ private readonly webSocketFactory;
695
+ constructor(client: HttpClient, options?: WebsocketClientOptions);
696
+ start(): Promise<WebsocketEnvelope>;
697
+ authenticate(token: string, id?: WebsocketMessageId): Promise<WebsocketAuthOk>;
698
+ ping(id?: WebsocketMessageId): Promise<number>;
699
+ createTransfer(payload: TransferCreateRequest, id?: WebsocketMessageId): Promise<Transfer>;
700
+ getTransfer(transferId: string, id?: WebsocketMessageId): Promise<Transfer>;
701
+ cancelTransfer(transferId: string, id?: WebsocketMessageId): Promise<Transfer>;
702
+ listTransfers(id?: WebsocketMessageId): Promise<Transfer[]>;
703
+ listTargets(id?: WebsocketMessageId): Promise<TransferTarget[]>;
704
+ onTransferUpdate(listener: (transfer: Transfer) => void): () => void;
705
+ close(code?: number, reason?: string): void;
706
+ isConnected(): boolean;
707
+ private sendAndWait;
708
+ private handleMessageEvent;
709
+ private emitTransferUpdate;
710
+ private safeParse;
711
+ private createRequestId;
712
+ private rejectAllPending;
713
+ private buildWebSocketUrl;
714
+ private getDefaultWebSocketFactory;
715
+ }
716
+
635
717
  interface KrawletClientConfig {
636
718
  baseUrl?: string;
637
719
  apiKey?: string;
@@ -652,6 +734,7 @@ declare class KrawletClient {
652
734
  readonly reports: ReportsResource;
653
735
  readonly apiKey: ApiKeyResource;
654
736
  readonly transfers: TransfersResource;
737
+ readonly websockets: WebsocketsResource;
655
738
  constructor(config?: KrawletClientConfig);
656
739
  getRateLimit(): RateLimit | undefined;
657
740
  }
@@ -668,4 +751,4 @@ declare class KrawletError extends Error {
668
751
  isRateLimitError(): boolean;
669
752
  }
670
753
 
671
- export { AddressesResource, type ApiKeyInfo, ApiKeyResource, type ApiKeyTier, type ApiKeyUsage, type ApiResponse, type ApiResponseMeta, type BaseQueryParams, type ChangeLogOptions, type ChangeLogResult, type ChatboxServiceInfo, type DetailedHealthResponse, type DiscordServiceInfo, type EStorageEntityType, type EnderStorageApiPayload, type EnderStorageChest, type EnderStorageCollection, type EnderStorageColor, type EnderStorageItem, type EnderStorageMeta, type EnderStoragePayload, ErrorCode, type ErrorResponse, type HealthChecks, HealthResource, type HealthResponse, type HealthServices, type HealthServicesDetailed, type Item, type ItemChangeLog, type ItemChangeLogResponse, type ItemChangeRecord, type ItemChangeType, type ItemChangesParams, type ItemSummary, type ItemUpdateSummary, ItemsResource, type KnownAddress, type KnownAddressType, KrawletClient, type KrawletClientConfig, KrawletError, type KromerServiceInfo, type Player, type PlayerNotifications, PlayersResource, type Price, type PriceChangeLog, type PriceChangeLogResponse, type PriceChangesParams, type PublicStorageSourceEntity, type PublicStorageTransferRequest, type PublicStorageTransferResponse, type QuickCodeGenerateResponse, type QuickCodeRedeemResponse, type RateLimit, type ReportRecords, type ReporterStats, ReportsResource, type RequestLog, type RequestLogsResponse, type ServiceInfo, type ServiceName, type ServiceStatus, type Shop, type ShopChangeField, type ShopChangeLog, type ShopChangeLogResponse, type ShopChangeRecord, type ShopChangesParams, type ShopSourceType, type ShopSyncData, ShopsResource, type StorageData, StorageResource, type StorageSlotContents, type StorageSlotItem, type SuccessfulPostRecord, type Transfer, type TransferCreateRequest, type TransferStatus, type TransferTarget, type TransferTargetLink, TransfersResource, type ValidationFailureRecord };
754
+ export { AddressesResource, type ApiKeyInfo, ApiKeyResource, type ApiKeyTier, type ApiKeyUsage, type ApiResponse, type ApiResponseMeta, type BaseQueryParams, type ChangeLogOptions, type ChangeLogResult, type ChatboxServiceInfo, type DetailedHealthResponse, type DiscordServiceInfo, type EStorageEntityType, type EnderStorageApiPayload, type EnderStorageChest, type EnderStorageCollection, type EnderStorageColor, type EnderStorageItem, type EnderStorageMeta, type EnderStoragePayload, ErrorCode, type ErrorResponse, type HealthChecks, HealthResource, type HealthResponse, type HealthServices, type HealthServicesDetailed, type Item, type ItemChangeLog, type ItemChangeLogResponse, type ItemChangeRecord, type ItemChangeType, type ItemChangesParams, type ItemSummary, type ItemUpdateSummary, ItemsResource, type KnownAddress, type KnownAddressType, KrawletClient, type KrawletClientConfig, KrawletError, type KromerServiceInfo, type Player, type PlayerNotifications, PlayersResource, type Price, type PriceChangeLog, type PriceChangeLogResponse, type PriceChangesParams, type PublicStorageSourceEntity, type PublicStorageTransferRequest, type PublicStorageTransferResponse, type QuickCodeGenerateResponse, type QuickCodeRedeemResponse, type RateLimit, type ReportRecords, type ReporterStats, ReportsResource, type RequestLog, type RequestLogsResponse, type ServiceInfo, type ServiceName, type ServiceStatus, type Shop, type ShopChangeField, type ShopChangeLog, type ShopChangeLogResponse, type ShopChangeRecord, type ShopChangesParams, type ShopSourceType, type ShopSyncData, ShopsResource, type StorageData, StorageResource, type StorageSlotContents, type StorageSlotItem, type SuccessfulPostRecord, type Transfer, type TransferCreateRequest, type TransferStatus, type TransferTarget, type TransferTargetLink, TransfersResource, type ValidationFailureRecord, type WebsocketAuthOk, type WebsocketClientOptions, type WebsocketEnvelope, type WebsocketErrorPayload, type WebsocketHelloPayload, type WebsocketMessageId, WebsocketProtocolError, type WebsocketTargetListPayload, type WebsocketTransferListPayload, WebsocketsResource };
package/dist/index.d.ts CHANGED
@@ -458,6 +458,31 @@ interface PublicStorageTransferResponse {
458
458
  transfer: Transfer;
459
459
  sourceEntity: PublicStorageSourceEntity;
460
460
  }
461
+ type WebsocketMessageId = string | number;
462
+ interface WebsocketEnvelope<TPayload = unknown> {
463
+ type: string;
464
+ id?: WebsocketMessageId;
465
+ payload: TPayload;
466
+ }
467
+ interface WebsocketErrorPayload {
468
+ code: string;
469
+ message: string;
470
+ }
471
+ interface WebsocketHelloPayload {
472
+ supportedClientMessages?: string[];
473
+ [key: string]: unknown;
474
+ }
475
+ interface WebsocketAuthOk {
476
+ tier: ApiKeyTier;
477
+ name: string;
478
+ role: 'client';
479
+ }
480
+ interface WebsocketTransferListPayload {
481
+ transfers: Transfer[];
482
+ }
483
+ interface WebsocketTargetListPayload {
484
+ targets: TransferTarget[];
485
+ }
461
486
  interface ReportRecords<TRecord = unknown> {
462
487
  count: number;
463
488
  records: TRecord[];
@@ -522,6 +547,7 @@ declare class HttpClient {
522
547
  private lastRateLimit?;
523
548
  constructor(config: HttpClientConfig);
524
549
  getRateLimit(): RateLimit | undefined;
550
+ getBaseUrl(): string;
525
551
  request<T>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
526
552
  private buildUrl;
527
553
  private buildHeaders;
@@ -632,6 +658,62 @@ declare class TransfersResource {
632
658
  requestPublicStorage(data: PublicStorageTransferRequest): Promise<PublicStorageTransferResponse>;
633
659
  }
634
660
 
661
+ interface WebSocketLike {
662
+ readyState: number;
663
+ send(data: string): void;
664
+ close(code?: number, reason?: string): void;
665
+ onopen: (() => void) | null;
666
+ onmessage: ((event: {
667
+ data: string;
668
+ }) => void) | null;
669
+ onerror: (() => void) | null;
670
+ onclose: ((event: {
671
+ code: number;
672
+ reason: string;
673
+ }) => void) | null;
674
+ }
675
+ interface WebsocketClientOptions {
676
+ connectTimeoutMs?: number;
677
+ requestTimeoutMs?: number;
678
+ webSocketFactory?: (url: string) => WebSocketLike;
679
+ }
680
+ declare class WebsocketProtocolError extends Error {
681
+ readonly code: string;
682
+ readonly requestId?: WebsocketMessageId;
683
+ constructor(message: string, code: string, requestId?: WebsocketMessageId);
684
+ }
685
+ declare class WebsocketsResource {
686
+ private client;
687
+ private ws;
688
+ private connectPromise;
689
+ private pending;
690
+ private transferUpdateListeners;
691
+ private nextRequestId;
692
+ private readonly connectTimeoutMs;
693
+ private readonly requestTimeoutMs;
694
+ private readonly webSocketFactory;
695
+ constructor(client: HttpClient, options?: WebsocketClientOptions);
696
+ start(): Promise<WebsocketEnvelope>;
697
+ authenticate(token: string, id?: WebsocketMessageId): Promise<WebsocketAuthOk>;
698
+ ping(id?: WebsocketMessageId): Promise<number>;
699
+ createTransfer(payload: TransferCreateRequest, id?: WebsocketMessageId): Promise<Transfer>;
700
+ getTransfer(transferId: string, id?: WebsocketMessageId): Promise<Transfer>;
701
+ cancelTransfer(transferId: string, id?: WebsocketMessageId): Promise<Transfer>;
702
+ listTransfers(id?: WebsocketMessageId): Promise<Transfer[]>;
703
+ listTargets(id?: WebsocketMessageId): Promise<TransferTarget[]>;
704
+ onTransferUpdate(listener: (transfer: Transfer) => void): () => void;
705
+ close(code?: number, reason?: string): void;
706
+ isConnected(): boolean;
707
+ private sendAndWait;
708
+ private handleMessageEvent;
709
+ private emitTransferUpdate;
710
+ private safeParse;
711
+ private createRequestId;
712
+ private rejectAllPending;
713
+ private buildWebSocketUrl;
714
+ private getDefaultWebSocketFactory;
715
+ }
716
+
635
717
  interface KrawletClientConfig {
636
718
  baseUrl?: string;
637
719
  apiKey?: string;
@@ -652,6 +734,7 @@ declare class KrawletClient {
652
734
  readonly reports: ReportsResource;
653
735
  readonly apiKey: ApiKeyResource;
654
736
  readonly transfers: TransfersResource;
737
+ readonly websockets: WebsocketsResource;
655
738
  constructor(config?: KrawletClientConfig);
656
739
  getRateLimit(): RateLimit | undefined;
657
740
  }
@@ -668,4 +751,4 @@ declare class KrawletError extends Error {
668
751
  isRateLimitError(): boolean;
669
752
  }
670
753
 
671
- export { AddressesResource, type ApiKeyInfo, ApiKeyResource, type ApiKeyTier, type ApiKeyUsage, type ApiResponse, type ApiResponseMeta, type BaseQueryParams, type ChangeLogOptions, type ChangeLogResult, type ChatboxServiceInfo, type DetailedHealthResponse, type DiscordServiceInfo, type EStorageEntityType, type EnderStorageApiPayload, type EnderStorageChest, type EnderStorageCollection, type EnderStorageColor, type EnderStorageItem, type EnderStorageMeta, type EnderStoragePayload, ErrorCode, type ErrorResponse, type HealthChecks, HealthResource, type HealthResponse, type HealthServices, type HealthServicesDetailed, type Item, type ItemChangeLog, type ItemChangeLogResponse, type ItemChangeRecord, type ItemChangeType, type ItemChangesParams, type ItemSummary, type ItemUpdateSummary, ItemsResource, type KnownAddress, type KnownAddressType, KrawletClient, type KrawletClientConfig, KrawletError, type KromerServiceInfo, type Player, type PlayerNotifications, PlayersResource, type Price, type PriceChangeLog, type PriceChangeLogResponse, type PriceChangesParams, type PublicStorageSourceEntity, type PublicStorageTransferRequest, type PublicStorageTransferResponse, type QuickCodeGenerateResponse, type QuickCodeRedeemResponse, type RateLimit, type ReportRecords, type ReporterStats, ReportsResource, type RequestLog, type RequestLogsResponse, type ServiceInfo, type ServiceName, type ServiceStatus, type Shop, type ShopChangeField, type ShopChangeLog, type ShopChangeLogResponse, type ShopChangeRecord, type ShopChangesParams, type ShopSourceType, type ShopSyncData, ShopsResource, type StorageData, StorageResource, type StorageSlotContents, type StorageSlotItem, type SuccessfulPostRecord, type Transfer, type TransferCreateRequest, type TransferStatus, type TransferTarget, type TransferTargetLink, TransfersResource, type ValidationFailureRecord };
754
+ export { AddressesResource, type ApiKeyInfo, ApiKeyResource, type ApiKeyTier, type ApiKeyUsage, type ApiResponse, type ApiResponseMeta, type BaseQueryParams, type ChangeLogOptions, type ChangeLogResult, type ChatboxServiceInfo, type DetailedHealthResponse, type DiscordServiceInfo, type EStorageEntityType, type EnderStorageApiPayload, type EnderStorageChest, type EnderStorageCollection, type EnderStorageColor, type EnderStorageItem, type EnderStorageMeta, type EnderStoragePayload, ErrorCode, type ErrorResponse, type HealthChecks, HealthResource, type HealthResponse, type HealthServices, type HealthServicesDetailed, type Item, type ItemChangeLog, type ItemChangeLogResponse, type ItemChangeRecord, type ItemChangeType, type ItemChangesParams, type ItemSummary, type ItemUpdateSummary, ItemsResource, type KnownAddress, type KnownAddressType, KrawletClient, type KrawletClientConfig, KrawletError, type KromerServiceInfo, type Player, type PlayerNotifications, PlayersResource, type Price, type PriceChangeLog, type PriceChangeLogResponse, type PriceChangesParams, type PublicStorageSourceEntity, type PublicStorageTransferRequest, type PublicStorageTransferResponse, type QuickCodeGenerateResponse, type QuickCodeRedeemResponse, type RateLimit, type ReportRecords, type ReporterStats, ReportsResource, type RequestLog, type RequestLogsResponse, type ServiceInfo, type ServiceName, type ServiceStatus, type Shop, type ShopChangeField, type ShopChangeLog, type ShopChangeLogResponse, type ShopChangeRecord, type ShopChangesParams, type ShopSourceType, type ShopSyncData, ShopsResource, type StorageData, StorageResource, type StorageSlotContents, type StorageSlotItem, type SuccessfulPostRecord, type Transfer, type TransferCreateRequest, type TransferStatus, type TransferTarget, type TransferTargetLink, TransfersResource, type ValidationFailureRecord, type WebsocketAuthOk, type WebsocketClientOptions, type WebsocketEnvelope, type WebsocketErrorPayload, type WebsocketHelloPayload, type WebsocketMessageId, WebsocketProtocolError, type WebsocketTargetListPayload, type WebsocketTransferListPayload, WebsocketsResource };
package/dist/index.js CHANGED
@@ -51,6 +51,12 @@ var HttpClient = class {
51
51
  getRateLimit() {
52
52
  return this.lastRateLimit;
53
53
  }
54
+ /**
55
+ * Get configured API base URL
56
+ */
57
+ getBaseUrl() {
58
+ return this.config.baseUrl;
59
+ }
54
60
  /**
55
61
  * Make an HTTP request
56
62
  */
@@ -791,6 +797,308 @@ var TransfersResource = class {
791
797
  }
792
798
  };
793
799
 
800
+ // src/resources/websockets.ts
801
+ var WebsocketProtocolError = class extends Error {
802
+ constructor(message, code, requestId) {
803
+ super(message);
804
+ this.name = "WebsocketProtocolError";
805
+ this.code = code;
806
+ this.requestId = requestId;
807
+ }
808
+ };
809
+ var WebsocketsResource = class {
810
+ constructor(client, options = {}) {
811
+ this.client = client;
812
+ this.ws = null;
813
+ this.connectPromise = null;
814
+ this.pending = /* @__PURE__ */ new Map();
815
+ this.transferUpdateListeners = /* @__PURE__ */ new Set();
816
+ this.nextRequestId = 1;
817
+ this.connectTimeoutMs = options.connectTimeoutMs ?? 1e4;
818
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 15e3;
819
+ this.webSocketFactory = options.webSocketFactory ?? this.getDefaultWebSocketFactory();
820
+ }
821
+ /**
822
+ * Open websocket connection and wait for initial hello
823
+ */
824
+ async start() {
825
+ if (this.connectPromise) {
826
+ return this.connectPromise;
827
+ }
828
+ const url = this.buildWebSocketUrl();
829
+ this.connectPromise = new Promise((resolve, reject) => {
830
+ const ws = this.webSocketFactory(url);
831
+ this.ws = ws;
832
+ const timeout = setTimeout(() => {
833
+ this.connectPromise = null;
834
+ this.ws = null;
835
+ try {
836
+ ws.close();
837
+ } catch {
838
+ }
839
+ reject(new Error(`WebSocket connection timeout after ${this.connectTimeoutMs}ms`));
840
+ }, this.connectTimeoutMs);
841
+ ws.onopen = () => {
842
+ };
843
+ ws.onmessage = (event) => {
844
+ this.handleMessageEvent(event.data, resolve, reject, timeout);
845
+ };
846
+ ws.onerror = () => {
847
+ clearTimeout(timeout);
848
+ this.connectPromise = null;
849
+ this.ws = null;
850
+ reject(new Error("WebSocket connection failed"));
851
+ };
852
+ ws.onclose = (event) => {
853
+ clearTimeout(timeout);
854
+ this.connectPromise = null;
855
+ this.ws = null;
856
+ this.rejectAllPending(
857
+ new Error(`WebSocket closed (${event.code}${event.reason ? `: ${event.reason}` : ""})`)
858
+ );
859
+ };
860
+ });
861
+ return this.connectPromise;
862
+ }
863
+ /**
864
+ * Authenticate this connection as a client key
865
+ */
866
+ async authenticate(token, id = this.createRequestId()) {
867
+ const response = await this.sendAndWait("auth", {
868
+ type: "auth",
869
+ id,
870
+ token
871
+ });
872
+ if (response.type !== "auth_ok") {
873
+ throw new Error(`Unexpected auth response type: ${response.type}`);
874
+ }
875
+ return response.payload;
876
+ }
877
+ /**
878
+ * Ping the websocket server
879
+ */
880
+ async ping(id = this.createRequestId()) {
881
+ const response = await this.sendAndWait("ping", {
882
+ type: "ping",
883
+ id
884
+ });
885
+ if (response.type !== "pong") {
886
+ throw new Error(`Unexpected ping response type: ${response.type}`);
887
+ }
888
+ return response.payload;
889
+ }
890
+ /**
891
+ * Create a transfer over websocket
892
+ */
893
+ async createTransfer(payload, id = this.createRequestId()) {
894
+ const response = await this.sendAndWait("create_transfer", {
895
+ type: "create_transfer",
896
+ id,
897
+ payload
898
+ });
899
+ if (response.type !== "create_transfer_ok") {
900
+ throw new Error(`Unexpected create_transfer response type: ${response.type}`);
901
+ }
902
+ return response.payload;
903
+ }
904
+ /**
905
+ * Fetch transfer by id
906
+ */
907
+ async getTransfer(transferId, id = this.createRequestId()) {
908
+ const response = await this.sendAndWait("get_transfer", {
909
+ type: "get_transfer",
910
+ id,
911
+ payload: { transferId }
912
+ });
913
+ if (response.type !== "get_transfer_ok") {
914
+ throw new Error(`Unexpected get_transfer response type: ${response.type}`);
915
+ }
916
+ return response.payload;
917
+ }
918
+ /**
919
+ * Cancel transfer by id
920
+ */
921
+ async cancelTransfer(transferId, id = this.createRequestId()) {
922
+ const response = await this.sendAndWait("cancel_transfer", {
923
+ type: "cancel_transfer",
924
+ id,
925
+ payload: { transferId }
926
+ });
927
+ if (response.type !== "cancel_transfer_ok") {
928
+ throw new Error(`Unexpected cancel_transfer response type: ${response.type}`);
929
+ }
930
+ return response.payload;
931
+ }
932
+ /**
933
+ * List transfers for this client
934
+ */
935
+ async listTransfers(id = this.createRequestId()) {
936
+ const response = await this.sendAndWait("list_transfers", {
937
+ type: "list_transfers",
938
+ id,
939
+ payload: {}
940
+ });
941
+ if (response.type !== "list_transfers_ok") {
942
+ throw new Error(`Unexpected list_transfers response type: ${response.type}`);
943
+ }
944
+ return response.payload.transfers;
945
+ }
946
+ /**
947
+ * List targets available to this client
948
+ */
949
+ async listTargets(id = this.createRequestId()) {
950
+ const response = await this.sendAndWait("list_targets", {
951
+ type: "list_targets",
952
+ id,
953
+ payload: {}
954
+ });
955
+ if (response.type !== "list_targets_ok") {
956
+ throw new Error(`Unexpected list_targets response type: ${response.type}`);
957
+ }
958
+ return response.payload.targets;
959
+ }
960
+ /**
961
+ * Subscribe to unsolicited transfer updates.
962
+ * Returns an unsubscribe function.
963
+ */
964
+ onTransferUpdate(listener) {
965
+ this.transferUpdateListeners.add(listener);
966
+ return () => {
967
+ this.transferUpdateListeners.delete(listener);
968
+ };
969
+ }
970
+ /**
971
+ * Close the websocket connection
972
+ */
973
+ close(code, reason) {
974
+ if (!this.ws) {
975
+ return;
976
+ }
977
+ try {
978
+ this.ws.close(code, reason);
979
+ } finally {
980
+ this.ws = null;
981
+ this.connectPromise = null;
982
+ this.rejectAllPending(new Error("WebSocket connection closed"));
983
+ }
984
+ }
985
+ /**
986
+ * True if the websocket is currently open
987
+ */
988
+ isConnected() {
989
+ return Boolean(this.ws && this.ws.readyState === 1);
990
+ }
991
+ async sendAndWait(messageType, message) {
992
+ if (!this.ws || this.ws.readyState !== 1) {
993
+ await this.start();
994
+ }
995
+ if (!this.ws || this.ws.readyState !== 1) {
996
+ throw new Error("WebSocket is not connected");
997
+ }
998
+ const requestId = message.id;
999
+ const responsePromise = new Promise((resolve, reject) => {
1000
+ const timeout = setTimeout(() => {
1001
+ this.pending.delete(requestId);
1002
+ reject(new Error(`WebSocket ${messageType} request timed out after ${this.requestTimeoutMs}ms`));
1003
+ }, this.requestTimeoutMs);
1004
+ this.pending.set(requestId, {
1005
+ resolve: (value) => resolve(value),
1006
+ reject,
1007
+ timeout
1008
+ });
1009
+ });
1010
+ this.ws.send(JSON.stringify(message));
1011
+ return responsePromise;
1012
+ }
1013
+ handleMessageEvent(rawData, connectResolve, connectReject, connectTimeout) {
1014
+ const parsed = this.safeParse(rawData);
1015
+ if (!parsed) {
1016
+ return;
1017
+ }
1018
+ if (parsed.type === "hello") {
1019
+ clearTimeout(connectTimeout);
1020
+ connectResolve(parsed);
1021
+ return;
1022
+ }
1023
+ if (parsed.type === "transfer_update") {
1024
+ this.emitTransferUpdate(parsed.payload);
1025
+ return;
1026
+ }
1027
+ if (typeof parsed.id === "undefined") {
1028
+ if (parsed.type === "error") {
1029
+ const payload = parsed.payload;
1030
+ connectReject(new WebsocketProtocolError(payload.message, payload.code));
1031
+ }
1032
+ return;
1033
+ }
1034
+ const pending = this.pending.get(parsed.id);
1035
+ if (!pending) {
1036
+ return;
1037
+ }
1038
+ clearTimeout(pending.timeout);
1039
+ this.pending.delete(parsed.id);
1040
+ if (parsed.type === "error") {
1041
+ const payload = parsed.payload;
1042
+ pending.reject(new WebsocketProtocolError(payload.message, payload.code, parsed.id));
1043
+ return;
1044
+ }
1045
+ pending.resolve(parsed);
1046
+ }
1047
+ emitTransferUpdate(transfer) {
1048
+ for (const listener of this.transferUpdateListeners) {
1049
+ listener(transfer);
1050
+ }
1051
+ }
1052
+ safeParse(rawData) {
1053
+ try {
1054
+ const parsed = JSON.parse(rawData);
1055
+ if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
1056
+ return null;
1057
+ }
1058
+ return parsed;
1059
+ } catch {
1060
+ return null;
1061
+ }
1062
+ }
1063
+ createRequestId() {
1064
+ const id = this.nextRequestId;
1065
+ this.nextRequestId += 1;
1066
+ return id;
1067
+ }
1068
+ rejectAllPending(error) {
1069
+ for (const entry of this.pending.values()) {
1070
+ clearTimeout(entry.timeout);
1071
+ entry.reject(error);
1072
+ }
1073
+ this.pending.clear();
1074
+ }
1075
+ buildWebSocketUrl() {
1076
+ const base = new URL(this.client.getBaseUrl());
1077
+ base.protocol = base.protocol === "https:" ? "wss:" : "ws:";
1078
+ const trimmedPath = base.pathname.replace(/\/+$/, "");
1079
+ if (trimmedPath.endsWith("/api/v1")) {
1080
+ base.pathname = `${trimmedPath}/ws`;
1081
+ } else if (trimmedPath.endsWith("/api")) {
1082
+ base.pathname = `${trimmedPath}/v1/ws`;
1083
+ } else if (trimmedPath.endsWith("/v1")) {
1084
+ base.pathname = `${trimmedPath}/ws`;
1085
+ } else {
1086
+ base.pathname = "/v1/ws";
1087
+ }
1088
+ base.search = "";
1089
+ base.hash = "";
1090
+ return base.toString();
1091
+ }
1092
+ getDefaultWebSocketFactory() {
1093
+ return (url) => {
1094
+ if (typeof globalThis.WebSocket === "undefined") {
1095
+ throw new Error("WebSocket is not available. Provide a webSocketFactory in WebsocketsResource options.");
1096
+ }
1097
+ return new globalThis.WebSocket(url);
1098
+ };
1099
+ }
1100
+ };
1101
+
794
1102
  // src/client.ts
795
1103
  var KrawletClient = class {
796
1104
  /**
@@ -817,6 +1125,7 @@ var KrawletClient = class {
817
1125
  this.reports = new ReportsResource(this.httpClient);
818
1126
  this.apiKey = new ApiKeyResource(this.httpClient);
819
1127
  this.transfers = new TransfersResource(this.httpClient);
1128
+ this.websockets = new WebsocketsResource(this.httpClient);
820
1129
  }
821
1130
  /**
822
1131
  * Get the last known rate limit information
@@ -856,5 +1165,7 @@ export {
856
1165
  ReportsResource,
857
1166
  ShopsResource,
858
1167
  StorageResource,
859
- TransfersResource
1168
+ TransfersResource,
1169
+ WebsocketProtocolError,
1170
+ WebsocketsResource
860
1171
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "krawlet-js",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
4
4
  "description": "TypeScript/JavaScript client library for the Krawlet Minecraft economy tracking API",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",