@prismicio/simulator 0.1.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.
Files changed (127) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/dist/SimulatorAPI.cjs +50 -0
  4. package/dist/SimulatorAPI.cjs.map +1 -0
  5. package/dist/SimulatorAPI.d.ts +12 -0
  6. package/dist/SimulatorAPI.js +50 -0
  7. package/dist/SimulatorAPI.js.map +1 -0
  8. package/dist/SimulatorClient.cjs +54 -0
  9. package/dist/SimulatorClient.cjs.map +1 -0
  10. package/dist/SimulatorClient.d.ts +10 -0
  11. package/dist/SimulatorClient.js +54 -0
  12. package/dist/SimulatorClient.js.map +1 -0
  13. package/dist/channel/ChannelEmitter.cjs +146 -0
  14. package/dist/channel/ChannelEmitter.cjs.map +1 -0
  15. package/dist/channel/ChannelEmitter.d.ts +38 -0
  16. package/dist/channel/ChannelEmitter.js +146 -0
  17. package/dist/channel/ChannelEmitter.js.map +1 -0
  18. package/dist/channel/ChannelNetwork.cjs +112 -0
  19. package/dist/channel/ChannelNetwork.cjs.map +1 -0
  20. package/dist/channel/ChannelNetwork.d.ts +24 -0
  21. package/dist/channel/ChannelNetwork.js +112 -0
  22. package/dist/channel/ChannelNetwork.js.map +1 -0
  23. package/dist/channel/ChannelReceiver.cjs +95 -0
  24. package/dist/channel/ChannelReceiver.cjs.map +1 -0
  25. package/dist/channel/ChannelReceiver.d.ts +20 -0
  26. package/dist/channel/ChannelReceiver.js +95 -0
  27. package/dist/channel/ChannelReceiver.js.map +1 -0
  28. package/dist/channel/errors.cjs +54 -0
  29. package/dist/channel/errors.cjs.map +1 -0
  30. package/dist/channel/errors.d.ts +25 -0
  31. package/dist/channel/errors.js +54 -0
  32. package/dist/channel/errors.js.map +1 -0
  33. package/dist/channel/index.d.ts +10 -0
  34. package/dist/channel/messages.cjs +66 -0
  35. package/dist/channel/messages.cjs.map +1 -0
  36. package/dist/channel/messages.d.ts +10 -0
  37. package/dist/channel/messages.js +66 -0
  38. package/dist/channel/messages.js.map +1 -0
  39. package/dist/channel/types.cjs +11 -0
  40. package/dist/channel/types.cjs.map +1 -0
  41. package/dist/channel/types.d.ts +61 -0
  42. package/dist/channel/types.js +13 -0
  43. package/dist/channel/types.js.map +1 -0
  44. package/dist/index.cjs +33 -0
  45. package/dist/index.cjs.map +1 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.js +27 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/kit/SimulatorManager.cjs +140 -0
  50. package/dist/kit/SimulatorManager.cjs.map +1 -0
  51. package/dist/kit/SimulatorManager.d.ts +15 -0
  52. package/dist/kit/SimulatorManager.js +140 -0
  53. package/dist/kit/SimulatorManager.js.map +1 -0
  54. package/dist/kit/State.cjs +82 -0
  55. package/dist/kit/State.cjs.map +1 -0
  56. package/dist/kit/State.d.ts +25 -0
  57. package/dist/kit/State.js +82 -0
  58. package/dist/kit/State.js.map +1 -0
  59. package/dist/kit/domHelpers.cjs +53 -0
  60. package/dist/kit/domHelpers.cjs.map +1 -0
  61. package/dist/kit/domHelpers.d.ts +9 -0
  62. package/dist/kit/domHelpers.js +53 -0
  63. package/dist/kit/domHelpers.js.map +1 -0
  64. package/dist/kit/eventHandlers.cjs +15 -0
  65. package/dist/kit/eventHandlers.cjs.map +1 -0
  66. package/dist/kit/eventHandlers.d.ts +4 -0
  67. package/dist/kit/eventHandlers.js +15 -0
  68. package/dist/kit/eventHandlers.js.map +1 -0
  69. package/dist/kit/getDefault.cjs +16 -0
  70. package/dist/kit/getDefault.cjs.map +1 -0
  71. package/dist/kit/getDefault.d.ts +8 -0
  72. package/dist/kit/getDefault.js +16 -0
  73. package/dist/kit/getDefault.js.map +1 -0
  74. package/dist/kit/index.cjs +26 -0
  75. package/dist/kit/index.cjs.map +1 -0
  76. package/dist/kit/index.d.ts +8 -0
  77. package/dist/kit/index.js +23 -0
  78. package/dist/kit/index.js.map +1 -0
  79. package/dist/kit/messages.cjs +29 -0
  80. package/dist/kit/messages.cjs.map +1 -0
  81. package/dist/kit/messages.d.ts +1 -0
  82. package/dist/kit/messages.js +29 -0
  83. package/dist/kit/messages.js.map +1 -0
  84. package/dist/kit/types.cjs +9 -0
  85. package/dist/kit/types.cjs.map +1 -0
  86. package/dist/kit/types.d.ts +23 -0
  87. package/dist/kit/types.js +10 -0
  88. package/dist/kit/types.js.map +1 -0
  89. package/dist/lib/EventEmitter.cjs +27 -0
  90. package/dist/lib/EventEmitter.cjs.map +1 -0
  91. package/dist/lib/EventEmitter.d.ts +10 -0
  92. package/dist/lib/EventEmitter.js +27 -0
  93. package/dist/lib/EventEmitter.js.map +1 -0
  94. package/dist/lib/__PRODUCTION__.d.ts +7 -0
  95. package/dist/lib/throttle.cjs +24 -0
  96. package/dist/lib/throttle.cjs.map +1 -0
  97. package/dist/lib/throttle.d.ts +1 -0
  98. package/dist/lib/throttle.js +24 -0
  99. package/dist/lib/throttle.js.map +1 -0
  100. package/dist/types.cjs +14 -0
  101. package/dist/types.cjs.map +1 -0
  102. package/dist/types.d.ts +34 -0
  103. package/dist/types.js +16 -0
  104. package/dist/types.js.map +1 -0
  105. package/package.json +99 -0
  106. package/src/SimulatorAPI.ts +90 -0
  107. package/src/SimulatorClient.ts +96 -0
  108. package/src/channel/ChannelEmitter.ts +278 -0
  109. package/src/channel/ChannelNetwork.ts +216 -0
  110. package/src/channel/ChannelReceiver.ts +184 -0
  111. package/src/channel/errors.ts +53 -0
  112. package/src/channel/index.ts +75 -0
  113. package/src/channel/messages.ts +115 -0
  114. package/src/channel/types.ts +138 -0
  115. package/src/index.ts +42 -0
  116. package/src/kit/SimulatorManager.ts +199 -0
  117. package/src/kit/State.ts +113 -0
  118. package/src/kit/domHelpers.ts +75 -0
  119. package/src/kit/eventHandlers.ts +16 -0
  120. package/src/kit/getDefault.ts +18 -0
  121. package/src/kit/index.ts +33 -0
  122. package/src/kit/messages.ts +25 -0
  123. package/src/kit/types.ts +30 -0
  124. package/src/lib/EventEmitter.ts +41 -0
  125. package/src/lib/__PRODUCTION__.ts +12 -0
  126. package/src/lib/throttle.ts +26 -0
  127. package/src/types.ts +57 -0
@@ -0,0 +1,53 @@
1
+ import { UnknownErrorResponseMessage } from ".";
2
+
3
+ import { UnknownRequestMessage } from "./types";
4
+
5
+ export class ResponseError<
6
+ TErrorResponse extends UnknownErrorResponseMessage,
7
+ > extends Error {
8
+ public response: TErrorResponse;
9
+
10
+ constructor(errorResponse: TErrorResponse) {
11
+ super(errorResponse.msg);
12
+
13
+ this.response = errorResponse;
14
+ }
15
+ }
16
+
17
+ export class ConnectionTimeoutError extends Error {
18
+ constructor() {
19
+ super("Connection timed out");
20
+ }
21
+ }
22
+ export class TooManyConcurrentRequestsError extends Error {
23
+ public request: UnknownRequestMessage;
24
+
25
+ constructor(request: UnknownRequestMessage) {
26
+ super(`Too many concurrent requests`);
27
+
28
+ this.request = request;
29
+ }
30
+ }
31
+ export class RequestTimeoutError<
32
+ TRequest extends UnknownRequestMessage,
33
+ > extends Error {
34
+ public request: UnknownRequestMessage;
35
+
36
+ constructor(request: TRequest) {
37
+ super(`Request \`${request.requestID}\` timed out`);
38
+
39
+ this.request = request;
40
+ }
41
+ }
42
+
43
+ export class NotReadyError extends Error {}
44
+ export class PortNotSetError extends Error {
45
+ constructor() {
46
+ super("Port is not set");
47
+ }
48
+ }
49
+ export class ChannelNotSetError extends Error {
50
+ constructor() {
51
+ super("Channel is not set");
52
+ }
53
+ }
@@ -0,0 +1,75 @@
1
+ export { ChannelNetwork, channelNetworkDefaultOptions } from "./ChannelNetwork";
2
+ export type {
3
+ PostRequestOptions,
4
+ ChannelNetworkOptions,
5
+ } from "./ChannelNetwork";
6
+
7
+ export { ChannelEmitter, channelEmitterDefaultOptions } from "./ChannelEmitter";
8
+ export type {
9
+ ChannelEmitterOptions,
10
+ AllChannelEmitterOptions,
11
+ } from "./ChannelEmitter";
12
+
13
+ export {
14
+ ChannelReceiver,
15
+ channelReceiverDefaultOptions,
16
+ } from "./ChannelReceiver";
17
+ export type {
18
+ ChannelReceiverOptions,
19
+ AllChannelReceiverOptions,
20
+ } from "./ChannelReceiver";
21
+
22
+ export {
23
+ createRequestMessage,
24
+ createSuccessResponseMessage,
25
+ createErrorResponseMessage,
26
+ validateMessage,
27
+ isRequestMessage,
28
+ isResponseMessage,
29
+ isSuccessResponseMessage,
30
+ isErrorResponseMessage,
31
+ } from "./messages";
32
+
33
+ export {
34
+ ResponseError,
35
+ ConnectionTimeoutError,
36
+ TooManyConcurrentRequestsError,
37
+ RequestTimeoutError,
38
+ NotReadyError,
39
+ PortNotSetError,
40
+ ChannelNotSetError,
41
+ } from "./errors";
42
+
43
+ export {
44
+ InternalEmitterRequestType,
45
+ InternalReceiverRequestType,
46
+ } from "./types";
47
+ export type {
48
+ // Messages
49
+ RequestMessage,
50
+ ResponseMessage,
51
+ ErrorResponseMessage,
52
+ SuccessResponseMessage,
53
+ ExtractSuccessResponseMessage,
54
+ ExtractErrorResponseMessage,
55
+ // Unknown messages
56
+ UnknownRequestMessage,
57
+ UnknownSuccessResponseMessage,
58
+ UnknownErrorResponseMessage,
59
+ UnknownResponseMessage,
60
+ UnknownMessage,
61
+ // Transactions
62
+ Transaction,
63
+ TransactionMethod,
64
+ TransactionHandler,
65
+ TransactionsMethods,
66
+ TransactionsHandlers,
67
+ // Unknown transactions
68
+ UnknownTransaction,
69
+ UnknownTransactionMethod,
70
+ UnknownTransactionHandler,
71
+ // Emitter
72
+ InternalEmitterTransactions,
73
+ // Receiver
74
+ InternalReceiverTransactions,
75
+ } from "./types";
@@ -0,0 +1,115 @@
1
+ import statuses from "statuses";
2
+
3
+ import {
4
+ ErrorResponseMessage,
5
+ RequestMessage,
6
+ SuccessResponseMessage,
7
+ UnknownErrorResponseMessage,
8
+ UnknownMessage,
9
+ UnknownRequestMessage,
10
+ UnknownResponseMessage,
11
+ UnknownSuccessResponseMessage,
12
+ } from "./types";
13
+
14
+ let requestID = 0;
15
+
16
+ export const createRequestMessage = <
17
+ TType extends string = string,
18
+ TData = undefined,
19
+ >(
20
+ type: TType,
21
+ data: TData,
22
+ prefix = "",
23
+ ): RequestMessage<TType, TData> => {
24
+ return {
25
+ requestID: `${prefix}${requestID++}`,
26
+ type,
27
+ data,
28
+ };
29
+ };
30
+
31
+ export const createSuccessResponseMessage = <TData = undefined>(
32
+ requestID: string,
33
+ data: TData,
34
+ status: keyof typeof statuses.message = 200,
35
+ ): SuccessResponseMessage<TData> => {
36
+ if (status >= 400) {
37
+ throw new TypeError(
38
+ `Invalid success status code, expected status to be within \`[100;400[\`, got \`${status}\``,
39
+ );
40
+ }
41
+
42
+ return {
43
+ requestID,
44
+ status,
45
+ msg: statuses.message[status]?.replace(/\.$/, "").toLowerCase() ?? "",
46
+ data,
47
+ };
48
+ };
49
+
50
+ export const createErrorResponseMessage = <TError = undefined>(
51
+ requestID: string,
52
+ error: TError,
53
+ status: keyof typeof statuses.message = 400,
54
+ ): ErrorResponseMessage<TError> => {
55
+ if (status < 400) {
56
+ throw new TypeError(
57
+ `Invalid error status code, expected status to be within \`[500;600[\`, got \`${status}\``,
58
+ );
59
+ }
60
+
61
+ return {
62
+ requestID,
63
+ status,
64
+ msg: statuses.message[status]?.replace(/\.$/, "").toLowerCase() ?? "",
65
+ error,
66
+ };
67
+ };
68
+
69
+ export const validateMessage = (message: unknown): UnknownMessage => {
70
+ if (typeof message !== "object" || message === null) {
71
+ throw new TypeError(
72
+ `Invalid message received, expected message to be of type \`object\`, got \`${typeof message}\``,
73
+ );
74
+ } else if (
75
+ !Object.keys(message).every((key) =>
76
+ ["requestID", "type", "data", "status", "msg", "error"].includes(key),
77
+ )
78
+ ) {
79
+ throw new TypeError(`Invalid message received: ${JSON.stringify(message)}`);
80
+ } else if (
81
+ typeof (message as Record<string, unknown>).requestID !== "string"
82
+ ) {
83
+ throw new TypeError(
84
+ `Invalid message received, expected \`message.requestID\` to be of type \`string\`, got \`${typeof (
85
+ message as Record<string, unknown>
86
+ ).id}\``,
87
+ );
88
+ }
89
+
90
+ return message as UnknownMessage;
91
+ };
92
+
93
+ export const isRequestMessage = (
94
+ message: UnknownMessage,
95
+ ): message is UnknownRequestMessage => {
96
+ return "type" in message;
97
+ };
98
+
99
+ export const isResponseMessage = (
100
+ message: UnknownMessage,
101
+ ): message is UnknownResponseMessage => {
102
+ return !("type" in message);
103
+ };
104
+
105
+ export const isSuccessResponseMessage = (
106
+ message: UnknownMessage,
107
+ ): message is UnknownSuccessResponseMessage => {
108
+ return isResponseMessage(message) && !("error" in message);
109
+ };
110
+
111
+ export const isErrorResponseMessage = (
112
+ message: UnknownMessage,
113
+ ): message is UnknownErrorResponseMessage => {
114
+ return isResponseMessage(message) && !("data" in message);
115
+ };
@@ -0,0 +1,138 @@
1
+ // Messages
2
+ export type MessageBase = {
3
+ requestID: string;
4
+ };
5
+
6
+ export type RequestMessage<TType extends string = string, TData = void> = {
7
+ type: TType;
8
+ data: TData;
9
+ } & MessageBase;
10
+
11
+ export type ResponseMessageBase = {
12
+ status: number;
13
+ msg: string;
14
+ } & MessageBase;
15
+
16
+ export type SuccessResponseMessage<TData = void> = {
17
+ data: TData;
18
+ error?: never;
19
+ } & ResponseMessageBase;
20
+
21
+ export type ErrorResponseMessage<TError = unknown> = {
22
+ data?: never;
23
+ error: unknown | TError;
24
+ } & ResponseMessageBase;
25
+
26
+ export type ResponseMessage<TData = void, TError = unknown> =
27
+ | SuccessResponseMessage<TData>
28
+ | ErrorResponseMessage<TError>;
29
+
30
+ export type ExtractSuccessResponseMessage<
31
+ TResponse extends UnknownResponseMessage,
32
+ > = Extract<TResponse, { error?: never }>;
33
+
34
+ export type ExtractErrorResponseMessage<
35
+ TResponse extends UnknownResponseMessage,
36
+ > = Extract<TResponse, { data?: never }>;
37
+
38
+ // Unknown messages
39
+ export type UnknownRequestMessage = RequestMessage<string, unknown>;
40
+
41
+ export type UnknownSuccessResponseMessage = SuccessResponseMessage<unknown>;
42
+
43
+ export type UnknownErrorResponseMessage = ErrorResponseMessage<unknown>;
44
+
45
+ export type UnknownResponseMessage =
46
+ | UnknownSuccessResponseMessage
47
+ | UnknownErrorResponseMessage;
48
+
49
+ export type UnknownMessage = UnknownRequestMessage | UnknownResponseMessage;
50
+
51
+ // Transactions
52
+ export type Transaction<
53
+ TRequest extends UnknownRequestMessage,
54
+ TResponse extends UnknownResponseMessage = ResponseMessage<void>,
55
+ > = {
56
+ request: TRequest;
57
+ response: TResponse;
58
+ };
59
+
60
+ export type TransactionMethod<
61
+ TTransaction extends Transaction<
62
+ UnknownRequestMessage,
63
+ UnknownResponseMessage
64
+ >,
65
+ > = (
66
+ data: TTransaction["request"]["data"],
67
+ ) => Promise<ExtractSuccessResponseMessage<TTransaction["response"]>>;
68
+
69
+ export type TransactionHandler<
70
+ TTransaction extends Transaction<
71
+ UnknownRequestMessage,
72
+ UnknownResponseMessage
73
+ >,
74
+ > = (
75
+ request: TTransaction["request"],
76
+ response: {
77
+ success: (
78
+ data: ExtractSuccessResponseMessage<TTransaction["response"]>["data"],
79
+ status?: number,
80
+ ) => ExtractSuccessResponseMessage<TTransaction["response"]>;
81
+ error: (
82
+ error: ExtractErrorResponseMessage<TTransaction["response"]>["error"],
83
+ status?: number,
84
+ ) => ExtractErrorResponseMessage<TTransaction["response"]>;
85
+ },
86
+ ) => Promise<TTransaction["response"]> | TTransaction["response"];
87
+
88
+ export type TransactionsMethods<
89
+ TTransactions extends Record<string, UnknownTransaction>,
90
+ > = {
91
+ [Key in keyof TTransactions]: TransactionMethod<TTransactions[Key]>;
92
+ };
93
+
94
+ export type TransactionsHandlers<
95
+ TTransactions extends Record<string, UnknownTransaction>,
96
+ > = {
97
+ [Key in keyof TTransactions]: TransactionHandler<TTransactions[Key]>;
98
+ };
99
+
100
+ // Unknown transactions
101
+ export type UnknownTransaction = Transaction<
102
+ UnknownRequestMessage,
103
+ UnknownResponseMessage
104
+ >;
105
+
106
+ export type UnknownTransactionMethod = TransactionMethod<UnknownTransaction>;
107
+
108
+ export type UnknownTransactionHandler = TransactionHandler<UnknownTransaction>;
109
+
110
+ // Emitter
111
+ export enum InternalEmitterRequestType {
112
+ Connect = "connect",
113
+ }
114
+
115
+ export type InternalEmitterTransactions<
116
+ TReceiverOptions extends Record<string, unknown> = Record<string, unknown>,
117
+ > = {
118
+ [InternalEmitterRequestType.Connect]: Transaction<
119
+ RequestMessage<
120
+ InternalEmitterRequestType.Connect,
121
+ | Partial<
122
+ Omit<TReceiverOptions, "debug" | "requestIDPrefix" | "readyTimeout">
123
+ >
124
+ | undefined
125
+ >
126
+ >;
127
+ };
128
+
129
+ // Receiver
130
+ export enum InternalReceiverRequestType {
131
+ Ready = "ready",
132
+ }
133
+
134
+ export type InternalReceiverTransactions = {
135
+ [InternalReceiverRequestType.Ready]: Transaction<
136
+ RequestMessage<InternalReceiverRequestType.Ready>
137
+ >;
138
+ };
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ export {
2
+ SimulatorClient,
3
+ simulatorClientDefaultOptions,
4
+ } from "./SimulatorClient";
5
+
6
+ export { SimulatorAPI, simulatorAPIDefaultOptions } from "./SimulatorAPI";
7
+ export type { SimulatorAPIOptions } from "./SimulatorAPI";
8
+
9
+ export {
10
+ // Validators
11
+ validateMessage,
12
+ isRequestMessage,
13
+ isResponseMessage,
14
+ isSuccessResponseMessage,
15
+ isErrorResponseMessage,
16
+ // Error classes
17
+ ResponseError,
18
+ ConnectionTimeoutError,
19
+ TooManyConcurrentRequestsError,
20
+ RequestTimeoutError,
21
+ NotReadyError,
22
+ PortNotSetError,
23
+ ChannelNotSetError,
24
+ } from "./channel";
25
+ export type {
26
+ RequestMessage,
27
+ ResponseMessage,
28
+ ErrorResponseMessage,
29
+ SuccessResponseMessage,
30
+ AllChannelEmitterOptions,
31
+ AllChannelReceiverOptions,
32
+ } from "./channel";
33
+
34
+ export { APIRequestType, ClientRequestType } from "./types";
35
+ export type {
36
+ ActiveSlice,
37
+ VariationSummary,
38
+ SliceSummary,
39
+ LibrarySummary,
40
+ APITransactions,
41
+ ClientTransactions,
42
+ } from "./types";
@@ -0,0 +1,199 @@
1
+ import type { SliceZone } from "@prismicio/client";
2
+
3
+ import { throttle } from "../lib/throttle";
4
+
5
+ import { ClientRequestType } from "../types";
6
+ import { StateEventType } from "./types";
7
+
8
+ import { SimulatorAPI } from "../SimulatorAPI";
9
+ import { ResponseError } from "../channel";
10
+
11
+ import { State } from "./State";
12
+ import {
13
+ getSimulatorDOM,
14
+ getSimulatorRootDOM,
15
+ getSliceZoneDOM,
16
+ } from "./domHelpers";
17
+ import { sliceSimulatorAccessedDirectly } from "./messages";
18
+
19
+ type ManagerConstructorArgs = {
20
+ slices?: SliceZone;
21
+ };
22
+
23
+ export class SimulatorManager {
24
+ public state: State;
25
+ private _api: SimulatorAPI | null;
26
+ private _initialized: boolean;
27
+
28
+ constructor(args?: ManagerConstructorArgs) {
29
+ this.state = new State(args);
30
+ this._api = null;
31
+ this._initialized = false;
32
+ }
33
+
34
+ async init(): Promise<void> {
35
+ if (this._initialized) {
36
+ return;
37
+ } else {
38
+ this._initialized = true;
39
+ }
40
+
41
+ // Init state manager
42
+ await this.state.init();
43
+
44
+ // Init API
45
+ try {
46
+ await this._initAPI();
47
+ } catch (error) {
48
+ if (
49
+ error instanceof Error &&
50
+ error.message === "Receiver is currently not embedded as an iframe" &&
51
+ !this.state.slices.length
52
+ ) {
53
+ // Catch not embedded error and display message
54
+ this.state.message = sliceSimulatorAccessedDirectly;
55
+ }
56
+ console.error(error);
57
+
58
+ return;
59
+ }
60
+
61
+ // Init listeners
62
+ this._initListeners();
63
+ }
64
+
65
+ private async _initAPI(): Promise<void> {
66
+ // Register SimulatorAPI request handlers
67
+ this._api = new SimulatorAPI({
68
+ [ClientRequestType.SetSliceZone]: (req, res) => {
69
+ this.state.setSliceZone(req.data);
70
+
71
+ return res.success();
72
+ },
73
+ [ClientRequestType.ScrollToSlice]: (req, res) => {
74
+ // Error if `sliceIndex` is invalid
75
+ if (req.data.sliceIndex < 0) {
76
+ return res.error("`sliceIndex` must be > 0", 400);
77
+ } else if (req.data.sliceIndex >= this.state.slices.length) {
78
+ return res.error(
79
+ `\`sliceIndex\` must be < ${this.state.slices.length} (\`<SliceZone />\` current length)`,
80
+ 400,
81
+ );
82
+ }
83
+
84
+ const $sliceZone = getSliceZoneDOM(this.state.slices.length);
85
+ if (!$sliceZone) {
86
+ return res.error("Failed to find `<SliceZone />`", 500);
87
+ }
88
+
89
+ // Destroy existing active slice as we're about to scroll
90
+ this.state.activeSlice = null;
91
+
92
+ const $slice = $sliceZone.children[req.data.sliceIndex];
93
+ if (!$slice) {
94
+ return res.error(
95
+ `Failed fo find slice at index $\`{req.data.sliceIndex}\` in \`<SliceZone />\``,
96
+ 500,
97
+ );
98
+ }
99
+
100
+ // Scroll to Slice
101
+ $slice.scrollIntoView({
102
+ behavior: req.data.behavior,
103
+ block: req.data.block,
104
+ inline: req.data.inline,
105
+ });
106
+
107
+ // Update active slice after scrolling
108
+ this._api?.options.activeSliceAPI &&
109
+ setTimeout(this.state.setActiveSlice, 750);
110
+
111
+ return res.success();
112
+ },
113
+ });
114
+
115
+ // Mark API as ready
116
+ await this._api.ready();
117
+ }
118
+
119
+ private _initListeners(): void {
120
+ if (this._api?.options.activeSliceAPI) {
121
+ // Update active slice on mouse move
122
+ window.addEventListener("mousemove", () => {
123
+ this.state.setActiveSlice();
124
+ });
125
+ // Update active slice on events impacting slices rendering
126
+ window.addEventListener("resize", () => {
127
+ this.state.setActiveSlice();
128
+ });
129
+ window.addEventListener("mousewheel", () => {
130
+ this._api?.options.activeSliceAPI &&
131
+ setTimeout(this.state.setActiveSlice, 200);
132
+ });
133
+
134
+ // Update active slice when slices are set
135
+ this.state.on(StateEventType.Slices, () => {
136
+ this.state.setActiveSlice();
137
+ });
138
+ // Send active slices to renderer
139
+ this.state.on(StateEventType.ActiveSlice, async (activeSlice) => {
140
+ if (this._api) {
141
+ try {
142
+ await this._api.setActiveSlice(activeSlice);
143
+ } catch (error) {
144
+ // Just log bad requests, throw everything else
145
+ if (
146
+ error instanceof ResponseError &&
147
+ error.response.status === 400
148
+ ) {
149
+ console.error(error.response);
150
+ } else {
151
+ throw error;
152
+ }
153
+ }
154
+ }
155
+ });
156
+ }
157
+
158
+ if (this._api?.options.sliceZoneSizeAPI) {
159
+ // Monitor Simulator root node size
160
+ const resizeObserver = new ResizeObserver(
161
+ throttle(async (entries) => {
162
+ const [entry] = entries;
163
+
164
+ if (this._api && entry) {
165
+ try {
166
+ await this._api.setSliceZoneSize({ rect: entry.contentRect });
167
+ } catch (error) {
168
+ // Just log bad requests, throw everything else
169
+ if (
170
+ error instanceof ResponseError &&
171
+ error.response.status === 400
172
+ ) {
173
+ console.error(error.response);
174
+ } else {
175
+ throw error;
176
+ }
177
+ }
178
+ }
179
+ }, 16),
180
+ );
181
+ const observeSimulatorRoot = () => {
182
+ const simulatorRootDOM = getSimulatorRootDOM();
183
+ if (simulatorRootDOM) {
184
+ resizeObserver.disconnect();
185
+ resizeObserver.observe(simulatorRootDOM);
186
+ }
187
+ };
188
+
189
+ // Monitor Simulator root node in DOM
190
+ const mutationObserver = new MutationObserver(observeSimulatorRoot);
191
+ // We want it to fail if not found
192
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
193
+ mutationObserver.observe(getSimulatorDOM()!, {
194
+ subtree: false,
195
+ childList: true,
196
+ });
197
+ }
198
+ }
199
+ }