├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── components
│ ├── avatar-creator-raw.tsx
│ ├── avatar-creator.tsx
│ └── index.ts
├── events
│ ├── asset-unlocked.event.ts
│ ├── avatar-exported.event.ts
│ ├── index.ts
│ ├── user-authorized.event.ts
│ └── user-set.event.ts
├── hooks
│ └── use-avatar-creator-url.ts
├── index.tsx
├── types.ts
└── utils.ts
└── tsconfig.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 | storybook-static
7 | .idea/
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Ready Player Me
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ready Player Me - React Avatar Creator
2 |
3 | **Ready Player Me - React Avatar Creator** is a set of components and helper methods to help implementing the Ready Player Me Avatar Creator into React projects.
4 |
5 | Codesandbox example for loading the Avatar Creator: https://codesandbox.io/p/sandbox/ready-player-me-react-avatar-creator-qxkqjf
6 |
7 | Codesandbox example for loading the Avatar Creator and visualizing the avatar with the Visage package: https://codesandbox.io/p/sandbox/ready-player-me-visage-example-l4m2k2
8 |
9 | ## Installation
10 |
11 | Ready Player Me React Avatar Creator is available as a [npm package](https://www.npmjs.com/package/@readyplayerme/react-avatar-creator).
12 |
13 | ```bash
14 | npm i @readyplayerme/react-avatar-creator
15 | ```
16 |
17 | ## Usage
18 |
19 | ```tsx
20 | import { AvatarCreator } from '@readyplayerme/react-avatar-creator';
21 |
22 | export default function App() {
23 | return ;
24 | }
25 | ```
26 |
27 | https://user-images.githubusercontent.com/3163281/235168912-a9dacd91-af3a-4b35-81c3-b025e12e333a.mp4
28 |
29 | ---
30 |
31 | # Components
32 |
33 | ## AvatarCreator
34 |
35 | AvatarCreator component helps you load Ready Player Me in an iframe where you can edit your avatar and receive your avatar URL as a post message once your avatar is exported.
36 |
37 | ### Parameters
38 |
39 | **subdomain** _[required]_: string
40 |
41 | - Your Ready Player Me subdomain. You can get one from [Ready Player Me Studio](https://studio.readyplayer.me/).
42 |
43 | **className** _[optional]_: string
44 |
45 | - The css classes to apply to the iframe.
46 |
47 | **style** _[optional]_: CSSProperties
48 |
49 | - The css styles to apply to the iframe.
50 |
51 | **config** _[optional]_: AvatarCreatorConfig
52 |
53 | - Editor Configuration is where you can set url properties of Ready Player Me editor. Read more about these options in [Ready Player Me documentations](https://docs.readyplayer.me/ready-player-me/integration-guides/web-and-native-integration/avatar-creator-integration#configuration-1).
54 |
55 | **onAvatarExported** _[optional]_: (event: AvatarExportedEvent) => void
56 |
57 | - Callback function that is called when avatar is exported.
58 |
59 | **onUserSet** _[optional]_: (event: UserSetEvent) => void
60 |
61 | - Callback function that is called when user id is set.
62 |
63 | **onAssetUnlocked** _[optional]_: (event: AssetUnlockedEvent) => void
64 |
65 | - Callback function that is called when an asset is unlocked.
66 |
67 | **onUserAuthorized** _[optional]_: (event: UserAuthorizedEvent) => void
68 |
69 | - Callback function that is called when the user is authorized.
70 |
71 | ### Example
72 |
73 | ```tsx
74 | import { AvatarCreator, AvatarCreatorConfig, AvatarExportedEvent, UserSetEvent } from '@readyplayerme/react-avatar-creator';
75 |
76 | const config: AvatarCreatorConfig = {
77 | clearCache: true,
78 | bodyType: 'fullbody',
79 | quickStart: false,
80 | language: 'en',
81 | };
82 |
83 | const style = { width: '100%', height: '100vh', border: 'none' };
84 |
85 | export default function App() {
86 | const handleOnUserSet = (event: UserSetEvent) => {
87 | console.log(`User ID is: ${event.data.id}`);
88 | };
89 |
90 | const handleOnAvatarExported = (event: AvatarExportedEvent) => {
91 | console.log(`Avatar URL is: ${event.data.url}`);
92 | };
93 |
94 | return (
95 | <>
96 |
97 | >
98 | );
99 | }
100 | ```
101 |
102 | ## AvatarCreatorRaw
103 |
104 | AvatarCreatorRaw is a lower level component that gives you everything found in the avatar creator, but without explicit callbacks for each event, so you can have the ability to create your own custom logic around these events, if you choose to do so.
105 |
106 | ### Parameters
107 |
108 | **subdomain** _[required]_: string
109 |
110 | - Your Ready Player Me subdomain. You can get one from [Ready Player Me Studio](https://studio.readyplayer.me/).
111 |
112 | **className** _[optional]_: string
113 |
114 | - The css classes to apply to the iframe.
115 |
116 | **style** _[optional]_: CSSProperties
117 |
118 | - The css styles to apply to the iframe.
119 |
120 | **config** _[optional]_: AvatarCreatorConfig
121 |
122 | - Editor Configuration is where you can set url properties of Ready Player Me editor. Read more about these options in [Ready Player Me documentations](https://docs.readyplayer.me/ready-player-me/integration-guides/web-and-native-integration/avatar-creator-integration#configuration-1).
123 |
124 | **onEventReceived** _[required]_: (event: IFrameEvent) => void
125 |
126 | - Callback function that is called whenever an AvatarCreatorEvent is published
127 |
128 | ### Example
129 |
130 | ```tsx
131 | import { AvatarCreatorConfig, AvatarCreatorEvent, AvatarCreatorRaw } from '@readyplayerme/react-avatar-creator';
132 |
133 | const config: AvatarCreatorConfig = {
134 | clearCache: true,
135 | bodyType: 'fullbody',
136 | quickStart: false,
137 | language: 'en',
138 | };
139 |
140 | const style = { width: '100%', height: '100vh', border: 'none' };
141 |
142 | export default function App() {
143 | const handleCustomEvent = (event: AvatarCreatorEvent) => {
144 | console.log(`Received custom event`, event);
145 | };
146 |
147 | return (
148 | <>
149 |
150 | >
151 | );
152 | }
153 | ```
154 |
155 | ## Using AvatarCreator with Visage
156 |
157 | If you would like to use Visage, with its full capability to edit camera and light properties of the scene and more, you can use AvatarCreator component and Visage components together.
158 |
159 | ```tsx
160 | import { Avatar } from '@readyplayerme/visage';
161 | import { AvatarCreator, AvatarCreatorConfig } from '@readyplayerme/react-avatar-creator';
162 | import { useState } from 'react';
163 |
164 | const subdomain = 'demo';
165 |
166 | const config: AvatarCreatorConfig = {
167 | clearCache: true,
168 | bodyType: 'fullbody',
169 | quickStart: false,
170 | language: 'en',
171 | };
172 |
173 | const style = { width: '100%', height: '100vh', border: 'none' };
174 |
175 | export const YourCustomComponent = () => {
176 | const [url, setUrl] = useState();
177 |
178 | if (!url) {
179 | return setUrl(event.data.url)} />;
180 | }
181 | return ;
182 | };
183 | ```
184 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@readyplayerme/react-avatar-creator",
3 | "version": "0.5.0",
4 | "description": "Ready Player Me - React Avatar Creator",
5 | "keywords": [
6 | "readyplayerme",
7 | "avatar",
8 | "react",
9 | "typescript",
10 | "glb"
11 | ],
12 | "license": "MIT",
13 | "author": "Ready Player Me",
14 | "main": "dist/index.js",
15 | "module": "dist/index.esm.js",
16 | "typings": "dist/index.d.ts",
17 | "scripts": {
18 | "analyze": "size-limit --why",
19 | "build": "rollup -c",
20 | "lint": "dts lint",
21 | "prepare": "rollup -c",
22 | "size": "size-limit",
23 | "start": "dts watch",
24 | "test": "dts test --passWithNoTests"
25 | },
26 | "husky": {
27 | "hooks": {
28 | "pre-commit": "dts lint"
29 | }
30 | },
31 | "prettier": {
32 | "printWidth": 400,
33 | "semi": true,
34 | "singleQuote": true,
35 | "trailingComma": "es5",
36 | "endOfLine": "auto"
37 | },
38 | "engines": {
39 | "node": ">=12"
40 | },
41 | "devDependencies": {
42 | "@babel/core": "^7.21.4",
43 | "@rollup/plugin-typescript": "^11.1.2",
44 | "@size-limit/preset-small-lib": "^8.2.4",
45 | "@tsconfig/recommended": "^1.0.2",
46 | "@tsconfig/vite-react": "^1.0.1",
47 | "@types/react": "^17.0.40",
48 | "@types/react-dom": "^17.0.13",
49 | "babel-loader": "^9.1.2",
50 | "dts-cli": "^2.0.2",
51 | "husky": "^8.0.3",
52 | "react": ">=17.0",
53 | "react-dom": ">=17.0",
54 | "react-is": "^18.2.0",
55 | "rollup": "^3.26.0",
56 | "rollup-plugin-dts": "^5.3.0",
57 | "size-limit": "^8.2.4",
58 | "tslib": "^2.5.0",
59 | "typescript": "^5.0.4",
60 | "vite": "^4.3.1"
61 | },
62 | "peerDependencies": {
63 | "react": ">=16"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | const typescript = require('@rollup/plugin-typescript');
2 | const { default: dts } = require('rollup-plugin-dts');
3 | const packageJson = require('./package.json');
4 |
5 | // rollup.config.js
6 | module.exports = [
7 | {
8 | input: 'src/index.tsx',
9 | output: [
10 | {
11 | file: packageJson.main,
12 | format: 'cjs',
13 | sourcemap: false,
14 | },
15 | {
16 | file: packageJson.module,
17 | format: 'esm',
18 | sourcemap: false,
19 | },
20 | ],
21 | plugins: [
22 | typescript({
23 | tsconfig: './tsconfig.json',
24 | }),
25 | ],
26 | external: ['react'],
27 | },
28 | {
29 | input: 'src/index.tsx',
30 | output: [{ file: 'dist/index.d.ts' }],
31 | plugins: [dts()],
32 | },
33 | ];
34 |
--------------------------------------------------------------------------------
/src/components/avatar-creator-raw.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties, FC, useEffect, useRef } from 'react';
2 | import { AvatarCreatorConfig } from '../types';
3 | import { JSONTryParse } from '../utils';
4 | import { AvatarCreatorEvent } from '../events';
5 | import { useAvatarCreatorUrl } from '../hooks/use-avatar-creator-url';
6 |
7 | const MESSAGE_EVENT = 'message';
8 | const RPM_TARGET = 'readyplayerme';
9 | const IFRAME_READY_EVENT = 'v1.frame.ready';
10 |
11 | export type AvatarCreatorRawProps = {
12 | subdomain: string;
13 | className?: string;
14 | style?: CSSProperties;
15 | config?: AvatarCreatorConfig;
16 | };
17 |
18 | export type EventReceivedProps = {
19 | onEventReceived?: (event: AvatarCreatorEvent) => void;
20 | };
21 |
22 | /**
23 | * AvatarCreatorRaw is a React component that allows you to create an avatar using Ready Player Me and receive avatar URL. It exposes the raw events in one callback to allow you to write more custom logic around the event handling.
24 | * @param subdomain The subdomain of your Ready Player Me instance.
25 | * @param className The css classes to apply to this iframe.
26 | * @param style The css styles to apply to this iframe.
27 | * @param avatarCreatorConfig The configuration for the AvatarCreator component.
28 | * @param onEventReceived A callback that is called when an avatar creator event is received.
29 | * @returns A React component.
30 | */
31 | export const AvatarCreatorRaw: FC = ({ subdomain, className, style, config, onEventReceived }) => {
32 | const frameRef = useRef(null);
33 | const url = useAvatarCreatorUrl(subdomain, config);
34 |
35 | const subscribeToAvatarCreatorEvents = () => {
36 | if (!frameRef.current?.contentWindow) return;
37 |
38 | frameRef.current?.contentWindow?.postMessage(
39 | JSON.stringify({
40 | target: RPM_TARGET,
41 | type: 'subscribe',
42 | eventName: 'v1.**',
43 | }),
44 | '*'
45 | );
46 | };
47 |
48 | const subscribe = (event: MessageEvent) => {
49 | const avatarCreatorEvent = JSONTryParse(event.data);
50 |
51 | if (avatarCreatorEvent?.source !== RPM_TARGET) return;
52 |
53 | if (avatarCreatorEvent?.eventName === IFRAME_READY_EVENT) {
54 | subscribeToAvatarCreatorEvents();
55 | return;
56 | }
57 |
58 | onEventReceived?.(avatarCreatorEvent);
59 | };
60 |
61 | useEffect(() => {
62 | window.addEventListener(MESSAGE_EVENT, subscribe);
63 |
64 | return () => {
65 | window.removeEventListener(MESSAGE_EVENT, subscribe);
66 | };
67 | }, []);
68 |
69 | return ;
70 | };
71 |
--------------------------------------------------------------------------------
/src/components/avatar-creator.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { AvatarCreatorEvent, UserAuthorizedEvent, AssetUnlockedEvent, AvatarExportedEvent, UserSetEvent } from '../events';
3 | import { AvatarCreatorRaw, AvatarCreatorRawProps } from './avatar-creator-raw';
4 |
5 | export type AvatarCreatorProps = {
6 | onUserSet?: (event: UserSetEvent) => void;
7 | onAvatarExported?: (event: AvatarExportedEvent) => void;
8 | onUserAuthorized?: (event: UserAuthorizedEvent) => void;
9 | onAssetUnlock?: (event: AssetUnlockedEvent) => void;
10 | } & AvatarCreatorRawProps;
11 |
12 | /**
13 | * AvatarCreator is a React component that allows you to create an avatar using Ready Player Me and receive avatar URL. It wraps AvatarCreatorRaw to provide more type safety, and to provide explicit callbacks per event type.
14 | * @param subdomain The subdomain of your Ready Player Me instance.
15 | * @param className The css classes to apply to this iframe.
16 | * @param style The css styles to apply to this iframe.
17 | * @param config The configuration for the AvatarCreator component.
18 | * @param onUserSet A callback that is called when a user is set.
19 | * @param onAvatarExported A callback that is called when an avatar is exported.
20 | * @param onUserAuthorized A callback that is called when a user is authorized.
21 | * @param onAssetUnlock A callback that is called when an asset unlock button is pressed in RPM.
22 | * @returns A React component.
23 | */
24 | export const AvatarCreator: FC = ({ subdomain, className, style, config, onUserSet, onAvatarExported, onUserAuthorized, onAssetUnlock }) => {
25 | const supportedEvents = {
26 | 'v1.avatar.exported': onAvatarExported,
27 | 'v1.user.set': onUserSet,
28 | 'v1.user.authorized': onUserAuthorized,
29 | 'v1.asset.unlock': onAssetUnlock,
30 | } as Record;
31 |
32 | const handleEvents = (event: AvatarCreatorEvent) => {
33 | supportedEvents[event.eventName!]?.(event);
34 | };
35 |
36 | return ;
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { AvatarCreator } from './avatar-creator';
2 | export { AvatarCreatorRaw } from './avatar-creator-raw';
3 |
--------------------------------------------------------------------------------
/src/events/asset-unlocked.event.ts:
--------------------------------------------------------------------------------
1 | import { IFrameEvent } from '../types';
2 |
3 | export type AssetUnlockedEventPayload = {
4 | userId: string;
5 | assetId: string;
6 | };
7 |
8 | export type AssetUnlockedEvent = IFrameEvent;
9 |
--------------------------------------------------------------------------------
/src/events/avatar-exported.event.ts:
--------------------------------------------------------------------------------
1 | import { IFrameEvent } from '../types';
2 |
3 | export type AvatarExportedEventPayload = {
4 | url: string;
5 | userId: string;
6 | avatarId: string;
7 | };
8 |
9 | export type AvatarExportedEvent = IFrameEvent;
10 |
--------------------------------------------------------------------------------
/src/events/index.ts:
--------------------------------------------------------------------------------
1 | import { UserSetEvent } from './user-set.event';
2 | import { AssetUnlockedEvent } from './asset-unlocked.event';
3 | import { UserAuthorizedEvent } from './user-authorized.event';
4 | import { AvatarExportedEvent } from './avatar-exported.event';
5 |
6 | export type { AssetUnlockedEvent, AssetUnlockedEventPayload } from './asset-unlocked.event';
7 | export type { AvatarExportedEvent, AvatarExportedEventPayload } from './avatar-exported.event';
8 | export type { UserAuthorizedEvent, UserAuthorizedEventPayload } from './user-authorized.event';
9 | export type { UserSetEvent, UserSetEventPayload } from './user-set.event';
10 |
11 | export type AvatarCreatorEvent = UserSetEvent | AssetUnlockedEvent | UserAuthorizedEvent | AvatarExportedEvent;
12 |
--------------------------------------------------------------------------------
/src/events/user-authorized.event.ts:
--------------------------------------------------------------------------------
1 | import { IFrameEvent } from '../types';
2 |
3 | export type UserAuthorizedEventPayload = {
4 | url: string;
5 | };
6 |
7 | export type UserAuthorizedEvent = IFrameEvent;
8 |
--------------------------------------------------------------------------------
/src/events/user-set.event.ts:
--------------------------------------------------------------------------------
1 | import { IFrameEvent } from '../types';
2 |
3 | export type UserSetEventPayload = {
4 | id: string;
5 | };
6 |
7 | export type UserSetEvent = IFrameEvent;
8 |
--------------------------------------------------------------------------------
/src/hooks/use-avatar-creator-url.ts:
--------------------------------------------------------------------------------
1 | import { AvatarCreatorConfig } from '../types';
2 | import { useMemo } from 'react';
3 |
4 | export const useAvatarCreatorUrl = (subdomain: string, config: AvatarCreatorConfig | undefined): string => {
5 | return useMemo(() => {
6 | let url = `https://${subdomain || `demo`}.readyplayer.me`;
7 |
8 | if (config?.language) url += `/${config.language}`;
9 | url += `/avatar?frameApi&source=react-avatar-creator`;
10 | if (config?.clearCache) url += '&clearCache';
11 | if (config?.quickStart) url += '&quickStart';
12 | if (config?.bodyType) url += `&bodyType=${config?.bodyType}`;
13 | if (config?.token) url += `&token=${config?.token}`;
14 | if (config?.avatarId) url += `&id=${config?.avatarId}`;
15 |
16 | return url;
17 | }, [subdomain, config]);
18 | };
19 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | export { AvatarCreator, AvatarCreatorRaw } from './components';
2 | export type { AvatarCreatorConfig, Language, BodyType, IFrameEvent } from './types';
3 | export type * from './events';
4 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export type BodyType = 'halfbody' | 'fullbody';
2 |
3 | export type Language = 'en' | 'en-IE' | 'de' | 'fr' | 'es' | 'es-MX' | 'it' | 'pt' | 'pt-BR' | 'tr' | 'ja' | 'kr' | 'ch';
4 |
5 | export type AvatarCreatorConfig = {
6 | clearCache?: boolean;
7 | bodyType?: BodyType;
8 | quickStart?: boolean;
9 | language?: Language;
10 | token?: string;
11 | avatarId?: string;
12 | };
13 |
14 | export type IFrameEvent = {
15 | eventName?: string;
16 | source?: string;
17 | data: TPayload;
18 | };
19 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Tries to parse the JSON data, and returns undefined if parsing fails.
3 | * @param jsonString The string to be parsed.
4 | * @returns The parsed JSON data or undefined if the data is not valid JSON.
5 | */
6 | export const JSONTryParse = (jsonString: any): T | undefined => {
7 | try {
8 | return JSON.parse(jsonString);
9 | } catch (error) {
10 | return undefined;
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
3 | "compilerOptions": {
4 | "jsx": "react",
5 | },
6 | "extends": "@tsconfig/vite-react/tsconfig.json",
7 | "include": [
8 | "src",
9 | "types",
10 | "utils"
11 | ],
12 | }
13 |
--------------------------------------------------------------------------------