Skip to content

dev; changes #161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions demo/with-next/components/ClientContent/SendHaptic.tsx
Original file line number Diff line number Diff line change
@@ -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<SendHapticFeedbackErrorCodes>()
.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<Record<string, any> | 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 (
<div>
<div className="grid gap-y-2">
<h2 className="text-2xl font-bold">Sign Typed Data</h2>

<div>
<div className="bg-gray-300 min-h-[100px] p-2">
<pre className="break-all whitespace-break-spaces max-h-[250px] overflow-y-scroll ">
{JSON.stringify(sentHapticFeedbackPayload, null, 2)}
</pre>
</div>
</div>
<div className="grid grid-cols-2 gap-x-3">
{allPossibleHaptics.map((haptic, i) => (
<button
key={i}
className="bg-black text-white rounded-lg p-4 w-full"
onClick={() => onSendHapticFeedback(haptic)}
>
{`Send ${haptic.hapticsType}-${haptic?.style}`}
</button>
))}
</div>
</div>
</div>
);
};
23 changes: 10 additions & 13 deletions demo/with-next/components/ClientContent/SignMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
Expand All @@ -92,29 +91,28 @@ 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');
}
}
});

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 (
<div>
Expand All @@ -131,15 +129,14 @@ export const SignMessage = () => {
<div className="grid gap-y-2">
<button
className="bg-black text-white rounded-lg p-4 w-full"
onClick={onSignMessage}
onClick={() => onSignMessage('hello world')}
>
Sign Message
</button>
<button
className="bg-black text-white rounded-lg p-4 w-full"
onClick={async () => {
setMessageToSign('world-chat-authentication:test');
await onSignMessage();
await onSignMessage('world-chat-authentication:test');
}}
>
Fail Message
Expand Down
10 changes: 6 additions & 4 deletions demo/with-next/components/ClientContent/Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -532,10 +532,12 @@ export const SendTransaction = () => {
<div className="grid gap-y-1">
<p>Verification:</p>
{/* {sendTransactionVerificationMessage ?? "No verification yet"} */}
{transactionId && <p>Transaction ID: {transactionId}</p>}
{isConfirming && <p>Waiting for confirmation...</p>}
{isConfirmed && <p>Transaction confirmed.</p>}
{isError && <p>{error?.message}</p>}
<div className="grid gap-y-1 bg-gray-300 p-2">
{transactionId && <p>Transaction ID: {transactionId}</p>}
{isConfirming && <p>Waiting for confirmation...</p>}
{isConfirmed && <p>Transaction confirmed.</p>}
{isError && <p>{error?.message}</p>}
</div>
</div>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions demo/with-next/components/ClientContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -84,6 +85,7 @@ export const ClientContent = () => {
)}

<div className="grid gap-y-8">
test 2
<VersionsNoSSR />
<hr />
<VerifyAction />
Expand All @@ -106,6 +108,8 @@ export const ClientContent = () => {
<hr />
<CheckRequests />
<hr />
<SendHapticFeedback />
<hr />
<input className="text-xs border-black border-2" />
<ExternalLinks />
<hr />
Expand Down
47 changes: 47 additions & 0 deletions packages/core/minikit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
PayCommandPayload,
RequestPermissionInput,
RequestPermissionPayload,
SendHapticFeedbackInput,
SendHapticFeedbackPayload,
SendTransactionInput,
SendTransactionPayload,
ShareContactsPayload,
Expand All @@ -40,6 +42,7 @@ import {
MiniAppGetPermissionsPayload,
MiniAppPaymentPayload,
MiniAppRequestPermissionPayload,
MiniAppSendHapticFeedbackPayload,
MiniAppSendTransactionPayload,
MiniAppShareContactsPayload,
MiniAppSignMessagePayload,
Expand Down Expand Up @@ -71,6 +74,7 @@ export class MiniKit {
[Command.ShareContacts]: 1,
[Command.RequestPermission]: 1,
[Command.GetPermissions]: 1,
[Command.SendHapticFeedback]: 1,
};

private static isCommandAvailable = {
Expand All @@ -83,6 +87,7 @@ export class MiniKit {
[Command.ShareContacts]: false,
[Command.RequestPermission]: false,
[Command.GetPermissions]: false,
[Command.SendHapticFeedback]: false,
};

private static listeners: Record<ResponseEvent, EventHandler> = {
Expand All @@ -95,6 +100,7 @@ export class MiniKit {
[ResponseEvent.MiniAppShareContacts]: () => {},
[ResponseEvent.MiniAppRequestPermission]: () => {},
[ResponseEvent.MiniAppGetPermissions]: () => {},
[ResponseEvent.MiniAppSendHapticFeedback]: () => {},
};

public static appId: string | null = null;
Expand Down Expand Up @@ -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<WebViewBasePayload>({
command: Command.SendHapticFeedback,
version: 1,
payload,
});

return payload;
},
};

/**
Expand Down Expand Up @@ -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);
}
});
},
};
}
20 changes: 20 additions & 0 deletions packages/core/types/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum Command {
ShareContacts = 'share-contacts',
RequestPermission = 'request-permission',
GetPermissions = 'get-permissions',
SendHapticFeedback = 'send-haptic-feedback',
}

export type WebViewBasePayload = {
Expand Down Expand Up @@ -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;
Expand All @@ -129,6 +148,7 @@ type CommandReturnPayloadMap = {
[Command.ShareContacts]: ShareContactsPayload;
[Command.RequestPermission]: RequestPermissionPayload;
[Command.GetPermissions]: GetPermissionsPayload;
[Command.SendHapticFeedback]: SendHapticFeedbackPayload;
};
export type CommandReturnPayload<T extends Command> =
T extends keyof CommandReturnPayloadMap ? CommandReturnPayloadMap[T] : never;
11 changes: 11 additions & 0 deletions packages/core/types/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
};
Loading