@infosoftas/s4-orders-js-sdk 0.0.1-alpha1

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 (40) hide show
  1. package/.prettierignore +1 -0
  2. package/.prettierrc.json +7 -0
  3. package/package.json +41 -0
  4. package/src/App.scss +24 -0
  5. package/src/App.tsx +184 -0
  6. package/src/ErrorBoundary.tsx +41 -0
  7. package/src/api/OrdersApi.ts +44 -0
  8. package/src/api/SubscribeApi.ts +51 -0
  9. package/src/assets/styles/forms.scss +105 -0
  10. package/src/assets/styles/general.scss +38 -0
  11. package/src/assets/styles/variables.scss +29 -0
  12. package/src/components/Alert/Alert.tsx +22 -0
  13. package/src/components/Alert/alert.scss +21 -0
  14. package/src/components/Button/Button.tsx +43 -0
  15. package/src/components/Button/button.scss +48 -0
  16. package/src/components/FormFields/EmailField.tsx +53 -0
  17. package/src/components/FormFields/FormFields.helper.ts +16 -0
  18. package/src/components/FormFields/FormFieldsMapper.ts +13 -0
  19. package/src/components/FormFields/NameField.tsx +46 -0
  20. package/src/components/FormFields/PhoneField.tsx +55 -0
  21. package/src/components/Loader/Loader.tsx +22 -0
  22. package/src/components/Loader/loader.scss +37 -0
  23. package/src/components/MainIframe/MainIframe.tsx +42 -0
  24. package/src/components/OrderForm/OrderForm.tsx +235 -0
  25. package/src/components/OrderForm/orderForm.scss +10 -0
  26. package/src/constants/index.ts +8 -0
  27. package/src/enums/api.ts +4 -0
  28. package/src/enums/general.ts +12 -0
  29. package/src/hooks/useIframe.tsx +59 -0
  30. package/src/hooks/useMessageEvent.tsx +142 -0
  31. package/src/hooks/useQueryParams.ts +5 -0
  32. package/src/index.tsx +22 -0
  33. package/src/types/api.ts +12 -0
  34. package/src/types/general.ts +50 -0
  35. package/src/types/order.ts +41 -0
  36. package/src/utils/fetcher.ts +89 -0
  37. package/src/utils/helper.ts +18 -0
  38. package/src/utils/order.helper.ts +48 -0
  39. package/tsconfig.json +41 -0
  40. package/webpack.config.js +103 -0
@@ -0,0 +1,48 @@
1
+ @use "sass:color";
2
+
3
+ @import 'Assets/styles/variables.scss';
4
+
5
+ .sdk-btn {
6
+ appearance : button;
7
+ -webkit-appearance : button;
8
+ display : inline-block;
9
+ padding : 6px 12px;
10
+ margin-bottom : 0;
11
+ border : 1px solid $input-border-color;
12
+ border-radius : $border-radius;
13
+ white-space : nowrap;
14
+ vertical-align : middle;
15
+ font-size : $sm-font-size;
16
+ line-height : 1.42857143;
17
+ font-weight : 400;
18
+ text-align : center;
19
+ background-image : none;
20
+ background-color : $white;
21
+ color : $text-color;
22
+ cursor : pointer;
23
+ -webkit-user-select: none;
24
+ -moz-user-select : none;
25
+ -ms-user-select : none;
26
+ user-select : none;
27
+ -ms-touch-action : manipulation;
28
+ touch-action : manipulation;
29
+
30
+ &.loading,
31
+ &.disabled,
32
+ &:disabled {
33
+ opacity : 0.75;
34
+ pointer-events: none;
35
+ }
36
+
37
+ &.primary-btn {
38
+ color : $white;
39
+ background-color: $primary-color;
40
+ border-color : color.scale($primary-color, $lightness: -20%);
41
+
42
+ &:hover,
43
+ &:focus {
44
+ background-color: color.scale($primary-color, $lightness: -10%);
45
+ border-color : color.scale($primary-color, $lightness: -30%);
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,53 @@
1
+ import { FC } from 'react';
2
+ import { useFormContext } from 'react-hook-form';
3
+
4
+ type Props = {
5
+ name: string;
6
+ required: boolean;
7
+ readOnly: boolean;
8
+ label?: string;
9
+ errorReqMsg?: string;
10
+ errorInvalidEmailMsg?: string;
11
+ errors?: {
12
+ email?: {
13
+ message?: string;
14
+ };
15
+ };
16
+ };
17
+
18
+ const EmailField: FC<Props> = ({
19
+ required = false,
20
+ readOnly = false,
21
+ label = 'Email',
22
+ errorReqMsg = 'This field is required!',
23
+ errorInvalidEmailMsg = 'Invalid email address!',
24
+ errors,
25
+ }) => {
26
+ const { register } = useFormContext();
27
+ return (
28
+ <div className="field-wrapper" data-testid="sdk-email-field-id">
29
+ <input
30
+ autoComplete="email"
31
+ placeholder=" "
32
+ className={`input-control ${readOnly ? 'read-only' : ''}`}
33
+ readOnly={readOnly}
34
+ type="email"
35
+ {...register('email', {
36
+ required: required ? errorReqMsg : false,
37
+ pattern: {
38
+ value: /^[-!#-'*+/-9=?^-~]+(?:\.[-!#-'*+/-9=?^-~]+)*@[-!#-'*+/-9=?^-~]+(?:\.[-!#-'*+/-9=?^-~]{2,20})+$/i,
39
+ message: errorInvalidEmailMsg,
40
+ },
41
+ })}
42
+ />
43
+ <label className="label-control">
44
+ {label} {required && <span className="text-error">*</span>}
45
+ </label>
46
+ {errors && errors.email && (
47
+ <div className="text-error caption">{errors.email.message}</div>
48
+ )}
49
+ </div>
50
+ );
51
+ };
52
+
53
+ export default EmailField;
@@ -0,0 +1,16 @@
1
+ import { OrderFormFiledType } from 'Types/general';
2
+
3
+ export const FIELD_TYPES = {
4
+ PHONE: 'phoneNumber',
5
+ EMAIL: 'email',
6
+ NAME: 'name',
7
+ };
8
+
9
+ export const DEFAULT_ORDER_FORM_FIELDS: OrderFormFiledType[] = [
10
+ {
11
+ name: FIELD_TYPES.PHONE,
12
+ required: false,
13
+ readOnly: false,
14
+ label: '',
15
+ },
16
+ ];
@@ -0,0 +1,13 @@
1
+ import EmailField from 'Component/FormFields/EmailField';
2
+ import PhoneField from 'Component/FormFields/PhoneField';
3
+ import NameField from 'Component/FormFields/NameField';
4
+
5
+ import { FIELD_TYPES } from './FormFields.helper';
6
+
7
+ const formFieldsMapper = {
8
+ [FIELD_TYPES.PHONE]: PhoneField,
9
+ [FIELD_TYPES.NAME]: NameField,
10
+ [FIELD_TYPES.EMAIL]: EmailField,
11
+ };
12
+
13
+ export default formFieldsMapper;
@@ -0,0 +1,46 @@
1
+ import { FC } from 'react';
2
+ import { useFormContext } from 'react-hook-form';
3
+
4
+ type Props = {
5
+ name: string;
6
+ required: boolean;
7
+ readOnly: boolean;
8
+ label?: string;
9
+ errorReqMsg?: string;
10
+ errors?: {
11
+ name?: {
12
+ message?: string;
13
+ };
14
+ };
15
+ };
16
+
17
+ const NameField: FC<Props> = ({
18
+ required = false,
19
+ readOnly = false,
20
+ label = 'Name',
21
+ errorReqMsg = 'This field is required!',
22
+ errors,
23
+ }) => {
24
+ const { register } = useFormContext();
25
+ return (
26
+ <div className="field-wrapper" data-testid="sdk-name-field-id">
27
+ <input
28
+ autoComplete="name"
29
+ placeholder=" "
30
+ readOnly={readOnly}
31
+ className={`input-control ${readOnly ? 'read-only' : ''}`}
32
+ {...register('name', {
33
+ required: required ? errorReqMsg : false,
34
+ })}
35
+ />
36
+ <label className="label-control">
37
+ {label} {required && <span className="text-error">*</span>}
38
+ </label>
39
+ {errors && errors.name && (
40
+ <div className="text-error caption">{errors.name.message}</div>
41
+ )}
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export default NameField;
@@ -0,0 +1,55 @@
1
+ import { FC } from 'react';
2
+ import { useFormContext } from 'react-hook-form';
3
+
4
+ type Props = {
5
+ name: string;
6
+ required: boolean;
7
+ readOnly: boolean;
8
+ label?: string;
9
+ errorReqMsg?: string;
10
+ errorInvalidPhoneMsg?: string;
11
+ errors?: {
12
+ phoneNumber?: {
13
+ message?: string;
14
+ };
15
+ };
16
+ };
17
+
18
+ const PhoneFiled: FC<Props> = ({
19
+ required = false,
20
+ readOnly = false,
21
+ label = 'Phone number',
22
+ errorReqMsg = 'This field is required!',
23
+ errorInvalidPhoneMsg = 'Invalid phone address!',
24
+ errors,
25
+ }) => {
26
+ const { register } = useFormContext();
27
+ return (
28
+ <div className="field-wrapper" data-testid="sdk-phone-field-id">
29
+ <input
30
+ autoComplete="tel"
31
+ className={`input-control ${readOnly ? 'read-only' : ''}`}
32
+ placeholder=" "
33
+ readOnly={readOnly}
34
+ type="tel"
35
+ {...register('phoneNumber', {
36
+ required: required ? errorReqMsg : false,
37
+ pattern: {
38
+ value: /^[+]*[(]{0,1}[0-9]{1,3}[)]{0,1}[-\s\./0-9]{6,14}$/g,
39
+ message: errorInvalidPhoneMsg,
40
+ },
41
+ })}
42
+ />
43
+ <label className="label-control">
44
+ {label} {required && <span className="text-error">*</span>}
45
+ </label>
46
+ {errors && errors.phoneNumber && (
47
+ <div className="text-error caption">
48
+ {errors.phoneNumber.message}
49
+ </div>
50
+ )}
51
+ </div>
52
+ );
53
+ };
54
+
55
+ export default PhoneFiled;
@@ -0,0 +1,22 @@
1
+ import { FC } from 'react';
2
+
3
+ import './loader.scss';
4
+
5
+ type Props = {
6
+ className?: string;
7
+ size?: 'sm' | 'md' | 'lg';
8
+ dark?: boolean;
9
+ };
10
+
11
+ const Loader: FC<Props> = ({ className = '', size = 'sm', dark = false }) => {
12
+ return (
13
+ <div
14
+ data-testid="sdk-loader-id"
15
+ className={`sdk-loader ${className} size-${size} ${
16
+ dark ? 'dark-color' : ''
17
+ }`}
18
+ ></div>
19
+ );
20
+ };
21
+
22
+ export default Loader;
@@ -0,0 +1,37 @@
1
+ @import 'Assets/styles/variables.scss';
2
+
3
+ .sdk-loader {
4
+ border : 2px solid $light-gray;
5
+ border-top : 2px solid transparent;
6
+ border-radius: 50%;
7
+ width : 16px;
8
+ height : 16px;
9
+ animation : spin 2s linear infinite;
10
+
11
+ &.size-md {
12
+ border-width: 4px;
13
+ width : 32px;
14
+ height : 32px;
15
+ }
16
+
17
+ &.size-lg {
18
+ border-width: 8px;
19
+ width : 64px;
20
+ height : 64px;
21
+ }
22
+
23
+ &.dark-color {
24
+ border-color : $dark-gray;
25
+ border-top-color: transparent;
26
+ }
27
+ }
28
+
29
+ @keyframes spin {
30
+ 0% {
31
+ transform: rotate(0deg);
32
+ }
33
+
34
+ 100% {
35
+ transform: rotate(360deg);
36
+ }
37
+ }
@@ -0,0 +1,42 @@
1
+ import { FC, useEffect, useState } from 'react';
2
+
3
+ import useIframe from 'Hooks/useIframe';
4
+
5
+ type Props = {
6
+ iframeSrc: string | null;
7
+ };
8
+
9
+ const MainIframe: FC<Props> = ({ iframeSrc }) => {
10
+ const [loading, setLoading] = useState(false);
11
+
12
+ const { frameRef, messageReceiver } = useIframe();
13
+
14
+ useEffect(() => {
15
+ if (frameRef && iframeSrc) {
16
+ setLoading(true);
17
+ frameRef?.current &&
18
+ (frameRef?.current as HTMLIFrameElement).addEventListener(
19
+ 'load',
20
+ () => {
21
+ setLoading(false);
22
+ }
23
+ );
24
+ messageReceiver();
25
+ }
26
+ }, [frameRef, iframeSrc]);
27
+
28
+ return (
29
+ <div className="sdk-iframe-wrapper" data-testid="sdk-iframe-id">
30
+ {loading && <div>... Loading</div>}
31
+ {iframeSrc && (
32
+ <iframe
33
+ ref={frameRef}
34
+ src={iframeSrc}
35
+ style={{ minHeight: '830px', width: '100%' }}
36
+ ></iframe>
37
+ )}
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default MainIframe;
@@ -0,0 +1,235 @@
1
+ import { FC, ChangeEvent, Suspense, useState } from 'react';
2
+ import { FormProvider, useForm, SubmitHandler } from 'react-hook-form';
3
+
4
+ import { createSubscriber, mapSubscriberToUser } from 'API/SubscribeApi';
5
+ import { orderStart } from 'API/OrdersApi';
6
+ import { PaymentMethodEnum } from 'Enums/general';
7
+ import { WRONG_MSG, PAYMENT_METHOD_DEFAULT } from 'Constants/index';
8
+ import Alert from 'Component/Alert/Alert';
9
+ import Button from 'Component/Button/Button';
10
+ import formFieldsMapper from 'Component/FormFields/FormFieldsMapper';
11
+ import { DEFAULT_ORDER_FORM_FIELDS } from 'Component/FormFields/FormFields.helper';
12
+ import { OrderFormInputsType, OrderInfoType } from 'Types/order';
13
+ import { PaymentMethodOptionsType, OrderFormFiledType } from 'Types/general';
14
+ import { prepareAgreementModel } from 'Utils/order.helper';
15
+ import { prepareErrorMessage } from 'Utils/helper';
16
+
17
+ import './orderForm.scss';
18
+
19
+ type Props = {
20
+ callback: (url: string | null, orderInfo?: OrderInfoType | null) => void;
21
+ templatePackageId: string;
22
+ subscriberId?: string;
23
+ userId?: string;
24
+ identityProviderId?: string;
25
+ organizationId: string;
26
+ redirectUrl: string;
27
+ language: string;
28
+ merchantAgreementUrl: string;
29
+ paymentMethodsOptions?: PaymentMethodOptionsType;
30
+ defaultValues?: OrderFormInputsType;
31
+ paymentMethods?: { label: string; value: PaymentMethodEnum }[];
32
+ buttonText?: string;
33
+ paymentMethodLabel?: string;
34
+ errorReqMsg?: string;
35
+ errorInvalidEmailMsg?: string;
36
+ errorInvalidPhoneMsg?: string;
37
+ };
38
+
39
+ const initialData = {
40
+ name: '',
41
+ email: '',
42
+ phoneNumber: '',
43
+ paymentMethod: PAYMENT_METHOD_DEFAULT,
44
+ };
45
+
46
+ const OrderForm: FC<Props> = ({
47
+ callback,
48
+ templatePackageId,
49
+ subscriberId,
50
+ userId,
51
+ identityProviderId,
52
+ organizationId,
53
+ redirectUrl,
54
+ paymentMethodsOptions,
55
+ defaultValues,
56
+ language,
57
+ merchantAgreementUrl,
58
+ paymentMethods = [],
59
+ buttonText = 'Start',
60
+ paymentMethodLabel = 'Select Payment Method',
61
+ errorReqMsg = '',
62
+ errorInvalidEmailMsg = '',
63
+ errorInvalidPhoneMsg = '',
64
+ }) => {
65
+ const [loading, setLoading] = useState<boolean>(false);
66
+ const [apiErrorMsg, setApiErrorMsg] = useState<string>('');
67
+ const [orderFields, setOrderFields] = useState<OrderFormFiledType[]>(
68
+ paymentMethodsOptions?.[PAYMENT_METHOD_DEFAULT]?.orderFormFields ??
69
+ DEFAULT_ORDER_FORM_FIELDS
70
+ );
71
+
72
+ const methods = useForm<OrderFormInputsType>({
73
+ defaultValues: {
74
+ ...initialData,
75
+ ...(defaultValues ?? {}),
76
+ },
77
+ });
78
+
79
+ const {
80
+ register,
81
+ handleSubmit,
82
+ setValue,
83
+ formState: { errors },
84
+ } = methods;
85
+
86
+ const onSubmit: SubmitHandler<OrderFormInputsType> = async (
87
+ data
88
+ ): Promise<void> => {
89
+ setApiErrorMsg('');
90
+
91
+ setLoading(true);
92
+ try {
93
+ let id: string | undefined = subscriberId;
94
+ if (!subscriberId) {
95
+ const response = await createSubscriber({
96
+ name: data.name,
97
+ email: data.email,
98
+ phone: data.phoneNumber,
99
+ });
100
+ id = response.id;
101
+
102
+ if (userId && identityProviderId) {
103
+ await mapSubscriberToUser(id, {
104
+ userId: userId,
105
+ identityProviderId: identityProviderId,
106
+ });
107
+ }
108
+ }
109
+
110
+ if (id) {
111
+ const paymentMethod =
112
+ data.paymentMethod || PAYMENT_METHOD_DEFAULT;
113
+
114
+ const agreements = prepareAgreementModel({
115
+ paymentMethod,
116
+ redirectUrl,
117
+ data,
118
+ language,
119
+ merchantAgreementUrl,
120
+ generateSubscriberContact:
121
+ paymentMethodsOptions?.[
122
+ data.paymentMethod as PaymentMethodEnum
123
+ ]?.generateSubscriberContact || false,
124
+ accountId:
125
+ paymentMethodsOptions?.[
126
+ data.paymentMethod as PaymentMethodEnum
127
+ ]?.accountId,
128
+ });
129
+ const responseOrder = await orderStart({
130
+ subscriberId: id,
131
+ subscriptionPlan: {
132
+ organizationId,
133
+ templateSubscriptionPlanId: templatePackageId,
134
+ },
135
+ paymentAgreement: { ...agreements },
136
+ });
137
+ callback(responseOrder.terminalRedirectUrl, {
138
+ orderId: responseOrder.id,
139
+ subscriberId: id,
140
+ paymentMethod: paymentMethod,
141
+ });
142
+ return;
143
+ }
144
+ callback(null);
145
+ setApiErrorMsg(WRONG_MSG);
146
+ } catch (error) {
147
+ console.error(error);
148
+ setApiErrorMsg(prepareErrorMessage(error as Error));
149
+ callback(null);
150
+ } finally {
151
+ setLoading(false);
152
+ }
153
+ };
154
+
155
+ const handlePaymentChange = (e: ChangeEvent<HTMLInputElement>) => {
156
+ setValue('paymentMethod', e.target.value as PaymentMethodEnum);
157
+ const orderFieldsLocal =
158
+ paymentMethodsOptions?.[e.target.value as PaymentMethodEnum]
159
+ ?.orderFormFields ?? DEFAULT_ORDER_FORM_FIELDS;
160
+ setOrderFields(orderFieldsLocal);
161
+ };
162
+
163
+ return (
164
+ <FormProvider {...methods}>
165
+ <form
166
+ className="sdk-order-form"
167
+ onSubmit={handleSubmit(onSubmit)}
168
+ data-testid="sdk-order-form-id"
169
+ >
170
+ {paymentMethods?.length > 0 && (
171
+ <div className="field-wrapper radio-button-group">
172
+ <div className="field-label">{paymentMethodLabel}</div>
173
+ {paymentMethods.map((item) => (
174
+ <div
175
+ className="radio-button-control"
176
+ key={item.value}
177
+ >
178
+ <label className="radio-badge">
179
+ <input
180
+ {...register('paymentMethod', {
181
+ required: true,
182
+ })}
183
+ onChange={handlePaymentChange}
184
+ type="radio"
185
+ value={item.value}
186
+ />
187
+ <span className="value-block">
188
+ <span className="prefix-icon"></span>
189
+ <span className="text-block">
190
+ {item.label}
191
+ </span>
192
+ </span>
193
+ </label>
194
+ </div>
195
+ ))}
196
+ {errors.paymentMethod && (
197
+ <div className="text-error caption">
198
+ {errors.paymentMethod.message}
199
+ </div>
200
+ )}
201
+ </div>
202
+ )}
203
+ {orderFields?.length > 0 && (
204
+ <Suspense fallback={null}>
205
+ {orderFields.map((field) => {
206
+ const Component = formFieldsMapper[field.name];
207
+
208
+ return Component ? (
209
+ <Component
210
+ key={field.name}
211
+ name={field.name}
212
+ label={field.label}
213
+ required={field.required || false}
214
+ readOnly={field.readOnly || false}
215
+ errors={errors}
216
+ errorReqMsg={errorReqMsg}
217
+ errorInvalidEmailMsg={errorInvalidEmailMsg}
218
+ errorInvalidPhoneMsg={errorInvalidPhoneMsg}
219
+ />
220
+ ) : null;
221
+ })}
222
+ </Suspense>
223
+ )}
224
+ <Button
225
+ type="submit"
226
+ loading={loading}
227
+ buttonText={buttonText}
228
+ />
229
+ <Alert className="mt-2" msg={apiErrorMsg} />
230
+ </form>
231
+ </FormProvider>
232
+ );
233
+ };
234
+
235
+ export default OrderForm;
@@ -0,0 +1,10 @@
1
+ @import 'Assets/styles/variables.scss';
2
+
3
+ .sdk-order-form {
4
+ max-width : 320px;
5
+ margin : auto;
6
+ display : flex;
7
+ flex-direction: column;
8
+ align-items : center;
9
+ text-align : left;
10
+ }
@@ -0,0 +1,8 @@
1
+ import { PaymentMethodEnum } from 'Enums/general';
2
+
3
+ export const BASE_API = process.env.API_URL ?? '/';
4
+
5
+ export const WRONG_MSG = 'Something went wrong!';
6
+
7
+ export const PAYMENT_METHOD_DEFAULT: PaymentMethodEnum =
8
+ PaymentMethodEnum.Vipps;
@@ -0,0 +1,4 @@
1
+ export enum HttpStatusCode {
2
+ UNAUTHORIZED = 401,
3
+ FORBIDDEN = 403,
4
+ }
@@ -0,0 +1,12 @@
1
+ export enum PaymentMethodEnum {
2
+ Vipps = 'Vipps',
3
+ MobilePay = 'MobilePay',
4
+ SwedbankPay = 'SwedbankPay',
5
+ }
6
+
7
+ export enum MessageEventTypeEnum {
8
+ ORDER_FLOW_COMPLETE = 'orderFlowComplete',
9
+ VIPPS_COMPLETE = 'completeOrder',
10
+ CANCEL = 'cancel',
11
+ COMPLETE = 'complete',
12
+ }
@@ -0,0 +1,59 @@
1
+ import { useRef } from 'react';
2
+
3
+ let currentMessageReceiver: (event: Event) => void;
4
+
5
+ const useIframe = () => {
6
+ const frameRef = useRef(null);
7
+
8
+ const messageReceiver = (
9
+ callback?: (type: string, value: string) => void
10
+ ) => {
11
+ if (currentMessageReceiver) {
12
+ window.removeEventListener('message', currentMessageReceiver);
13
+ }
14
+
15
+ currentMessageReceiver = (event: Event) => {
16
+ if (!frameRef.current) {
17
+ return;
18
+ }
19
+
20
+ const childWindow = (frameRef.current as HTMLIFrameElement)
21
+ .contentWindow;
22
+
23
+ if ((event as MessageEvent).source !== childWindow) {
24
+ return; // Skip message in this event listener
25
+ }
26
+
27
+ let { value, type } = (event as MessageEvent).data as unknown as {
28
+ value: string;
29
+ type: string;
30
+ };
31
+
32
+ if (callback) {
33
+ callback(type, value);
34
+ }
35
+ };
36
+
37
+ window.addEventListener('message', currentMessageReceiver, false);
38
+
39
+ return () => {
40
+ removeMessageReceiver();
41
+ };
42
+ };
43
+
44
+ const removeMessageReceiver = () => {
45
+ if (currentMessageReceiver) {
46
+ window.removeEventListener(
47
+ 'addEventListener',
48
+ currentMessageReceiver
49
+ );
50
+ }
51
+ };
52
+
53
+ return {
54
+ frameRef,
55
+ messageReceiver,
56
+ };
57
+ };
58
+
59
+ export default useIframe;