@modbender/capacitor-play-games 0.3.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/CapacitorPlayGames.podspec +17 -0
  3. package/LICENSE +22 -0
  4. package/Package.swift +36 -0
  5. package/README.md +611 -0
  6. package/android/build.gradle +76 -0
  7. package/android/consumer-rules.pro +4 -0
  8. package/android/proguard-rules.pro +2 -0
  9. package/android/src/main/AndroidManifest.xml +2 -0
  10. package/android/src/main/java/com/idleflowgames/playgames/AchievementsModule.kt +26 -0
  11. package/android/src/main/java/com/idleflowgames/playgames/LeaderboardsModule.kt +27 -0
  12. package/android/src/main/java/com/idleflowgames/playgames/Pgs.kt +48 -0
  13. package/android/src/main/java/com/idleflowgames/playgames/PlayGamesPlugin.kt +88 -0
  14. package/android/src/main/java/com/idleflowgames/playgames/SavedGamesModule.kt +119 -0
  15. package/android/src/main/java/com/idleflowgames/playgames/SignInModule.kt +83 -0
  16. package/dist/esm/definitions.d.ts +227 -0
  17. package/dist/esm/definitions.d.ts.map +1 -0
  18. package/dist/esm/definitions.js +2 -0
  19. package/dist/esm/definitions.js.map +1 -0
  20. package/dist/esm/index.d.ts +5 -0
  21. package/dist/esm/index.d.ts.map +1 -0
  22. package/dist/esm/index.js +7 -0
  23. package/dist/esm/index.js.map +1 -0
  24. package/dist/esm/web.d.ts +33 -0
  25. package/dist/esm/web.d.ts.map +1 -0
  26. package/dist/esm/web.js +45 -0
  27. package/dist/esm/web.js.map +1 -0
  28. package/dist/plugin.cjs.js +59 -0
  29. package/dist/plugin.cjs.js.map +1 -0
  30. package/dist/plugin.js +62 -0
  31. package/dist/plugin.js.map +1 -0
  32. package/ios/Plugin/AchievementsModule.swift +73 -0
  33. package/ios/Plugin/LeaderboardsModule.swift +75 -0
  34. package/ios/Plugin/Pgs.swift +40 -0
  35. package/ios/Plugin/Plugin.swift +74 -0
  36. package/ios/Plugin/SavedGamesModule.swift +102 -0
  37. package/ios/Plugin/SignInModule.swift +139 -0
  38. package/package.json +80 -0
@@ -0,0 +1,139 @@
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
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@modbender/capacitor-play-games",
3
+ "version": "0.3.0",
4
+ "description": "Capacitor 8 plugin for Google Play Games Services (Android) and Apple GameKit (iOS): sign-in, achievements, leaderboards, and saved games.",
5
+ "type": "module",
6
+ "main": "dist/plugin.cjs.js",
7
+ "module": "dist/esm/index.js",
8
+ "types": "dist/esm/index.d.ts",
9
+ "unpkg": "dist/plugin.js",
10
+ "files": [
11
+ "android/src/main/",
12
+ "android/build.gradle",
13
+ "android/consumer-rules.pro",
14
+ "android/proguard-rules.pro",
15
+ "dist/",
16
+ "ios/Plugin/",
17
+ "CapacitorPlayGames.podspec",
18
+ "Package.swift",
19
+ "CHANGELOG.md"
20
+ ],
21
+ "author": "modbender",
22
+ "license": "MIT",
23
+ "homepage": "https://github.com/modbender/capacitor-play-games#readme",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/modbender/capacitor-play-games.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/modbender/capacitor-play-games/issues"
30
+ },
31
+ "keywords": [
32
+ "capacitor",
33
+ "plugin",
34
+ "native",
35
+ "ios",
36
+ "android",
37
+ "play-games",
38
+ "google-play-games",
39
+ "pgs",
40
+ "gamekit",
41
+ "game-center",
42
+ "achievements",
43
+ "leaderboards",
44
+ "saved-games"
45
+ ],
46
+ "capacitor": {
47
+ "ios": {
48
+ "src": "ios"
49
+ },
50
+ "android": {
51
+ "src": "android"
52
+ }
53
+ },
54
+ "peerDependencies": {
55
+ "@capacitor/core": "^8.0.0"
56
+ },
57
+ "devDependencies": {
58
+ "@capacitor/cli": "^8.5.1",
59
+ "@capacitor/core": "^8.5.1",
60
+ "@capacitor/docgen": "^0.3.1",
61
+ "rimraf": "^6.1.3",
62
+ "rollup": "^4.63.1",
63
+ "typescript": "^7.0.2"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public",
67
+ "registry": "https://registry.npmjs.org/"
68
+ },
69
+ "engines": {
70
+ "node": ">=22"
71
+ },
72
+ "scripts": {
73
+ "clean": "rimraf dist",
74
+ "build": "rimraf dist && tsc && rollup -c rollup.config.mjs",
75
+ "docgen": "docgen --api PlayGamesPlugin --output-readme README.md",
76
+ "typecheck": "tsc --noEmit",
77
+ "verify": "bun run typecheck && bun run build",
78
+ "prepublishOnly": "bun run verify"
79
+ }
80
+ }