@modbender/capacitor-play-games 0.3.0 → 0.4.0
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/CHANGELOG.md +16 -0
- package/README.md +51 -110
- package/dist/esm/definitions.d.ts +15 -58
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/web.d.ts +1 -2
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +0 -10
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +0 -10
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +0 -10
- package/dist/plugin.js.map +1 -1
- package/package.json +4 -12
- package/CapacitorPlayGames.podspec +0 -17
- package/Package.swift +0 -36
- package/ios/Plugin/AchievementsModule.swift +0 -73
- package/ios/Plugin/LeaderboardsModule.swift +0 -75
- package/ios/Plugin/Pgs.swift +0 -40
- package/ios/Plugin/Plugin.swift +0 -74
- package/ios/Plugin/SavedGamesModule.swift +0 -102
- package/ios/Plugin/SignInModule.swift +0 -139
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-09-08
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
|
|
12
|
+
- iOS support, entirely: the `ios/` sources, `Package.swift`,
|
|
13
|
+
`CapacitorPlayGames.podspec`, and `fetchIdentityVerificationSignature()` (the
|
|
14
|
+
GameKit identity-verification bundle it returned) along with the
|
|
15
|
+
`IdentityVerificationSignature` interface. The Swift was inherited from
|
|
16
|
+
upstream and had never been compiled or run — nothing had been through
|
|
17
|
+
Xcode — so this deletes no working functionality. The package is now
|
|
18
|
+
Android plus a safe web no-op fallback.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `package.json` `description` no longer claims iOS support.
|
|
23
|
+
|
|
8
24
|
## [0.3.0] - 2026-09-08
|
|
9
25
|
|
|
10
26
|
Forked from `@idleflowgames/capacitor-play-games` 0.2.1. Upstream's GitHub
|
package/README.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# @modbender/capacitor-play-games
|
|
2
2
|
|
|
3
|
-
Capacitor 8 plugin for
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
leaderboards, and saved games across both platforms.
|
|
3
|
+
Capacitor 8 plugin for **Google Play Games Services (PGS v2)** on Android,
|
|
4
|
+
with a safe no-op fallback on web. One TypeScript API covers sign-in,
|
|
5
|
+
achievements, leaderboards, and saved games.
|
|
7
6
|
|
|
8
7
|
## About this fork
|
|
9
8
|
|
|
@@ -13,17 +12,22 @@ This is a fork of [`@idleflowgames/capacitor-play-games`](https://www.npmjs.com/
|
|
|
13
12
|
Upstream's GitHub repository — `github.com/idleflowgames/capacitor-play-games`,
|
|
14
13
|
the URL its own `package.json` still points at — returned 404 on 2026-09-08,
|
|
15
14
|
while the npm package remained published. The source here was recovered from
|
|
16
|
-
that published tarball rather than forked through GitHub.
|
|
17
|
-
Gradle and podspec files
|
|
18
|
-
TypeScript layer
|
|
19
|
-
sourcemaps set `sourcesContent: false` — so
|
|
20
|
-
`dist/esm/*.js` plus the emitted `.d.ts`.
|
|
21
|
-
comment, which
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
that published tarball rather than forked through GitHub. At recovery, the
|
|
16
|
+
Kotlin, Swift, Gradle and podspec files shipped in the tarball and were
|
|
17
|
+
vendored byte-for-byte. The TypeScript layer did **not** ship — the package
|
|
18
|
+
carried `dist/` only, and its sourcemaps set `sourcesContent: false` — so
|
|
19
|
+
`src/` here was reconstructed from `dist/esm/*.js` plus the emitted `.d.ts`.
|
|
20
|
+
The declarations retained every doc comment, which made that reconstruction
|
|
21
|
+
faithful rather than a rewrite.
|
|
22
|
+
|
|
23
|
+
That claim was checked rather than asserted, at 0.3.0, before any of the
|
|
24
|
+
changes below: building `src/` as reconstructed reproduced upstream's
|
|
25
|
+
published `dist/` exactly — all six emitted files, both the `.js` and the
|
|
26
|
+
`.d.ts`, were identical to the ones in the 0.2.1 tarball once formatting was
|
|
27
|
+
normalised. That is what established the reconstruction was faithful. It is a
|
|
28
|
+
historical checkpoint, not a standing guarantee — the 0.4.0 changes below
|
|
29
|
+
(iOS removed, in particular) mean this tree no longer reproduces upstream's
|
|
30
|
+
`dist/`, by design.
|
|
27
31
|
|
|
28
32
|
### Changes from upstream 0.2.1
|
|
29
33
|
|
|
@@ -32,9 +36,12 @@ normalised.
|
|
|
32
36
|
`kotlin { compilerOptions { ... } }` block, but never applies the plugin, so
|
|
33
37
|
Gradle rejects that block with `Could not find method kotlin()` before
|
|
34
38
|
compiling any source. One line.
|
|
35
|
-
- Renamed to the `@modbender` scope
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
- Renamed to the `@modbender` scope.
|
|
40
|
+
- **iOS support removed entirely** (0.4.0): the `ios/` sources, `Package.swift`,
|
|
41
|
+
`CapacitorPlayGames.podspec`, and `fetchIdentityVerificationSignature()`. The
|
|
42
|
+
Swift was inherited from upstream and had never been compiled or run —
|
|
43
|
+
nothing had been through Xcode. The package is now Android plus a safe web
|
|
44
|
+
no-op fallback; see the changelog for the full rationale.
|
|
38
45
|
|
|
39
46
|
Deliberately unchanged: the Android namespace is still
|
|
40
47
|
`com.idleflowgames.playgames`. Keeping it means this tree diffs cleanly against
|
|
@@ -61,8 +68,7 @@ Worth knowing: the module keeps upstream's own buildscript classpath pinning AGP
|
|
|
61
68
|
That combination was expected to be a second conflict and is not — it resolves
|
|
62
69
|
and builds green as-is.
|
|
63
70
|
|
|
64
|
-
The TypeScript builds and typechecks clean on TypeScript 7.0.2.
|
|
65
|
-
untested**; nothing here has been through Xcode.
|
|
71
|
+
The TypeScript builds and typechecks clean on TypeScript 7.0.2.
|
|
66
72
|
|
|
67
73
|
The original MIT copyright is retained in [LICENSE](./LICENSE) alongside this
|
|
68
74
|
fork's.
|
|
@@ -76,21 +82,16 @@ bunx cap sync
|
|
|
76
82
|
|
|
77
83
|
## Supported platforms
|
|
78
84
|
|
|
79
|
-
| Platform | Backing API
|
|
80
|
-
| -------- |
|
|
81
|
-
| Android | Google Play Games Services v2 (`play-services-games-v2`) | Requires PGS configured in the Google Play Console.
|
|
82
|
-
|
|
|
83
|
-
| Web | none | Every method resolves to a safe default (signed out, empty). |
|
|
85
|
+
| Platform | Backing API | Notes |
|
|
86
|
+
| -------- | -------------------------------------------------------- | ------------------------------------------------------------ |
|
|
87
|
+
| Android | Google Play Games Services v2 (`play-services-games-v2`) | Requires PGS configured in the Google Play Console. |
|
|
88
|
+
| Web | none | Every method resolves to a safe default (signed out, empty). |
|
|
84
89
|
|
|
85
90
|
## Platform setup
|
|
86
91
|
|
|
87
92
|
Achievement and leaderboard **ids are yours**: the plugin takes opaque id strings
|
|
88
|
-
and passes them straight through to
|
|
89
|
-
|
|
90
|
-
matching id at each call site. The two platforms issue different ids for the same
|
|
91
|
-
logical achievement, so keep a per-platform map in your app.
|
|
92
|
-
|
|
93
|
-
### Android
|
|
93
|
+
and passes them straight through to Play Games Services. Create them in the
|
|
94
|
+
Google Play Console, then pass the matching id at each call site.
|
|
94
95
|
|
|
95
96
|
Configure Play Games Services v2 in the Google Play Console, then wire your
|
|
96
97
|
project id into Android resources and the app manifest (see Google's
|
|
@@ -113,17 +114,6 @@ project id into Android resources and the app manifest (see Google's
|
|
|
113
114
|
`google-services.json` is not required for PGS v2 on its own; it is only needed
|
|
114
115
|
if you also wire Firebase.
|
|
115
116
|
|
|
116
|
-
### iOS
|
|
117
|
-
|
|
118
|
-
Enable the **Game Center** capability on your app target in Xcode and create the
|
|
119
|
-
app's achievements / leaderboards in App Store Connect. The system presents the
|
|
120
|
-
Game Center sign-in UI.
|
|
121
|
-
|
|
122
|
-
> If your app shows an App Tracking Transparency (ATT) prompt, request it
|
|
123
|
-
> **before** calling `initialize()`. iOS suppresses an ATT prompt shown while
|
|
124
|
-
> Game Center's "Welcome back" banner is on screen, and `initialize()` is what
|
|
125
|
-
> can trigger that banner.
|
|
126
|
-
|
|
127
117
|
## Usage
|
|
128
118
|
|
|
129
119
|
```ts
|
|
@@ -137,8 +127,8 @@ if (!signedIn) {
|
|
|
137
127
|
await PlayGames.signIn({ silent: false });
|
|
138
128
|
}
|
|
139
129
|
|
|
140
|
-
await PlayGames.unlockAchievement({ id:
|
|
141
|
-
await PlayGames.submitScore({ leaderboardId
|
|
130
|
+
await PlayGames.unlockAchievement({ id: achievementId });
|
|
131
|
+
await PlayGames.submitScore({ leaderboardId, score: 1234 });
|
|
142
132
|
|
|
143
133
|
// Cross-device saves:
|
|
144
134
|
await PlayGames.saveSnapshot({ name: "main", data: JSON.stringify(state) });
|
|
@@ -162,7 +152,6 @@ On web every method resolves to a safe default, so gate feature usage behind
|
|
|
162
152
|
* [`isSignedIn()`](#issignedin)
|
|
163
153
|
* [`getPlayer()`](#getplayer)
|
|
164
154
|
* [`requestServerSideAccess(...)`](#requestserversideaccess)
|
|
165
|
-
* [`fetchIdentityVerificationSignature()`](#fetchidentityverificationsignature)
|
|
166
155
|
* [`unlockAchievement(...)`](#unlockachievement)
|
|
167
156
|
* [`incrementAchievement(...)`](#incrementachievement)
|
|
168
157
|
* [`showAchievements()`](#showachievements)
|
|
@@ -189,11 +178,9 @@ On web every method resolves to a safe default, so gate feature usage behind
|
|
|
189
178
|
initialize() => Promise<void>
|
|
190
179
|
```
|
|
191
180
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
GameKit authentication handler. Call once, after any App Tracking
|
|
196
|
-
Transparency prompt has resolved, before the other methods.
|
|
181
|
+
No-op. `PlayGamesSdk.initialize` runs automatically when the plugin
|
|
182
|
+
loads, driven by the Capacitor bridge — this call exists only to keep
|
|
183
|
+
the API symmetric with the web fallback.
|
|
197
184
|
|
|
198
185
|
**Since:** 0.1.0
|
|
199
186
|
|
|
@@ -206,7 +193,7 @@ Transparency prompt has resolved, before the other methods.
|
|
|
206
193
|
signIn(opts?: { silent?: boolean | undefined; } | undefined) => Promise<SignInResult>
|
|
207
194
|
```
|
|
208
195
|
|
|
209
|
-
Sign in to
|
|
196
|
+
Sign in to Google Play Games.
|
|
210
197
|
|
|
211
198
|
`silent` (default `true`) attempts auto sign-in with no UI; on most devices
|
|
212
199
|
this succeeds if the player has previously authenticated this game. Pass
|
|
@@ -247,8 +234,8 @@ getPlayer() => Promise<PlayerInfo>
|
|
|
247
234
|
|
|
248
235
|
Get the signed-in player's profile.
|
|
249
236
|
|
|
250
|
-
|
|
251
|
-
|
|
237
|
+
Rejects when no player is signed in. On web (the no-op fallback) it
|
|
238
|
+
resolves an empty profile (`playerId: ""`).
|
|
252
239
|
|
|
253
240
|
**Returns:** <code>Promise<<a href="#playerinfo">PlayerInfo</a>></code>
|
|
254
241
|
|
|
@@ -271,8 +258,7 @@ Games Services v2 `GamesSignInClient.requestServerSideAccess`).
|
|
|
271
258
|
is redeemed against it server-side. `forceRefresh` (default `false`) requests a
|
|
272
259
|
fresh code even if one was recently granted.
|
|
273
260
|
|
|
274
|
-
|
|
275
|
-
`authCode`.
|
|
261
|
+
The web fallback resolves an empty `authCode`.
|
|
276
262
|
|
|
277
263
|
| Param | Type |
|
|
278
264
|
| ---------- | ---------------------------------------------------------------- |
|
|
@@ -285,36 +271,13 @@ Android only. iOS rejects (unimplemented); the web fallback resolves an empty
|
|
|
285
271
|
--------------------
|
|
286
272
|
|
|
287
273
|
|
|
288
|
-
### fetchIdentityVerificationSignature()
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
fetchIdentityVerificationSignature() => Promise<IdentityVerificationSignature>
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
Fetch a GameKit identity-verification signature for the signed-in Game Center
|
|
295
|
-
player (`GKLocalPlayer.fetchItems(forIdentityVerificationSignature:)`). A
|
|
296
|
-
backend verifies the returned bundle against Apple's certificate to trust the
|
|
297
|
-
player id rather than the untrusted client's claim. Rejects when no player is
|
|
298
|
-
signed in.
|
|
299
|
-
|
|
300
|
-
iOS only. Android rejects (unimplemented); the web fallback resolves an empty
|
|
301
|
-
bundle.
|
|
302
|
-
|
|
303
|
-
**Returns:** <code>Promise<<a href="#identityverificationsignature">IdentityVerificationSignature</a>></code>
|
|
304
|
-
|
|
305
|
-
**Since:** 0.2.0
|
|
306
|
-
|
|
307
|
-
--------------------
|
|
308
|
-
|
|
309
|
-
|
|
310
274
|
### unlockAchievement(...)
|
|
311
275
|
|
|
312
276
|
```typescript
|
|
313
277
|
unlockAchievement(opts: { id: string; }) => Promise<void>
|
|
314
278
|
```
|
|
315
279
|
|
|
316
|
-
Unlock an achievement by its
|
|
317
|
-
Android, App Store Connect / Game Center id on iOS).
|
|
280
|
+
Unlock an achievement by its Play Console achievement id.
|
|
318
281
|
|
|
319
282
|
| Param | Type |
|
|
320
283
|
| ---------- | ---------------------------- |
|
|
@@ -333,11 +296,8 @@ incrementAchievement(opts: { id: string; steps: number; }) => Promise<void>
|
|
|
333
296
|
|
|
334
297
|
Increment a partial (incremental) achievement.
|
|
335
298
|
|
|
336
|
-
`steps` is
|
|
337
|
-
|
|
338
|
-
iOS (GameKit) it is added to `percentComplete` as percentage points.
|
|
339
|
-
Compute a platform-appropriate value (e.g. via `Capacitor.getPlatform()`)
|
|
340
|
-
so progress matches on both stores.
|
|
299
|
+
`steps` is a discrete step count toward the achievement's Play Console
|
|
300
|
+
step total, and must be greater than 0 — the call rejects otherwise.
|
|
341
301
|
|
|
342
302
|
| Param | Type |
|
|
343
303
|
| ---------- | ------------------------------------------- |
|
|
@@ -354,7 +314,7 @@ so progress matches on both stores.
|
|
|
354
314
|
showAchievements() => Promise<void>
|
|
355
315
|
```
|
|
356
316
|
|
|
357
|
-
Show the
|
|
317
|
+
Show the native Google Play Games achievements UI.
|
|
358
318
|
|
|
359
319
|
**Since:** 0.1.0
|
|
360
320
|
|
|
@@ -367,7 +327,7 @@ Show the platform's native achievements UI.
|
|
|
367
327
|
submitScore(opts: { leaderboardId: string; score: number; }) => Promise<void>
|
|
368
328
|
```
|
|
369
329
|
|
|
370
|
-
Submit a score to a leaderboard by its
|
|
330
|
+
Submit a score to a leaderboard by its Play Console leaderboard id.
|
|
371
331
|
|
|
372
332
|
| Param | Type |
|
|
373
333
|
| ---------- | ------------------------------------------------------ |
|
|
@@ -485,7 +445,7 @@ addListener(event: "signInStateChanged", listener: (e: SignInStateChangedEvent)
|
|
|
485
445
|
```
|
|
486
446
|
|
|
487
447
|
Listen for sign-in state changes: an interactive sign-in completing, or the
|
|
488
|
-
player signing out of
|
|
448
|
+
player signing out of Google Play Games system-wide.
|
|
489
449
|
|
|
490
450
|
| Param | Type |
|
|
491
451
|
| -------------- | --------------------------------------------------------------------- |
|
|
@@ -531,28 +491,9 @@ A signed-in player's public profile.
|
|
|
531
491
|
|
|
532
492
|
| Prop | Type | Description |
|
|
533
493
|
| ----------------- | ------------------- | ----------------------------------------------------------------------------- |
|
|
534
|
-
| **`playerId`** | <code>string</code> | Stable, platform-assigned
|
|
535
|
-
| **`displayName`** | <code>string</code> | Display name as shown in Google Play Games
|
|
536
|
-
| **`avatarUrl`** | <code>string</code> | URL of the player's avatar image, when the platform exposes one.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
#### IdentityVerificationSignature
|
|
540
|
-
|
|
541
|
-
A GameKit identity-verification bundle
|
|
542
|
-
(`GKLocalPlayer.fetchItems(forIdentityVerificationSignature:)`). A third-party
|
|
543
|
-
server verifies `signature` against the certificate at `publicKeyUrl` to trust
|
|
544
|
-
the Game Center `playerId` without relaying it through the untrusted client.
|
|
545
|
-
|
|
546
|
-
| Prop | Type | Description |
|
|
547
|
-
| ------------------ | ------------------- | ----------------------------------------------------------------------------- |
|
|
548
|
-
| **`publicKeyUrl`** | <code>string</code> | URL of Apple's public-key certificate used to verify `signature`. |
|
|
549
|
-
| **`signature`** | <code>string</code> | Base64-encoded signature over the verification payload. |
|
|
550
|
-
| **`salt`** | <code>string</code> | Base64-encoded random salt Apple mixed into the signed payload. |
|
|
551
|
-
| **`timestamp`** | <code>number</code> | Signature creation time, in epoch milliseconds (check freshness server-side). |
|
|
552
|
-
| **`playerId`** | <code>string</code> | The player id the signature attests (GameKit `gamePlayerID`). |
|
|
553
|
-
| **`bundleId`** | <code>string</code> | The app's bundle id, part of the signed payload. |
|
|
554
|
-
| **`teamPlayerId`** | <code>string</code> | GameKit `teamPlayerID` (stable across the team's games), when available. |
|
|
555
|
-
| **`gamePlayerId`** | <code>string</code> | GameKit `gamePlayerID` (stable per game), when available. |
|
|
494
|
+
| **`playerId`** | <code>string</code> | Stable, platform-assigned Play Games player id. |
|
|
495
|
+
| **`displayName`** | <code>string</code> | Display name as shown in Google Play Games. |
|
|
496
|
+
| **`avatarUrl`** | <code>string</code> | URL of the player's avatar image, when the platform exposes one. |
|
|
556
497
|
|
|
557
498
|
|
|
558
499
|
#### Snapshot
|
|
@@ -604,7 +545,7 @@ bun run verify # typecheck + build
|
|
|
604
545
|
```
|
|
605
546
|
|
|
606
547
|
The TypeScript bridge is built to `dist/` (ESM + CJS + types). The native sources
|
|
607
|
-
under `android/`
|
|
548
|
+
under `android/` ship in the package and are wired up by `npx cap sync`.
|
|
608
549
|
|
|
609
550
|
## License
|
|
610
551
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { PluginListenerHandle } from "@capacitor/core";
|
|
2
2
|
/** A signed-in player's public profile. */
|
|
3
3
|
export interface PlayerInfo {
|
|
4
|
-
/** Stable, platform-assigned
|
|
4
|
+
/** Stable, platform-assigned Play Games player id. */
|
|
5
5
|
playerId: string;
|
|
6
|
-
/** Display name as shown in Google Play Games
|
|
6
|
+
/** Display name as shown in Google Play Games. */
|
|
7
7
|
displayName: string;
|
|
8
8
|
/** URL of the player's avatar image, when the platform exposes one. */
|
|
9
9
|
avatarUrl?: string;
|
|
@@ -35,45 +35,19 @@ export interface SnapshotMeta {
|
|
|
35
35
|
/** Last-modified time, in epoch milliseconds. */
|
|
36
36
|
modifiedAt: number;
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* A GameKit identity-verification bundle
|
|
40
|
-
* (`GKLocalPlayer.fetchItems(forIdentityVerificationSignature:)`). A third-party
|
|
41
|
-
* server verifies `signature` against the certificate at `publicKeyUrl` to trust
|
|
42
|
-
* the Game Center `playerId` without relaying it through the untrusted client.
|
|
43
|
-
*/
|
|
44
|
-
export interface IdentityVerificationSignature {
|
|
45
|
-
/** URL of Apple's public-key certificate used to verify `signature`. */
|
|
46
|
-
publicKeyUrl: string;
|
|
47
|
-
/** Base64-encoded signature over the verification payload. */
|
|
48
|
-
signature: string;
|
|
49
|
-
/** Base64-encoded random salt Apple mixed into the signed payload. */
|
|
50
|
-
salt: string;
|
|
51
|
-
/** Signature creation time, in epoch milliseconds (check freshness server-side). */
|
|
52
|
-
timestamp: number;
|
|
53
|
-
/** The player id the signature attests (GameKit `gamePlayerID`). */
|
|
54
|
-
playerId: string;
|
|
55
|
-
/** The app's bundle id, part of the signed payload. */
|
|
56
|
-
bundleId: string;
|
|
57
|
-
/** GameKit `teamPlayerID` (stable across the team's games), when available. */
|
|
58
|
-
teamPlayerId?: string;
|
|
59
|
-
/** GameKit `gamePlayerID` (stable per game), when available. */
|
|
60
|
-
gamePlayerId?: string;
|
|
61
|
-
}
|
|
62
38
|
/** Payload of the `signInStateChanged` event. */
|
|
63
39
|
export type SignInStateChangedEvent = SignInResult;
|
|
64
40
|
export interface PlayGamesPlugin {
|
|
65
41
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* GameKit authentication handler. Call once, after any App Tracking
|
|
70
|
-
* Transparency prompt has resolved, before the other methods.
|
|
42
|
+
* No-op. `PlayGamesSdk.initialize` runs automatically when the plugin
|
|
43
|
+
* loads, driven by the Capacitor bridge — this call exists only to keep
|
|
44
|
+
* the API symmetric with the web fallback.
|
|
71
45
|
*
|
|
72
46
|
* @since 0.1.0
|
|
73
47
|
*/
|
|
74
48
|
initialize(): Promise<void>;
|
|
75
49
|
/**
|
|
76
|
-
* Sign in to
|
|
50
|
+
* Sign in to Google Play Games.
|
|
77
51
|
*
|
|
78
52
|
* `silent` (default `true`) attempts auto sign-in with no UI; on most devices
|
|
79
53
|
* this succeeds if the player has previously authenticated this game. Pass
|
|
@@ -95,8 +69,8 @@ export interface PlayGamesPlugin {
|
|
|
95
69
|
/**
|
|
96
70
|
* Get the signed-in player's profile.
|
|
97
71
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
72
|
+
* Rejects when no player is signed in. On web (the no-op fallback) it
|
|
73
|
+
* resolves an empty profile (`playerId: ""`).
|
|
100
74
|
* @since 0.1.0
|
|
101
75
|
*/
|
|
102
76
|
getPlayer(): Promise<PlayerInfo>;
|
|
@@ -109,8 +83,7 @@ export interface PlayGamesPlugin {
|
|
|
109
83
|
* is redeemed against it server-side. `forceRefresh` (default `false`) requests a
|
|
110
84
|
* fresh code even if one was recently granted.
|
|
111
85
|
*
|
|
112
|
-
*
|
|
113
|
-
* `authCode`.
|
|
86
|
+
* The web fallback resolves an empty `authCode`.
|
|
114
87
|
* @since 0.2.0
|
|
115
88
|
*/
|
|
116
89
|
requestServerSideAccess(opts: {
|
|
@@ -120,20 +93,7 @@ export interface PlayGamesPlugin {
|
|
|
120
93
|
authCode: string;
|
|
121
94
|
}>;
|
|
122
95
|
/**
|
|
123
|
-
*
|
|
124
|
-
* player (`GKLocalPlayer.fetchItems(forIdentityVerificationSignature:)`). A
|
|
125
|
-
* backend verifies the returned bundle against Apple's certificate to trust the
|
|
126
|
-
* player id rather than the untrusted client's claim. Rejects when no player is
|
|
127
|
-
* signed in.
|
|
128
|
-
*
|
|
129
|
-
* iOS only. Android rejects (unimplemented); the web fallback resolves an empty
|
|
130
|
-
* bundle.
|
|
131
|
-
* @since 0.2.0
|
|
132
|
-
*/
|
|
133
|
-
fetchIdentityVerificationSignature(): Promise<IdentityVerificationSignature>;
|
|
134
|
-
/**
|
|
135
|
-
* Unlock an achievement by its platform id (Play Console achievement id on
|
|
136
|
-
* Android, App Store Connect / Game Center id on iOS).
|
|
96
|
+
* Unlock an achievement by its Play Console achievement id.
|
|
137
97
|
* @since 0.1.0
|
|
138
98
|
*/
|
|
139
99
|
unlockAchievement(opts: {
|
|
@@ -142,11 +102,8 @@ export interface PlayGamesPlugin {
|
|
|
142
102
|
/**
|
|
143
103
|
* Increment a partial (incremental) achievement.
|
|
144
104
|
*
|
|
145
|
-
* `steps` is
|
|
146
|
-
*
|
|
147
|
-
* iOS (GameKit) it is added to `percentComplete` as percentage points.
|
|
148
|
-
* Compute a platform-appropriate value (e.g. via `Capacitor.getPlatform()`)
|
|
149
|
-
* so progress matches on both stores.
|
|
105
|
+
* `steps` is a discrete step count toward the achievement's Play Console
|
|
106
|
+
* step total, and must be greater than 0 — the call rejects otherwise.
|
|
150
107
|
* @since 0.1.0
|
|
151
108
|
*/
|
|
152
109
|
incrementAchievement(opts: {
|
|
@@ -154,12 +111,12 @@ export interface PlayGamesPlugin {
|
|
|
154
111
|
steps: number;
|
|
155
112
|
}): Promise<void>;
|
|
156
113
|
/**
|
|
157
|
-
* Show the
|
|
114
|
+
* Show the native Google Play Games achievements UI.
|
|
158
115
|
* @since 0.1.0
|
|
159
116
|
*/
|
|
160
117
|
showAchievements(): Promise<void>;
|
|
161
118
|
/**
|
|
162
|
-
* Submit a score to a leaderboard by its
|
|
119
|
+
* Submit a score to a leaderboard by its Play Console leaderboard id.
|
|
163
120
|
* @since 0.1.0
|
|
164
121
|
*/
|
|
165
122
|
submitScore(opts: {
|
|
@@ -214,7 +171,7 @@ export interface PlayGamesPlugin {
|
|
|
214
171
|
}): Promise<void>;
|
|
215
172
|
/**
|
|
216
173
|
* Listen for sign-in state changes: an interactive sign-in completing, or the
|
|
217
|
-
* player signing out of
|
|
174
|
+
* player signing out of Google Play Games system-wide.
|
|
218
175
|
* @since 0.1.0
|
|
219
176
|
*/
|
|
220
177
|
addListener(event: "signInStateChanged", listener: (e: SignInStateChangedEvent) => void): Promise<PluginListenerHandle>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,
|
|
1
|
+
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iDAAiD;AACjD,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B;;;;;;;;;OASG;IACH,MAAM,CAAC,IAAI,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC1B;;;OAGG;IACH,UAAU,IAAI,OAAO,CAAC;QACpB,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;IACH;;;;;;OAMG;IACH,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IACjC;;;;;;;;;;;OAWG;IACH,uBAAuB,CAAC,IAAI,EAAE;QAC5B,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC;QACV,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH;;;OAGG;IACH,iBAAiB,CAAC,IAAI,EAAE;QACtB,EAAE,EAAE,MAAM,CAAC;KACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB;;;;;;OAMG;IACH,oBAAoB,CAAC,IAAI,EAAE;QACzB,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB;;;OAGG;IACH,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE;QAChB,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB;;;OAGG;IACH,eAAe,CAAC,IAAI,EAAE;QACpB,aAAa,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB;;;OAGG;IACH,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,OAAO,CAAC;QACV,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;KAC3B,CAAC,CAAC;IACH;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC;QACvB,SAAS,EAAE,YAAY,EAAE,CAAC;KAC3B,CAAC,CAAC;IACH;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACxH;;;OAGG;IACH,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC"}
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WebPlugin } from "@capacitor/core";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PlayerInfo, PlayGamesPlugin, SignInResult, Snapshot, SnapshotMeta } from "./definitions";
|
|
3
3
|
/**
|
|
4
4
|
* Web / non-native fallback: every method resolves to a safe default (signed
|
|
5
5
|
* out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.
|
|
@@ -14,7 +14,6 @@ export declare class PlayGamesWeb extends WebPlugin implements PlayGamesPlugin {
|
|
|
14
14
|
requestServerSideAccess(): Promise<{
|
|
15
15
|
authCode: string;
|
|
16
16
|
}>;
|
|
17
|
-
fetchIdentityVerificationSignature(): Promise<IdentityVerificationSignature>;
|
|
18
17
|
unlockAchievement(): Promise<void>;
|
|
19
18
|
incrementAchievement(): Promise<void>;
|
|
20
19
|
showAchievements(): Promise<void>;
|
package/dist/esm/web.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,YAAY,EACb,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,qBAAa,YAAa,SAAQ,SAAU,YAAW,eAAe;IAC9D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAE9B,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC,CAEpC;IAEK,UAAU,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAEjD;IAEK,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,CAErC;IAEK,uBAAuB,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAE7D;IAEK,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAErC,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAExC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAEpC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAE/B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAEnC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAEvC,YAAY,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;KAAE,CAAC,CAE3D;IAEK,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAEhC,aAAa,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC,CAE5D;IAEK,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;CACzC"}
|
package/dist/esm/web.js
CHANGED
|
@@ -17,16 +17,6 @@ export class PlayGamesWeb extends WebPlugin {
|
|
|
17
17
|
async requestServerSideAccess() {
|
|
18
18
|
return { authCode: "" };
|
|
19
19
|
}
|
|
20
|
-
async fetchIdentityVerificationSignature() {
|
|
21
|
-
return {
|
|
22
|
-
publicKeyUrl: "",
|
|
23
|
-
signature: "",
|
|
24
|
-
salt: "",
|
|
25
|
-
timestamp: 0,
|
|
26
|
-
playerId: "",
|
|
27
|
-
bundleId: "",
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
20
|
async unlockAchievement() { }
|
|
31
21
|
async incrementAchievement() { }
|
|
32
22
|
async showAchievements() { }
|
package/dist/esm/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAU5C;;;GAGG;AACH,MAAM,OAAO,YAAa,SAAQ,SAAS;IACzC,KAAK,CAAC,UAAU,KAAmB,CAAC;IAEpC,KAAK,CAAC,MAAM;QACV,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,SAAS;QACb,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,iBAAiB,KAAmB,CAAC;IAE3C,KAAK,CAAC,oBAAoB,KAAmB,CAAC;IAE9C,KAAK,CAAC,gBAAgB,KAAmB,CAAC;IAE1C,KAAK,CAAC,WAAW,KAAmB,CAAC;IAErC,KAAK,CAAC,eAAe,KAAmB,CAAC;IAEzC,KAAK,CAAC,mBAAmB,KAAmB,CAAC;IAE7C,KAAK,CAAC,YAAY;QAChB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,YAAY,KAAmB,CAAC;IAEtC,KAAK,CAAC,aAAa;QACjB,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,cAAc,KAAmB,CAAC;CACzC"}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -24,16 +24,6 @@ class PlayGamesWeb extends core.WebPlugin {
|
|
|
24
24
|
async requestServerSideAccess() {
|
|
25
25
|
return { authCode: "" };
|
|
26
26
|
}
|
|
27
|
-
async fetchIdentityVerificationSignature() {
|
|
28
|
-
return {
|
|
29
|
-
publicKeyUrl: "",
|
|
30
|
-
signature: "",
|
|
31
|
-
salt: "",
|
|
32
|
-
timestamp: 0,
|
|
33
|
-
playerId: "",
|
|
34
|
-
bundleId: "",
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
27
|
async unlockAchievement() { }
|
|
38
28
|
async incrementAchievement() { }
|
|
39
29
|
async showAchievements() { }
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nconst PlayGames = registerPlugin(\"PlayGames\", {\n web: () => import(\"./web\").then((m) => new m.PlayGamesWeb()),\n});\nexport * from \"./definitions\";\nexport { PlayGames };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from \"@capacitor/core\";\n/**\n * Web / non-native fallback: every method resolves to a safe default (signed\n * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.\n */\nexport class PlayGamesWeb extends WebPlugin {\n async initialize() { }\n async signIn() {\n return { signedIn: false };\n }\n async isSignedIn() {\n return { signedIn: false };\n }\n async getPlayer() {\n return { playerId: \"\", displayName: \"\" };\n }\n async requestServerSideAccess() {\n return { authCode: \"\" };\n }\n async
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nconst PlayGames = registerPlugin(\"PlayGames\", {\n web: () => import(\"./web\").then((m) => new m.PlayGamesWeb()),\n});\nexport * from \"./definitions\";\nexport { PlayGames };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from \"@capacitor/core\";\n/**\n * Web / non-native fallback: every method resolves to a safe default (signed\n * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.\n */\nexport class PlayGamesWeb extends WebPlugin {\n async initialize() { }\n async signIn() {\n return { signedIn: false };\n }\n async isSignedIn() {\n return { signedIn: false };\n }\n async getPlayer() {\n return { playerId: \"\", displayName: \"\" };\n }\n async requestServerSideAccess() {\n return { authCode: \"\" };\n }\n async unlockAchievement() { }\n async incrementAchievement() { }\n async showAchievements() { }\n async submitScore() { }\n async showLeaderboard() { }\n async showAllLeaderboards() { }\n async loadSnapshot() {\n return { snapshot: null };\n }\n async saveSnapshot() { }\n async listSnapshots() {\n return { snapshots: [] };\n }\n async deleteSnapshot() { }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,SAAS,GAAGA,mBAAc,CAAC,WAAW,EAAE;AAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;AAChE,CAAC;;ACFD;AACA;AACA;AACA;AACO,MAAM,YAAY,SAASC,cAAS,CAAC;AAC5C,IAAI,MAAM,UAAU,GAAG,EAAE;AACzB,IAAI,MAAM,MAAM,GAAG;AACnB,QAAQ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;AAClC,IAAI;AACJ,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;AAClC,IAAI;AACJ,IAAI,MAAM,SAAS,GAAG;AACtB,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;AAChD,IAAI;AACJ,IAAI,MAAM,uBAAuB,GAAG;AACpC,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;AAC/B,IAAI;AACJ,IAAI,MAAM,iBAAiB,GAAG,EAAE;AAChC,IAAI,MAAM,oBAAoB,GAAG,EAAE;AACnC,IAAI,MAAM,gBAAgB,GAAG,EAAE;AAC/B,IAAI,MAAM,WAAW,GAAG,EAAE;AAC1B,IAAI,MAAM,eAAe,GAAG,EAAE;AAC9B,IAAI,MAAM,mBAAmB,GAAG,EAAE;AAClC,IAAI,MAAM,YAAY,GAAG;AACzB,QAAQ,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;AACjC,IAAI;AACJ,IAAI,MAAM,YAAY,GAAG,EAAE;AAC3B,IAAI,MAAM,aAAa,GAAG;AAC1B,QAAQ,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE;AAChC,IAAI;AACJ,IAAI,MAAM,cAAc,GAAG,EAAE;AAC7B;;;;;;;"}
|
package/dist/plugin.js
CHANGED
|
@@ -23,16 +23,6 @@ var capacitorPlayGames = (function (exports, core) {
|
|
|
23
23
|
async requestServerSideAccess() {
|
|
24
24
|
return { authCode: "" };
|
|
25
25
|
}
|
|
26
|
-
async fetchIdentityVerificationSignature() {
|
|
27
|
-
return {
|
|
28
|
-
publicKeyUrl: "",
|
|
29
|
-
signature: "",
|
|
30
|
-
salt: "",
|
|
31
|
-
timestamp: 0,
|
|
32
|
-
playerId: "",
|
|
33
|
-
bundleId: "",
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
26
|
async unlockAchievement() { }
|
|
37
27
|
async incrementAchievement() { }
|
|
38
28
|
async showAchievements() { }
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nconst PlayGames = registerPlugin(\"PlayGames\", {\n web: () => import(\"./web\").then((m) => new m.PlayGamesWeb()),\n});\nexport * from \"./definitions\";\nexport { PlayGames };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from \"@capacitor/core\";\n/**\n * Web / non-native fallback: every method resolves to a safe default (signed\n * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.\n */\nexport class PlayGamesWeb extends WebPlugin {\n async initialize() { }\n async signIn() {\n return { signedIn: false };\n }\n async isSignedIn() {\n return { signedIn: false };\n }\n async getPlayer() {\n return { playerId: \"\", displayName: \"\" };\n }\n async requestServerSideAccess() {\n return { authCode: \"\" };\n }\n async
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nconst PlayGames = registerPlugin(\"PlayGames\", {\n web: () => import(\"./web\").then((m) => new m.PlayGamesWeb()),\n});\nexport * from \"./definitions\";\nexport { PlayGames };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from \"@capacitor/core\";\n/**\n * Web / non-native fallback: every method resolves to a safe default (signed\n * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.\n */\nexport class PlayGamesWeb extends WebPlugin {\n async initialize() { }\n async signIn() {\n return { signedIn: false };\n }\n async isSignedIn() {\n return { signedIn: false };\n }\n async getPlayer() {\n return { playerId: \"\", displayName: \"\" };\n }\n async requestServerSideAccess() {\n return { authCode: \"\" };\n }\n async unlockAchievement() { }\n async incrementAchievement() { }\n async showAchievements() { }\n async submitScore() { }\n async showLeaderboard() { }\n async showAllLeaderboards() { }\n async loadSnapshot() {\n return { snapshot: null };\n }\n async saveSnapshot() { }\n async listSnapshots() {\n return { snapshots: [] };\n }\n async deleteSnapshot() { }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,SAAS,GAAGA,mBAAc,CAAC,WAAW,EAAE;IAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;IAChE,CAAC;;ICFD;IACA;IACA;IACA;IACO,MAAM,YAAY,SAASC,cAAS,CAAC;IAC5C,IAAI,MAAM,UAAU,GAAG,EAAE;IACzB,IAAI,MAAM,MAAM,GAAG;IACnB,QAAQ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;IAClC,IAAI;IACJ,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;IAClC,IAAI;IACJ,IAAI,MAAM,SAAS,GAAG;IACtB,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;IAChD,IAAI;IACJ,IAAI,MAAM,uBAAuB,GAAG;IACpC,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC/B,IAAI;IACJ,IAAI,MAAM,iBAAiB,GAAG,EAAE;IAChC,IAAI,MAAM,oBAAoB,GAAG,EAAE;IACnC,IAAI,MAAM,gBAAgB,GAAG,EAAE;IAC/B,IAAI,MAAM,WAAW,GAAG,EAAE;IAC1B,IAAI,MAAM,eAAe,GAAG,EAAE;IAC9B,IAAI,MAAM,mBAAmB,GAAG,EAAE;IAClC,IAAI,MAAM,YAAY,GAAG;IACzB,QAAQ,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;IACjC,IAAI;IACJ,IAAI,MAAM,YAAY,GAAG,EAAE;IAC3B,IAAI,MAAM,aAAa,GAAG;IAC1B,QAAQ,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE;IAChC,IAAI;IACJ,IAAI,MAAM,cAAc,GAAG,EAAE;IAC7B;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modbender/capacitor-play-games",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Capacitor 8 plugin for Google Play Games Services (Android)
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Capacitor 8 plugin for Google Play Games Services (Android): sign-in, achievements, leaderboards, and saved games, with a safe no-op web fallback.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
7
7
|
"module": "dist/esm/index.js",
|
|
@@ -13,9 +13,6 @@
|
|
|
13
13
|
"android/consumer-rules.pro",
|
|
14
14
|
"android/proguard-rules.pro",
|
|
15
15
|
"dist/",
|
|
16
|
-
"ios/Plugin/",
|
|
17
|
-
"CapacitorPlayGames.podspec",
|
|
18
|
-
"Package.swift",
|
|
19
16
|
"CHANGELOG.md"
|
|
20
17
|
],
|
|
21
18
|
"author": "modbender",
|
|
@@ -32,21 +29,15 @@
|
|
|
32
29
|
"capacitor",
|
|
33
30
|
"plugin",
|
|
34
31
|
"native",
|
|
35
|
-
"ios",
|
|
36
32
|
"android",
|
|
37
33
|
"play-games",
|
|
38
34
|
"google-play-games",
|
|
39
35
|
"pgs",
|
|
40
|
-
"gamekit",
|
|
41
|
-
"game-center",
|
|
42
36
|
"achievements",
|
|
43
37
|
"leaderboards",
|
|
44
38
|
"saved-games"
|
|
45
39
|
],
|
|
46
40
|
"capacitor": {
|
|
47
|
-
"ios": {
|
|
48
|
-
"src": "ios"
|
|
49
|
-
},
|
|
50
41
|
"android": {
|
|
51
42
|
"src": "android"
|
|
52
43
|
}
|
|
@@ -73,8 +64,9 @@
|
|
|
73
64
|
"clean": "rimraf dist",
|
|
74
65
|
"build": "rimraf dist && tsc && rollup -c rollup.config.mjs",
|
|
75
66
|
"docgen": "docgen --api PlayGamesPlugin --output-readme README.md",
|
|
67
|
+
"check-docs": "bun scripts/check-docs.ts",
|
|
76
68
|
"typecheck": "tsc --noEmit",
|
|
77
|
-
"verify": "bun run typecheck && bun run build",
|
|
69
|
+
"verify": "bun run typecheck && bun run check-docs && bun run build",
|
|
78
70
|
"prepublishOnly": "bun run verify"
|
|
79
71
|
}
|
|
80
72
|
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
require 'json'
|
|
2
|
-
|
|
3
|
-
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
-
|
|
5
|
-
Pod::Spec.new do |s|
|
|
6
|
-
s.name = 'CapacitorPlayGames'
|
|
7
|
-
s.version = package['version']
|
|
8
|
-
s.summary = package['description']
|
|
9
|
-
s.license = package['license']
|
|
10
|
-
s.homepage = 'https://github.com/modbender/capacitor-play-games'
|
|
11
|
-
s.author = 'modbender'
|
|
12
|
-
s.source = { :git => 'https://github.com/modbender/capacitor-play-games.git', :tag => s.version.to_s }
|
|
13
|
-
s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
-
s.ios.deployment_target = '14.0'
|
|
15
|
-
s.dependency 'Capacitor'
|
|
16
|
-
s.swift_version = '5.1'
|
|
17
|
-
end
|
package/Package.swift
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// swift-tools-version: 5.9
|
|
2
|
-
import PackageDescription
|
|
3
|
-
|
|
4
|
-
// Package/product/target names carry the npm scope ("Modbender…") because
|
|
5
|
-
// Capacitor's `cap sync ios` derives the SwiftPM product name from the FULL
|
|
6
|
-
// scoped package name (@modbender/capacitor-play-games →
|
|
7
|
-
// ModbenderCapacitorPlayGames, PascalCased scope+name, same as
|
|
8
|
-
// @capacitor-community/admob → CapacitorCommunityAdmob). The consuming app's
|
|
9
|
-
// generated Package.swift references that derived name, so the product here must
|
|
10
|
-
// match it exactly or `xcodebuild -resolvePackageDependencies` fails with
|
|
11
|
-
// "product 'ModbenderCapacitorPlayGames' not found". The Swift module is
|
|
12
|
-
// renamed with it; nothing imports it by name and Capacitor registers the
|
|
13
|
-
// plugin via the Objective-C runtime, so the rename is source-compatible.
|
|
14
|
-
let package = Package(
|
|
15
|
-
name: "ModbenderCapacitorPlayGames",
|
|
16
|
-
platforms: [.iOS(.v14)],
|
|
17
|
-
products: [
|
|
18
|
-
.library(
|
|
19
|
-
name: "ModbenderCapacitorPlayGames",
|
|
20
|
-
targets: ["ModbenderCapacitorPlayGames"]
|
|
21
|
-
)
|
|
22
|
-
],
|
|
23
|
-
dependencies: [
|
|
24
|
-
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.4.2")
|
|
25
|
-
],
|
|
26
|
-
targets: [
|
|
27
|
-
.target(
|
|
28
|
-
name: "ModbenderCapacitorPlayGames",
|
|
29
|
-
dependencies: [
|
|
30
|
-
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
31
|
-
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
32
|
-
],
|
|
33
|
-
path: "ios/Plugin"
|
|
34
|
-
)
|
|
35
|
-
]
|
|
36
|
-
)
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import Capacitor
|
|
2
|
-
import GameKit
|
|
3
|
-
import UIKit
|
|
4
|
-
|
|
5
|
-
/// GameKit has no increment API (report overwrites percentComplete), so
|
|
6
|
-
/// increment() loads current progress, adds the steps (percentage points),
|
|
7
|
-
/// then reports the new total.
|
|
8
|
-
internal class AchievementsModule: PgsModule, GKGameCenterControllerDelegate {
|
|
9
|
-
func unlock(_ call: CAPPluginCall) {
|
|
10
|
-
guard let id = call.getString("id") else {
|
|
11
|
-
call.reject("missing id"); return
|
|
12
|
-
}
|
|
13
|
-
let achievement = GKAchievement(identifier: id)
|
|
14
|
-
achievement.percentComplete = 100
|
|
15
|
-
achievement.showsCompletionBanner = true
|
|
16
|
-
GKAchievement.report([achievement]) { error in
|
|
17
|
-
if let error = error {
|
|
18
|
-
call.reject(error.localizedDescription, nil, error)
|
|
19
|
-
} else {
|
|
20
|
-
call.resolve()
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
func increment(_ call: CAPPluginCall) {
|
|
26
|
-
guard let id = call.getString("id") else {
|
|
27
|
-
call.reject("missing id"); return
|
|
28
|
-
}
|
|
29
|
-
let steps = call.getDouble("steps") ?? 0
|
|
30
|
-
|
|
31
|
-
GKAchievement.loadAchievements { existing, error in
|
|
32
|
-
if let error = error {
|
|
33
|
-
call.reject(error.localizedDescription, nil, error)
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
let current = existing?.first(where: { $0.identifier == id })
|
|
37
|
-
let achievement = current ?? GKAchievement(identifier: id)
|
|
38
|
-
let next = min(100, (achievement.percentComplete) + steps)
|
|
39
|
-
achievement.percentComplete = next
|
|
40
|
-
achievement.showsCompletionBanner = next >= 100
|
|
41
|
-
GKAchievement.report([achievement]) { reportError in
|
|
42
|
-
if let reportError = reportError {
|
|
43
|
-
call.reject(reportError.localizedDescription, nil, reportError)
|
|
44
|
-
} else {
|
|
45
|
-
call.resolve()
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func show(_ call: CAPPluginCall) {
|
|
52
|
-
DispatchQueue.main.async {
|
|
53
|
-
guard let presenter = self.topViewController() else {
|
|
54
|
-
call.reject("no presenter available"); return
|
|
55
|
-
}
|
|
56
|
-
let vc: GKGameCenterViewController
|
|
57
|
-
if #available(iOS 14.0, *) {
|
|
58
|
-
vc = GKGameCenterViewController(state: .achievements)
|
|
59
|
-
} else {
|
|
60
|
-
vc = GKGameCenterViewController()
|
|
61
|
-
vc.viewState = .achievements
|
|
62
|
-
}
|
|
63
|
-
vc.gameCenterDelegate = self
|
|
64
|
-
presenter.present(vc, animated: true) { call.resolve() }
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// MARK: - GKGameCenterControllerDelegate
|
|
69
|
-
|
|
70
|
-
func gameCenterViewControllerDidFinish(_ controller: GKGameCenterViewController) {
|
|
71
|
-
controller.dismiss(animated: true)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import Capacitor
|
|
2
|
-
import GameKit
|
|
3
|
-
import UIKit
|
|
4
|
-
|
|
5
|
-
/// GameKit leaderboards: score submission + the native leaderboard overlay.
|
|
6
|
-
internal class LeaderboardsModule: PgsModule, GKGameCenterControllerDelegate {
|
|
7
|
-
func submit(_ call: CAPPluginCall) {
|
|
8
|
-
guard let leaderboardID = call.getString("leaderboardId") else {
|
|
9
|
-
call.reject("missing leaderboardId"); return
|
|
10
|
-
}
|
|
11
|
-
let scoreDouble = call.getDouble("score") ?? 0
|
|
12
|
-
let score = Int(scoreDouble)
|
|
13
|
-
|
|
14
|
-
GKLeaderboard.submitScore(
|
|
15
|
-
score,
|
|
16
|
-
context: 0,
|
|
17
|
-
player: GKLocalPlayer.local,
|
|
18
|
-
leaderboardIDs: [leaderboardID]
|
|
19
|
-
) { error in
|
|
20
|
-
if let error = error {
|
|
21
|
-
call.reject(error.localizedDescription, nil, error)
|
|
22
|
-
} else {
|
|
23
|
-
call.resolve()
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
func show(_ call: CAPPluginCall) {
|
|
29
|
-
guard let leaderboardID = call.getString("leaderboardId") else {
|
|
30
|
-
call.reject("missing leaderboardId"); return
|
|
31
|
-
}
|
|
32
|
-
DispatchQueue.main.async {
|
|
33
|
-
guard let presenter = self.topViewController() else {
|
|
34
|
-
call.reject("no presenter available"); return
|
|
35
|
-
}
|
|
36
|
-
let vc: GKGameCenterViewController
|
|
37
|
-
if #available(iOS 14.0, *) {
|
|
38
|
-
vc = GKGameCenterViewController(
|
|
39
|
-
leaderboardID: leaderboardID,
|
|
40
|
-
playerScope: .global,
|
|
41
|
-
timeScope: .allTime
|
|
42
|
-
)
|
|
43
|
-
} else {
|
|
44
|
-
vc = GKGameCenterViewController()
|
|
45
|
-
vc.viewState = .leaderboards
|
|
46
|
-
vc.leaderboardIdentifier = leaderboardID
|
|
47
|
-
}
|
|
48
|
-
vc.gameCenterDelegate = self
|
|
49
|
-
presenter.present(vc, animated: true) { call.resolve() }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
func showAll(_ call: CAPPluginCall) {
|
|
54
|
-
DispatchQueue.main.async {
|
|
55
|
-
guard let presenter = self.topViewController() else {
|
|
56
|
-
call.reject("no presenter available"); return
|
|
57
|
-
}
|
|
58
|
-
let vc: GKGameCenterViewController
|
|
59
|
-
if #available(iOS 14.0, *) {
|
|
60
|
-
vc = GKGameCenterViewController(state: .leaderboards)
|
|
61
|
-
} else {
|
|
62
|
-
vc = GKGameCenterViewController()
|
|
63
|
-
vc.viewState = .leaderboards
|
|
64
|
-
}
|
|
65
|
-
vc.gameCenterDelegate = self
|
|
66
|
-
presenter.present(vc, animated: true) { call.resolve() }
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// MARK: - GKGameCenterControllerDelegate
|
|
71
|
-
|
|
72
|
-
func gameCenterViewControllerDidFinish(_ controller: GKGameCenterViewController) {
|
|
73
|
-
controller.dismiss(animated: true)
|
|
74
|
-
}
|
|
75
|
-
}
|
package/ios/Plugin/Pgs.swift
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import Capacitor
|
|
2
|
-
import Foundation
|
|
3
|
-
import GameKit
|
|
4
|
-
import UIKit
|
|
5
|
-
|
|
6
|
-
/// Shared base for per-feature modules. Inherits `NSObject` so subclasses can
|
|
7
|
-
/// adopt @objc protocols (e.g. `GKGameCenterControllerDelegate`).
|
|
8
|
-
internal class PgsModule: NSObject {
|
|
9
|
-
unowned let plugin: PlayGamesPlugin
|
|
10
|
-
init(_ plugin: PlayGamesPlugin) {
|
|
11
|
-
self.plugin = plugin
|
|
12
|
-
super.init()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/// Top-most presented view controller. Callers must be on the main queue.
|
|
16
|
-
func topViewController() -> UIViewController? {
|
|
17
|
-
guard let scene = UIApplication.shared.connectedScenes
|
|
18
|
-
.compactMap({ $0 as? UIWindowScene })
|
|
19
|
-
.first(where: { $0.activationState == .foregroundActive })
|
|
20
|
-
?? UIApplication.shared.connectedScenes
|
|
21
|
-
.compactMap({ $0 as? UIWindowScene }).first,
|
|
22
|
-
let rootVC = scene.windows.first(where: { $0.isKeyWindow })?.rootViewController
|
|
23
|
-
?? scene.windows.first?.rootViewController
|
|
24
|
-
else { return nil }
|
|
25
|
-
|
|
26
|
-
var top = rootVC
|
|
27
|
-
while let presented = top.presentedViewController { top = presented }
|
|
28
|
-
return top
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/// Serialise a GameKit player as the JS-side `PlayerInfo`. Uses the stable
|
|
33
|
-
/// cross-device `gamePlayerID` (not the per-team `teamPlayerID`).
|
|
34
|
-
internal func playerInfo(from player: GKLocalPlayer) -> [String: Any] {
|
|
35
|
-
let info: [String: Any] = [
|
|
36
|
-
"playerId": player.gamePlayerID,
|
|
37
|
-
"displayName": player.displayName,
|
|
38
|
-
]
|
|
39
|
-
return info
|
|
40
|
-
}
|
package/ios/Plugin/Plugin.swift
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import Capacitor
|
|
2
|
-
import Foundation
|
|
3
|
-
import GameKit
|
|
4
|
-
|
|
5
|
-
/// Capacitor 8 plugin wrapping Apple GameKit (sign-in, achievements,
|
|
6
|
-
/// leaderboards, saved games). Thin dispatcher to four per-feature modules.
|
|
7
|
-
@objc(PlayGamesPlugin)
|
|
8
|
-
public class PlayGamesPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
9
|
-
// CAPBridgedPlugin self-registers the plugin on iOS (Capacitor 8). `jsName`
|
|
10
|
-
// must match registerPlugin("PlayGames"); addListener/removeAllListeners
|
|
11
|
-
// come from CAPPlugin and aren't listed here.
|
|
12
|
-
public let identifier = "PlayGamesPlugin"
|
|
13
|
-
public let jsName = "PlayGames"
|
|
14
|
-
public let pluginMethods: [CAPPluginMethod] = [
|
|
15
|
-
CAPPluginMethod(name: "initialize", returnType: CAPPluginReturnPromise),
|
|
16
|
-
CAPPluginMethod(name: "signIn", returnType: CAPPluginReturnPromise),
|
|
17
|
-
CAPPluginMethod(name: "isSignedIn", returnType: CAPPluginReturnPromise),
|
|
18
|
-
CAPPluginMethod(name: "getPlayer", returnType: CAPPluginReturnPromise),
|
|
19
|
-
CAPPluginMethod(name: "fetchIdentityVerificationSignature", returnType: CAPPluginReturnPromise),
|
|
20
|
-
CAPPluginMethod(name: "unlockAchievement", returnType: CAPPluginReturnPromise),
|
|
21
|
-
CAPPluginMethod(name: "incrementAchievement", returnType: CAPPluginReturnPromise),
|
|
22
|
-
CAPPluginMethod(name: "showAchievements", returnType: CAPPluginReturnPromise),
|
|
23
|
-
CAPPluginMethod(name: "submitScore", returnType: CAPPluginReturnPromise),
|
|
24
|
-
CAPPluginMethod(name: "showLeaderboard", returnType: CAPPluginReturnPromise),
|
|
25
|
-
CAPPluginMethod(name: "showAllLeaderboards", returnType: CAPPluginReturnPromise),
|
|
26
|
-
CAPPluginMethod(name: "loadSnapshot", returnType: CAPPluginReturnPromise),
|
|
27
|
-
CAPPluginMethod(name: "saveSnapshot", returnType: CAPPluginReturnPromise),
|
|
28
|
-
CAPPluginMethod(name: "listSnapshots", returnType: CAPPluginReturnPromise),
|
|
29
|
-
CAPPluginMethod(name: "deleteSnapshot", returnType: CAPPluginReturnPromise),
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
private lazy var signInModule = SignInModule(self)
|
|
33
|
-
private lazy var achievementsModule = AchievementsModule(self)
|
|
34
|
-
private lazy var leaderboardsModule = LeaderboardsModule(self)
|
|
35
|
-
private lazy var savedGamesModule = SavedGamesModule(self)
|
|
36
|
-
|
|
37
|
-
// The GameKit authenticateHandler is installed in initialize(), NOT load():
|
|
38
|
-
// see initialize() for the ATT-ordering reason.
|
|
39
|
-
|
|
40
|
-
/// Expose listener emission to the modules.
|
|
41
|
-
internal func emit(event: String, data: [String: Any]) {
|
|
42
|
-
notifyListeners(event, data: data)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// MARK: - JS-callable surface
|
|
46
|
-
|
|
47
|
-
@objc func initialize(_ call: CAPPluginCall) {
|
|
48
|
-
// Setting the handler can auto-present GameKit's "Welcome back" banner,
|
|
49
|
-
// which suppresses an ATT prompt shown while it's on screen, so call
|
|
50
|
-
// initialize() only after the app has requested ATT.
|
|
51
|
-
signInModule.installAuthHandlerIfNeeded()
|
|
52
|
-
call.resolve()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@objc func signIn(_ call: CAPPluginCall) { signInModule.signIn(call) }
|
|
56
|
-
@objc func isSignedIn(_ call: CAPPluginCall) { signInModule.isSignedIn(call) }
|
|
57
|
-
@objc func getPlayer(_ call: CAPPluginCall) { signInModule.getPlayer(call) }
|
|
58
|
-
@objc func fetchIdentityVerificationSignature(_ call: CAPPluginCall) {
|
|
59
|
-
signInModule.fetchIdentityVerificationSignature(call)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@objc func unlockAchievement(_ call: CAPPluginCall) { achievementsModule.unlock(call) }
|
|
63
|
-
@objc func incrementAchievement(_ call: CAPPluginCall) { achievementsModule.increment(call) }
|
|
64
|
-
@objc func showAchievements(_ call: CAPPluginCall) { achievementsModule.show(call) }
|
|
65
|
-
|
|
66
|
-
@objc func submitScore(_ call: CAPPluginCall) { leaderboardsModule.submit(call) }
|
|
67
|
-
@objc func showLeaderboard(_ call: CAPPluginCall) { leaderboardsModule.show(call) }
|
|
68
|
-
@objc func showAllLeaderboards(_ call: CAPPluginCall) { leaderboardsModule.showAll(call) }
|
|
69
|
-
|
|
70
|
-
@objc func loadSnapshot(_ call: CAPPluginCall) { savedGamesModule.load(call) }
|
|
71
|
-
@objc func saveSnapshot(_ call: CAPPluginCall) { savedGamesModule.save(call) }
|
|
72
|
-
@objc func listSnapshots(_ call: CAPPluginCall) { savedGamesModule.list(call) }
|
|
73
|
-
@objc func deleteSnapshot(_ call: CAPPluginCall) { savedGamesModule.delete(call) }
|
|
74
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import Capacitor
|
|
2
|
-
import GameKit
|
|
3
|
-
|
|
4
|
-
/// GameKit Saved Games: load/save/list/delete by stable name. Conflicts resolve
|
|
5
|
-
/// most-recently-modified-wins. Requires Game Center auth; otherwise GKLocalPlayer
|
|
6
|
-
/// fails with `GKErrorNotAuthenticated`, forwarded to the caller.
|
|
7
|
-
internal class SavedGamesModule: PgsModule {
|
|
8
|
-
private var player: GKLocalPlayer { GKLocalPlayer.local }
|
|
9
|
-
|
|
10
|
-
func load(_ call: CAPPluginCall) {
|
|
11
|
-
guard let name = call.getString("name") else {
|
|
12
|
-
call.reject("missing name"); return
|
|
13
|
-
}
|
|
14
|
-
player.fetchSavedGames { saved, error in
|
|
15
|
-
if let error = error {
|
|
16
|
-
call.reject(error.localizedDescription, nil, error)
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
let matches = (saved ?? []).filter { $0.name == name }
|
|
20
|
-
guard let pick = matches.max(by: { ($0.modificationDate ?? .distantPast) < ($1.modificationDate ?? .distantPast) }) else {
|
|
21
|
-
call.resolve(["snapshot": NSNull()])
|
|
22
|
-
return
|
|
23
|
-
}
|
|
24
|
-
if matches.count > 1 {
|
|
25
|
-
self.player.resolveConflictingSavedGames(matches, with: Data()) { _, _ in }
|
|
26
|
-
}
|
|
27
|
-
pick.loadData { data, dataError in
|
|
28
|
-
if let dataError = dataError {
|
|
29
|
-
call.reject(dataError.localizedDescription, nil, dataError)
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
let bytes = data ?? Data()
|
|
33
|
-
let payload: [String: Any] = [
|
|
34
|
-
"name": pick.name ?? name,
|
|
35
|
-
// GKSavedGame has no description field; empty for API parity.
|
|
36
|
-
"description": "",
|
|
37
|
-
"modifiedAt": Int((pick.modificationDate ?? Date()).timeIntervalSince1970 * 1000),
|
|
38
|
-
"data": String(data: bytes, encoding: .utf8) ?? "",
|
|
39
|
-
]
|
|
40
|
-
call.resolve(["snapshot": payload])
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
func save(_ call: CAPPluginCall) {
|
|
46
|
-
guard let name = call.getString("name") else {
|
|
47
|
-
call.reject("missing name"); return
|
|
48
|
-
}
|
|
49
|
-
guard let data = call.getString("data") else {
|
|
50
|
-
call.reject("missing data"); return
|
|
51
|
-
}
|
|
52
|
-
let bytes = Data(data.utf8)
|
|
53
|
-
player.saveGameData(bytes, withName: name) { _, error in
|
|
54
|
-
if let error = error {
|
|
55
|
-
call.reject(error.localizedDescription, nil, error)
|
|
56
|
-
} else {
|
|
57
|
-
call.resolve()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
func list(_ call: CAPPluginCall) {
|
|
63
|
-
player.fetchSavedGames { saved, error in
|
|
64
|
-
if let error = error {
|
|
65
|
-
call.reject(error.localizedDescription, nil, error)
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
// Collapse cross-device conflicts to one entry per name.
|
|
69
|
-
var byName: [String: GKSavedGame] = [:]
|
|
70
|
-
for s in saved ?? [] {
|
|
71
|
-
let key = s.name ?? ""
|
|
72
|
-
if key.isEmpty { continue }
|
|
73
|
-
if let existing = byName[key],
|
|
74
|
-
(existing.modificationDate ?? .distantPast) >= (s.modificationDate ?? .distantPast) {
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
77
|
-
byName[key] = s
|
|
78
|
-
}
|
|
79
|
-
let snapshots = byName.values.map { s -> [String: Any] in
|
|
80
|
-
[
|
|
81
|
-
"name": s.name ?? "",
|
|
82
|
-
"description": "",
|
|
83
|
-
"modifiedAt": Int((s.modificationDate ?? Date()).timeIntervalSince1970 * 1000),
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
call.resolve(["snapshots": snapshots])
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
func delete(_ call: CAPPluginCall) {
|
|
91
|
-
guard let name = call.getString("name") else {
|
|
92
|
-
call.reject("missing name"); return
|
|
93
|
-
}
|
|
94
|
-
player.deleteSavedGames(withName: name) { error in
|
|
95
|
-
if let error = error {
|
|
96
|
-
call.reject(error.localizedDescription, nil, error)
|
|
97
|
-
} else {
|
|
98
|
-
call.resolve()
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import Capacitor
|
|
2
|
-
import GameKit
|
|
3
|
-
|
|
4
|
-
/// Wraps `GKLocalPlayer.authenticateHandler` (callback-driven; fires on every
|
|
5
|
-
/// auth state change). `silent=true` only inspects `isAuthenticated`;
|
|
6
|
-
/// `silent=false` presents the system sign-in UI when GameKit offers it.
|
|
7
|
-
internal class SignInModule: PgsModule {
|
|
8
|
-
private var handlerInstalled = false
|
|
9
|
-
|
|
10
|
-
/// Idempotent. NOT installed in load(): setting the handler can auto-present
|
|
11
|
-
/// GameKit's "Welcome back" banner, which would suppress an ATT prompt racing
|
|
12
|
-
/// boot, so it is held until the app has requested ATT and called initialize().
|
|
13
|
-
func installAuthHandlerIfNeeded() {
|
|
14
|
-
if handlerInstalled { return }
|
|
15
|
-
handlerInstalled = true
|
|
16
|
-
|
|
17
|
-
GKLocalPlayer.local.authenticateHandler = { [weak self] viewController, _ in
|
|
18
|
-
guard let self = self else { return }
|
|
19
|
-
if let vc = viewController {
|
|
20
|
-
// Stash the sign-in VC for the next interactive signIn(); don't auto-present.
|
|
21
|
-
self.pendingSignInVC = vc
|
|
22
|
-
} else {
|
|
23
|
-
self.pendingSignInVC = nil
|
|
24
|
-
// No UI offered: re-emit current auth state (also fires on sign-out).
|
|
25
|
-
self.emitState()
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
private var pendingSignInVC: UIViewController?
|
|
31
|
-
|
|
32
|
-
func signIn(_ call: CAPPluginCall) {
|
|
33
|
-
let silent = call.getBool("silent", true)
|
|
34
|
-
installAuthHandlerIfNeeded()
|
|
35
|
-
|
|
36
|
-
if GKLocalPlayer.local.isAuthenticated {
|
|
37
|
-
resolveSignedIn(call)
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if silent {
|
|
42
|
-
resolveSignedOut(call)
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Interactive: present the pending GameKit sign-in VC, if any.
|
|
47
|
-
DispatchQueue.main.async {
|
|
48
|
-
if let vc = self.pendingSignInVC, let presenter = self.topViewController() {
|
|
49
|
-
self.pendingSignInVC = nil
|
|
50
|
-
presenter.present(vc, animated: true) {
|
|
51
|
-
if GKLocalPlayer.local.isAuthenticated {
|
|
52
|
-
self.resolveSignedIn(call)
|
|
53
|
-
} else {
|
|
54
|
-
self.resolveSignedOut(call)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
} else if GKLocalPlayer.local.isAuthenticated {
|
|
58
|
-
self.resolveSignedIn(call)
|
|
59
|
-
} else {
|
|
60
|
-
// No sign-in UI to present (device signed out of Game Center, or
|
|
61
|
-
// the app isn't recognised by Game Center yet).
|
|
62
|
-
self.resolveSignedOut(call)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
func isSignedIn(_ call: CAPPluginCall) {
|
|
68
|
-
call.resolve(["signedIn": GKLocalPlayer.local.isAuthenticated])
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
func getPlayer(_ call: CAPPluginCall) {
|
|
72
|
-
guard GKLocalPlayer.local.isAuthenticated else {
|
|
73
|
-
call.reject("not signed in")
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
call.resolve(playerInfo(from: GKLocalPlayer.local))
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
func fetchIdentityVerificationSignature(_ call: CAPPluginCall) {
|
|
80
|
-
let localPlayer = GKLocalPlayer.local
|
|
81
|
-
guard localPlayer.isAuthenticated else {
|
|
82
|
-
call.reject("not signed in")
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
localPlayer.fetchItems(forIdentityVerificationSignature: {
|
|
86
|
-
publicKeyURL, signature, salt, timestamp, error in
|
|
87
|
-
if let error = error {
|
|
88
|
-
call.reject(error.localizedDescription, nil, error)
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
guard let publicKeyURL = publicKeyURL,
|
|
92
|
-
let signature = signature,
|
|
93
|
-
let salt = salt
|
|
94
|
-
else {
|
|
95
|
-
call.reject("identity verification signature unavailable")
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
call.resolve([
|
|
99
|
-
"publicKeyUrl": publicKeyURL.absoluteString,
|
|
100
|
-
"signature": signature.base64EncodedString(),
|
|
101
|
-
"salt": salt.base64EncodedString(),
|
|
102
|
-
"timestamp": timestamp,
|
|
103
|
-
"playerId": localPlayer.gamePlayerID,
|
|
104
|
-
"bundleId": Bundle.main.bundleIdentifier ?? "",
|
|
105
|
-
"teamPlayerId": localPlayer.teamPlayerID,
|
|
106
|
-
"gamePlayerId": localPlayer.gamePlayerID,
|
|
107
|
-
])
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private func resolveSignedIn(_ call: CAPPluginCall) {
|
|
112
|
-
let body: [String: Any] = [
|
|
113
|
-
"signedIn": true,
|
|
114
|
-
"player": playerInfo(from: GKLocalPlayer.local),
|
|
115
|
-
]
|
|
116
|
-
plugin.emit(event: "signInStateChanged", data: body)
|
|
117
|
-
call.resolve(body)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private func resolveSignedOut(_ call: CAPPluginCall) {
|
|
121
|
-
let body: [String: Any] = ["signedIn": false]
|
|
122
|
-
plugin.emit(event: "signInStateChanged", data: body)
|
|
123
|
-
call.resolve(body)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/// Re-emit current auth state on a handler-driven change (e.g. signed out via Settings).
|
|
127
|
-
private func emitState() {
|
|
128
|
-
let body: [String: Any]
|
|
129
|
-
if GKLocalPlayer.local.isAuthenticated {
|
|
130
|
-
body = [
|
|
131
|
-
"signedIn": true,
|
|
132
|
-
"player": playerInfo(from: GKLocalPlayer.local),
|
|
133
|
-
]
|
|
134
|
-
} else {
|
|
135
|
-
body = ["signedIn": false]
|
|
136
|
-
}
|
|
137
|
-
plugin.emit(event: "signInStateChanged", data: body)
|
|
138
|
-
}
|
|
139
|
-
}
|