@byvik/capacitor-play-games 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Google Play Games Services **v2** for Capacitor: sign-in, leaderboards, achievem
7
7
  - Leaderboard standing: the player's rank and the number of scores in one call.
8
8
  - Android only. On iOS and web every method rejects, so guard calls with `Capacitor.getPlatform() === 'android'`.
9
9
 
10
- > **Status (0.1.0):** extracted from a game in production on Google Play. Since the extraction, the plugin has been run on a device only while signed out, to check the setup and error paths. The signed-in flows have not been re-run in this packaged form yet, and `incrementAchievement`, `revealAchievement`, `showAllLeaderboards` and the queued `submitScore` are new. Please open an issue if something breaks.
10
+ Tested on a physical device (Android 12) against a real Play Games Services project: sign-in, player, score submission, standing, achievements, saved games and Google's leaderboard and achievement screens.
11
11
 
12
12
  ## Install
13
13
 
@@ -415,11 +415,11 @@ rejects when it could not be read.
415
415
 
416
416
  #### LeaderboardOptions
417
417
 
418
- | Prop | Type | Default | Since |
419
- | ------------------- | ----------------------------------------------------------------------- | ---------------------- | ----- |
420
- | **`leaderboardId`** | <code>string</code> | | 0.1.0 |
421
- | **`timeSpan`** | <code><a href="#leaderboardtimespan">LeaderboardTimeSpan</a></code> | <code>'allTime'</code> | 0.1.0 |
422
- | **`collection`** | <code><a href="#leaderboardcollection">LeaderboardCollection</a></code> | <code>'public'</code> | 0.1.0 |
418
+ | Prop | Type | Description | Default | Since |
419
+ | ------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ----- |
420
+ | **`leaderboardId`** | <code>string</code> | | | 0.1.0 |
421
+ | **`timeSpan`** | <code><a href="#leaderboardtimespan">LeaderboardTimeSpan</a></code> | | <code>'allTime'</code> | 0.1.0 |
422
+ | **`collection`** | <code><a href="#leaderboardcollection">LeaderboardCollection</a></code> | `'friends'` needs the player's permission to read their friends list. Until they have granted it, `getLeaderboardStanding` rejects with code `'26703'` (CONSENT_REQUIRED). | <code>'public'</code> | 0.1.0 |
423
423
 
424
424
 
425
425
  #### AchievementOptions
@@ -11,6 +11,7 @@ import com.getcapacitor.annotation.CapacitorPlugin;
11
11
  import com.google.android.gms.common.api.ApiException;
12
12
  import com.google.android.gms.games.AuthenticationResult;
13
13
  import com.google.android.gms.games.GamesClientStatusCodes;
14
+ import com.google.android.gms.games.LeaderboardsClient;
14
15
  import com.google.android.gms.games.PlayGames;
15
16
  import com.google.android.gms.games.PlayGamesSdk;
16
17
  import com.google.android.gms.games.SnapshotsClient;
@@ -163,6 +164,10 @@ public class PlayGamesPlugin extends Plugin {
163
164
 
164
165
  // Rank, score and number of entries come from the same leaderboard variant, so they are read
165
166
  // in one call: fetched separately, a rank could be paired with a count from another moment.
167
+ //
168
+ // A variant only carries those values once this client has loaded scores for its time span and
169
+ // collection; plain leaderboard metadata reports every other variant as unknown. Loading the
170
+ // player-centered page of the requested variant is what fills it in.
166
171
  @PluginMethod
167
172
  public void getLeaderboardStanding(PluginCall call) {
168
173
  String leaderboardId = call.getString("leaderboardId");
@@ -174,31 +179,19 @@ public class PlayGamesPlugin extends Plugin {
174
179
  int collection = collection(call);
175
180
  boolean forceReload = Boolean.TRUE.equals(call.getBoolean("forceReload", false));
176
181
  PlayGames.getLeaderboardsClient(getActivity())
177
- .loadLeaderboardMetadata(leaderboardId, forceReload)
182
+ .loadPlayerCenteredScores(leaderboardId, timeSpan, collection, 1, forceReload)
178
183
  .addOnSuccessListener((data) -> {
179
184
  // Play reports "unknown" as -1; it is surfaced as null instead.
180
185
  JSObject ret = new JSObject();
181
186
  ret.put("rank", JSONObject.NULL);
182
187
  ret.put("score", JSONObject.NULL);
183
188
  ret.put("numScores", JSONObject.NULL);
184
- Leaderboard leaderboard = data == null ? null : data.get();
185
- if (leaderboard != null && leaderboard.getVariants() != null) {
186
- for (LeaderboardVariant variant : leaderboard.getVariants()) {
187
- if (variant.getTimeSpan() != timeSpan || variant.getCollection() != collection) {
188
- continue;
189
- }
190
- if (variant.getNumScores() != LeaderboardVariant.NUM_SCORES_UNKNOWN) {
191
- ret.put("numScores", variant.getNumScores());
192
- }
193
- if (variant.hasPlayerInfo()) {
194
- if (variant.getPlayerRank() != LeaderboardVariant.PLAYER_RANK_UNKNOWN) {
195
- ret.put("rank", variant.getPlayerRank());
196
- }
197
- if (variant.getRawPlayerScore() != LeaderboardVariant.PLAYER_SCORE_UNKNOWN) {
198
- ret.put("score", variant.getRawPlayerScore());
199
- }
200
- }
201
- break;
189
+ LeaderboardsClient.LeaderboardScores scores = data == null ? null : data.get();
190
+ if (scores != null) {
191
+ try {
192
+ readStanding(scores.getLeaderboard(), timeSpan, collection, ret);
193
+ } finally {
194
+ scores.release();
202
195
  }
203
196
  }
204
197
  call.resolve(ret);
@@ -206,6 +199,29 @@ public class PlayGamesPlugin extends Plugin {
206
199
  .addOnFailureListener((e) -> reject(call, e));
207
200
  }
208
201
 
202
+ private static void readStanding(Leaderboard leaderboard, int timeSpan, int collection, JSObject ret) {
203
+ if (leaderboard == null || leaderboard.getVariants() == null) {
204
+ return;
205
+ }
206
+ for (LeaderboardVariant variant : leaderboard.getVariants()) {
207
+ if (variant.getTimeSpan() != timeSpan || variant.getCollection() != collection) {
208
+ continue;
209
+ }
210
+ if (variant.getNumScores() != LeaderboardVariant.NUM_SCORES_UNKNOWN) {
211
+ ret.put("numScores", variant.getNumScores());
212
+ }
213
+ if (variant.hasPlayerInfo()) {
214
+ if (variant.getPlayerRank() != LeaderboardVariant.PLAYER_RANK_UNKNOWN) {
215
+ ret.put("rank", variant.getPlayerRank());
216
+ }
217
+ if (variant.getRawPlayerScore() != LeaderboardVariant.PLAYER_SCORE_UNKNOWN) {
218
+ ret.put("score", variant.getRawPlayerScore());
219
+ }
220
+ }
221
+ return;
222
+ }
223
+ }
224
+
209
225
  @PluginMethod
210
226
  public void showLeaderboard(PluginCall call) {
211
227
  String leaderboardId = call.getString("leaderboardId");
@@ -409,7 +425,7 @@ public class PlayGamesPlugin extends Plugin {
409
425
  ApiException api = (ApiException) e;
410
426
  String name = GamesClientStatusCodes.getStatusCodeString(api.getStatusCode());
411
427
  String detail = api.getStatus().getStatusMessage();
412
- return detail == null || detail.isEmpty() ? name : name + ": " + detail;
428
+ return detail == null || detail.isEmpty() || detail.equals(name) ? name : name + ": " + detail;
413
429
  }
414
430
  return String.valueOf(e.getMessage());
415
431
  }
package/dist/docs.json CHANGED
@@ -572,7 +572,7 @@
572
572
  "name": "since"
573
573
  }
574
574
  ],
575
- "docs": "",
575
+ "docs": "`'friends'` needs the player's permission to read their friends list.\nUntil they have granted it, `getLeaderboardStanding` rejects with code\n`'26703'` (CONSENT_REQUIRED).",
576
576
  "complexTypes": [
577
577
  "LeaderboardCollection"
578
578
  ],
@@ -78,6 +78,10 @@ export interface LeaderboardOptions {
78
78
  */
79
79
  timeSpan?: LeaderboardTimeSpan;
80
80
  /**
81
+ * `'friends'` needs the player's permission to read their friends list.
82
+ * Until they have granted it, `getLeaderboardStanding` rejects with code
83
+ * `'26703'` (CONSENT_REQUIRED).
84
+ *
81
85
  * @default 'public'
82
86
  * @since 0.1.0
83
87
  */
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export type LeaderboardTimeSpan = 'daily' | 'weekly' | 'allTime';\n\nexport type LeaderboardCollection = 'public' | 'friends';\n\nexport interface AuthResult {\n /**\n * Whether a player is signed in to Play Games Services.\n *\n * @since 0.1.0\n */\n isAuthenticated: boolean;\n\n /**\n * Google API status code. Only present when the SDK reported an error.\n *\n * `10` (DEVELOPER_ERROR) means the app is misconfigured: the SHA-1 of this\n * build or its package name is missing from the OAuth credentials in the\n * Play Console. `16` and `12501` mean the player cancelled.\n *\n * @since 0.1.0\n */\n errorCode?: number;\n\n /**\n * Error message from the SDK. Only present when the SDK reported an error.\n *\n * @since 0.1.0\n */\n errorMessage?: string;\n}\n\nexport interface Player {\n /**\n * @since 0.1.0\n */\n playerId: string;\n\n /**\n * The name the player shows in Play Games.\n *\n * @since 0.1.0\n */\n displayName: string;\n\n /**\n * The player's Play Games title, if they have one.\n *\n * @since 0.1.0\n */\n title?: string;\n}\n\nexport interface SubmitScoreOptions {\n /**\n * @since 0.1.0\n */\n leaderboardId: string;\n\n /**\n * Raw score, an integer. For time leaderboards it is in milliseconds and for\n * currency leaderboards in 1/1,000,000ths of the main unit.\n *\n * @since 0.1.0\n */\n score: number;\n\n /**\n * Wait for the server to confirm the score.\n *\n * With `false` the SDK queues the score, even offline, and the call resolves\n * straight away. With `true` the call resolves once the server has the score\n * and rejects when it cannot be reached; use it when you read the standing\n * right after submitting.\n *\n * @default false\n * @since 0.1.0\n */\n immediate?: boolean;\n}\n\nexport interface LeaderboardOptions {\n /**\n * @since 0.1.0\n */\n leaderboardId: string;\n\n /**\n * @default 'allTime'\n * @since 0.1.0\n */\n timeSpan?: LeaderboardTimeSpan;\n\n /**\n * @default 'public'\n * @since 0.1.0\n */\n collection?: LeaderboardCollection;\n}\n\nexport interface LeaderboardStandingOptions extends LeaderboardOptions {\n /**\n * Skip the local cache and ask the server. The cached number of scores can\n * be stale, but Google rate-limits forced reloads: keep it for moments when\n * fresh data matters.\n *\n * @default false\n * @since 0.1.0\n */\n forceReload?: boolean;\n}\n\nexport interface LeaderboardStanding {\n /**\n * The player's rank, or `null` when unknown (for example, no score in this\n * time span).\n *\n * @since 0.1.0\n */\n rank: number | null;\n\n /**\n * The player's raw score, or `null` when unknown.\n *\n * @since 0.1.0\n */\n score: number | null;\n\n /**\n * How many scores the leaderboard has in this time span and collection, or\n * `null` when unknown.\n *\n * @since 0.1.0\n */\n numScores: number | null;\n}\n\nexport interface AchievementOptions {\n /**\n * @since 0.1.0\n */\n achievementId: string;\n}\n\nexport interface AchievementStepsOptions {\n /**\n * @since 0.1.0\n */\n achievementId: string;\n\n /**\n * @since 0.1.0\n */\n steps: number;\n}\n\nexport interface SaveGameOptions {\n /**\n * Unique name of the saved game: 1 to 100 characters from `a-z`, `A-Z`,\n * `0-9`, `-`, `.`, `_` and `~`.\n *\n * @since 0.1.0\n */\n name: string;\n\n /**\n * The contents, stored as UTF-8. Play allows up to 3 MB per saved game.\n *\n * @since 0.1.0\n */\n data: string;\n\n /**\n * Description shown in the Play Games saved games UI.\n *\n * @since 0.1.0\n */\n description?: string;\n}\n\nexport interface LoadGameOptions {\n /**\n * @since 0.1.0\n */\n name: string;\n}\n\nexport interface LoadGameResult {\n /**\n * The saved contents, or `null` when no saved game with that name exists.\n *\n * @since 0.1.0\n */\n data: string | null;\n}\n\nexport interface PlayGamesPlugin {\n /**\n * Whether a player is signed in. Shows no UI and never rejects.\n *\n * The SDK tries a silent sign-in when the app starts, so this is the call\n * to make before drawing anything that depends on the session.\n *\n * @since 0.1.0\n */\n isAuthenticated(): Promise<AuthResult>;\n\n /**\n * Ask the player to sign in. This is the only call that shows Google's\n * sign-in UI, so make it from a button the player pressed. Never rejects:\n * a cancelled or failed sign-in resolves with `isAuthenticated: false`.\n *\n * @since 0.1.0\n */\n signIn(): Promise<AuthResult>;\n\n /**\n * The signed-in player. Rejects when nobody is signed in.\n *\n * @since 0.1.0\n */\n getCurrentPlayer(): Promise<Player>;\n\n /**\n * Open the Play Games app, or its store listing when it is not installed.\n *\n * Useful when `signIn()` cannot succeed because the device has no Google\n * account or the player has no Play Games profile: both are created there.\n *\n * @since 0.1.0\n */\n openPlayGamesApp(): Promise<void>;\n\n /**\n * Submit a score to a leaderboard.\n *\n * @since 0.1.0\n */\n submitScore(options: SubmitScoreOptions): Promise<void>;\n\n /**\n * The player's rank and score, and the number of scores in the\n * leaderboard, read together for one time span and collection.\n *\n * @since 0.1.0\n */\n getLeaderboardStanding(options: LeaderboardStandingOptions): Promise<LeaderboardStanding>;\n\n /**\n * Open Google's UI for one leaderboard. Resolves when the UI opens.\n *\n * @since 0.1.0\n */\n showLeaderboard(options: LeaderboardOptions): Promise<void>;\n\n /**\n * Open Google's UI listing every leaderboard of the game.\n *\n * @since 0.1.0\n */\n showAllLeaderboards(): Promise<void>;\n\n /**\n * Unlock an achievement. Queued by the SDK when offline.\n *\n * @since 0.1.0\n */\n unlockAchievement(options: AchievementOptions): Promise<void>;\n\n /**\n * Reveal a hidden achievement. Queued by the SDK when offline.\n *\n * @since 0.1.0\n */\n revealAchievement(options: AchievementOptions): Promise<void>;\n\n /**\n * Add steps to an incremental achievement. Queued by the SDK when offline.\n *\n * @since 0.1.0\n */\n incrementAchievement(options: AchievementStepsOptions): Promise<void>;\n\n /**\n * Set the absolute progress of an incremental achievement. Play ignores a\n * value lower than the one it already has, which makes this the safe choice\n * when your own save holds the real counter.\n *\n * @since 0.1.0\n */\n setAchievementSteps(options: AchievementStepsOptions): Promise<void>;\n\n /**\n * Open Google's achievements UI. Resolves when the UI opens.\n *\n * @since 0.1.0\n */\n showAchievements(): Promise<void>;\n\n /**\n * Write a saved game to the player's Google account, creating it if needed.\n * Conflicts between devices are resolved by keeping the most recently\n * modified copy.\n *\n * @since 0.1.0\n */\n saveGame(options: SaveGameOptions): Promise<void>;\n\n /**\n * Read a saved game. Resolves with `data: null` when it does not exist and\n * rejects when it could not be read.\n *\n * @since 0.1.0\n */\n loadGame(options: LoadGameOptions): Promise<LoadGameResult>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export type LeaderboardTimeSpan = 'daily' | 'weekly' | 'allTime';\n\nexport type LeaderboardCollection = 'public' | 'friends';\n\nexport interface AuthResult {\n /**\n * Whether a player is signed in to Play Games Services.\n *\n * @since 0.1.0\n */\n isAuthenticated: boolean;\n\n /**\n * Google API status code. Only present when the SDK reported an error.\n *\n * `10` (DEVELOPER_ERROR) means the app is misconfigured: the SHA-1 of this\n * build or its package name is missing from the OAuth credentials in the\n * Play Console. `16` and `12501` mean the player cancelled.\n *\n * @since 0.1.0\n */\n errorCode?: number;\n\n /**\n * Error message from the SDK. Only present when the SDK reported an error.\n *\n * @since 0.1.0\n */\n errorMessage?: string;\n}\n\nexport interface Player {\n /**\n * @since 0.1.0\n */\n playerId: string;\n\n /**\n * The name the player shows in Play Games.\n *\n * @since 0.1.0\n */\n displayName: string;\n\n /**\n * The player's Play Games title, if they have one.\n *\n * @since 0.1.0\n */\n title?: string;\n}\n\nexport interface SubmitScoreOptions {\n /**\n * @since 0.1.0\n */\n leaderboardId: string;\n\n /**\n * Raw score, an integer. For time leaderboards it is in milliseconds and for\n * currency leaderboards in 1/1,000,000ths of the main unit.\n *\n * @since 0.1.0\n */\n score: number;\n\n /**\n * Wait for the server to confirm the score.\n *\n * With `false` the SDK queues the score, even offline, and the call resolves\n * straight away. With `true` the call resolves once the server has the score\n * and rejects when it cannot be reached; use it when you read the standing\n * right after submitting.\n *\n * @default false\n * @since 0.1.0\n */\n immediate?: boolean;\n}\n\nexport interface LeaderboardOptions {\n /**\n * @since 0.1.0\n */\n leaderboardId: string;\n\n /**\n * @default 'allTime'\n * @since 0.1.0\n */\n timeSpan?: LeaderboardTimeSpan;\n\n /**\n * `'friends'` needs the player's permission to read their friends list.\n * Until they have granted it, `getLeaderboardStanding` rejects with code\n * `'26703'` (CONSENT_REQUIRED).\n *\n * @default 'public'\n * @since 0.1.0\n */\n collection?: LeaderboardCollection;\n}\n\nexport interface LeaderboardStandingOptions extends LeaderboardOptions {\n /**\n * Skip the local cache and ask the server. The cached number of scores can\n * be stale, but Google rate-limits forced reloads: keep it for moments when\n * fresh data matters.\n *\n * @default false\n * @since 0.1.0\n */\n forceReload?: boolean;\n}\n\nexport interface LeaderboardStanding {\n /**\n * The player's rank, or `null` when unknown (for example, no score in this\n * time span).\n *\n * @since 0.1.0\n */\n rank: number | null;\n\n /**\n * The player's raw score, or `null` when unknown.\n *\n * @since 0.1.0\n */\n score: number | null;\n\n /**\n * How many scores the leaderboard has in this time span and collection, or\n * `null` when unknown.\n *\n * @since 0.1.0\n */\n numScores: number | null;\n}\n\nexport interface AchievementOptions {\n /**\n * @since 0.1.0\n */\n achievementId: string;\n}\n\nexport interface AchievementStepsOptions {\n /**\n * @since 0.1.0\n */\n achievementId: string;\n\n /**\n * @since 0.1.0\n */\n steps: number;\n}\n\nexport interface SaveGameOptions {\n /**\n * Unique name of the saved game: 1 to 100 characters from `a-z`, `A-Z`,\n * `0-9`, `-`, `.`, `_` and `~`.\n *\n * @since 0.1.0\n */\n name: string;\n\n /**\n * The contents, stored as UTF-8. Play allows up to 3 MB per saved game.\n *\n * @since 0.1.0\n */\n data: string;\n\n /**\n * Description shown in the Play Games saved games UI.\n *\n * @since 0.1.0\n */\n description?: string;\n}\n\nexport interface LoadGameOptions {\n /**\n * @since 0.1.0\n */\n name: string;\n}\n\nexport interface LoadGameResult {\n /**\n * The saved contents, or `null` when no saved game with that name exists.\n *\n * @since 0.1.0\n */\n data: string | null;\n}\n\nexport interface PlayGamesPlugin {\n /**\n * Whether a player is signed in. Shows no UI and never rejects.\n *\n * The SDK tries a silent sign-in when the app starts, so this is the call\n * to make before drawing anything that depends on the session.\n *\n * @since 0.1.0\n */\n isAuthenticated(): Promise<AuthResult>;\n\n /**\n * Ask the player to sign in. This is the only call that shows Google's\n * sign-in UI, so make it from a button the player pressed. Never rejects:\n * a cancelled or failed sign-in resolves with `isAuthenticated: false`.\n *\n * @since 0.1.0\n */\n signIn(): Promise<AuthResult>;\n\n /**\n * The signed-in player. Rejects when nobody is signed in.\n *\n * @since 0.1.0\n */\n getCurrentPlayer(): Promise<Player>;\n\n /**\n * Open the Play Games app, or its store listing when it is not installed.\n *\n * Useful when `signIn()` cannot succeed because the device has no Google\n * account or the player has no Play Games profile: both are created there.\n *\n * @since 0.1.0\n */\n openPlayGamesApp(): Promise<void>;\n\n /**\n * Submit a score to a leaderboard.\n *\n * @since 0.1.0\n */\n submitScore(options: SubmitScoreOptions): Promise<void>;\n\n /**\n * The player's rank and score, and the number of scores in the\n * leaderboard, read together for one time span and collection.\n *\n * @since 0.1.0\n */\n getLeaderboardStanding(options: LeaderboardStandingOptions): Promise<LeaderboardStanding>;\n\n /**\n * Open Google's UI for one leaderboard. Resolves when the UI opens.\n *\n * @since 0.1.0\n */\n showLeaderboard(options: LeaderboardOptions): Promise<void>;\n\n /**\n * Open Google's UI listing every leaderboard of the game.\n *\n * @since 0.1.0\n */\n showAllLeaderboards(): Promise<void>;\n\n /**\n * Unlock an achievement. Queued by the SDK when offline.\n *\n * @since 0.1.0\n */\n unlockAchievement(options: AchievementOptions): Promise<void>;\n\n /**\n * Reveal a hidden achievement. Queued by the SDK when offline.\n *\n * @since 0.1.0\n */\n revealAchievement(options: AchievementOptions): Promise<void>;\n\n /**\n * Add steps to an incremental achievement. Queued by the SDK when offline.\n *\n * @since 0.1.0\n */\n incrementAchievement(options: AchievementStepsOptions): Promise<void>;\n\n /**\n * Set the absolute progress of an incremental achievement. Play ignores a\n * value lower than the one it already has, which makes this the safe choice\n * when your own save holds the real counter.\n *\n * @since 0.1.0\n */\n setAchievementSteps(options: AchievementStepsOptions): Promise<void>;\n\n /**\n * Open Google's achievements UI. Resolves when the UI opens.\n *\n * @since 0.1.0\n */\n showAchievements(): Promise<void>;\n\n /**\n * Write a saved game to the player's Google account, creating it if needed.\n * Conflicts between devices are resolved by keeping the most recently\n * modified copy.\n *\n * @since 0.1.0\n */\n saveGame(options: SaveGameOptions): Promise<void>;\n\n /**\n * Read a saved game. Resolves with `data: null` when it does not exist and\n * rejects when it could not be read.\n *\n * @since 0.1.0\n */\n loadGame(options: LoadGameOptions): Promise<LoadGameResult>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byvik/capacitor-play-games",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Google Play Games Services v2 for Capacitor: sign-in, leaderboards, achievements and cloud saved games.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",