├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── .nojekyll
├── assets
│ ├── highlight.css
│ ├── main.js
│ ├── search.js
│ └── style.css
├── classes
│ ├── nakama_js.Client.html
│ ├── nakama_js.DefaultSocket.html
│ ├── nakama_js.Session.html
│ ├── nakama_js.WebSocketAdapterText.html
│ ├── satori_js.Client.html
│ └── satori_js.Session.html
├── index.html
├── interfaces
│ ├── nakama_js.Channel.html
│ ├── nakama_js.ChannelMessage.html
│ ├── nakama_js.ChannelMessageAck.html
│ ├── nakama_js.ChannelMessageList.html
│ ├── nakama_js.ChannelPresenceEvent.html
│ ├── nakama_js.Friend.html
│ ├── nakama_js.FriendOfFriend.html
│ ├── nakama_js.Friends.html
│ ├── nakama_js.FriendsOfFriends.html
│ ├── nakama_js.Group.html
│ ├── nakama_js.GroupList.html
│ ├── nakama_js.GroupUser.html
│ ├── nakama_js.GroupUserList.html
│ ├── nakama_js.ISession.html
│ ├── nakama_js.LeaderboardRecord.html
│ ├── nakama_js.LeaderboardRecordList.html
│ ├── nakama_js.Match.html
│ ├── nakama_js.MatchData.html
│ ├── nakama_js.MatchPresenceEvent.html
│ ├── nakama_js.MatchmakerMatched.html
│ ├── nakama_js.MatchmakerTicket.html
│ ├── nakama_js.MatchmakerUser.html
│ ├── nakama_js.Notification.html
│ ├── nakama_js.NotificationList.html
│ ├── nakama_js.Party.html
│ ├── nakama_js.PartyCreate.html
│ ├── nakama_js.PartyData.html
│ ├── nakama_js.PartyJoinRequest.html
│ ├── nakama_js.PartyJoinRequestList.html
│ ├── nakama_js.PartyLeader.html
│ ├── nakama_js.PartyMatchmakerTicket.html
│ ├── nakama_js.PartyPresenceEvent.html
│ ├── nakama_js.Presence.html
│ ├── nakama_js.RpcResponse.html
│ ├── nakama_js.Socket.html
│ ├── nakama_js.SocketCloseHandler.html
│ ├── nakama_js.SocketError.html
│ ├── nakama_js.SocketErrorHandler.html
│ ├── nakama_js.SocketMessageHandler.html
│ ├── nakama_js.SocketOpenHandler.html
│ ├── nakama_js.Status.html
│ ├── nakama_js.StatusPresenceEvent.html
│ ├── nakama_js.StorageObject.html
│ ├── nakama_js.StorageObjectList.html
│ ├── nakama_js.StorageObjects.html
│ ├── nakama_js.StreamData.html
│ ├── nakama_js.StreamId.html
│ ├── nakama_js.StreamPresenceEvent.html
│ ├── nakama_js.SubscriptionList.html
│ ├── nakama_js.Tournament.html
│ ├── nakama_js.TournamentList.html
│ ├── nakama_js.TournamentRecordList.html
│ ├── nakama_js.User.html
│ ├── nakama_js.UserGroup.html
│ ├── nakama_js.UserGroupList.html
│ ├── nakama_js.Users.html
│ ├── nakama_js.ValidatedSubscription.html
│ ├── nakama_js.WebSocketAdapter.html
│ ├── nakama_js.WriteLeaderboardRecord.html
│ ├── nakama_js.WriteStorageObject.html
│ ├── nakama_js.WriteTournamentRecord.html
│ └── satori_js.ISession.html
├── modules.html
└── modules
│ ├── nakama_js.html
│ └── satori_js.html
├── openapi-gen
├── README.md
└── main.go
├── package-lock.json
├── package.json
├── packages
├── nakama-js-iife-example
│ ├── README.md
│ ├── dist
│ │ └── nakama-js-example.iife.js
│ ├── index.html
│ ├── index.ts
│ └── package.json
├── nakama-js-protobuf
│ ├── .gitignore
│ ├── README.md
│ ├── api
│ │ └── api.ts
│ ├── build.js
│ ├── build.mjs
│ ├── dist
│ │ ├── nakama-js-protobuf.cjs.js
│ │ ├── nakama-js-protobuf.esm.mjs
│ │ ├── nakama-js-protobuf.iife.js
│ │ ├── nakama-js-protobuf
│ │ │ ├── api
│ │ │ │ └── api.d.ts
│ │ │ ├── google
│ │ │ │ └── protobuf
│ │ │ │ │ ├── timestamp.d.ts
│ │ │ │ │ └── wrappers.d.ts
│ │ │ ├── index.d.ts
│ │ │ ├── rtapi
│ │ │ │ └── realtime.d.ts
│ │ │ └── web_socket_adapter_pb.d.ts
│ │ └── nakama-js
│ │ │ └── web_socket_adapter.d.ts
│ ├── google
│ │ └── protobuf
│ │ │ ├── timestamp.ts
│ │ │ └── wrappers.ts
│ ├── index.ts
│ ├── package.json
│ ├── rtapi
│ │ └── realtime.ts
│ ├── tsconfig.json
│ └── web_socket_adapter_pb.ts
├── nakama-js-test
│ ├── client-authenticate.test.ts
│ ├── client-friend.test.ts
│ ├── client-group.test.ts
│ ├── client-leaderboard.test.ts
│ ├── client-link.test.ts
│ ├── client-rpc.test.ts
│ ├── client-storage.test.ts
│ ├── client-user.test.ts
│ ├── client.test.ts
│ ├── index.html
│ ├── jest.config.js
│ ├── package.json
│ ├── session.test.ts
│ ├── socket-channel.test.ts
│ ├── socket-match.test.ts
│ ├── socket-matchmaker.test.ts
│ ├── socket-notification.test.ts
│ ├── socket-party.test.ts
│ ├── socket-status.test.ts
│ ├── socket.test.ts
│ ├── tsconfig.test.json
│ └── utils.ts
├── nakama-js-webpack-example
│ ├── README.md
│ ├── dist
│ │ └── main.js
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── tsconfig.json
│ └── webpack.config.js
├── nakama-js
│ ├── .gitignore
│ ├── README.md
│ ├── api.gen.ts
│ ├── build.mjs
│ ├── client.ts
│ ├── dist
│ │ ├── api.gen.d.ts
│ │ ├── client.d.ts
│ │ ├── index.d.ts
│ │ ├── nakama-js.cjs.js
│ │ ├── nakama-js.esm.mjs
│ │ ├── nakama-js.iife.js
│ │ ├── nakama-js.umd.js
│ │ ├── session.d.ts
│ │ ├── socket.d.ts
│ │ ├── utils.d.ts
│ │ └── web_socket_adapter.d.ts
│ ├── index.ts
│ ├── package.json
│ ├── rollup.config.js
│ ├── session.ts
│ ├── socket.ts
│ ├── tsconfig.json
│ ├── utils.ts
│ └── web_socket_adapter.ts
├── satori-js-test
│ ├── client.test.ts
│ ├── index.html
│ ├── jest.config.js
│ ├── package.json
│ ├── tsconfig.test.json
│ └── utils.ts
└── satori-js
│ ├── README.md
│ ├── api.gen.ts
│ ├── build.mjs
│ ├── client.ts
│ ├── dist
│ ├── api.gen.d.ts
│ ├── client.d.ts
│ ├── index.d.ts
│ ├── satori-js.cjs.js
│ ├── satori-js.esm.mjs
│ ├── satori-js.iife.js
│ ├── satori-js.umd.js
│ ├── session.d.ts
│ └── utils.d.ts
│ ├── index.ts
│ ├── package.json
│ ├── rollup.config.js
│ ├── session.ts
│ ├── tsconfig.json
│ └── utils.ts
├── tsconfig.base.json
├── tsconfig.typedoc.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/node,linux,macos,windows,sublimetext,webstorm+all
2 |
3 | .rpt2_cache/
4 |
5 | ### Linux ###
6 | *~
7 |
8 | # temporary files which can be created if a process still has a handle open of a deleted file
9 | .fuse_hidden*
10 |
11 | # KDE directory preferences
12 | .directory
13 |
14 | # Linux trash folder which might appear on any partition or disk
15 | .Trash-*
16 |
17 | # .nfs files are created when an open file is removed but is still being accessed
18 | .nfs*
19 |
20 | ### macOS ###
21 | *.DS_Store
22 | .AppleDouble
23 | .LSOverride
24 |
25 | # Icon must end with two \r
26 | Icon
27 |
28 | # Thumbnails
29 | ._*
30 |
31 | # Files that might appear in the root of a volume
32 | .DocumentRevisions-V100
33 | .fseventsd
34 | .Spotlight-V100
35 | .TemporaryItems
36 | .Trashes
37 | .VolumeIcon.icns
38 | .com.apple.timemachine.donotpresent
39 |
40 | # Directories potentially created on remote AFP share
41 | .AppleDB
42 | .AppleDesktop
43 | Network Trash Folder
44 | Temporary Items
45 | .apdisk
46 |
47 | ### Node ###
48 | # Logs
49 | logs
50 | *.log
51 | npm-debug.log*
52 | yarn-debug.log*
53 | yarn-error.log*
54 |
55 | # Runtime data
56 | pids
57 | *.pid
58 | *.seed
59 | *.pid.lock
60 |
61 | # Directory for instrumented libs generated by jscoverage/JSCover
62 | lib-cov
63 |
64 | # Coverage directory used by tools like istanbul
65 | coverage
66 |
67 | # nyc test coverage
68 | .nyc_output
69 |
70 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
71 | .grunt
72 |
73 | # Bower dependency directory (https://bower.io/)
74 | bower_components
75 |
76 | # node-waf configuration
77 | .lock-wscript
78 |
79 | # Compiled binary addons (http://nodejs.org/api/addons.html)
80 | build/Release
81 |
82 | # Dependency directories
83 | node_modules/
84 | jspm_packages/
85 |
86 | # Typescript v1 declaration files
87 | typings/
88 |
89 | # Optional npm cache directory
90 | .npm
91 |
92 | # Optional eslint cache
93 | .eslintcache
94 |
95 | # Optional REPL history
96 | .node_repl_history
97 |
98 | # Output of 'npm pack'
99 | *.tgz
100 |
101 | # Yarn
102 | .yarn-integrity
103 |
104 | **/.yarn
105 | !.yarn/releases
106 | !.yarn/plugins
107 | !.yarn/sdks
108 | !.yarn/versions
109 | .pnp.*
110 |
111 | # dotenv environment variables file
112 | .env
113 |
114 |
115 | ### SublimeText ###
116 | # cache files for sublime text
117 | *.tmlanguage.cache
118 | *.tmPreferences.cache
119 | *.stTheme.cache
120 |
121 | # workspace files are user-specific
122 | *.sublime-workspace
123 |
124 | # project files should be checked into the repository, unless a significant
125 | # proportion of contributors will probably not be using SublimeText
126 | # *.sublime-project
127 |
128 | # sftp configuration file
129 | sftp-config.json
130 |
131 | # Package control specific files
132 | Package Control.last-run
133 | Package Control.ca-list
134 | Package Control.ca-bundle
135 | Package Control.system-ca-bundle
136 | Package Control.cache/
137 | Package Control.ca-certs/
138 | Package Control.merged-ca-bundle
139 | Package Control.user-ca-bundle
140 | oscrypto-ca-bundle.crt
141 | bh_unicode_properties.cache
142 |
143 | # Sublime-github package stores a github token in this file
144 | # https://packagecontrol.io/packages/sublime-github
145 | GitHub.sublime-settings
146 |
147 | ### WebStorm+all ###
148 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
149 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
150 |
151 | # User-specific stuff:
152 | .idea/**/workspace.xml
153 | .idea/**/tasks.xml
154 | .idea/dictionaries
155 |
156 | # Sensitive or high-churn files:
157 | .idea/**/dataSources/
158 | .idea/**/dataSources.ids
159 | .idea/**/dataSources.xml
160 | .idea/**/dataSources.local.xml
161 | .idea/**/sqlDataSources.xml
162 | .idea/**/dynamic.xml
163 | .idea/**/uiDesigner.xml
164 |
165 | # Gradle:
166 | .idea/**/gradle.xml
167 | .idea/**/libraries
168 |
169 | # CMake
170 | cmake-build-debug/
171 |
172 | # Mongo Explorer plugin:
173 | .idea/**/mongoSettings.xml
174 |
175 | ## File-based project format:
176 | *.iws
177 |
178 | ## Plugin-specific files:
179 |
180 | # IntelliJ
181 | /out/
182 |
183 | # mpeltonen/sbt-idea plugin
184 | .idea_modules/
185 |
186 | # JIRA plugin
187 | atlassian-ide-plugin.xml
188 |
189 | # Cursive Clojure plugin
190 | .idea/replstate.xml
191 |
192 | # Ruby plugin and RubyMine
193 | /.rakeTasks
194 |
195 | # Crashlytics plugin (for Android Studio and IntelliJ)
196 | com_crashlytics_export_strings.xml
197 | crashlytics.properties
198 | crashlytics-build.properties
199 | fabric.properties
200 |
201 | ### WebStorm+all Patch ###
202 | # Ignores the whole idea folder
203 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
204 |
205 | .idea/
206 |
207 | ### Windows ###
208 | # Windows thumbnail cache files
209 | Thumbs.db
210 | ehthumbs.db
211 | ehthumbs_vista.db
212 |
213 | # Folder config file
214 | Desktop.ini
215 |
216 | # Recycle Bin used on file shares
217 | $RECYCLE.BIN/
218 |
219 | # Windows Installer files
220 | *.cab
221 | *.msi
222 | *.msm
223 | *.msp
224 |
225 | # Windows shortcuts
226 | *.lnk
227 |
228 | # End of https://www.gitignore.io/api/node,linux,macos,windows,sublimetext,webstorm+all
229 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/node,linux,macos,windows,sublimetext,webstorm+all
2 |
3 | src/
4 |
5 | ### Linux ###
6 | *~
7 |
8 | # temporary files which can be created if a process still has a handle open of a deleted file
9 | .fuse_hidden*
10 |
11 | # KDE directory preferences
12 | .directory
13 |
14 | # Linux trash folder which might appear on any partition or disk
15 | .Trash-*
16 |
17 | # .nfs files are created when an open file is removed but is still being accessed
18 | .nfs*
19 |
20 | ### macOS ###
21 | *.DS_Store
22 | .AppleDouble
23 | .LSOverride
24 |
25 | # Icon must end with two \r
26 | Icon
27 |
28 | # Thumbnails
29 | ._*
30 |
31 | # Files that might appear in the root of a volume
32 | .DocumentRevisions-V100
33 | .fseventsd
34 | .Spotlight-V100
35 | .TemporaryItems
36 | .Trashes
37 | .VolumeIcon.icns
38 | .com.apple.timemachine.donotpresent
39 |
40 | # Directories potentially created on remote AFP share
41 | .AppleDB
42 | .AppleDesktop
43 | Network Trash Folder
44 | Temporary Items
45 | .apdisk
46 |
47 | ### Node ###
48 | # Logs
49 | logs
50 | *.log
51 | npm-debug.log*
52 | yarn-debug.log*
53 | yarn-error.log*
54 |
55 | # Runtime data
56 | pids
57 | *.pid
58 | *.seed
59 | *.pid.lock
60 |
61 | # Directory for instrumented libs generated by jscoverage/JSCover
62 | lib-cov
63 |
64 | # Coverage directory used by tools like istanbul
65 | coverage
66 |
67 | # nyc test coverage
68 | .nyc_output
69 |
70 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
71 | .grunt
72 |
73 | # Bower dependency directory (https://bower.io/)
74 | bower_components
75 |
76 | # node-waf configuration
77 | .lock-wscript
78 |
79 | # Compiled binary addons (http://nodejs.org/api/addons.html)
80 | build/Release
81 |
82 | # Dependency directories
83 | node_modules/
84 | jspm_packages/
85 |
86 | # Typescript v1 declaration files
87 | typings/
88 |
89 | # Optional npm cache directory
90 | .npm
91 |
92 | # Optional eslint cache
93 | .eslintcache
94 |
95 | # Optional REPL history
96 | .node_repl_history
97 |
98 | # Output of 'npm pack'
99 | *.tgz
100 |
101 | # Yarn Integrity file
102 | .yarn-integrity
103 |
104 | # dotenv environment variables file
105 | .env
106 |
107 |
108 | ### SublimeText ###
109 | # cache files for sublime text
110 | *.tmlanguage.cache
111 | *.tmPreferences.cache
112 | *.stTheme.cache
113 |
114 | # workspace files are user-specific
115 | *.sublime-workspace
116 |
117 | # project files should be checked into the repository, unless a significant
118 | # proportion of contributors will probably not be using SublimeText
119 | # *.sublime-project
120 |
121 | # sftp configuration file
122 | sftp-config.json
123 |
124 | # Package control specific files
125 | Package Control.last-run
126 | Package Control.ca-list
127 | Package Control.ca-bundle
128 | Package Control.system-ca-bundle
129 | Package Control.cache/
130 | Package Control.ca-certs/
131 | Package Control.merged-ca-bundle
132 | Package Control.user-ca-bundle
133 | oscrypto-ca-bundle.crt
134 | bh_unicode_properties.cache
135 |
136 | # Sublime-github package stores a github token in this file
137 | # https://packagecontrol.io/packages/sublime-github
138 | GitHub.sublime-settings
139 |
140 | ### WebStorm+all ###
141 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
142 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
143 |
144 | # User-specific stuff:
145 | .idea/**/workspace.xml
146 | .idea/**/tasks.xml
147 | .idea/dictionaries
148 |
149 | # Sensitive or high-churn files:
150 | .idea/**/dataSources/
151 | .idea/**/dataSources.ids
152 | .idea/**/dataSources.xml
153 | .idea/**/dataSources.local.xml
154 | .idea/**/sqlDataSources.xml
155 | .idea/**/dynamic.xml
156 | .idea/**/uiDesigner.xml
157 |
158 | # Gradle:
159 | .idea/**/gradle.xml
160 | .idea/**/libraries
161 |
162 | # CMake
163 | cmake-build-debug/
164 |
165 | # Mongo Explorer plugin:
166 | .idea/**/mongoSettings.xml
167 |
168 | ## File-based project format:
169 | *.iws
170 |
171 | ## Plugin-specific files:
172 |
173 | # IntelliJ
174 | /out/
175 |
176 | # mpeltonen/sbt-idea plugin
177 | .idea_modules/
178 |
179 | # JIRA plugin
180 | atlassian-ide-plugin.xml
181 |
182 | # Cursive Clojure plugin
183 | .idea/replstate.xml
184 |
185 | # Ruby plugin and RubyMine
186 | /.rakeTasks
187 |
188 | # Crashlytics plugin (for Android Studio and IntelliJ)
189 | com_crashlytics_export_strings.xml
190 | crashlytics.properties
191 | crashlytics-build.properties
192 | fabric.properties
193 |
194 | ### WebStorm+all Patch ###
195 | # Ignores the whole idea folder
196 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
197 |
198 | .idea/
199 |
200 | ### Windows ###
201 | # Windows thumbnail cache files
202 | Thumbs.db
203 | ehthumbs.db
204 | ehthumbs_vista.db
205 |
206 | # Folder config file
207 | Desktop.ini
208 |
209 | # Recycle Bin used on file shares
210 | $RECYCLE.BIN/
211 |
212 | # Windows Installer files
213 | *.cab
214 | *.msi
215 | *.msm
216 | *.msp
217 |
218 | # Windows shortcuts
219 | *.lnk
220 |
221 | # End of https://www.gitignore.io/api/node,linux,macos,windows,sublimetext,webstorm+all
222 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | packages/nakama-js/README.md
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
--------------------------------------------------------------------------------
/docs/assets/highlight.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --light-hl-0: #000000;
3 | --dark-hl-0: #D4D4D4;
4 | --light-hl-1: #A31515;
5 | --dark-hl-1: #CE9178;
6 | --light-hl-2: #AF00DB;
7 | --dark-hl-2: #C586C0;
8 | --light-hl-3: #001080;
9 | --dark-hl-3: #9CDCFE;
10 | --light-hl-4: #0000FF;
11 | --dark-hl-4: #569CD6;
12 | --light-hl-5: #008000;
13 | --dark-hl-5: #6A9955;
14 | --light-hl-6: #795E26;
15 | --dark-hl-6: #DCDCAA;
16 | --light-hl-7: #0070C1;
17 | --dark-hl-7: #4FC1FF;
18 | --light-hl-8: #098658;
19 | --dark-hl-8: #B5CEA8;
20 | --light-hl-9: #267F99;
21 | --dark-hl-9: #4EC9B0;
22 | --light-hl-10: #EE0000;
23 | --dark-hl-10: #D7BA7D;
24 | --light-code-background: #FFFFFF;
25 | --dark-code-background: #1E1E1E;
26 | }
27 |
28 | @media (prefers-color-scheme: light) { :root {
29 | --hl-0: var(--light-hl-0);
30 | --hl-1: var(--light-hl-1);
31 | --hl-2: var(--light-hl-2);
32 | --hl-3: var(--light-hl-3);
33 | --hl-4: var(--light-hl-4);
34 | --hl-5: var(--light-hl-5);
35 | --hl-6: var(--light-hl-6);
36 | --hl-7: var(--light-hl-7);
37 | --hl-8: var(--light-hl-8);
38 | --hl-9: var(--light-hl-9);
39 | --hl-10: var(--light-hl-10);
40 | --code-background: var(--light-code-background);
41 | } }
42 |
43 | @media (prefers-color-scheme: dark) { :root {
44 | --hl-0: var(--dark-hl-0);
45 | --hl-1: var(--dark-hl-1);
46 | --hl-2: var(--dark-hl-2);
47 | --hl-3: var(--dark-hl-3);
48 | --hl-4: var(--dark-hl-4);
49 | --hl-5: var(--dark-hl-5);
50 | --hl-6: var(--dark-hl-6);
51 | --hl-7: var(--dark-hl-7);
52 | --hl-8: var(--dark-hl-8);
53 | --hl-9: var(--dark-hl-9);
54 | --hl-10: var(--dark-hl-10);
55 | --code-background: var(--dark-code-background);
56 | } }
57 |
58 | :root[data-theme='light'] {
59 | --hl-0: var(--light-hl-0);
60 | --hl-1: var(--light-hl-1);
61 | --hl-2: var(--light-hl-2);
62 | --hl-3: var(--light-hl-3);
63 | --hl-4: var(--light-hl-4);
64 | --hl-5: var(--light-hl-5);
65 | --hl-6: var(--light-hl-6);
66 | --hl-7: var(--light-hl-7);
67 | --hl-8: var(--light-hl-8);
68 | --hl-9: var(--light-hl-9);
69 | --hl-10: var(--light-hl-10);
70 | --code-background: var(--light-code-background);
71 | }
72 |
73 | :root[data-theme='dark'] {
74 | --hl-0: var(--dark-hl-0);
75 | --hl-1: var(--dark-hl-1);
76 | --hl-2: var(--dark-hl-2);
77 | --hl-3: var(--dark-hl-3);
78 | --hl-4: var(--dark-hl-4);
79 | --hl-5: var(--dark-hl-5);
80 | --hl-6: var(--dark-hl-6);
81 | --hl-7: var(--dark-hl-7);
82 | --hl-8: var(--dark-hl-8);
83 | --hl-9: var(--dark-hl-9);
84 | --hl-10: var(--dark-hl-10);
85 | --code-background: var(--dark-code-background);
86 | }
87 |
88 | .hl-0 { color: var(--hl-0); }
89 | .hl-1 { color: var(--hl-1); }
90 | .hl-2 { color: var(--hl-2); }
91 | .hl-3 { color: var(--hl-3); }
92 | .hl-4 { color: var(--hl-4); }
93 | .hl-5 { color: var(--hl-5); }
94 | .hl-6 { color: var(--hl-6); }
95 | .hl-7 { color: var(--hl-7); }
96 | .hl-8 { color: var(--hl-8); }
97 | .hl-9 { color: var(--hl-9); }
98 | .hl-10 { color: var(--hl-10); }
99 | pre, code { background: var(--code-background); }
100 |
--------------------------------------------------------------------------------
/docs/modules.html:
--------------------------------------------------------------------------------
1 |
@heroiclabs/nakama-js-base
11 |
12 |
13 |
14 |
@heroiclabs/nakama-js-base
15 |
23 |
46 |
48 |
--------------------------------------------------------------------------------
/docs/modules/satori_js.html:
--------------------------------------------------------------------------------
1 | satori-js | @heroiclabs/nakama-js-base
11 |
12 |
13 |
14 |
17 |
Module satori-js
20 |
22 |
34 |
62 |
64 |
--------------------------------------------------------------------------------
/openapi-gen/README.md:
--------------------------------------------------------------------------------
1 | openapi-gen
2 | ===========
3 |
4 | > A util command to generate Nakama server's API client from the Swagger specification.
5 |
6 | ## Usage
7 |
8 | ### Nakama
9 |
10 | ```shell
11 | go run main.go "$GOPATH/src/github.com/heroiclabs/nakama/apigrpc/apigrpc.swagger.json" "Nakama" > ../packages/nakama-js/api.gen.ts
12 | ```
13 |
14 | ### Satori
15 |
16 | ```shell
17 | go run main.go "$GOPATH/src/github.com/heroiclabs/satori/api/satori.swagger.json" "Satori" > ../packages/satori-js/api.gen.ts
18 | ```
19 |
20 | ### Rationale
21 |
22 | The TypeScript generator available with swagger-codegen depends on Node's `"url"` package. The usage in the generated code does not warrant the need for it's inclusion. We wanted to generate lean and simple code output with minimal dependencies so we built our own. This gives us complete control over the dependencies required by the Nakama JS client.
23 |
24 | The only dependencies with the generated code is implicit usage of `"fetch"` which can be resolved with a polyfill and `"base64-js"`.
25 |
26 | ### Limitations
27 |
28 | The code generator has __only__ been checked against the Swagger specification generated for Nakama server. YMMV.
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heroiclabs/nakama-js-base",
3 | "keywords": [
4 | "app server",
5 | "client library",
6 | "game server",
7 | "nakama",
8 | "realtime",
9 | "realtime chat"
10 | ],
11 | "scripts": {
12 | "docs": "typedoc --tsconfig tsconfig.typedoc.json --entryPoints ./packages/nakama-js/index.ts --entryPoints ./packages/satori-js/index.ts --gaID UA-89839802-1 --out docs"
13 | },
14 | "repository": "https://github.com/heroiclabs/nakama-js",
15 | "homepage": "https://heroiclabs.com",
16 | "bugs": "https://github.com/heroiclabs/nakama-js/issues",
17 | "author": "Chris Molozian ",
18 | "contributors": [
19 | "Andrei Mihu ",
20 | "Mo Firouz "
21 | ],
22 | "license": "Apache-2.0",
23 | "devDependencies": {
24 | "esbuild": "^0.24.0",
25 | "typedoc": "^0.26.11",
26 | "typescript": "^5.6.3"
27 | },
28 | "private": true,
29 | "workspaces": [
30 | "packages/nakama-js",
31 | "packages/nakama-js-test",
32 | "packages/satori-js",
33 | "packages/satori-js-test",
34 | "packages/nakama-js-iife-example",
35 | "packages/nakama-js-protobuf",
36 | "packages/nakama-js-webpack-example"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/packages/nakama-js-iife-example/README.md:
--------------------------------------------------------------------------------
1 | To run this example, run `yarn build` and open `index.html`.
--------------------------------------------------------------------------------
/packages/nakama-js-iife-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Nakama JS Example Browser Test
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/nakama-js-iife-example/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Client} from "@heroiclabs/nakama-js";
18 |
19 | var useSSL = false; // Enable if server is run with an SSL certificate.
20 | var client = new Client("defaultkey", "127.0.0.1", "7350", useSSL);
21 |
22 | client.authenticateCustom("test_id").then(
23 | session => { console.log("authenticated.");
24 | }).catch(e => {
25 | console.log("error authenticating.");
26 | });
27 |
--------------------------------------------------------------------------------
/packages/nakama-js-iife-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heroiclabs/nakama-js-iife-example",
3 | "version": "1.0.0",
4 | "description": "An example project that utilizes nakama-js",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "dependencies": {
8 | "@heroiclabs/nakama-js": "2.8.0"
9 | },
10 | "scripts": {
11 | "build": "npx esbuild --bundle index.ts --target=es6 --format=iife --outfile=dist/nakama-js-example.iife.js"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/.gitignore:
--------------------------------------------------------------------------------
1 | dist/**/*.js
2 | dist/**/*.js.map
3 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/README.md:
--------------------------------------------------------------------------------
1 | Nakama JavaScript Protobuf adapter
2 | ========================
3 |
4 | > Websocket adapter adding protocol buffer support to the [nakama-js](https://www.npmjs.com/package/@heroiclabs/nakama-js) client.
5 |
6 | [Nakama](https://github.com/heroiclabs/nakama) is an open-source server designed to power modern games and apps. Features include user accounts, chat, social, matchmaker, realtime multiplayer, and much [more](https://heroiclabs.com).
7 |
8 |
9 | ## Getting Started
10 |
11 | 1. Import the adapter into your project:
12 |
13 | ```shell
14 | yarn add "@heroiclabs/nakama-js-protobuf"
15 | ```
16 |
17 | 2. Pass the Protobuf adapter to build the socket object.
18 |
19 | ```js
20 | import {Client} from "@heroiclabs/nakama-js";
21 | import {WebSocketAdapterPb} from "@heroiclabs/nakama-js-protobuf"
22 |
23 | const useSSL = false; // Enable if server is run with an SSL certificate.
24 | const client = new Client("defaultkey", "127.0.0.1", 7350, useSSL);
25 |
26 | const trace = false;
27 | const socket = client.createSocket(useSSL, trace, new WebSocketAdapterPb());
28 | ```
29 |
30 | 3. Use the WebSocket:
31 |
32 | ```js
33 | socket.ondisconnect = (evt) => {
34 | console.info("Disconnected", evt);
35 | };
36 |
37 | const session = await socket.connect(session);
38 | // Socket is open.
39 | ```
40 |
41 | ### License
42 |
43 | This project is licensed under the [Apache-2 License](https://github.com/heroiclabs/nakama-js/blob/master/LICENSE).
44 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/build.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Nakama Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | const { execSync } = require("child_process");
16 |
17 | function esbuild(args) {
18 | execSync("npx esbuild --bundle index.ts --target=es6 --global-name=nakamajsprotobuf " + args)
19 | }
20 |
21 | // emit .d.ts files and perform type checking
22 | execSync("npx typescript --project tsconfig.json", {stdio: 'inherit'})
23 |
24 | esbuild(" --format=cjs --outfile=dist/nakama-js-protobuf.cjs.js")
25 | esbuild(" --format=esm --outfile=dist/nakama-js-protobuf.esm.mjs")
26 | esbuild(" --format=iife --outfile=dist/nakama-js-protobuf.iife.js")
27 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/build.mjs:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Nakama Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import esbuild from 'esbuild';
16 |
17 | // Shared esbuild config
18 | const config = {
19 | logLevel: 'info',
20 | entryPoints: ['index.ts'],
21 | bundle: true,
22 | target: 'es6',
23 | globalName: 'nakamajsprotobuf'
24 | };
25 |
26 | // Build CommonJS
27 | await esbuild.build({
28 | ...config,
29 | format: 'cjs',
30 | outfile: 'dist/nakama-js-protobuf.cjs.js'
31 | });
32 |
33 | // Build ESM
34 | await esbuild.build({
35 | ...config,
36 | format: 'esm',
37 | outfile: 'dist/nakama-js-protobuf.esm.mjs'
38 | });
39 |
40 | // Build IIFE
41 | await esbuild.build({
42 | ...config,
43 | format: 'iife',
44 | outfile: 'dist/nakama-js-protobuf.iife.js'
45 | });
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/dist/nakama-js-protobuf/google/protobuf/timestamp.d.ts:
--------------------------------------------------------------------------------
1 | import { Writer, Reader } from 'protobufjs/minimal';
2 | /**
3 | * A Timestamp represents a point in time independent of any time zone or local
4 | * calendar, encoded as a count of seconds and fractions of seconds at
5 | * nanosecond resolution. The count is relative to an epoch at UTC midnight on
6 | * January 1, 1970, in the proleptic Gregorian calendar which extends the
7 | * Gregorian calendar backwards to year one.
8 | *
9 | * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
10 | * second table is needed for interpretation, using a [24-hour linear
11 | * smear](https://developers.google.com/time/smear).
12 | *
13 | * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
14 | * restricting to that range, we ensure that we can convert to and from [RFC
15 | * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
16 | *
17 | * # Examples
18 | *
19 | * Example 1: Compute Timestamp from POSIX `time()`.
20 | *
21 | * Timestamp timestamp;
22 | * timestamp.set_seconds(time(NULL));
23 | * timestamp.set_nanos(0);
24 | *
25 | * Example 2: Compute Timestamp from POSIX `gettimeofday()`.
26 | *
27 | * struct timeval tv;
28 | * gettimeofday(&tv, NULL);
29 | *
30 | * Timestamp timestamp;
31 | * timestamp.set_seconds(tv.tv_sec);
32 | * timestamp.set_nanos(tv.tv_usec * 1000);
33 | *
34 | * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
35 | *
36 | * FILETIME ft;
37 | * GetSystemTimeAsFileTime(&ft);
38 | * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
39 | *
40 | * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
41 | * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
42 | * Timestamp timestamp;
43 | * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
44 | * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
45 | *
46 | * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
47 | *
48 | * long millis = System.currentTimeMillis();
49 | *
50 | * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
51 | * .setNanos((int) ((millis % 1000) * 1000000)).build();
52 | *
53 | *
54 | * Example 5: Compute Timestamp from current time in Python.
55 | *
56 | * timestamp = Timestamp()
57 | * timestamp.GetCurrentTime()
58 | *
59 | * # JSON Mapping
60 | *
61 | * In JSON format, the Timestamp type is encoded as a string in the
62 | * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
63 | * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
64 | * where {year} is always expressed using four digits while {month}, {day},
65 | * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
66 | * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
67 | * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
68 | * is required. A proto3 JSON serializer should always use UTC (as indicated by
69 | * "Z") when printing the Timestamp type and a proto3 JSON parser should be
70 | * able to accept both UTC and other timezones (as indicated by an offset).
71 | *
72 | * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
73 | * 01:30 UTC on January 15, 2017.
74 | *
75 | * In JavaScript, one can convert a Date object to this format using the
76 | * standard
77 | * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
78 | * method. In Python, a standard `datetime.datetime` object can be converted
79 | * to this format using
80 | * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
81 | * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
82 | * the Joda Time's [`ISODateTimeFormat.dateTime()`](
83 | * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
84 | * ) to obtain a formatter capable of generating timestamps in this format.
85 | *
86 | *
87 | */
88 | export interface Timestamp {
89 | /**
90 | * Represents seconds of UTC time since Unix epoch
91 | * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
92 | * 9999-12-31T23:59:59Z inclusive.
93 | */
94 | seconds: number;
95 | /**
96 | * Non-negative fractions of a second at nanosecond resolution. Negative
97 | * second values with fractions must still have non-negative nanos values
98 | * that count forward in time. Must be from 0 to 999,999,999
99 | * inclusive.
100 | */
101 | nanos: number;
102 | }
103 | export declare const protobufPackage = "google.protobuf";
104 | export declare const Timestamp: {
105 | encode(message: Timestamp, writer?: Writer): Writer;
106 | decode(input: Uint8Array | Reader, length?: number): Timestamp;
107 | fromJSON(object: any): Timestamp;
108 | fromPartial(object: DeepPartial): Timestamp;
109 | toJSON(message: Timestamp): unknown;
110 | };
111 | type Builtin = Date | Function | Uint8Array | string | number | undefined;
112 | export type DeepPartial = T extends Builtin ? T : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends {
113 | $case: string;
114 | } ? {
115 | [K in keyof Omit]?: DeepPartial;
116 | } & {
117 | $case: T['$case'];
118 | } : T extends {} ? {
119 | [K in keyof T]?: DeepPartial;
120 | } : Partial;
121 | export {};
122 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/dist/nakama-js-protobuf/google/protobuf/wrappers.d.ts:
--------------------------------------------------------------------------------
1 | import { Writer, Reader } from 'protobufjs/minimal';
2 | /**
3 | * Wrapper message for `double`.
4 | *
5 | * The JSON representation for `DoubleValue` is JSON number.
6 | */
7 | export interface DoubleValue {
8 | /**
9 | * The double value.
10 | */
11 | value: number;
12 | }
13 | /**
14 | * Wrapper message for `float`.
15 | *
16 | * The JSON representation for `FloatValue` is JSON number.
17 | */
18 | export interface FloatValue {
19 | /**
20 | * The float value.
21 | */
22 | value: number;
23 | }
24 | /**
25 | * Wrapper message for `int64`.
26 | *
27 | * The JSON representation for `Int64Value` is JSON string.
28 | */
29 | export interface Int64Value {
30 | /**
31 | * The int64 value.
32 | */
33 | value: number;
34 | }
35 | /**
36 | * Wrapper message for `uint64`.
37 | *
38 | * The JSON representation for `UInt64Value` is JSON string.
39 | */
40 | export interface UInt64Value {
41 | /**
42 | * The uint64 value.
43 | */
44 | value: number;
45 | }
46 | /**
47 | * Wrapper message for `int32`.
48 | *
49 | * The JSON representation for `Int32Value` is JSON number.
50 | */
51 | export interface Int32Value {
52 | /**
53 | * The int32 value.
54 | */
55 | value: number;
56 | }
57 | /**
58 | * Wrapper message for `uint32`.
59 | *
60 | * The JSON representation for `UInt32Value` is JSON number.
61 | */
62 | export interface UInt32Value {
63 | /**
64 | * The uint32 value.
65 | */
66 | value: number;
67 | }
68 | /**
69 | * Wrapper message for `bool`.
70 | *
71 | * The JSON representation for `BoolValue` is JSON `true` and `false`.
72 | */
73 | export interface BoolValue {
74 | /**
75 | * The bool value.
76 | */
77 | value: boolean;
78 | }
79 | /**
80 | * Wrapper message for `string`.
81 | *
82 | * The JSON representation for `StringValue` is JSON string.
83 | */
84 | export interface StringValue {
85 | /**
86 | * The string value.
87 | */
88 | value: string;
89 | }
90 | /**
91 | * Wrapper message for `bytes`.
92 | *
93 | * The JSON representation for `BytesValue` is JSON string.
94 | */
95 | export interface BytesValue {
96 | /**
97 | * The bytes value.
98 | */
99 | value: Uint8Array;
100 | }
101 | export declare const protobufPackage = "google.protobuf";
102 | export declare const DoubleValue: {
103 | encode(message: DoubleValue, writer?: Writer): Writer;
104 | decode(input: Uint8Array | Reader, length?: number): DoubleValue;
105 | fromJSON(object: any): DoubleValue;
106 | fromPartial(object: DeepPartial): DoubleValue;
107 | toJSON(message: DoubleValue): unknown;
108 | };
109 | export declare const FloatValue: {
110 | encode(message: FloatValue, writer?: Writer): Writer;
111 | decode(input: Uint8Array | Reader, length?: number): FloatValue;
112 | fromJSON(object: any): FloatValue;
113 | fromPartial(object: DeepPartial): FloatValue;
114 | toJSON(message: FloatValue): unknown;
115 | };
116 | export declare const Int64Value: {
117 | encode(message: Int64Value, writer?: Writer): Writer;
118 | decode(input: Uint8Array | Reader, length?: number): Int64Value;
119 | fromJSON(object: any): Int64Value;
120 | fromPartial(object: DeepPartial): Int64Value;
121 | toJSON(message: Int64Value): unknown;
122 | };
123 | export declare const UInt64Value: {
124 | encode(message: UInt64Value, writer?: Writer): Writer;
125 | decode(input: Uint8Array | Reader, length?: number): UInt64Value;
126 | fromJSON(object: any): UInt64Value;
127 | fromPartial(object: DeepPartial): UInt64Value;
128 | toJSON(message: UInt64Value): unknown;
129 | };
130 | export declare const Int32Value: {
131 | encode(message: Int32Value, writer?: Writer): Writer;
132 | decode(input: Uint8Array | Reader, length?: number): Int32Value;
133 | fromJSON(object: any): Int32Value;
134 | fromPartial(object: DeepPartial): Int32Value;
135 | toJSON(message: Int32Value): unknown;
136 | };
137 | export declare const UInt32Value: {
138 | encode(message: UInt32Value, writer?: Writer): Writer;
139 | decode(input: Uint8Array | Reader, length?: number): UInt32Value;
140 | fromJSON(object: any): UInt32Value;
141 | fromPartial(object: DeepPartial): UInt32Value;
142 | toJSON(message: UInt32Value): unknown;
143 | };
144 | export declare const BoolValue: {
145 | encode(message: BoolValue, writer?: Writer): Writer;
146 | decode(input: Uint8Array | Reader, length?: number): BoolValue;
147 | fromJSON(object: any): BoolValue;
148 | fromPartial(object: DeepPartial): BoolValue;
149 | toJSON(message: BoolValue): unknown;
150 | };
151 | export declare const StringValue: {
152 | encode(message: StringValue, writer?: Writer): Writer;
153 | decode(input: Uint8Array | Reader, length?: number): StringValue;
154 | fromJSON(object: any): StringValue;
155 | fromPartial(object: DeepPartial): StringValue;
156 | toJSON(message: StringValue): unknown;
157 | };
158 | export declare const BytesValue: {
159 | encode(message: BytesValue, writer?: Writer): Writer;
160 | decode(input: Uint8Array | Reader, length?: number): BytesValue;
161 | fromJSON(object: any): BytesValue;
162 | fromPartial(object: DeepPartial): BytesValue;
163 | toJSON(message: BytesValue): unknown;
164 | };
165 | type Builtin = Date | Function | Uint8Array | string | number | undefined;
166 | export type DeepPartial = T extends Builtin ? T : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends {
167 | $case: string;
168 | } ? {
169 | [K in keyof Omit]?: DeepPartial;
170 | } & {
171 | $case: T['$case'];
172 | } : T extends {} ? {
173 | [K in keyof T]?: DeepPartial;
174 | } : Partial;
175 | export {};
176 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/dist/nakama-js-protobuf/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from "./web_socket_adapter_pb";
2 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/dist/nakama-js-protobuf/web_socket_adapter_pb.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import { WebSocketAdapter, SocketCloseHandler, SocketErrorHandler, SocketMessageHandler, SocketOpenHandler } from "../nakama-js/web_socket_adapter";
17 | /**
18 | * A protocol buffer socket adapter that accepts and transmits payloads using the protobuf binary wire format.
19 | */
20 | export declare class WebSocketAdapterPb implements WebSocketAdapter {
21 | private _socket?;
22 | constructor();
23 | get onClose(): SocketCloseHandler | null;
24 | set onClose(value: SocketCloseHandler | null);
25 | get onError(): SocketErrorHandler | null;
26 | set onError(value: SocketErrorHandler | null);
27 | get onMessage(): SocketMessageHandler | null;
28 | set onMessage(value: SocketMessageHandler | null);
29 | get onOpen(): SocketOpenHandler | null;
30 | set onOpen(value: SocketOpenHandler | null);
31 | isOpen(): boolean;
32 | close(): void;
33 | connect(scheme: string, host: string, port: string, createStatus: boolean, token: string): void;
34 | send(msg: any): void;
35 | }
36 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/dist/nakama-js/web_socket_adapter.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * An interface used by Nakama's web socket to determine the payload protocol.
18 | */
19 | export interface WebSocketAdapter {
20 | /**
21 | * Dispatched when the web socket closes.
22 | */
23 | onClose: SocketCloseHandler | null;
24 | /**
25 | * Dispatched when the web socket receives an error.
26 | */
27 | onError: SocketErrorHandler | null;
28 | /**
29 | * Dispatched when the web socket receives a normal message.
30 | */
31 | onMessage: SocketMessageHandler | null;
32 | /**
33 | * Dispatched when the web socket opens.
34 | */
35 | onOpen: SocketOpenHandler | null;
36 | isOpen(): boolean;
37 | close(): void;
38 | connect(scheme: string, host: string, port: string, createStatus: boolean, token: string): void;
39 | send(message: any): void;
40 | }
41 | /**
42 | * SocketCloseHandler defines a lambda that handles WebSocket close events.
43 | */
44 | export interface SocketCloseHandler {
45 | (this: WebSocket, evt: CloseEvent): void;
46 | }
47 | /**
48 | * SocketErrorHandler defines a lambda that handles responses from the server via WebSocket
49 | * that indicate an error.
50 | */
51 | export interface SocketErrorHandler {
52 | (this: WebSocket, evt: Event): void;
53 | }
54 | /**
55 | * SocketMessageHandler defines a lambda that handles valid WebSocket messages.
56 | */
57 | export interface SocketMessageHandler {
58 | (message: any): void;
59 | }
60 | /**
61 | * SocketOpenHandler defines a lambda that handles WebSocket open events.
62 | */
63 | export interface SocketOpenHandler {
64 | (this: WebSocket, evt: Event): void;
65 | }
66 | /**
67 | * A text-based socket adapter that accepts and transmits payloads over UTF-8.
68 | */
69 | export declare class WebSocketAdapterText implements WebSocketAdapter {
70 | private _socket?;
71 | get onClose(): SocketCloseHandler | null;
72 | set onClose(value: SocketCloseHandler | null);
73 | get onError(): SocketErrorHandler | null;
74 | set onError(value: SocketErrorHandler | null);
75 | get onMessage(): SocketMessageHandler | null;
76 | set onMessage(value: SocketMessageHandler | null);
77 | get onOpen(): SocketOpenHandler | null;
78 | set onOpen(value: SocketOpenHandler | null);
79 | isOpen(): boolean;
80 | connect(scheme: string, host: string, port: string, createStatus: boolean, token: string): void;
81 | close(): void;
82 | send(msg: any): void;
83 | }
84 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/google/protobuf/timestamp.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // @ts-ignore
3 | import * as Long from 'long';
4 | import { Writer, Reader, util, configure } from 'protobufjs/minimal';
5 |
6 |
7 | /**
8 | * A Timestamp represents a point in time independent of any time zone or local
9 | * calendar, encoded as a count of seconds and fractions of seconds at
10 | * nanosecond resolution. The count is relative to an epoch at UTC midnight on
11 | * January 1, 1970, in the proleptic Gregorian calendar which extends the
12 | * Gregorian calendar backwards to year one.
13 | *
14 | * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
15 | * second table is needed for interpretation, using a [24-hour linear
16 | * smear](https://developers.google.com/time/smear).
17 | *
18 | * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
19 | * restricting to that range, we ensure that we can convert to and from [RFC
20 | * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
21 | *
22 | * # Examples
23 | *
24 | * Example 1: Compute Timestamp from POSIX `time()`.
25 | *
26 | * Timestamp timestamp;
27 | * timestamp.set_seconds(time(NULL));
28 | * timestamp.set_nanos(0);
29 | *
30 | * Example 2: Compute Timestamp from POSIX `gettimeofday()`.
31 | *
32 | * struct timeval tv;
33 | * gettimeofday(&tv, NULL);
34 | *
35 | * Timestamp timestamp;
36 | * timestamp.set_seconds(tv.tv_sec);
37 | * timestamp.set_nanos(tv.tv_usec * 1000);
38 | *
39 | * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
40 | *
41 | * FILETIME ft;
42 | * GetSystemTimeAsFileTime(&ft);
43 | * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
44 | *
45 | * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
46 | * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
47 | * Timestamp timestamp;
48 | * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
49 | * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
50 | *
51 | * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
52 | *
53 | * long millis = System.currentTimeMillis();
54 | *
55 | * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
56 | * .setNanos((int) ((millis % 1000) * 1000000)).build();
57 | *
58 | *
59 | * Example 5: Compute Timestamp from current time in Python.
60 | *
61 | * timestamp = Timestamp()
62 | * timestamp.GetCurrentTime()
63 | *
64 | * # JSON Mapping
65 | *
66 | * In JSON format, the Timestamp type is encoded as a string in the
67 | * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
68 | * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
69 | * where {year} is always expressed using four digits while {month}, {day},
70 | * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
71 | * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
72 | * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
73 | * is required. A proto3 JSON serializer should always use UTC (as indicated by
74 | * "Z") when printing the Timestamp type and a proto3 JSON parser should be
75 | * able to accept both UTC and other timezones (as indicated by an offset).
76 | *
77 | * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
78 | * 01:30 UTC on January 15, 2017.
79 | *
80 | * In JavaScript, one can convert a Date object to this format using the
81 | * standard
82 | * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
83 | * method. In Python, a standard `datetime.datetime` object can be converted
84 | * to this format using
85 | * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
86 | * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
87 | * the Joda Time's [`ISODateTimeFormat.dateTime()`](
88 | * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
89 | * ) to obtain a formatter capable of generating timestamps in this format.
90 | *
91 | *
92 | */
93 | export interface Timestamp {
94 | /**
95 | * Represents seconds of UTC time since Unix epoch
96 | * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
97 | * 9999-12-31T23:59:59Z inclusive.
98 | */
99 | seconds: number;
100 | /**
101 | * Non-negative fractions of a second at nanosecond resolution. Negative
102 | * second values with fractions must still have non-negative nanos values
103 | * that count forward in time. Must be from 0 to 999,999,999
104 | * inclusive.
105 | */
106 | nanos: number;
107 | }
108 |
109 | const baseTimestamp: object = {
110 | seconds: 0,
111 | nanos: 0,
112 | };
113 |
114 | function longToNumber(long: Long) {
115 | if (long.gt(Number.MAX_SAFE_INTEGER)) {
116 | throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
117 | }
118 | return long.toNumber();
119 | }
120 |
121 | export const protobufPackage = 'google.protobuf'
122 |
123 | export const Timestamp = {
124 | encode(message: Timestamp, writer: Writer = Writer.create()): Writer {
125 | writer.uint32(8).int64(message.seconds);
126 | writer.uint32(16).int32(message.nanos);
127 | return writer;
128 | },
129 | decode(input: Uint8Array | Reader, length?: number): Timestamp {
130 | const reader = input instanceof Uint8Array ? new Reader(input) : input;
131 | let end = length === undefined ? reader.len : reader.pos + length;
132 | const message = { ...baseTimestamp } as Timestamp;
133 | while (reader.pos < end) {
134 | const tag = reader.uint32();
135 | switch (tag >>> 3) {
136 | case 1:
137 | message.seconds = longToNumber(reader.int64() as Long);
138 | break;
139 | case 2:
140 | message.nanos = reader.int32();
141 | break;
142 | default:
143 | reader.skipType(tag & 7);
144 | break;
145 | }
146 | }
147 | return message;
148 | },
149 | fromJSON(object: any): Timestamp {
150 | const message = { ...baseTimestamp } as Timestamp;
151 | if (object.seconds !== undefined && object.seconds !== null) {
152 | message.seconds = Number(object.seconds);
153 | }
154 | if (object.nanos !== undefined && object.nanos !== null) {
155 | message.nanos = Number(object.nanos);
156 | }
157 | return message;
158 | },
159 | fromPartial(object: DeepPartial): Timestamp {
160 | const message = { ...baseTimestamp } as Timestamp;
161 | if (object.seconds !== undefined && object.seconds !== null) {
162 | message.seconds = object.seconds;
163 | }
164 | if (object.nanos !== undefined && object.nanos !== null) {
165 | message.nanos = object.nanos;
166 | }
167 | return message;
168 | },
169 | toJSON(message: Timestamp): unknown {
170 | const obj: any = {};
171 | message.seconds !== undefined && (obj.seconds = message.seconds);
172 | message.nanos !== undefined && (obj.nanos = message.nanos);
173 | return obj;
174 | },
175 | };
176 |
177 | if (util.Long !== Long as any) {
178 | util.Long = Long as any;
179 | configure();
180 | }
181 |
182 | type Builtin = Date | Function | Uint8Array | string | number | undefined;
183 | export type DeepPartial = T extends Builtin
184 | ? T
185 | : T extends Array
186 | ? Array>
187 | : T extends ReadonlyArray
188 | ? ReadonlyArray>
189 | : T extends { $case: string }
190 | ? { [K in keyof Omit]?: DeepPartial } & { $case: T['$case'] }
191 | : T extends {}
192 | ? { [K in keyof T]?: DeepPartial }
193 | : Partial;
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./web_socket_adapter_pb";
2 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heroiclabs/nakama-js-protobuf",
3 | "version": "1.5.0",
4 | "description": "Websocket adapter adding protocol buffer support to the Nakama Javascript client.",
5 | "main": "dist/nakama-js-protobuf.cjs.js",
6 | "module": "dist/nakama-js-protobuf.esm.mjs",
7 | "types": "dist/nakama-js-protobuf/index.d.ts",
8 | "exports": {
9 | "./package.json": "./package.json",
10 | ".": {
11 | "import": "./dist/nakama-js-protobuf.esm.mjs",
12 | "require": "./dist/nakama-js-protobuf.cjs.js"
13 | }
14 | },
15 | "scripts": {
16 | "build": "npx tsc && node build.mjs"
17 | },
18 | "devDependencies": {
19 | "ts-proto": "^1.80.1",
20 | "ts-proto-descriptors": "1.2.1"
21 | },
22 | "keywords": [
23 | "app server",
24 | "client library",
25 | "game server",
26 | "nakama",
27 | "realtime",
28 | "realtime chat"
29 | ],
30 | "repository": "https://github.com/heroiclabs/nakama-js",
31 | "homepage": "https://heroiclabs.com",
32 | "bugs": "https://github.com/heroiclabs/nakama-js/issues",
33 | "author": "Chris Molozian ",
34 | "contributors": [
35 | "Andrei Mihu ",
36 | "Mo Firouz ",
37 | "Milton Candelero "
38 | ],
39 | "license": "Apache-2.0",
40 | "dependencies": {
41 | "@scarf/scarf": "^1.4.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": [
4 | "./*",
5 | ],
6 | "compilerOptions": {
7 | "outDir": "dist"
8 | },
9 | "declarationDir": "dist"
10 | }
11 |
--------------------------------------------------------------------------------
/packages/nakama-js-protobuf/web_socket_adapter_pb.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { WebSocketAdapter, SocketCloseHandler, SocketErrorHandler, SocketMessageHandler, SocketOpenHandler } from "../nakama-js/web_socket_adapter"
18 | import * as tsproto from "./rtapi/realtime"
19 |
20 | /**
21 | * A protocol buffer socket adapter that accepts and transmits payloads using the protobuf binary wire format.
22 | */
23 | export class WebSocketAdapterPb implements WebSocketAdapter {
24 |
25 | private _socket?: WebSocket;
26 |
27 | constructor() {
28 | }
29 |
30 | get onClose(): SocketCloseHandler | null {
31 | return this._socket!.onclose;
32 | }
33 |
34 | set onClose(value: SocketCloseHandler | null) {
35 | this._socket!.onclose = value;
36 | }
37 |
38 | get onError(): SocketErrorHandler | null {
39 | return this._socket!.onerror;
40 | }
41 |
42 | set onError(value: SocketErrorHandler | null) {
43 | this._socket!.onerror = value;
44 | }
45 |
46 | get onMessage(): SocketMessageHandler | null {
47 | return this._socket!.onmessage;
48 | }
49 |
50 | set onMessage(value: SocketMessageHandler | null) {
51 |
52 | if (value) {
53 | this._socket!.onmessage = (evt: MessageEvent) => {
54 | const buffer: ArrayBuffer = evt.data;
55 | const uintBuffer: Uint8Array = new Uint8Array(buffer);
56 | const envelope = tsproto.Envelope.decode(uintBuffer);
57 |
58 | if (envelope.channel_message) {
59 | if (envelope.channel_message.code == undefined) {
60 | //protobuf plugin does not default-initialize missing Int32Value fields
61 | envelope.channel_message.code = 0;
62 | }
63 | if (envelope.channel_message.persistent == undefined) {
64 | //protobuf plugin does not default-initialize missing BoolValue fields
65 | envelope.channel_message.persistent = false;
66 | }
67 | }
68 |
69 | value!(envelope);
70 | };
71 | }
72 | else {
73 | value = null;
74 | }
75 | }
76 |
77 | get onOpen(): SocketOpenHandler | null {
78 | return this._socket!.onopen;
79 | }
80 |
81 | set onOpen(value: SocketOpenHandler | null) {
82 | this._socket!.onopen = value;
83 | }
84 |
85 | isOpen(): boolean {
86 | return this._socket?.readyState == WebSocket.OPEN;
87 | }
88 |
89 | close() {
90 | this._socket!.close();
91 | this._socket = undefined;
92 | }
93 |
94 | connect(scheme: string, host: string, port: string, createStatus: boolean, token: string): void {
95 | const url = `${scheme}${host}:${port}/ws?lang=en&status=${encodeURIComponent(createStatus.toString())}&token=${encodeURIComponent(token)}&format=protobuf`;
96 | this._socket = new WebSocket(url);
97 | this._socket.binaryType = "arraybuffer";
98 | }
99 |
100 | send(msg: any): void {
101 |
102 | if (msg.match_data_send) {
103 | let payload = msg.match_data_send.data;
104 | // can't send a string over protobuf
105 | if (typeof payload == "string") {
106 | msg.match_data_send.data = new TextEncoder().encode(payload);
107 | }
108 | } else if (msg.party_data_send) {
109 | let payload = msg.party_data_send.data;
110 | // can't send a string over protobuf
111 | if (typeof payload == "string") {
112 | msg.party_data_send.data = new TextEncoder().encode(payload);
113 | }
114 | }
115 |
116 | const envelopeWriter = tsproto.Envelope.encode(tsproto.Envelope.fromPartial(msg));
117 | const encodedMsg = envelopeWriter.finish();
118 | this._socket!.send(encodedMsg);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/client-authenticate.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Page} from "puppeteer";
18 | import * as nakamajs from "@heroiclabs/nakama-js";
19 | import {createPage, createFacebookInstantGameAuthToken, generateid} from "./utils";
20 | import {describe, expect, it} from '@jest/globals'
21 |
22 | describe('Authenticate Tests', () => {
23 |
24 | it('should authenticate with email', async () => {
25 | const page : Page = await createPage();
26 |
27 | const email = generateid() + "@example.com";
28 | const password = generateid();
29 |
30 | const session = await page.evaluate(async (email, password) => {
31 | const client = new nakamajs.Client();
32 | const promise = client.authenticateEmail(email, password);
33 | return promise;
34 | }, email, password);
35 |
36 | expect(session).not.toBeNull();
37 | expect(session.token).not.toBeNull();
38 | });
39 |
40 | it('should authenticate with device id', async () => {
41 | const page : Page = await createPage();
42 |
43 | const deviceid = generateid();
44 |
45 | const session = await page.evaluate((deviceid) => {
46 | const client = new nakamajs.Client();
47 | return client.authenticateDevice(deviceid);
48 | }, deviceid);
49 |
50 | expect(session).not.toBeNull();
51 | expect(session.token).not.toBeNull();
52 | });
53 |
54 | it('should authenticate with custom id', async () => {
55 | const page : Page = await createPage();
56 |
57 | const customid = generateid();
58 |
59 | const session = await page.evaluate((customid) => {
60 | const client = new nakamajs.Client();
61 | return client.authenticateCustom(customid);
62 | }, customid);
63 |
64 | expect(session).not.toBeNull();
65 | expect(session.token).not.toBeNull();
66 | });
67 |
68 | it('should authenticate with user variables', async () => {
69 | const page : Page = await createPage();
70 |
71 | const customid = generateid();
72 | const customUsername = generateid();
73 |
74 | const session = await page.evaluate((customid, customUsername) => {
75 | let vars = {};
76 | vars["testString"] = "testValue";
77 |
78 | const client = new nakamajs.Client();
79 | return client.authenticateCustom(customid, true, customUsername, vars);
80 | }, customid, customUsername);
81 |
82 | expect(session).not.toBeNull();
83 | expect(session.token).not.toBeNull();
84 | expect(session.vars).not.toBeNull();
85 | expect(session.vars["testString"]).toBe("testValue");
86 |
87 | });
88 |
89 | it('should fail to authenticate with new custom id', async () => {
90 | const page : Page = await createPage();
91 |
92 | const customid = generateid();
93 | const result = await page.evaluate(async (customid) => {
94 | const client = new nakamajs.Client();
95 | try {
96 | // Expects exception.
97 | return await client.authenticateCustom(customid, false);
98 | } catch (err) {
99 | return err;
100 | }
101 | }, customid);
102 |
103 | expect(result).not.toBeNull();
104 | });
105 |
106 | it('should authenticate with custom id twice', async () => {
107 | const page : Page = await createPage();
108 |
109 | const customid = "someuniquecustomid";
110 |
111 | const session = await page.evaluate(async (customid) => {
112 | const client = new nakamajs.Client();
113 | await client.authenticateCustom(customid);
114 | return await client.authenticateCustom(customid);
115 | }, customid);
116 |
117 | expect(session).not.toBeNull();
118 | expect(session.token).not.toBeNull();
119 | });
120 |
121 | it('should fail authenticate with custom id', async () => {
122 | const page : Page = await createPage();
123 |
124 | const result = await page.evaluate(async () => {
125 | const client = new nakamajs.Client();
126 | try {
127 | // Expects exception.
128 | return await client.authenticateCustom("");
129 | } catch (err) {
130 | return err;
131 | }
132 | });
133 |
134 | expect(result).not.toBeNull();
135 | });
136 |
137 | it.skip('should authenticate with facebook instant games', async () => {
138 | let token : string = createFacebookInstantGameAuthToken("a_player_id");
139 | const page : Page = await createPage();
140 |
141 | const session = await page.evaluate((token) => {
142 | const client = new nakamajs.Client();
143 | return client.authenticateFacebookInstantGame(token);
144 | }, token);
145 |
146 | expect(session).not.toBeNull();
147 | expect(session.token).not.toBeNull();
148 | });
149 | });
--------------------------------------------------------------------------------
/packages/nakama-js-test/client-friend.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Page} from "puppeteer"
18 | import * as nakamajs from "@heroiclabs/nakama-js/client";
19 | import {createFacebookInstantGameAuthToken, createPage, generateid} from "./utils";
20 | import {describe, expect, it} from '@jest/globals'
21 |
22 | describe('Friend Tests', () => {
23 |
24 | it('should add friend, then list', async () => {
25 | const page : Page = await createPage();
26 |
27 | const customid1 = generateid();
28 | const customid2 = generateid();
29 |
30 | const result = await page.evaluate(async (customid1, customid2) => {
31 | const client1 = new nakamajs.Client();
32 | const session1 = await client1.authenticateCustom(customid1);
33 | const client2 = new nakamajs.Client();
34 | const session2 = await client2.authenticateCustom(customid2);
35 |
36 | await client1.addFriends(session1, [session2.user_id]);
37 | return await client1.listFriends(session1);
38 | }, customid1, customid2);
39 |
40 | expect(result).not.toBeNull();
41 | expect(result.friends!.length).toBe(1);
42 | expect(result.friends![0].state).toBe(1);
43 | });
44 |
45 | it('should receive friend invite, then list', async () => {
46 | const page : Page = await createPage();
47 |
48 | const customid1 = generateid();
49 | const customid2 = generateid();
50 |
51 | const result = await page.evaluate(async (customid1, customid2) => {
52 | const client1 = new nakamajs.Client();
53 | const session1 = await client1.authenticateCustom(customid1);
54 | const client2 = new nakamajs.Client();
55 | const session2 = await client2.authenticateCustom(customid2);
56 |
57 | await client1.addFriends(session1, [session2.user_id]);
58 | return await client2.listFriends(session2);
59 | }, customid1, customid2);
60 |
61 | expect(result).not.toBeNull();
62 | expect(result.friends!.length).toBe(1);
63 | expect(result.friends![0].state).toBe(2);
64 | });
65 |
66 | it('should block friend, then list', async () => {
67 | const page : Page = await createPage();
68 |
69 | const customid1 = generateid();
70 | const customid2 = generateid();
71 |
72 | const result = await page.evaluate(async (customid1, customid2) => {
73 | const client1 = new nakamajs.Client();
74 | const session1 = await client1.authenticateCustom(customid1);
75 | const client2 = new nakamajs.Client();
76 | const session2 = await client2.authenticateCustom(customid2);
77 |
78 | await client1.blockFriends(session1, [session2.user_id]);
79 | return await client1.listFriends(session1);
80 | }, customid1, customid2);
81 |
82 | expect(result).not.toBeNull();
83 | expect(result.friends!.length).toBe(1);
84 | expect(result.friends![0].state).toBe(3);
85 | });
86 |
87 | it('should add friend, accept, then list', async () => {
88 | const page : Page = await createPage();
89 |
90 | const customid1 = generateid();
91 | const customid2 = generateid();
92 |
93 | const result = await page.evaluate(async (customid1, customid2) => {
94 | const client1 = new nakamajs.Client();
95 | const session1 = await client1.authenticateCustom(customid1);
96 | const client2 = new nakamajs.Client();
97 | const session2 = await client2.authenticateCustom(customid2);
98 |
99 | await client1.addFriends(session1, [session2.user_id]);
100 | await client2.addFriends(session2, [session1.user_id]);
101 | return await client1.listFriends(session1);
102 | }, customid1, customid2);
103 |
104 | expect(result).not.toBeNull();
105 | expect(result.friends!.length).toBe(1);
106 | expect(result.friends![0].state).toBe(0);
107 | });
108 |
109 | it('should add friend, reject, then list', async () => {
110 | const page : Page = await createPage();
111 |
112 | const customid1 = generateid();
113 | const customid2 = generateid();
114 |
115 | const result = await page.evaluate(async (customid1, customid2) => {
116 | const client1 = new nakamajs.Client();
117 | const session1 = await client1.authenticateCustom(customid1);
118 | const client2 = new nakamajs.Client();
119 | const session2 = await client2.authenticateCustom(customid2);
120 |
121 | await client1.addFriends(session1, [session2.user_id]);
122 | await client2.deleteFriends(session2, [session1.user_id]);
123 | return await client1.listFriends(session1);
124 | }, customid1, customid2);
125 |
126 | expect(result).not.toBeNull();
127 | expect(result.friends!.length).toBe(0);
128 | });
129 |
130 | it.skip('should add friend authenticated via facebook instant, then list', async () => {
131 | const page : Page = await createPage();
132 |
133 | const customid1 = generateid();
134 | const customid2 = generateid();
135 |
136 | const result = await page.evaluate(async (customid1, token2) => {
137 | const client1 = new nakamajs.Client();
138 | const session1 = await client1.authenticateCustom(customid1);
139 | const client2 = new nakamajs.Client();
140 | const session2 = await client2.authenticateFacebookInstantGame(token2, true);
141 | await client1.addFriends(session1, [session2.user_id]);
142 | return await client1.listFriends(session1);
143 | }, customid1, createFacebookInstantGameAuthToken(customid2));
144 |
145 | expect(result.friends![0]).not.toBeNull();
146 | expect(result.friends![0].user.facebook_instant_game_id).toEqual(customid2);
147 | });
148 |
149 | });
150 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/client-leaderboard.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Page} from "puppeteer"
18 | import * as nakamajs from "@heroiclabs/nakama-js";
19 | import {createPage, generateid} from "./utils"
20 | import {describe, expect, it} from '@jest/globals'
21 |
22 | describe('Leaderboard Tests', () => {
23 |
24 | it('should create leaderboard with Set operator and write record', async () => {
25 | const page : Page = await createPage();
26 |
27 | const customid = generateid();
28 | const rpcid = "clientrpc.create_leaderboard"
29 | const operator = "set"
30 | const score = {
31 | score: "10",
32 | subscore: "1",
33 | metadata: {"key": "value"}
34 | };
35 |
36 | const result = await page.evaluate(async (customid, rpcid, operator, score) => {
37 | const client = new nakamajs.Client();
38 | const session = await client.authenticateCustom(customid)
39 | const result = await client.rpc(session, rpcid, {"operator": operator});
40 | const leaderboardId =(result.payload).leaderboard_id;
41 | return await client.writeLeaderboardRecord(session, leaderboardId, score)
42 | }, customid, rpcid, operator, score);
43 |
44 | expect(result).not.toBeNull();
45 | expect(result.score).toBe(10);
46 | expect(result.subscore).toBe(1);
47 | expect(result.metadata).not.toBeNull();
48 | expect(( result.metadata).key).toBe("value");
49 | });
50 |
51 | it('should create leaderboard with Best operator and write record', async () => {
52 | const page : Page = await createPage();
53 |
54 | const customid = generateid();
55 | const rpcid = "clientrpc.create_leaderboard"
56 | const operator = "best"
57 | const score = {
58 | score: "10",
59 | subscore: "1",
60 | metadata: {"key": "value"}
61 | };
62 |
63 | const result = await page.evaluate(async (customid, rpcid, operator, score) => {
64 | const client = new nakamajs.Client();
65 | const session = await client.authenticateCustom(customid)
66 | const result = await client.rpc(session, rpcid, {"operator": operator});
67 | const leaderboardId = (result.payload).leaderboard_id;
68 | await client.writeLeaderboardRecord(session, leaderboardId, score)
69 |
70 | score.score = "1";
71 | score.subscore = "20";
72 | return await client.writeLeaderboardRecord(session, leaderboardId, score)
73 | }, customid, rpcid, operator, score);
74 |
75 | expect(result).not.toBeNull();
76 | expect(result.score).toBe(10);
77 | expect(result.subscore).toBe(20);
78 | expect(result.metadata).not.toBeNull();
79 | expect(( result.metadata).key).toBe("value");
80 | });
81 |
82 | it('should create leaderboard with Incr operator and write record', async () => {
83 | const page : Page = await createPage();
84 |
85 | const customid = generateid();
86 | const rpcid = "clientrpc.create_leaderboard"
87 | const operator = "incr"
88 | const score = {
89 | score: "10",
90 | subscore: "1",
91 | metadata: {"key": "value"}
92 | };
93 |
94 | const result = await page.evaluate(async (customid, rpcid, operator, score) => {
95 | const client = new nakamajs.Client();
96 | const session = await client.authenticateCustom(customid);
97 | const result = await client.rpc(session, rpcid, {"operator": operator});
98 | const leaderboardId = (result.payload).leaderboard_id;
99 | await client.writeLeaderboardRecord(session, leaderboardId, score);
100 |
101 | score.score = "1";
102 | score.subscore = "5";
103 | return await client.writeLeaderboardRecord(session, leaderboardId, score)
104 | }, customid, rpcid, operator, score);
105 |
106 | expect(result).not.toBeNull();
107 | expect(result.score).toBe(11);
108 | expect(result.subscore).toBe(6);
109 | expect(result.metadata).not.toBeNull();
110 | expect(( result.metadata).key).toBe("value");
111 | });
112 |
113 | it('should create leaderboard with Set operator and then list leaderboard records', async () => {
114 | const page : Page = await createPage();
115 |
116 | const customid = generateid();
117 | const rpcid = "clientrpc.create_leaderboard"
118 | const operator = "set"
119 | const score = {
120 | score: "10",
121 | subscore: "1",
122 | metadata: {"key": "value"}
123 | };
124 |
125 | const result = await page.evaluate(async (customid, rpcid, operator, score) => {
126 | const client = new nakamajs.Client();
127 | const session = await client.authenticateCustom(customid);
128 |
129 | const result = await client.rpc(session, rpcid, {"operator": operator});
130 | const leaderboardId = (result.payload).leaderboard_id;
131 | await client.writeLeaderboardRecord(session, leaderboardId, score);
132 | return await client.listLeaderboardRecords(session, leaderboardId);
133 | }, customid, rpcid, operator, score);
134 |
135 | expect(result).not.toBeNull();
136 | expect(result.records![0].score).toBe(10);
137 | expect(result.records![0].subscore).toBe(1);
138 | expect(result.records![0].metadata).not.toBeNull();
139 | expect(( result.records![0].metadata).key).toBe("value");
140 | expect(result.records![0].rank).toBe(1);
141 | });
142 |
143 | });
144 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/client-link.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const base64url = require("base64url");
18 | const crypto = require("crypto");
19 | import {Page} from "puppeteer"
20 | import * as nakamajs from "@heroiclabs/nakama-js";
21 | import {createPage, generateid} from "./utils";
22 | import {describe, expect, it} from '@jest/globals'
23 |
24 | describe('Link / Unlink Tests', () => {
25 |
26 | it('should link device ID', async () => {
27 | const page : Page = await createPage();
28 |
29 | const customid = generateid();
30 | const deviceid = generateid();
31 |
32 | const account = await page.evaluate(async (customid, deviceid) => {
33 | const client = new nakamajs.Client();
34 | const session = await client.authenticateCustom(customid)
35 | await client.linkDevice(session, { id: deviceid });
36 | return await client.getAccount(session);
37 | }, customid, deviceid);
38 |
39 | expect(account).not.toBeNull();
40 | expect(account.custom_id).not.toBeNull();
41 | expect(account.devices![0]).not.toBeNull();
42 | });
43 |
44 | it('should unlink device ID', async () => {
45 | const page : Page = await createPage();
46 |
47 | const customid = generateid();
48 | const deviceid = generateid();
49 |
50 | const account = await page.evaluate(async (customid, deviceid) => {
51 | const client = new nakamajs.Client();
52 | const session = await client.authenticateCustom(customid);
53 | await client.linkDevice(session, { id: deviceid });
54 | await client.unlinkDevice(session, {id: deviceid });
55 | return await client.getAccount(session);
56 | }, customid, deviceid);
57 |
58 | expect(account).not.toBeNull();
59 | expect(account.custom_id).not.toBeNull();
60 | expect(account.hasOwnProperty("devices")).toBe(false);
61 | });
62 |
63 | //optional test
64 | it.skip('should link to facebook instant games', async () => {
65 | const page : Page = await createPage();
66 |
67 | const fbid = generateid();
68 |
69 | const testSecret = "fb-instant-test-secret";
70 |
71 | const mockFbInstantPayload = JSON.stringify({
72 | algorithm: "HMAC-SHA256",
73 | issued_at: 1594867628,
74 | player_id: fbid,
75 | request_payload: ""
76 | });
77 |
78 | const encodedPayload = base64url(mockFbInstantPayload);
79 |
80 | const signature = crypto.createHmac('sha256', testSecret).update(encodedPayload).digest();
81 | const encodedSignature = base64url(signature);
82 |
83 | const token = encodedSignature + "." + encodedPayload;
84 |
85 | const customid = generateid();
86 |
87 | const account = await page.evaluate(async (customid, token) => {
88 | const client = new nakamajs.Client();
89 | const session = await client.authenticateCustom(customid);
90 | await client.linkFacebookInstantGame(session, { signed_player_info: token });
91 | return await client.getAccount(session);
92 | }, customid, token);
93 |
94 | ("account user is...");
95 | (account.user);
96 | expect(account).not.toBeNull();
97 | expect(account.user!.facebook_instant_game_id).not.toBeUndefined();
98 |
99 | });
100 |
101 | //optional test
102 | it.skip('should unlink to facebook instant games', async () => {
103 | const page : Page = await createPage();
104 |
105 | const fbid = generateid();
106 |
107 | const testSecret = "fb-instant-test-secret";
108 |
109 | const mockFbInstantPayload = JSON.stringify({
110 | algorithm: "HMAC-SHA256",
111 | issued_at: 1594867628,
112 | player_id: fbid,
113 | request_payload: ""
114 | });
115 |
116 | const encodedPayload = base64url(mockFbInstantPayload);
117 |
118 | const signature = crypto.createHmac('sha256', testSecret).update(encodedPayload).digest();
119 | const encodedSignature = base64url(signature);
120 |
121 | const token = encodedSignature + "." + encodedPayload;
122 |
123 | const customid = generateid();
124 |
125 | const account = await page.evaluate(async (customid, token) => {
126 | const client = new nakamajs.Client();
127 | const session = await client.authenticateCustom(customid);
128 | await client.linkFacebookInstantGame(session, { signed_player_info: token });
129 | await client.unlinkFacebookInstantGame(session, { signed_player_info: token });
130 |
131 | return await client.getAccount(session);
132 | }, customid, token);
133 |
134 | expect(account).not.toBeNull();
135 | expect(account.user!.facebook_instant_game_id).toBeUndefined();
136 | })
137 |
138 | });
139 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/client-rpc.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import {Page} from "puppeteer"
19 | import * as nakamajs from "@heroiclabs/nakama-js";
20 | import {createPage, generateid} from "./utils"
21 | import {describe, expect, it} from '@jest/globals'
22 |
23 | describe('RPC Tests', () => {
24 |
25 | it('should send rpc', async () => {
26 | const page : Page = await createPage();
27 |
28 | const customid = generateid();
29 | const rpcid = "clientrpc.rpc_get";
30 |
31 | const rpcResult = await page.evaluate(async (customid, rpcid) => {
32 | const client = new nakamajs.Client();
33 | const session = await client.authenticateCustom(customid)
34 | return await client.rpc(session, rpcid, {});
35 | }, customid, rpcid);
36 |
37 | expect(rpcResult).not.toBeNull();
38 | });
39 |
40 | it('should send rpc with payload', async () => {
41 | const page : Page = await createPage();
42 |
43 | const customid = generateid();
44 | const rpcid = "clientrpc.rpc";
45 | const request = {
46 | "hello": "world"
47 | };
48 |
49 | const rpcResult = await page.evaluate(async (customid, rpcid, request) => {
50 | const client = new nakamajs.Client();
51 | const session = await client.authenticateCustom(customid)
52 | return await client.rpc(session, rpcid, request);
53 | }, customid, rpcid, request);
54 |
55 | expect(rpcResult).not.toBeNull();
56 | expect(rpcResult.payload).not.toBeNull();
57 | expect(rpcResult.payload).toEqual(request);
58 | });
59 |
60 | it('should send rpc with httpKey', async() => {
61 | const page : Page = await createPage();
62 |
63 | const rpcid = "clientrpc.rpc_get";
64 | const HTTP_KEY = "defaulthttpkey";
65 |
66 | const rpcResult = await page.evaluate(async (rpcid, HTTP_KEY) => {
67 | const client = new nakamajs.Client();
68 | return await client.rpcHttpKey(HTTP_KEY, rpcid, null!);
69 | }, rpcid, HTTP_KEY);
70 |
71 | expect(rpcResult).not.toBeNull();
72 | })
73 | });
74 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/client-storage.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import {Page} from "puppeteer"
19 | import * as nakamajs from "@heroiclabs/nakama-js";
20 | import {createPage, generateid} from "./utils"
21 | import {describe, expect, it} from '@jest/globals'
22 |
23 | describe('Storage Tests', () => {
24 |
25 | it('should write and read storage', async () => {
26 |
27 | const page : Page = await createPage();
28 |
29 | const customid = generateid();
30 | const collection = "testcollection";
31 | const key = "testkey";
32 | const value = {"hello": "world"};
33 |
34 | const result = await page.evaluate(async (customid, collection, key, value) => {
35 | const client = new nakamajs.Client();
36 | const session = await client.authenticateCustom(customid);
37 | await client.writeStorageObjects(session, [
38 | {
39 | "collection": collection,
40 | "key": key,
41 | "value": value
42 | }
43 | ]);
44 | return await client.readStorageObjects(session, {
45 | object_ids: [{
46 | "collection": collection,
47 | "key": key,
48 | "user_id": session.user_id
49 | }]
50 | });
51 | }, customid, collection, key, value);
52 |
53 | expect(result).not.toBeNull();
54 | expect(result.objects.length).toBe(1);
55 | expect(result.objects[0]).not.toBeNull();
56 | expect(result.objects[0].collection).toBe(collection);
57 | expect(result.objects[0].key).toBe(key);
58 | expect(result.objects[0].value).toEqual(value);
59 | expect(result.objects[0].permission_read).toBe(1);
60 | expect(result.objects[0].permission_write).toBe(1);
61 | expect(result.objects[0].version).not.toBeNull();
62 | });
63 |
64 | it('should write and delete storage', async () => {
65 | const page : Page = await createPage();
66 |
67 | const customid = generateid();
68 | const collection = "testcollection";
69 | const key = "testkey";
70 | const value = {"hello": "world"};
71 |
72 | const result = await page.evaluate(async (customid, collection, key, value) => {
73 | const client = new nakamajs.Client();
74 | const session = await client.authenticateCustom(customid);
75 | await client.writeStorageObjects(session,[
76 | {
77 | "collection": collection,
78 | "key": key,
79 | "value": value
80 | }
81 | ]);
82 |
83 | await client.deleteStorageObjects(session, {
84 | object_ids: [{
85 | "collection": collection,
86 | "key": key
87 | }]
88 | });
89 |
90 | return await client.readStorageObjects(session, {
91 | object_ids: [{
92 | "collection": collection,
93 | "key": key,
94 | "user_id": session.user_id
95 | }]
96 | })
97 | }, customid, collection, key, value);
98 |
99 | expect(result).not.toBeNull();
100 | expect(result.objects.length).toBe(0);
101 | });
102 |
103 | it('should write and list storage', async () => {
104 | const page : Page = await createPage();
105 |
106 | const customid = generateid();
107 | const collection = "testcollection";
108 | const key = "testkey";
109 | const value = {"hello": "world"};
110 |
111 | const result = await page.evaluate(async (customid, collection, key, value) => {
112 | const client = new nakamajs.Client();
113 | const session = await client.authenticateCustom(customid);
114 |
115 | await client.writeStorageObjects(session,[
116 | {
117 | "collection": collection,
118 | "key": key,
119 | "value": value,
120 | "permission_read": 2,
121 | "permission_write": 1
122 | }
123 | ]);
124 |
125 | return await client.listStorageObjects(session, collection, session.user_id);
126 |
127 | }, customid, collection, key, value);
128 |
129 | expect(result).not.toBeNull();
130 | expect(result.objects.length).toBe(1);
131 | expect(result.objects[0]).not.toBeNull();
132 | expect(result.objects[0].collection).toBe(collection);
133 | expect(result.objects[0].key).toBe(key);
134 | expect(result.objects[0].value).toEqual(value);
135 | expect(result.objects[0].permission_read).toBe(2);
136 | expect(result.objects[0].permission_write).toBe(1);
137 | expect(result.objects[0].version).not.toBeNull();
138 | });
139 |
140 | });
141 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/client-user.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import {Page} from "puppeteer"
19 | import * as nakamajs from "@heroiclabs/nakama-js";
20 | import {createPage, generateid} from "./utils";
21 | import {describe, expect, it} from '@jest/globals'
22 |
23 | describe('User Tests', () => {
24 |
25 | it('should return current user account', async () => {
26 | const page : Page = await createPage();
27 |
28 | const customid = generateid();
29 |
30 | const account = await page.evaluate(async (customid) => {
31 | const client = new nakamajs.Client();
32 | return client.authenticateCustom(customid)
33 | .then(session => {
34 | return client.getAccount(session);
35 | });
36 | }, customid);
37 |
38 | expect(account).not.toBeNull();
39 | expect(account.custom_id).not.toBeNull();
40 | expect(account.wallet).not.toBeNull();
41 | expect(account.wallet).toBe("{}");
42 | expect(account.user).not.toBeNull();
43 | expect(account.user.id).not.toBeNull();
44 | expect(account.user.username).not.toBeNull();
45 | expect(account.user.lang_tag).not.toBeNull();
46 | expect(account.user.lang_tag).toBe("en");
47 | expect(account.user.metadata).not.toBeNull();
48 | expect(account.user.metadata).toBe("{}");
49 | });
50 |
51 | it('should update current user account', async () => {
52 |
53 | const page : Page = await createPage();
54 |
55 | const customid = generateid();
56 | const displayName = "display";
57 | const avatar = "avatar";
58 | const lang = "fa";
59 | const loc = "california";
60 |
61 | const account = await page.evaluate(async (customid, displayName, avatar, lang, loc) => {
62 | const client = new nakamajs.Client();
63 | const session = await client.authenticateCustom(customid);
64 |
65 | await client.updateAccount(session, {
66 | display_name: displayName,
67 | avatar_url: avatar,
68 | lang_tag: lang,
69 | location: loc
70 | });
71 |
72 | return await client.getAccount(session);
73 | }, customid, displayName, avatar, lang, loc);
74 |
75 | expect(account).not.toBeNull();
76 | expect(account.user.display_name).not.toBeNull();
77 | expect(account.user.display_name).toBe(displayName);
78 | expect(account.user.avatar_url).not.toBeNull();
79 | expect(account.user.avatar_url).toBe(avatar);
80 | expect(account.user.lang_tag).not.toBeNull();
81 | expect(account.user.lang_tag).toBe(lang);
82 | expect(account.user.location).not.toBeNull();
83 | expect(account.user.location).toBe(loc);
84 | });
85 |
86 | it('should update current user with same username', async () => {
87 | const page : Page = await createPage();
88 |
89 | const customid = generateid();
90 |
91 | const account = await page.evaluate(async (customid) => {
92 | const client = new nakamajs.Client();
93 | const session = await client.authenticateCustom(customid);
94 | const success = await client.updateAccount(session, {
95 | username: session.username
96 | });
97 | return client.getAccount(session);
98 | }, customid);
99 |
100 | expect(account).not.toBeNull();
101 | expect(account.custom_id).toBe(customid);
102 | });
103 |
104 | it('should return two users', async () => {
105 | const page : Page = await createPage();
106 |
107 | const customid = generateid();
108 | const customid2 = generateid();
109 |
110 | const users = await page.evaluate(async (customid, customid2) => {
111 | const client = new nakamajs.Client();
112 | const session1 = await client.authenticateCustom(customid);
113 | const session2 = await client.authenticateCustom(customid2)
114 | return await client.getUsers(session2, [session1.user_id], [session2.username], []);
115 | }, customid, customid2);
116 |
117 | expect(users).not.toBeNull();
118 | expect(users[0]).not.toBeNull();
119 | expect(users[1]).not.toBeNull();
120 | });
121 |
122 | it('should return no users', async () => {
123 | const page : Page = await createPage();
124 |
125 | const customid = generateid();
126 |
127 | const response = await page.evaluate(async (customid) => {
128 | const client = new nakamajs.Client();
129 | const session = await client.authenticateCustom(customid);
130 | return await client.getUsers(session, [], [], []);
131 | }, customid);
132 |
133 | expect(response).not.toBeNull();
134 | });
135 | });
136 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/client.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Page} from "puppeteer"
18 | import * as nakamajs from "@heroiclabs/nakama-js";
19 | import {createPage} from "./utils"
20 | import {describe, expect, it} from '@jest/globals'
21 |
22 | describe('Client Tests', () => {
23 |
24 | it('should create object with defaults', async () => {
25 | const page : Page = await createPage();
26 |
27 | const client = await page.evaluate(() => {
28 | return new nakamajs.Client();
29 | });
30 |
31 | expect(client).not.toBeNull();
32 | expect(client.serverkey).toBe("defaultkey");
33 | expect(client.host).toBe("127.0.0.1");
34 | expect(client.port).toBe("7350");
35 | expect(client.useSSL).toBe(false);
36 | expect(client.timeout).toBe(7000);
37 | });
38 |
39 | it('should create object with configuration', async () => {
40 | const page : Page = await createPage();
41 |
42 | const SERVER_KEY = "somesecret!";
43 | const HOST = "127.0.0.2";
44 | const PORT = "8080";
45 | const SSL = true;
46 | const TIMEOUT = 8000;
47 |
48 | const client = await page.evaluate((SERVER_KEY, HOST, PORT, SSL, TIMEOUT) => {
49 | return new nakamajs.Client(SERVER_KEY, HOST, PORT, SSL, TIMEOUT);
50 | }, SERVER_KEY, HOST, PORT, SSL, TIMEOUT);
51 |
52 | expect(client).not.toBeNull();
53 | expect(client.serverkey).toBe(SERVER_KEY);
54 | expect(client.host).toBe(HOST);
55 | expect(client.port).toBe(PORT);
56 | expect(client.useSSL).toBe(SSL);
57 | expect(client.timeout).toBe(TIMEOUT);
58 | });
59 |
60 | it('should obey timeout configuration option', async () => {
61 | const page : Page = await createPage();
62 |
63 | const err = await page.evaluate(() => {
64 | const client = new nakamajs.Client("defaultkey", "127.0.0.1", "7350", false, 0);
65 | return client.authenticateCustom("timeoutuseridentifier")
66 | .catch(err => err);
67 | });
68 |
69 | expect(err).not.toBeNull();
70 | expect(err).toBe("Request timed out.");
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Nakama JS Browser Test
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/jest.config.js:
--------------------------------------------------------------------------------
1 | const merge = require('merge')
2 | const ts_preset = require('ts-jest/jest-preset')
3 | const puppeteer_preset = require('jest-puppeteer/jest-preset')
4 |
5 | //use multiple jest presets by merging and exporting them as a single object
6 | module.exports = merge.recursive(ts_preset, puppeteer_preset, {
7 | globals: {
8 | 'ts-jest': {
9 | tsConfig: 'tsconfig.test.json'
10 | }
11 | }
12 | }
13 | )
14 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heroiclabs/nakama-js-test",
3 | "version": "1.0.0",
4 | "description": "A subproject that houses tests for Nakama JS",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "dependencies": {
8 | "@heroiclabs/nakama-js": "file:../nakama-js",
9 | "@heroiclabs/nakama-js-protobuf": "file:../nakama-js-protobuf",
10 | "base64url": "3.0.1",
11 | "merge": "^2.1.1"
12 | },
13 | "devDependencies": {
14 | "@types/expect-puppeteer": "^5.0.6",
15 | "@types/jest": "^29.5.14",
16 | "@types/jest-environment-puppeteer": "^5.0.6",
17 | "@types/node": "18.19.66",
18 | "@types/puppeteer": "^7.0.4",
19 | "jest": "29.7.0",
20 | "jest-puppeteer": "10.1.4",
21 | "puppeteer": "23.9.0",
22 | "ts-jest": "29.2.5"
23 | },
24 | "scripts": {
25 | "test": "npx tsc --project tsconfig.test.json && jest --runInBand"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/session.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import * as nakamajs from "@heroiclabs/nakama-js";
19 | import {createPage, generateid} from "./utils";
20 | import {Page} from "puppeteer";
21 | import {describe, expect, it} from '@jest/globals'
22 |
23 | describe('Session Tests', () => {
24 |
25 | it('should be expired', async () => {
26 | const page : Page = await createPage();
27 |
28 | const expired = await page.evaluate(() => {
29 | const nowUnixEpoch = Math.floor(Date.now() / 1000);
30 | const expiredJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTY5MTA5NzMsInVpZCI6ImY0MTU4ZjJiLTgwZjMtNDkyNi05NDZiLWE4Y2NmYzE2NTQ5MCIsInVzbiI6InZUR2RHSHl4dmwifQ.gzLaMQPaj5wEKoskOSALIeJLOYXEVFoPx3KY0Jm1EVU";
31 | const session = nakamajs.Session.restore(expiredJwt, expiredJwt);
32 | return session.isexpired(nowUnixEpoch);
33 | });
34 |
35 | expect(expired).toBeTruthy();
36 | });
37 |
38 | it('should have username and userId', async () => {
39 | const page : Page = await createPage();
40 |
41 | const session = await page.evaluate(() => {
42 | const expiredJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTY5MTA5NzMsInVpZCI6ImY0MTU4ZjJiLTgwZjMtNDkyNi05NDZiLWE4Y2NmYzE2NTQ5MCIsInVzbiI6InZUR2RHSHl4dmwifQ.gzLaMQPaj5wEKoskOSALIeJLOYXEVFoPx3KY0Jm1EVU";
43 | return nakamajs.Session.restore(expiredJwt, expiredJwt);
44 | });
45 |
46 | expect(session.username).not.toBeNull();
47 | expect(session.username).toBe("vTGdGHyxvl");
48 | expect(session.user_id).not.toBeNull();
49 | expect(session.user_id).toBe("f4158f2b-80f3-4926-946b-a8ccfc165490");
50 | });
51 |
52 | it("restored should have refresh token", async () => {
53 | const page : Page = await createPage();
54 |
55 | const session = await page.evaluate(() => {
56 | const expiredJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTY5MTA5NzMsInVpZCI6ImY0MTU4ZjJiLTgwZjMtNDkyNi05NDZiLWE4Y2NmYzE2NTQ5MCIsInVzbiI6InZUR2RHSHl4dmwifQ.gzLaMQPaj5wEKoskOSALIeJLOYXEVFoPx3KY0Jm1EVU";
57 | return nakamajs.Session.restore(expiredJwt, expiredJwt);
58 | });
59 |
60 | expect(session.refresh_token).not.toBeNull();
61 | expect(session.refresh_token).not.toBeUndefined();
62 | expect(session.refresh_token).not.toBe("");
63 | });
64 |
65 | it("restored should handle null refresh token", async () => {
66 | const page : Page = await createPage();
67 |
68 | await page.evaluate(() => {
69 | const expiredJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTY5MTA5NzMsInVpZCI6ImY0MTU4ZjJiLTgwZjMtNDkyNi05NDZiLWE4Y2NmYzE2NTQ5MCIsInVzbiI6InZUR2RHSHl4dmwifQ.gzLaMQPaj5wEKoskOSALIeJLOYXEVFoPx3KY0Jm1EVU";
70 | return nakamajs.Session.restore(expiredJwt, null);
71 | });
72 | });
73 |
74 | it("should refresh", async () => {
75 | const page : Page = await createPage();
76 | const customId = generateid();
77 |
78 | const tokens : any = await page.evaluate(async (customId) => {
79 |
80 | /* @ts-ignore */
81 | function timeoutPromise(ms : number) : Promise {
82 | return new Promise(resolve => setTimeout(resolve, ms));
83 | }
84 |
85 | const client = new nakamajs.Client();
86 | const session = await client.authenticateCustom(customId);
87 | const firstToken = session.token;
88 |
89 | await timeoutPromise(1000);
90 | const secondSession = await client.sessionRefresh(session);
91 | const secondToken = secondSession.token;
92 | return {"firstToken": firstToken, "secondToken": secondToken};
93 | }, customId);
94 |
95 | expect(tokens.secondToken).not.toBeNull();
96 | expect(tokens.secondToken).not.toBeUndefined();
97 | expect(tokens.secondToken).not.toEqual(tokens.firstToken);
98 | });
99 |
100 | it("should logout", async () => {
101 | const page : Page = await createPage();
102 | const customId = generateid();
103 |
104 | const status : any = await page.evaluate(async (customId) => {
105 | const client = new nakamajs.Client();
106 | const session = await client.authenticateCustom(customId);
107 | await client.sessionLogout(session, session.token, session.refresh_token);
108 |
109 | const obj = {
110 | "collection": "collection",
111 | "key": "this should fail",
112 | "value": {}
113 | };
114 |
115 | try
116 | {
117 | await client.writeStorageObjects(session, [obj]);
118 | return null;
119 | } catch (e) {
120 | return e.status;
121 | }
122 | }, customId);
123 |
124 | expect(status).toEqual(401);
125 | });
126 |
127 | it("should autorefresh session", async () => {
128 | const page : Page = await createPage();
129 | const customId = generateid();
130 |
131 | const tokens : any = await page.evaluate(async (customId) => {
132 |
133 | function timeoutPromise(ms : number) : Promise {
134 | return new Promise(resolve => setTimeout(resolve, ms));
135 | }
136 |
137 | const client = new nakamajs.Client();
138 | const session = await client.authenticateCustom(customId);
139 | const firstToken = session.token;
140 | session.expires_at = (Date.now() + client.expiredTimespanMs)/1000 - 1;
141 |
142 | /* @ts-ignore */
143 | await timeoutPromise(1000);
144 |
145 | const obj = {
146 | "collection": "collection",
147 | "key": "this should succeed",
148 | "value": {}
149 | };
150 |
151 | await client.writeStorageObjects(session, [obj]);
152 |
153 | const secondToken = session.token;
154 | return {"firstToken": firstToken, "secondToken": secondToken};
155 | }, customId);
156 |
157 | expect(tokens.secondToken).not.toBeNull();
158 | expect(tokens.secondToken).not.toBeUndefined();
159 | expect(tokens.secondToken).not.toEqual(tokens.firstToken);
160 | });
161 | });
162 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/socket-channel.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import * as nakamajs from "@heroiclabs/nakama-js";
19 | import * as nakamajsprotobuf from "../nakama-js-protobuf";
20 | import {generateid, createPage, adapters, AdapterType} from "./utils";
21 | import {describe, expect, it} from '@jest/globals'
22 |
23 | describe('Channel Tests', () => {
24 |
25 | it.each(adapters)('should join a channel', async (adapter) => {
26 |
27 | const page = await createPage();
28 | const customid = generateid();
29 | const channelid = generateid();
30 |
31 | const response = await page.evaluate(async (customid, channelid, adapter) => {
32 |
33 | const client = new nakamajs.Client();
34 |
35 | const socket = client.createSocket(false, false,
36 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
37 |
38 | const session = await client.authenticateCustom(customid)
39 | await socket.connect(session, false);
40 |
41 | //chat type: 1 = room, 2 = Direct Message 3 = Group
42 | return await socket.joinChat(channelid, 1, true, false);
43 | }, customid, channelid, adapter);
44 |
45 | expect(response).not.toBeNull();
46 | expect(response.id).not.toBeNull();
47 | expect(response.presences).not.toBeNull();
48 | expect(response.self).not.toBeNull();
49 | });
50 |
51 | it.each(adapters)('should join a room, then leave it', async (adapter) => {
52 | const page = await createPage();
53 |
54 | const customid = generateid();
55 | const channelid = generateid();
56 |
57 | const response = await page.evaluate(async (customid, channelid, adapter) => {
58 |
59 | const client = new nakamajs.Client();
60 | const socket = client.createSocket(false, true,
61 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
62 |
63 | const session = await client.authenticateCustom(customid)
64 | await socket.connect(session, false);
65 | //chat type: 1 = room, 2 = Direct Message 3 = Group
66 | const channel = await socket.joinChat(channelid, 1, true, false);
67 |
68 | return await socket.leaveChat(channel.id);
69 | }, customid, channelid, adapter);
70 |
71 | expect(response).not.toBeNull();
72 | });
73 |
74 | it.each(adapters)('should create a group, join group chat, then leave it', async (adapter) => {
75 | const page = await createPage();
76 |
77 | const customid = generateid();
78 | const group_name = generateid();
79 |
80 | const response = await page.evaluate(async (customid, group_name, adapter) => {
81 |
82 | const client = new nakamajs.Client();
83 | const socket = client.createSocket(false, false,
84 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
85 |
86 | const session = await client.authenticateCustom(customid)
87 | await socket.connect(session, false);
88 |
89 | const group = await client.createGroup(session, { name: group_name, open: true });
90 | //chat type: 1 = room, 2 = Direct Message 3 = Group
91 | const channel = await socket.joinChat(group.id!, 3, true, false);
92 | return await socket.leaveChat(channel.id);
93 | }, customid, group_name, adapter);
94 |
95 | expect(response).not.toBeNull();
96 | });
97 |
98 | it.each(adapters)('should join a room, then send message, receive message', async (adapter) => {
99 |
100 | const page = await createPage();
101 |
102 | const customid = generateid();
103 | const channelid = generateid();
104 | const payload = { "hello": "world" };
105 |
106 | const message : nakamajs.ChannelMessage | null = await page.evaluate(async (customid, channelid, payload, adapter) => {
107 | const client = new nakamajs.Client();
108 | const socket = client.createSocket(false, false,
109 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
110 |
111 | var promise1 = new Promise((resolve, reject) => {
112 | socket.onchannelmessage = (channelmessage) => {
113 | resolve(channelmessage);
114 | }
115 | });
116 |
117 | const session = await client.authenticateCustom(customid)
118 | await socket.connect(session, false);
119 |
120 | // chat type: 1 = room, 2 = Direct Message 3 = Group
121 | const channel = await socket.joinChat(channelid, 1, false, false);
122 |
123 | await socket.writeChatMessage(channel.id, payload);
124 |
125 | var promise2 = new Promise((resolve, reject) => {
126 | setTimeout(reject, 5000, "did not receive channel message - timed out.")
127 | });
128 |
129 | return Promise.race([promise1, promise2]);
130 | }, customid, channelid, payload, adapter);
131 |
132 | expect(message).not.toBeNull();
133 | expect(message!.channel_id).not.toBeNull();
134 | expect(message!.message_id).not.toBeNull();
135 | expect(message!.sender_id).not.toBeNull();
136 | expect(message!.content).toEqual(payload);
137 | expect(message!.code).toBe(0);
138 | expect(message!.persistent).toBe(false);
139 | });
140 |
141 | it.each(adapters)('should join a room, then send message, update message, then list messages', async (adapter) => {
142 | const page = await createPage();
143 |
144 | const customid = generateid();
145 | const channelid = generateid();
146 | const payload = { "hello": "world" };
147 | const updatedPayload = { "hello": "world2" };
148 |
149 | const response = await page.evaluate(async (customid, channelid, payload, updatedPayload, adapter) => {
150 | const client = new nakamajs.Client();
151 | const socket = client.createSocket(false, false,
152 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
153 |
154 | const session = await client.authenticateCustom(customid)
155 | await socket.connect(session, false);
156 |
157 | //chat type: 1 = room, 2 = Direct Message 3 = Group
158 | const channel = await socket.joinChat(channelid, 1, true, false);
159 |
160 | const ack = await socket.writeChatMessage(channel.id, payload);
161 | const ack2 = await socket.updateChatMessage(
162 | ack.channel_id,
163 | ack.message_id,
164 | updatedPayload);
165 |
166 | return await client.listChannelMessages(session, ack2.channel_id, 10)
167 | }, customid, channelid, payload, updatedPayload, adapter);
168 |
169 | console.log(response);
170 |
171 | expect(response).not.toBeNull();
172 | expect(response.messages).not.toBeNull();
173 | expect(response.messages?.length).toBe(1);
174 | expect(response.cacheable_cursor).not.toBeNull();
175 | expect(response.cacheable_cursor).not.toBeUndefined();
176 |
177 | response.messages?.forEach(message => {
178 | expect(message.content).toEqual(updatedPayload);
179 | expect(message.code).toEqual(0);
180 | expect(message.persistent).toBe(true);
181 | })
182 | });
183 |
184 | it.each(adapters)('should join a room, then send message, remove message, then list messages', async (adapter) => {
185 | const page = await createPage();
186 |
187 | const customid = generateid();
188 | const channelid = generateid();
189 | const payload = { "hello": "world" };
190 |
191 | const response = await page.evaluate(async (customid, channelid, payload, adapter) => {
192 |
193 | const client = new nakamajs.Client();
194 | const socket = client.createSocket(false, false,
195 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
196 |
197 | const session = await client.authenticateCustom(customid)
198 | await socket.connect(session, false);
199 |
200 | // chat type: 1 = room, 2 = Direct Message 3 = Group
201 | const channel = await socket.joinChat(channelid, 1, true, false);
202 |
203 | const ack = await socket.writeChatMessage(channel.id, payload);
204 |
205 | await socket.removeChatMessage(ack.channel_id,
206 | ack.message_id);
207 |
208 | return await client.listChannelMessages(session, ack.channel_id, 10)
209 | }, customid, channelid, payload, adapter);
210 |
211 | expect(response).not.toBeNull();
212 | expect(response.messages).not.toBeNull();
213 | expect(response.messages!.length).toBe(0);
214 | });
215 |
216 | });
217 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/socket-notification.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import {Page} from "puppeteer";
19 | import * as nakamajs from "@heroiclabs/nakama-js";
20 | import {createPage, generateid} from "./utils";
21 | import {describe, expect, it} from '@jest/globals'
22 |
23 | describe('Notification Tests', () => {
24 |
25 | it('should rpc and list notifications', async () => {
26 | const page : Page = await createPage();
27 |
28 | const customid = generateid();
29 |
30 | const notifications = await page.evaluate(async (customid) => {
31 | const client = new nakamajs.Client();
32 | const session = await client.authenticateCustom(customid);
33 | await client.rpc(session, "clientrpc.send_notification", {"user_id": session.user_id});
34 | await client.rpc(session, "clientrpc.send_notification", {"user_id": session.user_id});
35 | var notificationList = await client.listNotifications(session, 1, "");
36 | var notificationList = await client.listNotifications(session, 1, notificationList.cacheable_cursor);
37 |
38 | return notificationList;
39 | }, customid);
40 |
41 | expect(notifications).not.toBeNull();
42 | expect(notifications.notifications[0].id).not.toBeNull();
43 | expect(notifications.notifications[0].content).toEqual({reward_coins: 1000});
44 | expect(notifications.notifications[0].subject).toEqual("You've unlocked level 100!");
45 | expect(notifications.notifications[0].code).toEqual(1);
46 | });
47 |
48 | it('should rpc and delete notification', async () => {
49 | const page : Page = await createPage();
50 |
51 | const customid = generateid();
52 |
53 | const response = await page.evaluate(async (customid) => {
54 | const client = new nakamajs.Client();
55 | const session = await client.authenticateCustom(customid);
56 | const rpcSuccess = await client.rpc(session, "clientrpc.send_notification", {"user_id": session.user_id});
57 | const notificationsList = await client.listNotifications(session, 100, "");
58 |
59 | var notificationsDelete = []
60 | notificationsList.notifications.forEach((notification) => {
61 | notificationsDelete.push(notification.id);
62 | });
63 |
64 | await client.deleteNotifications(session, notificationsDelete);
65 | return client.listNotifications(session, 1, "");
66 | }, customid);
67 |
68 | expect(response).not.toBeNull();
69 | expect(response.notifications).not.toBeNull();
70 | expect(response.notifications.length).toEqual(0);
71 | });
72 |
73 | });
74 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/socket-status.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as nakamajs from "@heroiclabs/nakama-js";
18 | import {StatusPresenceEvent} from "@heroiclabs/nakama-js/socket";
19 | import * as nakamajsprotobuf from "../nakama-js-protobuf";
20 | import {generateid, createPage, adapters, AdapterType} from "./utils";
21 | import {describe, expect, it} from '@jest/globals'
22 |
23 | describe('Status Tests', () => {
24 |
25 | it.each(adapters)('should create status, and then update it', async (adapter) => {
26 | const page = await createPage();
27 |
28 | const customid = generateid();
29 |
30 | const response = await page.evaluate(async (customid, adapter) => {
31 |
32 | const client = new nakamajs.Client();
33 | const socket = client.createSocket(false, false,
34 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
35 |
36 | const session = await client.authenticateCustom(customid);
37 | await socket.connect(session, true);
38 |
39 | return socket.updateStatus("hello-world");
40 | }, customid, adapter);
41 |
42 | expect(response).not.toBeNull();
43 | });
44 |
45 | it.each(adapters)('should follow user2, and get status update when coming online', async (adapter) => {
46 | const page = await createPage();
47 |
48 | const customid1 = generateid();
49 | const customid2 = generateid();
50 |
51 | const response = await page.evaluate(async (customid1, customid2, adapter) => {
52 | const client1 = new nakamajs.Client();
53 | const client2 = new nakamajs.Client();
54 | const socket2 = client2.createSocket(false, false,
55 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
56 |
57 | const socket1 = client1.createSocket(false, false,
58 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
59 |
60 | const session1 = await client1.authenticateCustom(customid1);
61 | const session2 = await client2.authenticateCustom(customid2);
62 | await socket1.connect(session1, true);
63 | await socket1.followUsers([session2.user_id]);
64 |
65 | var promise1 = new Promise((resolve, reject) => {
66 | socket1.onstatuspresence = (statusPresence) => {
67 | resolve(statusPresence);
68 | }
69 | });
70 |
71 | const promise2 = socket2.connect(session2, true).then((session) => {
72 | return new Promise((resolve, reject) => {
73 | setTimeout(reject, 2500, "did not receive match data - timed out.");
74 | });
75 | });
76 |
77 | return Promise.race([promise1, promise2]);
78 | }, customid1, customid2, adapter);
79 |
80 | expect(response).not.toBeNull();
81 | expect(response.joins.length).toEqual(1);
82 | });
83 |
84 | it.each(adapters)('should follow user2, and unfollow user2', async (adapter) => {
85 | const page = await createPage();
86 |
87 | const customid1 = generateid();
88 | const customid2 = generateid();
89 |
90 | const response = await page.evaluate(async (customid1, customid2, adapter) => {
91 | const client1 = new nakamajs.Client();
92 | const client2 = new nakamajs.Client();
93 | const socket1 = client1.createSocket(false, false,
94 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
95 |
96 | const session1 = await client1.authenticateCustom(customid1);
97 | const session2 = await client2.authenticateCustom(customid2);
98 | await socket1.connect(session1, true);
99 | await socket1.followUsers([session2.user_id]);
100 | return socket1.unfollowUsers([session2.user_id]);
101 | }, customid1, customid2, adapter);
102 |
103 | expect(response).not.toBeNull();
104 | });
105 |
106 | });
107 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/socket.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as nakamajs from "@heroiclabs/nakama-js";
18 | import {StreamData} from "@heroiclabs/nakama-js/socket"
19 | import * as nakamajsprotobuf from "../nakama-js-protobuf";
20 | import {generateid, createPage, adapters, AdapterType} from "./utils"
21 | import {describe, expect, it} from '@jest/globals'
22 |
23 | describe('Socket Message Tests', () => {
24 |
25 | it.each(adapters)('should connect', async (adapter) => {
26 | const page = await createPage();
27 |
28 | const customid = generateid();
29 |
30 | const session = await page.evaluate(async (customid, adapter) => {
31 | const client = new nakamajs.Client();
32 | const session = await client.authenticateCustom(customid);
33 |
34 | const socket = client.createSocket(false, false,
35 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
36 |
37 | await socket.connect(session, false);
38 | socket.disconnect(false);
39 | }, customid, adapter);
40 | });
41 |
42 | it.each(adapters)('should rpc and receive stream data', async (adapter) => {
43 | const page = await createPage();
44 |
45 | const customid = generateid();
46 | const ID = "clientrpc.send_stream_data";
47 | const PAYLOAD = JSON.stringify({ "hello": "world" });
48 |
49 | const response = await page.evaluate(async (customid, id, payload, adapter) => {
50 | const client = new nakamajs.Client();
51 | const socket = client.createSocket(false, false,
52 | adapter == AdapterType.Protobuf ? new nakamajsprotobuf.WebSocketAdapterPb() : new nakamajs.WebSocketAdapterText());
53 |
54 | var promise1 = new Promise((resolve, reject) => {
55 | socket.onstreamdata = (streamdata) => {
56 | resolve(streamdata);
57 | }
58 | });
59 |
60 | const session = await client.authenticateCustom(customid)
61 | await socket.connect(session, false);
62 | await socket.rpc(id, payload);
63 | var promise2 = new Promise((resolve, reject) => {
64 | setTimeout(reject, 5000, "did not receive stream data - timed out.")
65 | });
66 |
67 | return Promise.race([promise1, promise2]);
68 | }, customid, ID, PAYLOAD, adapter);
69 |
70 | expect(response).not.toBeNull();
71 | expect(response.data).toBe(PAYLOAD);
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "es2015",
6 | "es2016",
7 | "es2017"
8 | ],
9 | "target": "es2018",
10 | "module": "es2015",
11 | "removeComments": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "moduleResolution": "node",
14 | "noEmitHelpers": false,
15 | "importHelpers": false,
16 | "baseUrl": "./",
17 | "noEmit": true,
18 | "allowSyntheticDefaultImports": true
19 | },
20 | "include": [
21 | "./*",
22 | ],
23 | "exclude": []
24 | }
25 |
--------------------------------------------------------------------------------
/packages/nakama-js-test/utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { Page, Browser} from "puppeteer";
18 | const fs = require("fs");
19 | const crypto = require("crypto");
20 | const base64url = require("base64url");
21 |
22 | // automatically assigned by puppeteer + Jest
23 | declare var browser : Browser;
24 |
25 | // automatically assigned by puppeteer + Jest
26 | declare var browser : Browser;
27 |
28 | // util to generate a random id.
29 | export function generateid(): string {
30 | const arr: string[] = [];
31 |
32 | for (let i: number = 0; i < 30; i++) {
33 | arr.push(Math.random().toString(36)[3]);
34 | }
35 |
36 | return arr.join("");
37 | };
38 |
39 | export async function createPage(): Promise {
40 |
41 | const page = await browser.newPage();
42 |
43 | page.on('console', msg => console.log('LOG:', msg.text()));
44 | page.on('error', handlePageError);
45 | page.on('pageerror', handlePageError);
46 |
47 | const nakamaJsLib = fs.readFileSync(__dirname + '/../nakama-js/dist/nakama-js.iife.js', 'utf8');
48 | const nakamaJsProtobufLib = fs.readFileSync(__dirname + '/../nakama-js-protobuf/dist/nakama-js-protobuf.iife.js', 'utf8');
49 |
50 | await page.evaluateOnNewDocument(nakamaJsLib);
51 | await page.evaluateOnNewDocument(nakamaJsProtobufLib);
52 | await page.evaluateOnNewDocument(() => {
53 | globalThis.timeoutPromise = function(ms) {
54 | return new Promise(resolve => setTimeout(resolve, ms));
55 | }
56 | })
57 |
58 | await page.goto('about:blank');
59 |
60 | return page;
61 | }
62 |
63 | function handlePageError(err) {
64 |
65 | let msg: string;
66 |
67 | if (err instanceof Object) {
68 | msg = JSON.stringify(err);
69 | }
70 | else {
71 | msg = err;
72 | }
73 |
74 | console.error('ERR:', msg);
75 | }
76 |
77 | export const enum AdapterType {
78 | Text = 0,
79 | Protobuf = 1
80 | }
81 |
82 | export const adapters = [AdapterType.Text, AdapterType.Protobuf];
83 |
84 | export function createFacebookInstantGameAuthToken(id : string) : string {
85 | const testSecret = "fb-instant-test-secret";
86 |
87 | const mockFbInstantPayload = JSON.stringify({
88 | algorithm: "HMAC-SHA256",
89 | issued_at: 1594867628,
90 | player_id: id,
91 | request_payload: ""
92 | });
93 |
94 | const encodedPayload = base64url(mockFbInstantPayload);
95 |
96 | const signature = crypto.createHmac('sha256', testSecret).update(encodedPayload).digest();
97 | const encodedSignature = base64url(signature);
98 |
99 | const token = encodedSignature + "." + encodedPayload;
100 | return token;
101 | }
102 |
103 | export const matchmakerTimeout = 20000;
104 |
--------------------------------------------------------------------------------
/packages/nakama-js-webpack-example/README.md:
--------------------------------------------------------------------------------
1 | To run this example, run `yarn build` and open `index.html`.
--------------------------------------------------------------------------------
/packages/nakama-js-webpack-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Nakama JS Example Browser Test
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/nakama-js-webpack-example/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Client} from "@heroiclabs/nakama-js";
18 |
19 | var useSSL = false; // Enable if server is run with an SSL certificate.
20 | var client = new Client("defaultkey", "127.0.0.1", "7350", useSSL);
21 |
22 | client.authenticateCustom("test_id").then(
23 | session => { console.log("authenticated.");
24 | }).catch(e => {
25 | console.log("error authenticating.");
26 | });
27 |
--------------------------------------------------------------------------------
/packages/nakama-js-webpack-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heroiclabs/nakama-js-webpack-example",
3 | "version": "1.0.0",
4 | "description": "An example project that utilizes nakama-js and Webpack",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "dependencies": {
8 | "@heroiclabs/nakama-js": "file:../nakama-js",
9 | "ts-loader": "^9.5.1",
10 | "webpack": "^5.96.1",
11 | "webpack-cli": "^5.1.4"
12 | },
13 | "scripts": {
14 | "build": "npx webpack --config webpack.config.js"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/nakama-js-webpack-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | },
4 | "files": ["index.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/nakama-js-webpack-example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './index.ts',
5 | output: {
6 | filename: 'main.js',
7 | path: path.resolve(__dirname, 'dist'),
8 | },
9 | module: {
10 | rules: [{
11 | test: /\.tsx?$/,
12 | use: 'ts-loader',
13 | }]
14 | }
15 | };
--------------------------------------------------------------------------------
/packages/nakama-js/.gitignore:
--------------------------------------------------------------------------------
1 | dist/*.js
2 | dist/*.js.map
3 |
--------------------------------------------------------------------------------
/packages/nakama-js/README.md:
--------------------------------------------------------------------------------
1 | Nakama JavaScript client
2 | ========================
3 |
4 | > JavaScript client for Nakama server written in TypeScript. For browser and React Native projects.
5 |
6 | [Nakama](https://github.com/heroiclabs/nakama) is an open-source server designed to power modern games and apps. Features include user accounts, chat, social, matchmaker, realtime multiplayer, and much [more](https://heroiclabs.com).
7 |
8 | This client implements the full API and socket options with the server. It's written in TypeScript with minimal dependencies to be compatible with all modern browsers and React Native.
9 |
10 | Full documentation is online - https://heroiclabs.com/docs/nakama/client-libraries/javascript/
11 |
12 | ## Getting Started
13 |
14 | You'll need to setup the server and database before you can connect with the client. The simplest way is to use Docker but have a look at the [server documentation](https://github.com/heroiclabs/nakama#getting-started) for other options.
15 |
16 | 1. Install and run the servers. Follow these [instructions](https://heroiclabs.com/docs/nakama/getting-started/install/docker/).
17 |
18 | 2. Import the client into your project. It's [available on NPM](https://www.npmjs.com/package/@heroiclabs/nakama-js) and can be also be added to a project with Bower or other package managers.
19 |
20 | ```shell
21 | npm install @heroiclabs/nakama-js
22 | ```
23 |
24 | You'll now see the code in the "node_modules" folder and package listed in your "package.json".
25 |
26 | Optionally, if you would like to use the Protocol Buffers wire format with your sockets, you can import the adapter found in this package:
27 |
28 | ```shell
29 | npm install @heroiclabs/nakama-js-protobuf
30 | ```
31 |
32 | 3. Use the connection credentials to build a client object.
33 |
34 | ```js
35 | import {Client} from "@heroiclabs/nakama-js";
36 |
37 | var useSSL = false; // Enable if server is run with an SSL certificate.
38 | var client = new Client("defaultkey", "127.0.0.1", "7350", useSSL);
39 | ```
40 |
41 | ## Usage
42 |
43 | The client object has many methods to execute various features in the server or open realtime socket connections with the server.
44 |
45 | ### Authenticate
46 |
47 | There's a variety of ways to [authenticate](https://heroiclabs.com/docs/authentication) with the server. Authentication can create a user if they don't already exist with those credentials. It's also easy to authenticate with a social profile from Google Play Games, Facebook, Game Center, etc.
48 |
49 | ```js
50 | var email = "super@heroes.com";
51 | var password = "batsignal";
52 | const session = await client.authenticateEmail(email, password);
53 | console.info(session);
54 | ```
55 |
56 | ### Sessions
57 |
58 | When authenticated the server responds with an auth token (JWT) which contains useful properties and gets deserialized into a `Session` object.
59 |
60 | ```js
61 | console.info(session.token); // raw JWT token
62 | console.info(session.refreshToken); // refresh token
63 | console.info(session.userId);
64 | console.info(session.username);
65 | console.info("Session has expired?", session.isexpired(Date.now() / 1000));
66 | const expiresat = session.expires_at;
67 | console.warn("Session will expire at", new Date(expiresat * 1000).toISOString());
68 | ```
69 |
70 | It is recommended to store the auth token from the session and check at startup if it has expired. If the token has expired you must reauthenticate. The expiry time of the token can be changed as a setting in the server.
71 |
72 | ```js
73 | // Assume we've stored the auth token in browser Web Storage.
74 | const authtoken = window.localStorage.getItem("nkauthtoken");
75 | const refreshtoken = window.localStorage.getItem("nkrefreshtoken");
76 |
77 | let session = nakamajs.Session.restore(authtoken, refreshtoken);
78 |
79 | // Check whether a session is close to expiry.
80 |
81 | const unixTimeInFuture = Date.now() + 8.64e+7; // one day from now
82 |
83 | if (session.isexpired(unixTimeInFuture / 1000)) {
84 | try
85 | {
86 | session = await client.sessionRefresh(session);
87 | }
88 | catch (e)
89 | {
90 | console.info("Session can no longer be refreshed. Must reauthenticate!");
91 | }
92 | }
93 | ```
94 |
95 | ### Requests
96 |
97 | The client includes lots of builtin APIs for various features of the game server. These can be accessed with the methods which return Promise objects. It can also call custom logic as RPC functions on the server. These can also be executed with a socket object.
98 |
99 | All requests are sent with a session object which authorizes the client.
100 |
101 | ```js
102 | const account = await client.getAccount(session);
103 | console.info(account.user.id);
104 | console.info(account.user.username);
105 | console.info(account.wallet);
106 | ```
107 |
108 | ### Socket
109 |
110 | The client can create one or more sockets with the server. Each socket can have it's own event listeners registered for responses received from the server.
111 |
112 | ```js
113 | const secure = false; // Enable if server is run with an SSL certificate
114 | const trace = false;
115 | const socket = client.createSocket(secure, trace);
116 | socket.ondisconnect = (evt) => {
117 | console.info("Disconnected", evt);
118 | };
119 |
120 | const session = await socket.connect(session);
121 | // Socket is open.
122 | ```
123 |
124 | If you are using the optional protocol buffer adapter, pass the adapter to the Socket object during construction:
125 |
126 | ```js
127 | import {WebSocketAdapterPb} from "@heroiclabs/nakama-js-protobuf"
128 |
129 | const secure = false; // Enable if server is run with an SSL certificate
130 | const trace = false;
131 | const socket = client.createSocket(secure, trace, new WebSocketAdapterPb());
132 | ```
133 |
134 | There's many messages for chat, realtime, status events, notifications, etc. which can be sent or received from the socket.
135 |
136 | ```js
137 | socket.onchannelmessage = (message) => {
138 | console.info("Message received from channel", message.channel_id);
139 | console.info("Received message", message);
140 | };
141 |
142 |
143 | // 1 = room, 2 = Direct Message, 3 = Group
144 | const roomname = "mychannel";
145 | const type: number = 1;
146 | const persistence: boolean = false;
147 | const hidden: boolean = false;
148 |
149 | const channel = await socket.joinChat(roomname, type, persistence, hidden);
150 |
151 | const message = { hello: "world" };
152 | socket.writeChatMessage(channel.id, message);
153 | ```
154 |
155 | ## Handling errors
156 |
157 | For any errors in client requests, we return the original error objects from the Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
158 |
159 | In order to capture the Nakama server response associated with the error, when you wrap your client requests in a `try...catch` statement you can invoke `await error.json()` on the `error` object in the catch block:
160 |
161 | ```js
162 | try {
163 | const account = await client.getAccount(session);
164 | console.info(account.user.id);
165 | console.info(account.user.username);
166 | console.info(account.wallet);
167 | } catch (error) {
168 | console.info("Inner Nakama error", await error.json());
169 | }
170 | ```
171 |
172 | ## Contribute
173 |
174 | The development roadmap is managed as GitHub issues and pull requests are welcome. If you're interested in enhancing the code please open an issue to discuss the changes or drop in and discuss it in the [community forum](https://forum.heroiclabs.com).
175 |
176 | ### Source Builds
177 |
178 | Ensure you are using Node v18>.
179 |
180 | The codebase is multi-package monorepo written in TypeScript and can be built with [esbuild](https://github.com/evanw/esbuild). All dependencies are managed with NPM.
181 |
182 | To build from source, first install all workspace dependencies from the repository root with `npm install`.
183 |
184 | Then to build a specific workspace, pass the `--workspace` flag to your build command, for example:
185 |
186 | ```shell
187 | npm run build --workspace=@heroiclabs/nakama-js
188 | ```
189 |
190 | ### Run Tests
191 |
192 | To run tests you will need to run the server and database. Most tests are written as integration tests which execute against the server. A quick approach we use with our test workflow is to use the Docker compose file described in the [documentation](https://heroiclabs.com/docs/nakama/getting-started/install/docker/).
193 |
194 | Tests are run against each workspace bundle; if you have made source code changes, you should `npm run build --workspace=` prior to running tests.
195 |
196 | ```shell
197 | docker-compose -f ./docker-compose.yml up
198 | npm run test --workspace=@heroiclabs/nakama-js-test
199 | ```
200 |
201 | ### Protocol Buffer Web Socket Adapter
202 |
203 | To update the generated Typescript required for using the protocol buffer adapter, `cd` into
204 | `packages/nakama-js-protobuf` and run the following:
205 |
206 | ```shell
207 | npx protoc \
208 | --plugin="./node_modules/.bin/protoc-gen-ts_proto" \
209 | --proto_path=$GOPATH/src/github.com/heroiclabs/nakama-common \
210 | --ts_proto_out=. \
211 | --ts_proto_opt=snakeToCamel=false \
212 | --ts_proto_opt=esModuleInterop=true \
213 | $GOPATH/src/github.com/heroiclabs/nakama-common/rtapi/realtime.proto \
214 | $GOPATH/src/github.com/heroiclabs/nakama-common/api/api.proto
215 | ```
216 |
217 | ### Release Process
218 |
219 | To release onto NPM if you have access to the "@heroiclabs" organization you can use NPM.
220 |
221 | ```shell
222 | npm run build --workspace= && npm publish --access=public --workspace=
223 | ```
224 |
225 | ### Generate Docs
226 |
227 | API docs are generated with typedoc and deployed to GitHub pages.
228 |
229 | To run typedoc:
230 |
231 | ```
232 | npm install && npm run docs
233 | ```
234 |
235 | ### License
236 |
237 | This project is licensed under the [Apache-2 License](https://github.com/heroiclabs/nakama-js/blob/master/LICENSE).
238 |
--------------------------------------------------------------------------------
/packages/nakama-js/build.mjs:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Nakama Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import esbuild from 'esbuild';
16 |
17 | // Shared esbuild config
18 | const config = {
19 | logLevel: 'info',
20 | entryPoints: ['index.ts'],
21 | bundle: true,
22 | target: 'es6',
23 | globalName: 'nakamajs'
24 | };
25 |
26 | // Build CommonJS
27 | await esbuild.build({
28 | ...config,
29 | format: 'cjs',
30 | outfile: 'dist/nakama-js.cjs.js'
31 | });
32 |
33 | // Build ESM
34 | await esbuild.build({
35 | ...config,
36 | format: 'esm',
37 | outfile: 'dist/nakama-js.esm.mjs'
38 | });
39 |
40 | // Build IIFE
41 | await esbuild.build({
42 | ...config,
43 | format: 'iife',
44 | outfile: 'dist/nakama-js.iife.js'
45 | });
--------------------------------------------------------------------------------
/packages/nakama-js/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import "whatwg-fetch";
17 | export * from "./client";
18 | export * from "./session";
19 | export * from "./socket";
20 | export * from "./web_socket_adapter";
21 | /**
22 | * Reexported due to duplicate definition of ChannelMessage in [Client]{@link ./client.ts} and [Session]{@link ./session.ts}
23 | */
24 | export { ChannelMessage } from "./client";
25 |
--------------------------------------------------------------------------------
/packages/nakama-js/dist/session.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /** A session authenticated for a user with Nakama server. */
17 | export interface ISession {
18 | /** Claims */
19 | /** The authorization token used to construct this session. */
20 | token: string;
21 | /** If the user account for this session was just created. */
22 | created: boolean;
23 | /** The UNIX timestamp when this session was created. */
24 | readonly created_at: number;
25 | /** The UNIX timestamp when this session will expire. */
26 | expires_at?: number;
27 | /** The UNIX timestamp when the refresh token will expire. */
28 | refresh_expires_at?: number;
29 | /** Refresh token that can be used for session token renewal. */
30 | refresh_token: string;
31 | /** The username of the user who owns this session. */
32 | username?: string;
33 | /** The ID of the user who owns this session. */
34 | user_id?: string;
35 | /** Any custom properties associated with this session. */
36 | vars?: object;
37 | /** Validate token */
38 | /** If the session has expired. */
39 | isexpired(currenttime: number): boolean;
40 | /** If the refresh token has expired. */
41 | isrefreshexpired(currenttime: number): boolean;
42 | }
43 | export declare class Session implements ISession {
44 | readonly created: boolean;
45 | token: string;
46 | readonly created_at: number;
47 | expires_at?: number;
48 | refresh_expires_at?: number;
49 | refresh_token: string;
50 | username?: string;
51 | user_id?: string;
52 | vars?: object;
53 | constructor(token: string, refresh_token: string, created: boolean);
54 | isexpired(currenttime: number): boolean;
55 | isrefreshexpired(currenttime: number): boolean;
56 | update(token: string, refreshToken: string): void;
57 | decodeJWT(token: string): any;
58 | static restore(token: string, refreshToken: string): Session;
59 | }
60 |
--------------------------------------------------------------------------------
/packages/nakama-js/dist/utils.d.ts:
--------------------------------------------------------------------------------
1 | export declare function buildFetchOptions(method: string, options: any, bodyJson: string): any;
2 | export declare function b64EncodeUnicode(str: string): string;
3 | export declare function b64DecodeUnicode(str: string): string;
4 |
--------------------------------------------------------------------------------
/packages/nakama-js/dist/web_socket_adapter.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * An interface used by Nakama's web socket to determine the payload protocol.
18 | */
19 | export interface WebSocketAdapter {
20 | /**
21 | * Dispatched when the web socket closes.
22 | */
23 | onClose: SocketCloseHandler | null;
24 | /**
25 | * Dispatched when the web socket receives an error.
26 | */
27 | onError: SocketErrorHandler | null;
28 | /**
29 | * Dispatched when the web socket receives a normal message.
30 | */
31 | onMessage: SocketMessageHandler | null;
32 | /**
33 | * Dispatched when the web socket opens.
34 | */
35 | onOpen: SocketOpenHandler | null;
36 | isOpen(): boolean;
37 | close(): void;
38 | connect(scheme: string, host: string, port: string, createStatus: boolean, token: string): void;
39 | send(message: any): void;
40 | }
41 | /**
42 | * SocketCloseHandler defines a lambda that handles WebSocket close events.
43 | */
44 | export interface SocketCloseHandler {
45 | (this: WebSocket, evt: CloseEvent): void;
46 | }
47 | /**
48 | * SocketErrorHandler defines a lambda that handles responses from the server via WebSocket
49 | * that indicate an error.
50 | */
51 | export interface SocketErrorHandler {
52 | (this: WebSocket, evt: Event): void;
53 | }
54 | /**
55 | * SocketMessageHandler defines a lambda that handles valid WebSocket messages.
56 | */
57 | export interface SocketMessageHandler {
58 | (message: any): void;
59 | }
60 | /**
61 | * SocketOpenHandler defines a lambda that handles WebSocket open events.
62 | */
63 | export interface SocketOpenHandler {
64 | (this: WebSocket, evt: Event): void;
65 | }
66 | /**
67 | * A text-based socket adapter that accepts and transmits payloads over UTF-8.
68 | */
69 | export declare class WebSocketAdapterText implements WebSocketAdapter {
70 | private _socket?;
71 | get onClose(): SocketCloseHandler | null;
72 | set onClose(value: SocketCloseHandler | null);
73 | get onError(): SocketErrorHandler | null;
74 | set onError(value: SocketErrorHandler | null);
75 | get onMessage(): SocketMessageHandler | null;
76 | set onMessage(value: SocketMessageHandler | null);
77 | get onOpen(): SocketOpenHandler | null;
78 | set onOpen(value: SocketOpenHandler | null);
79 | isOpen(): boolean;
80 | connect(scheme: string, host: string, port: string, createStatus: boolean, token: string): void;
81 | close(): void;
82 | send(msg: any): void;
83 | }
84 |
--------------------------------------------------------------------------------
/packages/nakama-js/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import "whatwg-fetch";
18 |
19 | export * from "./client";
20 | export * from "./session";
21 | export * from "./socket";
22 | export * from "./web_socket_adapter";
23 |
24 | /**
25 | * Reexported due to duplicate definition of ChannelMessage in [Client]{@link ./client.ts} and [Session]{@link ./session.ts}
26 | */
27 | export { ChannelMessage } from "./client";
28 |
--------------------------------------------------------------------------------
/packages/nakama-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heroiclabs/nakama-js",
3 | "version": "2.9.0",
4 | "scripts": {
5 | "build": "npx tsc && npx rollup -c --bundleConfigAsCjs && node build.mjs"
6 | },
7 | "description": "JavaScript client for Nakama server written in TypeScript.",
8 | "main": "dist/nakama-js.cjs.js",
9 | "module": "dist/nakama-js.esm.mjs",
10 | "types": "dist/index.d.ts",
11 | "exports": {
12 | "./package.json": "./package.json",
13 | ".": {
14 | "types": "./dist/index.d.ts",
15 | "import": "./dist/nakama-js.esm.mjs",
16 | "require": "./dist/nakama-js.cjs.js"
17 | }
18 | },
19 | "keywords": [
20 | "app server",
21 | "client library",
22 | "game server",
23 | "nakama",
24 | "realtime",
25 | "realtime chat"
26 | ],
27 | "repository": "https://github.com/heroiclabs/nakama-js",
28 | "homepage": "https://heroiclabs.com",
29 | "bugs": "https://github.com/heroiclabs/nakama-js/issues",
30 | "author": "Chris Molozian ",
31 | "contributors": [
32 | "Andrei Mihu ",
33 | "Mo Firouz "
34 | ],
35 | "license": "Apache-2.0",
36 | "dependencies": {
37 | "@scarf/scarf": "^1.4.0",
38 | "base64-arraybuffer": "^1.0.2",
39 | "js-base64": "^3.7.7",
40 | "whatwg-fetch": "^3.6.20"
41 | },
42 | "devDependencies": {
43 | "@rollup/plugin-node-resolve": "^15.3.0",
44 | "@rollup/plugin-typescript": "^12.1.1",
45 | "rollup": "^4.27.4",
46 | "tslib": "^2.8.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/nakama-js/rollup.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Heroic Labs
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Rollup is the legacy build system for nakama-js and is only used for cocos2d-x-js support.
18 |
19 | import typescript from '@rollup/plugin-typescript';
20 | import { nodeResolve } from '@rollup/plugin-node-resolve';
21 |
22 | export default {
23 | input: './index.ts',
24 | output: {
25 | format: 'umd',
26 | name: 'nakamajs',
27 | dir: "dist",
28 | entryFileNames: "nakama-js.umd.js" // workaround for TS requirement that dir is specified in config
29 | },
30 | plugins: [
31 | typescript({
32 | include: ["**/*.ts"],
33 | target: "es5"
34 | }),
35 | nodeResolve()
36 | ],
37 | moduleContext: {
38 | [require.resolve('whatwg-fetch')]: 'window'
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/packages/nakama-js/session.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import * as base64 from "js-base64"
19 |
20 | /** A session authenticated for a user with Nakama server. */
21 | export interface ISession {
22 | /** Claims */
23 | /** The authorization token used to construct this session. */
24 | token: string;
25 | /** If the user account for this session was just created. */
26 | created: boolean
27 | /** The UNIX timestamp when this session was created. */
28 | readonly created_at: number;
29 | /** The UNIX timestamp when this session will expire. */
30 | expires_at?: number;
31 | /** The UNIX timestamp when the refresh token will expire. */
32 | refresh_expires_at?: number;
33 | /** Refresh token that can be used for session token renewal. */
34 | refresh_token: string;
35 | /** The username of the user who owns this session. */
36 | username?: string;
37 | /** The ID of the user who owns this session. */
38 | user_id?: string;
39 | /** Any custom properties associated with this session. */
40 | vars?: object;
41 |
42 | /** Validate token */
43 | /** If the session has expired. */
44 | isexpired(currenttime: number): boolean;
45 | /** If the refresh token has expired. */
46 | isrefreshexpired(currenttime: number): boolean;
47 | }
48 |
49 | export class Session implements ISession {
50 |
51 | token : string;
52 | readonly created_at: number;
53 | expires_at?: number;
54 | refresh_expires_at?: number;
55 | refresh_token: string;
56 | username?: string;
57 | user_id?: string;
58 | vars?: object;
59 |
60 | constructor(
61 | token: string,
62 | refresh_token: string,
63 | readonly created: boolean) {
64 | this.token = token;
65 | this.refresh_token = refresh_token;
66 | this.created_at = Math.floor(new Date().getTime() / 1000);
67 | this.update(token, refresh_token);
68 | }
69 |
70 | isexpired(currenttime: number): boolean {
71 | return (this.expires_at! - currenttime) < 0;
72 | }
73 |
74 | isrefreshexpired(currenttime: number): boolean {
75 | return (this.refresh_expires_at! - currenttime) < 0;
76 | }
77 |
78 | update(token: string, refreshToken: string) {
79 | const tokenDecoded = this.decodeJWT(token);
80 | const tokenExpiresAt = Math.floor(parseInt(tokenDecoded['exp']));
81 |
82 | /** clients that have just updated to the refresh tokens */
83 | /** client release will not have a cached refresh token */
84 | if (refreshToken) {
85 | const refreshTokenDecoded = this.decodeJWT(refreshToken);
86 | const refreshTokenExpiresAt = Math.floor(parseInt(refreshTokenDecoded['exp']));
87 | this.refresh_expires_at = refreshTokenExpiresAt;
88 | this.refresh_token = refreshToken;
89 | }
90 |
91 | this.token = token;
92 | this.expires_at = tokenExpiresAt;
93 | this.username = tokenDecoded['usn'];
94 | this.user_id = tokenDecoded['uid'];
95 | this.vars = tokenDecoded['vrs'];
96 | }
97 |
98 | decodeJWT(token: string) {
99 | const { 1: base64Raw } = token.split('.')
100 | const _base64 = base64Raw.replace(/-/g, '+').replace(/_/g, '/')
101 | const jsonPayload = decodeURIComponent(base64.atob(_base64).split('').map((c) => {
102 | return `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`
103 | }).join(''))
104 |
105 | return JSON.parse(jsonPayload)
106 | }
107 |
108 | static restore(token: string, refreshToken: string): Session {
109 | return new Session(token, refreshToken, false);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/packages/nakama-js/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": [
4 | "./*",
5 | ],
6 | "compilerOptions": {
7 | "outDir": "dist"
8 | },
9 | "declarationDir": "dist"
10 | }
11 |
--------------------------------------------------------------------------------
/packages/nakama-js/utils.ts:
--------------------------------------------------------------------------------
1 | import {encode, decode} from "js-base64"
2 |
3 | export function buildFetchOptions(method: string, options: any, bodyJson: string) {
4 | const fetchOptions = {...{ method: method }, ...options};
5 | fetchOptions.headers = {...options.headers};
6 |
7 | if (typeof XMLHttpRequest !== "undefined") {
8 | const descriptor = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "withCredentials");
9 |
10 | // in Cocos Creator, XMLHttpRequest.withCredentials is not writable, so make the fetch
11 | // polyfill avoid writing to it.
12 | if (!descriptor?.set) {
13 | fetchOptions.credentials = 'cocos-ignore'; // string value is arbitrary, cannot be 'omit' or 'include
14 | }
15 | }
16 |
17 | if(!Object.keys(fetchOptions.headers).includes("Accept")) {
18 | fetchOptions.headers["Accept"] = "application/json";
19 | }
20 |
21 | if(!Object.keys(fetchOptions.headers).includes("Content-Type")) {
22 | fetchOptions.headers["Content-Type"] = "application/json";
23 | }
24 |
25 | Object.keys(fetchOptions.headers).forEach((key: string) => {
26 | if (!fetchOptions.headers[key]) {
27 | delete fetchOptions.headers[key];
28 | }
29 | });
30 |
31 | if (bodyJson) {
32 | fetchOptions.body = bodyJson;
33 | }
34 |
35 | return fetchOptions;
36 | }
37 |
38 | export function b64EncodeUnicode(str:string) {
39 | return encode(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
40 | function toSolidBytes(_match:string, p1) {
41 | return String.fromCharCode(Number('0x' + p1));
42 | }));
43 | }
44 |
45 | export function b64DecodeUnicode(str: string) {
46 | return decodeURIComponent(decode(str).split('').map(function(c) {
47 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
48 | }).join(''));
49 | }
50 |
--------------------------------------------------------------------------------
/packages/nakama-js/web_socket_adapter.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { decode, encode } from "base64-arraybuffer";
18 | import { btoa } from "js-base64"
19 |
20 | /**
21 | * An interface used by Nakama's web socket to determine the payload protocol.
22 | */
23 | export interface WebSocketAdapter {
24 |
25 | /**
26 | * Dispatched when the web socket closes.
27 | */
28 | onClose : SocketCloseHandler | null;
29 |
30 | /**
31 | * Dispatched when the web socket receives an error.
32 | */
33 | onError : SocketErrorHandler | null;
34 |
35 | /**
36 | * Dispatched when the web socket receives a normal message.
37 | */
38 | onMessage : SocketMessageHandler | null;
39 |
40 | /**
41 | * Dispatched when the web socket opens.
42 | */
43 | onOpen : SocketOpenHandler | null;
44 |
45 | isOpen(): boolean;
46 | close() : void;
47 | connect(scheme: string, host: string, port : string, createStatus: boolean, token : string) : void;
48 | send(message: any) : void;
49 | }
50 |
51 | /**
52 | * SocketCloseHandler defines a lambda that handles WebSocket close events.
53 | */
54 | export interface SocketCloseHandler {
55 | (this : WebSocket, evt: CloseEvent): void;
56 | }
57 |
58 | /**
59 | * SocketErrorHandler defines a lambda that handles responses from the server via WebSocket
60 | * that indicate an error.
61 | */
62 | export interface SocketErrorHandler {
63 | (this : WebSocket, evt: Event): void;
64 | }
65 |
66 | /**
67 | * SocketMessageHandler defines a lambda that handles valid WebSocket messages.
68 | */
69 | export interface SocketMessageHandler {
70 | (message: any): void;
71 | }
72 |
73 | /**
74 | * SocketOpenHandler defines a lambda that handles WebSocket open events.
75 | */
76 | export interface SocketOpenHandler {
77 | (this : WebSocket, evt : Event) : void
78 | }
79 |
80 | /**
81 | * A text-based socket adapter that accepts and transmits payloads over UTF-8.
82 | */
83 | export class WebSocketAdapterText implements WebSocketAdapter {
84 | private _socket?: WebSocket;
85 |
86 | get onClose(): SocketCloseHandler | null {
87 | return this._socket!.onclose;
88 | }
89 |
90 | set onClose(value: SocketCloseHandler | null) {
91 | this._socket!.onclose = value;
92 | }
93 |
94 | get onError(): SocketErrorHandler | null {
95 | return this._socket!.onerror;
96 | }
97 |
98 | set onError(value: SocketErrorHandler | null) {
99 | this._socket!.onerror = value;
100 | }
101 |
102 | get onMessage(): SocketMessageHandler | null {
103 | return this._socket!.onmessage;
104 | }
105 |
106 | set onMessage(value: SocketMessageHandler | null) {
107 | if (value) {
108 | this._socket!.onmessage = (evt: MessageEvent) => {
109 | const message: any = JSON.parse(evt.data);
110 |
111 | if (message.match_data && message.match_data.data) {
112 | message.match_data.data = new Uint8Array(decode(message.match_data.data));
113 | } else if (message.party_data && message.party_data.data) {
114 | message.party_data.data = new Uint8Array(decode(message.party_data.data));
115 | }
116 |
117 | value!(message);
118 | };
119 | }
120 | else {
121 | value = null;
122 | }
123 | }
124 |
125 | get onOpen(): SocketOpenHandler | null {
126 | return this._socket!.onopen;
127 | }
128 |
129 | set onOpen(value: SocketOpenHandler | null) {
130 | this._socket!.onopen = value;
131 | }
132 |
133 | isOpen(): boolean {
134 | return this._socket?.readyState == WebSocket.OPEN;
135 | }
136 |
137 | connect(scheme: string, host: string, port: string, createStatus: boolean, token: string): void {
138 | const url = `${scheme}${host}:${port}/ws?lang=en&status=${encodeURIComponent(createStatus.toString())}&token=${encodeURIComponent(token)}`;
139 | this._socket = new WebSocket(url);
140 | }
141 |
142 | close() {
143 | this._socket!.close();
144 | this._socket = undefined;
145 | }
146 |
147 | send(msg: any): void {
148 | if (msg.match_data_send) {
149 | // according to protobuf docs, int64 is encoded to JSON as string.
150 | msg.match_data_send.op_code = msg.match_data_send.op_code.toString();
151 | let payload = msg.match_data_send.data;
152 | if (payload && payload instanceof Uint8Array) {
153 | msg.match_data_send.data = encode(payload.buffer);
154 | } else if (payload) { // it's a string
155 | msg.match_data_send.data = btoa(payload);
156 | }
157 | } else if (msg.party_data_send) {
158 | // according to protobuf docs, int64 is encoded to JSON as string.
159 | msg.party_data_send.op_code = msg.party_data_send.op_code.toString();
160 | let payload = msg.party_data_send.data;
161 | if (payload && payload instanceof Uint8Array) {
162 | msg.party_data_send.data = encode(payload.buffer);
163 | } else if (payload) { // it's a string
164 | msg.party_data_send.data = btoa(payload);
165 | }
166 | }
167 |
168 | this._socket!.send(JSON.stringify(msg));
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/packages/satori-js-test/client.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Page} from "puppeteer"
18 | import * as satorijs from "@heroiclabs/satori-js";
19 | import {createPage} from "./utils"
20 | import {describe, expect, it} from '@jest/globals'
21 |
22 | describe('Client Tests', () => {
23 |
24 | it('should create object with defaults', async () => {
25 | const page : Page = await createPage();
26 |
27 | const client = await page.evaluate(() => {
28 | return new satorijs.Client();
29 | });
30 |
31 | expect(client).not.toBeNull();
32 | expect(client.serverkey).toBe("defaultkey");
33 | expect(client.host).toBe("127.0.0.1");
34 | expect(client.port).toBe("7350");
35 | expect(client.useSSL).toBe(false);
36 | expect(client.timeout).toBe(7000);
37 | });
38 |
39 | it('should create object with configuration', async () => {
40 | const page : Page = await createPage();
41 |
42 | const SERVER_KEY = "somesecret!";
43 | const HOST = "127.0.0.2";
44 | const PORT = "8080";
45 | const SSL = true;
46 | const TIMEOUT = 8000;
47 |
48 | const client = await page.evaluate((SERVER_KEY, HOST, PORT, SSL, TIMEOUT) => {
49 | return new satorijs.Client(SERVER_KEY, HOST, PORT, SSL, TIMEOUT);
50 | }, SERVER_KEY, HOST, PORT, SSL, TIMEOUT);
51 |
52 | expect(client).not.toBeNull();
53 | expect(client.serverkey).toBe(SERVER_KEY);
54 | expect(client.host).toBe(HOST);
55 | expect(client.port).toBe(PORT);
56 | expect(client.useSSL).toBe(SSL);
57 | expect(client.timeout).toBe(TIMEOUT);
58 | });
59 |
60 | it('should obey timeout configuration option', async () => {
61 | const page : Page = await createPage();
62 |
63 | const err = await page.evaluate(() => {
64 | const client = new satorijs.Client("defaultkey", "127.0.0.1", "7350", false, 0);
65 | return client.authenticate("timeoutuseridentifier")
66 | .catch(err => err);
67 | });
68 |
69 | expect(err).not.toBeNull();
70 | expect(err).toBe("Request timed out.");
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/packages/satori-js-test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Satori JS Browser Test
7 |
8 |
9 |
10 |
11 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/packages/satori-js-test/jest.config.js:
--------------------------------------------------------------------------------
1 | const merge = require('merge')
2 | const ts_preset = require('ts-jest/jest-preset')
3 | const puppeteer_preset = require('jest-puppeteer/jest-preset')
4 |
5 | //use multiple jest presets by merging and exporting them as a single object
6 | module.exports = merge.recursive(ts_preset, puppeteer_preset, {
7 | globals: {
8 | 'ts-jest': {
9 | tsConfig: 'tsconfig.test.json'
10 | }
11 | }
12 | }
13 | )
14 |
--------------------------------------------------------------------------------
/packages/satori-js-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heroiclabs/satori-js-test",
3 | "version": "1.0.0",
4 | "description": "A subproject that houses tests for Satori JS",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "dependencies": {
8 | "@heroiclabs/satori-js": "file:../satori-js",
9 | "base64url": "3.0.1"
10 | },
11 | "scripts": {
12 | "test": "npx typescript --project tsconfig.test.json && jest --runInBand"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/satori-js-test/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "es2015",
6 | "es2016",
7 | "es2017"
8 | ],
9 | "target": "es2018",
10 | "module": "es2015",
11 | "removeComments": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "moduleResolution": "node",
14 | "noEmitHelpers": false,
15 | "importHelpers": false,
16 | "baseUrl": "./",
17 | "noEmit": true,
18 | "allowSyntheticDefaultImports": true
19 | },
20 | "include": [
21 | "./*",
22 | ],
23 | "exclude": []
24 | }
25 |
--------------------------------------------------------------------------------
/packages/satori-js-test/utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { Page, Browser} from "puppeteer";
18 | const fs = require("fs");
19 | const crypto = require("crypto");
20 | const base64url = require("base64url");
21 |
22 | // automatically assigned by puppeteer + Jest
23 | declare var browser : Browser;
24 |
25 | // automatically assigned by puppeteer + Jest
26 | declare var browser : Browser;
27 |
28 | // util to generate a random id.
29 | export function generateid(): string {
30 | const arr: string[] = [];
31 |
32 | for (let i: number = 0; i < 30; i++) {
33 | arr.push(Math.random().toString(36)[3]);
34 | }
35 |
36 | return arr.join("");
37 | };
38 |
39 | export async function createPage(): Promise {
40 |
41 | const page = await browser.newPage();
42 |
43 | page.on('console', msg => console.log('LOG:', msg.text()));
44 | page.on('error', handlePageError);
45 | page.on('pageerror', handlePageError);
46 |
47 | const nakamaJsLib = fs.readFileSync(__dirname + '/../nakama-js/dist/nakama-js.iife.js', 'utf8');
48 | const nakamaJsProtobufLib = fs.readFileSync(__dirname + '/../nakama-js-protobuf/dist/nakama-js-protobuf.iife.js', 'utf8');
49 |
50 | await page.evaluateOnNewDocument(nakamaJsLib);
51 | await page.evaluateOnNewDocument(nakamaJsProtobufLib);
52 | await page.evaluateOnNewDocument(() => {
53 | globalThis.timeoutPromise = function(ms) {
54 | return new Promise(resolve => setTimeout(resolve, ms));
55 | }
56 | })
57 |
58 | await page.goto('about:blank');
59 |
60 | return page;
61 | }
62 |
63 | function handlePageError(err) {
64 |
65 | let msg: string;
66 |
67 | if (err instanceof Object) {
68 | msg = JSON.stringify(err);
69 | }
70 | else {
71 | msg = err;
72 | }
73 |
74 | console.error('ERR:', msg);
75 | }
76 |
77 | export const enum AdapterType {
78 | Text = 0,
79 | Protobuf = 1
80 | }
81 |
82 | export const adapters = [AdapterType.Text, AdapterType.Protobuf];
83 |
84 | export function createFacebookInstantGameAuthToken(id : string) : string {
85 | const testSecret = "fb-instant-test-secret";
86 |
87 | const mockFbInstantPayload = JSON.stringify({
88 | algorithm: "HMAC-SHA256",
89 | issued_at: 1594867628,
90 | player_id: id,
91 | request_payload: ""
92 | });
93 |
94 | const encodedPayload = base64url(mockFbInstantPayload);
95 |
96 | const signature = crypto.createHmac('sha256', testSecret).update(encodedPayload).digest();
97 | const encodedSignature = base64url(signature);
98 |
99 | const token = encodedSignature + "." + encodedPayload;
100 | return token;
101 | }
102 |
103 | export const matchmakerTimeout = 20000;
104 |
--------------------------------------------------------------------------------
/packages/satori-js/README.md:
--------------------------------------------------------------------------------
1 | Satori JavaScript Client
2 | ========================
3 |
4 | > JavaScript client for Satori server written in TypeScript. For browser and React Native projects.
5 |
6 | This client implements the full API for interacting with Satori server. It's written in TypeScript with minimal dependencies to be compatible with all modern browsers and React Native.
7 |
8 | Full documentation is online - https://heroiclabs.com/docs/javascript-client-guide
9 |
10 | ## Getting Started
11 |
12 | You'll need access to an instance of the Satori server before you can connect with the client.
13 |
14 | 1. Import the client into your project. It's [available on NPM](https://www.npmjs/package/@heroiclabs/satori-js).
15 |
16 | ```shell
17 | npm install @heroiclabs/satori-js
18 | ```
19 |
20 | You'll now see the code in the "node_modules" folder and package listed in your "package.json".
21 |
22 | 2. Use the connection credentials to build a client object.
23 |
24 | ```js
25 | import {Client} from "@heroiclabs/satori-js";
26 |
27 | const useSSL = false;
28 | const client = new Client("apiKey", "127.0.0.1", 7450, useSSL);
29 | ```
30 |
31 | ## Usage
32 |
33 | The client object has many method to execute various features in the server.
34 |
35 | ### Authenticate
36 |
37 | To authenticate with the Satori server you must provide an identifier for the user.
38 |
39 | ```js
40 | const userId = "";
41 |
42 | client.authenticate(userId)
43 | .then(session => {
44 | _session = session;
45 | console.info("Authenticated:", session);
46 | }).catch(error => {
47 | console.error("Error:", error);
48 | });
49 | ```
50 |
51 | ### Sessions
52 |
53 | When authenticated the server responds with an auth token (JWT) which contains useful properties and gets deserialized into a `Session` object.
54 |
55 | ```js
56 | console.info(session.token); // raw JWT token
57 | console.info(session.refreshToken); // refresh token
58 | console.info("Session has expired?", session.isexpired(Date.now() / 1000));
59 | const expiresAt = session.expires_at;
60 | console.warn("Session will expire at:", new Date(expiresAt * 1000).toISOString());
61 | ```
62 |
63 | It is recommended to store the auth token from the session and check at startup if it has expired. If the token has expired you must reauthenticate. The expiry time of the token can be changed as a setting in the server.
64 |
65 | ```js
66 | // Assume we've stored the auth token in browser Web Storage.
67 | const authtoken = window.localStorage.getItem("satori_authtoken");
68 | const refreshtoken = window.localStorage.getItem("satori_refreshtoken");
69 |
70 | let session = satorijs.Session.restore(authtoken, refreshtoken);
71 |
72 | // Check whether a session is close to expiry.
73 |
74 | const unixTimeInFuture = Date.now() + 8.64e+7; // one day from now
75 |
76 | if (session.isexpired(unixTimeInFuture / 1000)) {
77 | try
78 | {
79 | session = await client.sessionRefresh(session);
80 | }
81 | catch (e)
82 | {
83 | console.info("Session can no longer be refreshed. Must reauthenticate!");
84 | }
85 | }
86 | ```
87 |
88 | ### Requests
89 |
90 | The client includes lots of builtin APIs for various featyures of the Satori server. These can be accessed with the methods which return Promise objects.
91 |
92 | Most requests are sent with a session object which authorizes the client.
93 |
94 | ```js
95 | const flags = await client.getFlags(session);
96 | console.info("Flags:", flags);
97 | ```
98 |
99 | ## Contribute
100 |
101 | The development roadmap is managed as GitHub issues and pull requests are welcome. If you're interested in enhancing the code please open an issue to discuss the changes or drop in and discuss it in the [community forum](https://forum.heroiclabs.com).
102 |
103 | ### Source Builds
104 |
105 | Ensure you are using Node v18>.
106 |
107 | The codebase is multi-package monorepo written in TypeScript and can be built with [esbuild](https://github.com/evanw/esbuild). All dependencies are managed with Yarn.
108 |
109 | To build from source, install dependencies and build the `satori-js` package:
110 |
111 | ```shell
112 | npm install --workspace=@heroiclabs/satori-js && npm run build --workspace=@heroiclabs/satori-js
113 | ```
114 |
115 | ### Run Tests
116 |
117 | To run tests you will need access to an instance of the Satori server.
118 |
119 | Tests are run against each workspace bundle; if you have made source code changes, you should `npm run build --workspace=` prior to running tests.
120 |
121 | ```shell
122 | npm run test --workspace=@heroiclabs/satori-js-test
123 | ```
124 |
125 | ### Release Process
126 |
127 | To release onto NPM if you have access to the "@heroiclabs" organization you can use NPM.
128 |
129 | ```shell
130 | npm run build --workspace= && npm publish --access=public --workspace=
131 | ```
132 |
133 | ### Generate Docs
134 |
135 | API docs are generated with typedoc and deployed to GitHub pages.
136 |
137 | To run typedoc:
138 |
139 | ```
140 | npm install && npm run docs
141 | ```
142 |
143 | ### License
144 |
145 | This project is licensed under the [Apache-2 License](https://github.com/heroiclabs/nakama-js/blob/master/LICENSE).
--------------------------------------------------------------------------------
/packages/satori-js/build.mjs:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Nakama Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import esbuild from 'esbuild';
16 |
17 | // Shared esbuild config
18 | const config = {
19 | logLevel: 'info',
20 | entryPoints: ['index.ts'],
21 | bundle: true,
22 | target: 'es6',
23 | globalName: 'satorijs'
24 | };
25 |
26 | // Build CommonJS
27 | await esbuild.build({
28 | ...config,
29 | format: 'cjs',
30 | outfile: 'dist/satori-js.cjs.js'
31 | });
32 |
33 | // Build ESM
34 | await esbuild.build({
35 | ...config,
36 | format: 'esm',
37 | outfile: 'dist/satori-js.esm.mjs'
38 | });
39 |
40 | // Build IIFE
41 | await esbuild.build({
42 | ...config,
43 | format: 'iife',
44 | outfile: 'dist/satori-js.iife.js'
45 | });
--------------------------------------------------------------------------------
/packages/satori-js/dist/api.gen.d.ts:
--------------------------------------------------------------------------------
1 | /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */
2 | export interface ApiAuthenticateLogoutRequest {
3 | refresh_token?: string;
4 | token?: string;
5 | }
6 | /** Authenticate against the server with a refresh token. */
7 | export interface ApiAuthenticateRefreshRequest {
8 | refresh_token?: string;
9 | }
10 | /** */
11 | export interface ApiAuthenticateRequest {
12 | custom?: Record;
13 | default?: Record;
14 | id?: string;
15 | }
16 | /** A single event. Usually, but not necessarily, part of a batch. */
17 | export interface ApiEvent {
18 | id?: string;
19 | metadata?: Record;
20 | name?: string;
21 | timestamp?: string;
22 | value?: string;
23 | }
24 | /** */
25 | export interface ApiEventRequest {
26 | events?: Array;
27 | }
28 | /** An experiment that this user is partaking. */
29 | export interface ApiExperiment {
30 | name?: string;
31 | value?: string;
32 | }
33 | /** All experiments that this identity is involved with. */
34 | export interface ApiExperimentList {
35 | experiments?: Array;
36 | }
37 | /** Feature flag available to the identity. */
38 | export interface ApiFlag {
39 | condition_changed?: boolean;
40 | name?: string;
41 | value?: string;
42 | }
43 | /** */
44 | export interface ApiFlagList {
45 | flags?: Array;
46 | }
47 | /** A response containing all the messages for an identity. */
48 | export interface ApiGetMessageListResponse {
49 | cacheable_cursor?: string;
50 | messages?: Array;
51 | next_cursor?: string;
52 | prev_cursor?: string;
53 | }
54 | /** Enrich/replace the current session with a new ID. */
55 | export interface ApiIdentifyRequest {
56 | custom?: Record;
57 | default?: Record;
58 | id?: string;
59 | }
60 | /** A single live event. */
61 | export interface ApiLiveEvent {
62 | active_end_time_sec?: string;
63 | active_start_time_sec?: string;
64 | description?: string;
65 | id?: string;
66 | name?: string;
67 | value?: string;
68 | }
69 | /** List of Live events. */
70 | export interface ApiLiveEventList {
71 | live_events?: Array;
72 | }
73 | /** A scheduled message. */
74 | export interface ApiMessage {
75 | consume_time?: string;
76 | create_time?: string;
77 | metadata?: Record;
78 | read_time?: string;
79 | schedule_id?: string;
80 | send_time?: string;
81 | text?: string;
82 | update_time?: string;
83 | }
84 | /** Properties associated with an identity. */
85 | export interface ApiProperties {
86 | computed?: Record;
87 | custom?: Record;
88 | default?: Record;
89 | }
90 | /** A session. */
91 | export interface ApiSession {
92 | properties?: ApiProperties;
93 | refresh_token?: string;
94 | token?: string;
95 | }
96 | /** The request to update the status of a message. */
97 | export interface ApiUpdateMessageRequest {
98 | consume_time?: string;
99 | id?: string;
100 | read_time?: string;
101 | }
102 | /** Update Properties associated with this identity. */
103 | export interface ApiUpdatePropertiesRequest {
104 | custom?: Record;
105 | default?: Record;
106 | recompute?: boolean;
107 | }
108 | /** */
109 | export interface ProtobufAny {
110 | type_url?: string;
111 | value?: string;
112 | }
113 | /** */
114 | export interface RpcStatus {
115 | code?: number;
116 | details?: Array;
117 | message?: string;
118 | }
119 | export declare class SatoriApi {
120 | readonly apiKey: string;
121 | readonly basePath: string;
122 | readonly timeoutMs: number;
123 | constructor(apiKey: string, basePath: string, timeoutMs: number);
124 | /** A healthcheck which load balancers can use to check the service. */
125 | satoriHealthcheck(bearerToken: string, options?: any): Promise;
126 | /** A readycheck which load balancers can use to check the service. */
127 | satoriReadycheck(bearerToken: string, options?: any): Promise;
128 | /** Authenticate against the server. */
129 | satoriAuthenticate(basicAuthUsername: string, basicAuthPassword: string, body: ApiAuthenticateRequest, options?: any): Promise;
130 | /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */
131 | satoriAuthenticateLogout(bearerToken: string, body: ApiAuthenticateLogoutRequest, options?: any): Promise;
132 | /** Refresh a user's session using a refresh token retrieved from a previous authentication request. */
133 | satoriAuthenticateRefresh(basicAuthUsername: string, basicAuthPassword: string, body: ApiAuthenticateRefreshRequest, options?: any): Promise;
134 | /** Publish an event for this session. */
135 | satoriEvent(bearerToken: string, body: ApiEventRequest, options?: any): Promise;
136 | /** Get or list all available experiments for this identity. */
137 | satoriGetExperiments(bearerToken: string, names?: Array, options?: any): Promise;
138 | /** List all available flags for this identity. */
139 | satoriGetFlags(bearerToken: string, basicAuthUsername: string, basicAuthPassword: string, names?: Array, options?: any): Promise;
140 | /** Enrich/replace the current session with new identifier. */
141 | satoriIdentify(bearerToken: string, body: ApiIdentifyRequest, options?: any): Promise;
142 | /** Delete the caller's identity and associated data. */
143 | satoriDeleteIdentity(bearerToken: string, options?: any): Promise;
144 | /** List available live events. */
145 | satoriGetLiveEvents(bearerToken: string, names?: Array, options?: any): Promise;
146 | /** Get the list of messages for the identity. */
147 | satoriGetMessageList(bearerToken: string, limit?: number, forward?: boolean, cursor?: string, options?: any): Promise;
148 | /** Deletes a message for an identity. */
149 | satoriDeleteMessage(bearerToken: string, id: string, options?: any): Promise;
150 | /** Updates a message for an identity. */
151 | satoriUpdateMessage(bearerToken: string, id: string, body: ApiUpdateMessageRequest, options?: any): Promise;
152 | /** List properties associated with this identity. */
153 | satoriListProperties(bearerToken: string, options?: any): Promise;
154 | /** Update identity properties. */
155 | satoriUpdateProperties(bearerToken: string, body: ApiUpdatePropertiesRequest, options?: any): Promise;
156 | buildFullUrl(basePath: string, fragment: string, queryParams: Map): string;
157 | }
158 |
--------------------------------------------------------------------------------
/packages/satori-js/dist/client.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import { SatoriApi, ApiEvent } from "./api.gen";
17 | import { Session } from "./session";
18 | /** A client for Satori server. */
19 | export declare class Client {
20 | readonly apiKey: string;
21 | readonly host: string;
22 | readonly port: string;
23 | readonly useSSL: boolean;
24 | readonly timeout: number;
25 | readonly autoRefreshSession: boolean;
26 | /** The expired timespan used to check session lifetime. */
27 | expiredTimespanMs: number;
28 | /** The low level API client for Nakama server. */
29 | readonly apiClient: SatoriApi;
30 | constructor(apiKey?: string, host?: string, port?: string, useSSL?: boolean, timeout?: number, autoRefreshSession?: boolean);
31 | /** Authenticate a user with an ID against the server. */
32 | authenticate(id: string, customProperties?: Record, defaultProperties?: Record): Promise;
33 | /** Refresh a user's session using a refresh token retrieved from a previous authentication request. */
34 | sessionRefresh(session: Session): Promise;
35 | /** Log out a session, invalidate a refresh token, or log out all sessions/refresh tokens for a user. */
36 | logout(session: Session): Promise;
37 | /** Publish an event for this session. */
38 | event(session: Session, event: ApiEvent): Promise;
39 | /** Publish multiple events for this session */
40 | events(session: Session, events: Array): Promise;
41 | /** Get or list all available experiments for this identity. */
42 | getExperiments(session: Session, names?: Array): Promise;
43 | /** Get a single flag for this identity. Throws an error when the flag does not exist. */
44 | getFlag(session: Session, name: string): Promise;
45 | /** Get a single flag for this identity. */
46 | getFlagWithFallback(session: Session, name: string, fallbackValue?: string): Promise<{
47 | name: string;
48 | value: string | undefined;
49 | }>;
50 | /** Get a single flag with its configured default value. Throws an error when the flag does not exist. */
51 | getFlagDefault(name: string): Promise;
52 | /** Get a single flag with its configured default value. */
53 | getFlagDefaultWithFallback(name: string, fallbackValue?: string): Promise<{
54 | name: string;
55 | value: string | undefined;
56 | }>;
57 | /** List all available flags for this identity. */
58 | getFlags(session: Session, names?: Array): Promise;
59 | /** List all available default flags. */
60 | getFlagsDefault(names?: Array): Promise;
61 | /** Enrich/replace the current session with new identifier. */
62 | identify(session: Session, id: string, defaultProperties?: Record, customProperties?: Record): Promise;
63 | /** List available live events. */
64 | getLiveEvents(session: Session, names?: Array): Promise;
65 | /** List properties associated with this identity. */
66 | listProperties(session: Session): Promise;
67 | /** Update identity properties. */
68 | updateProperties(session: Session, defaultProperties?: Record, customProperties?: Record, recompute?: boolean): Promise;
69 | /** Delete the caller's identity and associated data. */
70 | deleteIdentity(session: Session): Promise;
71 | getMessageList(session: Session): Promise;
72 | deleteMessage(session: Session, id: string): Promise;
73 | updateMessage(session: Session, id: string, consume_time?: string, read_time?: string): Promise;
74 | }
75 |
--------------------------------------------------------------------------------
/packages/satori-js/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import "whatwg-fetch";
17 | export * from "./client";
18 | export * from "./session";
19 |
--------------------------------------------------------------------------------
/packages/satori-js/dist/session.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /** A session authenticated for a user with Satori server. */
17 | export interface ISession {
18 | /** Claims */
19 | /** The authorization token used to construct this session. */
20 | token: string;
21 | /** The UNIX timestamp when this session was created. */
22 | readonly created_at: number;
23 | /** The UNIX timestamp when this session will expire. */
24 | expires_at?: number;
25 | /** The UNIX timestamp when the refresh token will expire. */
26 | refresh_expires_at?: number;
27 | /** Refresh token that can be used for session token renewal. */
28 | refresh_token: string;
29 | /** The ID of the user who owns this session. */
30 | user_id?: string;
31 | /** Any custom properties associated with this session. */
32 | vars?: object;
33 | /** Validate token */
34 | /** If the session has expired. */
35 | isexpired(currenttime: number): boolean;
36 | /** If the refresh token has expired. */
37 | isrefreshexpired(currenttime: number): boolean;
38 | }
39 | export declare class Session implements ISession {
40 | token: string;
41 | readonly created_at: number;
42 | expires_at?: number;
43 | refresh_expires_at?: number;
44 | refresh_token: string;
45 | user_id?: string;
46 | vars?: object;
47 | constructor(token: string, refresh_token: string);
48 | isexpired(currenttime: number): boolean;
49 | isrefreshexpired(currenttime: number): boolean;
50 | update(token: string, refreshToken: string): void;
51 | decodeJWT(token: string): any;
52 | static restore(token: string, refreshToken: string): Session;
53 | }
54 |
--------------------------------------------------------------------------------
/packages/satori-js/dist/utils.d.ts:
--------------------------------------------------------------------------------
1 | export declare function buildFetchOptions(method: string, options: any, bodyJson: string): any;
2 | export declare function b64EncodeUnicode(str: string): string;
3 | export declare function b64DecodeUnicode(str: string): string;
4 |
--------------------------------------------------------------------------------
/packages/satori-js/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import "whatwg-fetch";
18 |
19 | export * from "./client";
20 | export * from "./session";
--------------------------------------------------------------------------------
/packages/satori-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heroiclabs/satori-js",
3 | "version": "2.9.0",
4 | "scripts": {
5 | "build": "npx tsc && npx rollup -c --bundleConfigAsCjs && node build.mjs",
6 | "docs": "typedoc index.ts --gaID UA-89839802-1 --out ../../docs"
7 | },
8 | "description": "JavaScript client for Satori server written in TypeScript.",
9 | "main": "dist/satori-js.cjs.js",
10 | "module": "dist/satori-js.esm.mjs",
11 | "types": "dist/index.d.ts",
12 | "exports": {
13 | "./package.json": "./package.json",
14 | ".": {
15 | "types": "./dist/index.d.ts",
16 | "import": "./dist/satori-js.esm.mjs",
17 | "require": "./dist/satori-js.cjs.js"
18 | },
19 | "./api": {
20 | "types": "./dist/api.gen.d.ts"
21 | }
22 | },
23 | "keywords": [
24 | "app server",
25 | "client library",
26 | "game server",
27 | "satori",
28 | "live ops"
29 | ],
30 | "repository": "https://github.com/heroiclabs/nakama-js",
31 | "homepage": "https://heroiclabs.com",
32 | "bugs": "https://github.com/heroiclabs/nakama-js/issues",
33 | "author": "Chris Molozian ",
34 | "contributors": [
35 | "Andrei Mihu ",
36 | "Mo Firouz "
37 | ],
38 | "license": "Apache-2.0",
39 | "devDependencies": {
40 | "@rollup/plugin-node-resolve": "^15.3.0",
41 | "@rollup/plugin-typescript": "^12.1.1",
42 | "rollup": "^4.27.4",
43 | "tslib": "^2.8.1"
44 | },
45 | "dependencies": {
46 | "@scarf/scarf": "^1.4.0",
47 | "base64-arraybuffer": "^1.0.2",
48 | "js-base64": "^3.7.7",
49 | "whatwg-fetch": "^3.6.20"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/satori-js/rollup.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Heroic Labs
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Rollup is the legacy build system for nakama-js and is only used for cocos2d-x-js support.
18 |
19 | import typescript from '@rollup/plugin-typescript';
20 | import { nodeResolve } from '@rollup/plugin-node-resolve';
21 |
22 | export default {
23 | input: './index.ts',
24 | output: {
25 | format: 'umd',
26 | name: 'satorijs',
27 | dir: "dist",
28 | entryFileNames: "satori-js.umd.js" // workaround for TS requirement that dir is specified in config
29 | },
30 | plugins: [
31 | typescript({
32 | include: ["**/*.ts"],
33 | target: "es5"
34 | }),
35 | nodeResolve()
36 | ],
37 | moduleContext: {
38 | [require.resolve('whatwg-fetch')]: 'window'
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/packages/satori-js/session.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 The Nakama Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | import * as base64 from "js-base64"
19 |
20 | /** A session authenticated for a user with Satori server. */
21 | export interface ISession {
22 | /** Claims */
23 | /** The authorization token used to construct this session. */
24 | token: string;
25 | /** The UNIX timestamp when this session was created. */
26 | readonly created_at: number;
27 | /** The UNIX timestamp when this session will expire. */
28 | expires_at?: number;
29 | /** The UNIX timestamp when the refresh token will expire. */
30 | refresh_expires_at?: number;
31 | /** Refresh token that can be used for session token renewal. */
32 | refresh_token: string;
33 | /** The ID of the user who owns this session. */
34 | user_id?: string;
35 | /** Any custom properties associated with this session. */
36 | vars?: object;
37 |
38 | /** Validate token */
39 | /** If the session has expired. */
40 | isexpired(currenttime: number): boolean;
41 | /** If the refresh token has expired. */
42 | isrefreshexpired(currenttime: number): boolean;
43 | }
44 |
45 | export class Session implements ISession {
46 |
47 | token : string;
48 | readonly created_at: number;
49 | expires_at?: number;
50 | refresh_expires_at?: number;
51 | refresh_token: string;
52 | user_id?: string;
53 | vars?: object;
54 |
55 | constructor(
56 | token: string,
57 | refresh_token: string) {
58 | this.token = token;
59 | this.refresh_token = refresh_token;
60 | this.created_at = Math.floor(new Date().getTime() / 1000);
61 | this.update(token, refresh_token);
62 | }
63 |
64 | isexpired(currenttime: number): boolean {
65 | return (this.expires_at! - currenttime) < 0;
66 | }
67 |
68 | isrefreshexpired(currenttime: number): boolean {
69 | return (this.refresh_expires_at! - currenttime) < 0;
70 | }
71 |
72 | update(token: string, refreshToken: string) {
73 | const tokenDecoded = this.decodeJWT(token);
74 | const tokenExpiresAt = Math.floor(parseInt(tokenDecoded['exp']));
75 |
76 | /** clients that have just updated to the refresh tokens */
77 | /** client release will not have a cached refresh token */
78 | if (refreshToken) {
79 | const refreshTokenDecoded = this.decodeJWT(refreshToken);
80 | const refreshTokenExpiresAt = Math.floor(parseInt(refreshTokenDecoded['exp']));
81 | this.refresh_expires_at = refreshTokenExpiresAt;
82 | this.refresh_token = refreshToken;
83 | }
84 |
85 | this.token = token;
86 | this.expires_at = tokenExpiresAt;
87 | this.user_id = tokenDecoded['uid'];
88 | this.vars = tokenDecoded['vrs'];
89 | }
90 |
91 | decodeJWT(token: string) {
92 | const { 1: base64Raw } = token.split('.')
93 | const _base64 = base64Raw.replace(/-/g, '+').replace(/_/g, '/')
94 | const jsonPayload = decodeURIComponent(base64.atob(_base64).split('').map((c) => {
95 | return `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`
96 | }).join(''))
97 |
98 | return JSON.parse(jsonPayload)
99 | }
100 |
101 | static restore(token: string, refreshToken: string): Session {
102 | return new Session(token, refreshToken);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/packages/satori-js/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": [
4 | "./*",
5 | ],
6 | "compilerOptions": {
7 | "outDir": "dist"
8 | },
9 | "declarationDir": "dist"
10 | }
11 |
--------------------------------------------------------------------------------
/packages/satori-js/utils.ts:
--------------------------------------------------------------------------------
1 | import {encode, decode} from "js-base64"
2 |
3 | export function buildFetchOptions(method: string, options: any, bodyJson: string) {
4 | const fetchOptions = {...{ method: method }, ...options};
5 | fetchOptions.headers = {...options.headers};
6 |
7 | const descriptor = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "withCredentials");
8 |
9 | // in Cocos Creator, XMLHttpRequest.withCredentials is not writable, so make the fetch
10 | // polyfill avoid writing to it.
11 | if (!descriptor?.set) {
12 | fetchOptions.credentials = 'cocos-ignore'; // string value is arbitrary, cannot be 'omit' or 'include
13 | }
14 |
15 | if(!Object.keys(fetchOptions.headers).includes("Accept")) {
16 | fetchOptions.headers["Accept"] = "application/json";
17 | }
18 |
19 | if(!Object.keys(fetchOptions.headers).includes("Content-Type")) {
20 | fetchOptions.headers["Content-Type"] = "application/json";
21 | }
22 |
23 | Object.keys(fetchOptions.headers).forEach((key: string) => {
24 | if (!fetchOptions.headers[key]) {
25 | delete fetchOptions.headers[key];
26 | }
27 | });
28 |
29 | if (bodyJson) {
30 | fetchOptions.body = bodyJson;
31 | }
32 |
33 | return fetchOptions;
34 | }
35 |
36 | export function b64EncodeUnicode(str:string) {
37 | return encode(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
38 | function toSolidBytes(_match:string, p1) {
39 | return String.fromCharCode(Number('0x' + p1));
40 | }));
41 | }
42 |
43 | export function b64DecodeUnicode(str: string) {
44 | return decodeURIComponent(decode(str).split('').map(function(c) {
45 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
46 | }).join(''));
47 | }
48 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "es2015",
6 | "es2016"
7 | ],
8 | "target": "es6",
9 | "module": "es2015",
10 | "declaration": true,
11 | "emitDeclarationOnly": true,
12 | "sourceMap": true,
13 | "removeComments": false,
14 | "downlevelIteration": true,
15 | "noImplicitAny": true,
16 | "noImplicitThis": true,
17 | "noUnusedLocals": true,
18 | "noUnusedParameters": true,
19 | "noImplicitReturns": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "allowSyntheticDefaultImports": true,
22 | "moduleResolution": "node",
23 | "baseUrl": "./",
24 | "strict": true,
25 | "strictNullChecks": true,
26 | "strictFunctionTypes": true,
27 | "alwaysStrict": true,
28 | "skipLibCheck": true
29 | },
30 | "exclude": [
31 | "node_modules"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "include": [
4 | "packages/nakama-js/**/*",
5 | "packages/satori-js/**/*"
6 | ],
7 | "exclude": [
8 | "node_modules"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:latest"
5 | ],
6 | "jsRules": {},
7 | "rules": {},
8 | "rulesDirectory": []
9 | }
10 |
--------------------------------------------------------------------------------