diff --git a/demo/with-next/components/ClientContent/SendHaptic.tsx b/demo/with-next/components/ClientContent/SendHaptic.tsx new file mode 100644 index 00000000..bffee846 --- /dev/null +++ b/demo/with-next/components/ClientContent/SendHaptic.tsx @@ -0,0 +1,118 @@ +import { + MiniAppSendHapticFeedbackPayload, + MiniKit, + ResponseEvent, + SendHapticFeedbackErrorCodes, + SendHapticFeedbackInput, +} from '@worldcoin/minikit-js'; +import { useCallback, useEffect, useState } from 'react'; +import * as yup from 'yup'; +import { validateSchema } from './helpers/validate-schema'; + +const sendHapticFeedbackSuccessPayloadSchema = yup.object({ + status: yup.string<'success'>().oneOf(['success']), +}); + +const sendHapticFeedbackErrorPayloadSchema = yup.object({ + error_code: yup + .string() + .oneOf(Object.values(SendHapticFeedbackErrorCodes)) + .required(), + status: yup.string<'error'>().equals(['error']).required(), + version: yup.number().required(), +}); + +const allPossibleHaptics: SendHapticFeedbackInput[] = [ + { hapticsType: 'impact', style: 'heavy' }, + { hapticsType: 'impact', style: 'light' }, + { hapticsType: 'impact', style: 'medium' }, + { hapticsType: 'notification', style: 'error' }, + { hapticsType: 'notification', style: 'success' }, + { hapticsType: 'notification', style: 'warning' }, + { hapticsType: 'selection-changed' }, +]; + +export const SendHapticFeedback = () => { + const [sentHapticFeedbackPayload, setSentHapticFeedbackPayload] = + useState | null>(null); + + useEffect(() => { + if (!MiniKit.isInstalled()) { + return; + } + + MiniKit.subscribe( + ResponseEvent.MiniAppSendHapticFeedback, + async (payload: MiniAppSendHapticFeedbackPayload) => { + console.log('MiniAppSendHapticFeedback, SUBSCRIBE PAYLOAD', payload); + + if (payload.status === 'error') { + const validationErrorMessage = await validateSchema( + sendHapticFeedbackErrorPayloadSchema, + payload, + ); + + if (!validationErrorMessage) { + console.log('Payload is valid'); + } else { + console.error(validationErrorMessage); + } + } else { + const validationErrorMessage = await validateSchema( + sendHapticFeedbackSuccessPayloadSchema, + payload, + ); + + // This checks if the response format is correct + if (!validationErrorMessage) { + console.log('Payload is valid'); + } else { + console.error(validationErrorMessage); + } + } + }, + ); + + return () => { + MiniKit.unsubscribe(ResponseEvent.MiniAppSignTypedData); + }; + }, []); + + const onSendHapticFeedback = useCallback( + async (input: SendHapticFeedbackInput) => { + const payload = MiniKit.commands.sendHapticFeedback(input); + + setSentHapticFeedbackPayload({ + payload, + }); + }, + [], + ); + + return ( +
+
+

Sign Typed Data

+ +
+
+
+              {JSON.stringify(sentHapticFeedbackPayload, null, 2)}
+            
+
+
+
+ {allPossibleHaptics.map((haptic, i) => ( + + ))} +
+
+
+ ); +}; diff --git a/demo/with-next/components/ClientContent/SignMessage.tsx b/demo/with-next/components/ClientContent/SignMessage.tsx index a6f451bb..855410a9 100644 --- a/demo/with-next/components/ClientContent/SignMessage.tsx +++ b/demo/with-next/components/ClientContent/SignMessage.tsx @@ -5,7 +5,7 @@ import { SignMessageErrorCodes, SignMessageInput, } from '@worldcoin/minikit-js'; -import { useCallback, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import * as yup from 'yup'; import { validateSchema } from './helpers/validate-schema'; @@ -82,8 +82,7 @@ export const SignMessage = () => { const isValid = await ( await Safe.init({ - provider: - 'https://opt-mainnet.g.alchemy.com/v2/Ha76ahWcm6iDVBU7GNr5n-ONLgzWnkWc', + provider: 'https://worldchain-mainnet.g.alchemy.com/public', safeAddress: payload.address, }) ).isValidSignature(messageHash, payload.signature); @@ -92,9 +91,7 @@ export const SignMessage = () => { if (isValid) { setSignMessagePayloadVerificationMessage('Signature is valid'); } else { - setSignMessagePayloadVerificationMessage( - 'Signature is invalid (We are verifying on optimism, if you are using worldchain message andy', - ); + setSignMessagePayloadVerificationMessage('Signature is invalid'); } } }); @@ -102,19 +99,20 @@ export const SignMessage = () => { return () => { MiniKit.unsubscribe(ResponseEvent.MiniAppSignMessage); }; - }, [tempInstallFix]); + }, [messageToSign, tempInstallFix]); - const onSignMessage = useCallback(async () => { + const onSignMessage = async (message: string) => { const signMessagePayload: SignMessageInput = { - message: messageToSign, + message, }; + setMessageToSign(message); const payload = MiniKit.commands.signMessage(signMessagePayload); setSentSignMessagePayload({ payload, }); setTempInstallFix((prev) => prev + 1); - }, [messageToSign]); + }; return (
@@ -131,15 +129,14 @@ export const SignMessage = () => {
diff --git a/demo/with-next/components/ClientContent/index.tsx b/demo/with-next/components/ClientContent/index.tsx index fef4a8f5..aba89725 100644 --- a/demo/with-next/components/ClientContent/index.tsx +++ b/demo/with-next/components/ClientContent/index.tsx @@ -14,6 +14,7 @@ import { GetPermissions } from './GetPermissions'; import { Nav } from './Nav'; import { Pay } from './Pay'; import { RequestPermission } from './RequestPermissions'; +import { SendHapticFeedback } from './SendHaptic'; import { ShareContacts } from './ShareContacts'; import { SignMessage } from './SignMessage'; import { SignTypedData } from './SignTypedMessage'; @@ -84,6 +85,7 @@ export const ClientContent = () => { )}
+ test 2
@@ -106,6 +108,8 @@ export const ClientContent = () => {

+ +

diff --git a/packages/core/minikit.ts b/packages/core/minikit.ts index 3b168808..4bf8bcb7 100644 --- a/packages/core/minikit.ts +++ b/packages/core/minikit.ts @@ -15,6 +15,8 @@ import { PayCommandPayload, RequestPermissionInput, RequestPermissionPayload, + SendHapticFeedbackInput, + SendHapticFeedbackPayload, SendTransactionInput, SendTransactionPayload, ShareContactsPayload, @@ -40,6 +42,7 @@ import { MiniAppGetPermissionsPayload, MiniAppPaymentPayload, MiniAppRequestPermissionPayload, + MiniAppSendHapticFeedbackPayload, MiniAppSendTransactionPayload, MiniAppShareContactsPayload, MiniAppSignMessagePayload, @@ -71,6 +74,7 @@ export class MiniKit { [Command.ShareContacts]: 1, [Command.RequestPermission]: 1, [Command.GetPermissions]: 1, + [Command.SendHapticFeedback]: 1, }; private static isCommandAvailable = { @@ -83,6 +87,7 @@ export class MiniKit { [Command.ShareContacts]: false, [Command.RequestPermission]: false, [Command.GetPermissions]: false, + [Command.SendHapticFeedback]: false, }; private static listeners: Record = { @@ -95,6 +100,7 @@ export class MiniKit { [ResponseEvent.MiniAppShareContacts]: () => {}, [ResponseEvent.MiniAppRequestPermission]: () => {}, [ResponseEvent.MiniAppGetPermissions]: () => {}, + [ResponseEvent.MiniAppSendHapticFeedback]: () => {}, }; public static appId: string | null = null; @@ -526,6 +532,28 @@ export class MiniKit { status: 'sent', }; }, + + sendHapticFeedback: ( + payload: SendHapticFeedbackInput, + ): SendHapticFeedbackPayload | null => { + if ( + typeof window === 'undefined' || + !this.isCommandAvailable[Command.SendHapticFeedback] + ) { + console.error( + "'sendHapticFeedback' command is unavailable. Check MiniKit.install() or update the app version", + ); + return null; + } + + sendMiniKitEvent({ + command: Command.SendHapticFeedback, + version: 1, + payload, + }); + + return payload; + }, }; /** @@ -706,5 +734,24 @@ export class MiniKit { } }); }, + sendHapticFeedback: async ( + payload: SendHapticFeedbackInput, + ): AsyncHandlerReturn< + SendHapticFeedbackPayload | null, + MiniAppSendHapticFeedbackPayload + > => { + return new Promise(async (resolve, reject) => { + try { + const response = await MiniKit.awaitCommand( + ResponseEvent.MiniAppSendHapticFeedback, + Command.SendHapticFeedback, + () => this.commands.sendHapticFeedback(payload), + ); + resolve(response); + } catch (error) { + reject(error); + } + }); + }, }; } diff --git a/packages/core/types/commands.ts b/packages/core/types/commands.ts index d5c40f40..457d8e0b 100644 --- a/packages/core/types/commands.ts +++ b/packages/core/types/commands.ts @@ -14,6 +14,7 @@ export enum Command { ShareContacts = 'share-contacts', RequestPermission = 'request-permission', GetPermissions = 'get-permissions', + SendHapticFeedback = 'send-haptic-feedback', } export type WebViewBasePayload = { @@ -119,6 +120,24 @@ export type GetPermissionsInput = {}; export type GetPermissionsPayload = GetPermissionsInput; +// Anchor: Send Haptic Feedback Payload +export type SendHapticFeedbackInput = + | { + hapticsType: 'notification'; + style: 'error' | 'success' | 'warning'; + } + | { + hapticsType: 'selection-changed'; + // never necessary or used but improves DX + style?: never; + } + | { + hapticsType: 'impact'; + style: 'light' | 'medium' | 'heavy'; + }; + +export type SendHapticFeedbackPayload = SendHapticFeedbackInput; + type CommandReturnPayloadMap = { [Command.Verify]: VerifyCommandPayload; [Command.Pay]: PayCommandPayload; @@ -129,6 +148,7 @@ type CommandReturnPayloadMap = { [Command.ShareContacts]: ShareContactsPayload; [Command.RequestPermission]: RequestPermissionPayload; [Command.GetPermissions]: GetPermissionsPayload; + [Command.SendHapticFeedback]: SendHapticFeedbackPayload; }; export type CommandReturnPayload = T extends keyof CommandReturnPayloadMap ? CommandReturnPayloadMap[T] : never; diff --git a/packages/core/types/errors.ts b/packages/core/types/errors.ts index c30ef10e..74ba4626 100644 --- a/packages/core/types/errors.ts +++ b/packages/core/types/errors.ts @@ -216,3 +216,14 @@ export const GetPermissionsErrorMessage = { [GetPermissionsErrorCodes.GenericError]: 'Something unexpected went wrong. Please try again.', }; + +export enum SendHapticFeedbackErrorCodes { + GenericError = 'generic_error', + UserRejected = 'user_rejected', +} + +export const SendHapticFeedbackErrorMessage = { + [SendHapticFeedbackErrorCodes.GenericError]: + 'Something unexpected went wrong.', + [SendHapticFeedbackErrorCodes.UserRejected]: 'User rejected the request.', +}; diff --git a/packages/core/types/responses.ts b/packages/core/types/responses.ts index e740fd91..033d456b 100644 --- a/packages/core/types/responses.ts +++ b/packages/core/types/responses.ts @@ -4,6 +4,7 @@ import { GetPermissionsErrorCodes, PaymentErrorCodes, RequestPermissionErrorCodes, + SendHapticFeedbackErrorCodes, SendTransactionErrorCodes, ShareContactsErrorCodes, SignMessageErrorCodes, @@ -24,6 +25,7 @@ export enum ResponseEvent { MiniAppShareContacts = 'miniapp-share-contacts', MiniAppRequestPermission = 'miniapp-request-permission', MiniAppGetPermissions = 'miniapp-get-permissions', + MiniAppSendHapticFeedback = 'miniapp-send-haptic-feedback', } export type MiniAppVerifyActionSuccessPayload = { @@ -212,6 +214,22 @@ export type MiniAppGetPermissionsPayload = | MiniAppGetPermissionsSuccessPayload | MiniAppGetPermissionsErrorPayload; +export type MiniAppSendHapticFeedbackSuccessPayload = { + status: 'success'; + version: number; + timestamp: string; +}; + +export type MiniAppSendHapticFeedbackErrorPayload = { + status: 'error'; + error_code: SendHapticFeedbackErrorCodes; + version: number; +}; + +export type MiniAppSendHapticFeedbackPayload = + | MiniAppSendHapticFeedbackSuccessPayload + | MiniAppSendHapticFeedbackErrorPayload; + type EventPayloadMap = { [ResponseEvent.MiniAppVerifyAction]: MiniAppVerifyActionPayload; [ResponseEvent.MiniAppPayment]: MiniAppPaymentPayload; @@ -222,6 +240,7 @@ type EventPayloadMap = { [ResponseEvent.MiniAppShareContacts]: MiniAppShareContactsPayload; [ResponseEvent.MiniAppRequestPermission]: MiniAppRequestPermissionPayload; [ResponseEvent.MiniAppGetPermissions]: MiniAppGetPermissionsPayload; + [ResponseEvent.MiniAppSendHapticFeedback]: MiniAppSendHapticFeedbackPayload; }; export type EventPayload =