@idosgames/core 0.8.0 → 0.10.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 CHANGED
@@ -14,6 +14,118 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
14
14
 
15
15
  ## [Unreleased]
16
16
 
17
+ ## [0.10.0] - 2026-08-31
18
+
19
+ Where a player came from is now captured and delivered automatically, and playtime finally reaches
20
+ the publisher's analytics. Referral codes become short human codes with a server-built invite link.
21
+ Breaking — see below.
22
+
23
+ ### Breaking
24
+
25
+ - **`SpendRewardDefinition.Percent` is now `Rate`, and it is a FRACTION.** `0.05` means 5%, not
26
+ `5`. The old field never reached the server: it was dropped at deserialisation, so every kickback
27
+ rule written through it silently paid nobody. Render a percentage if you like, but keep the wire
28
+ value a fraction.
29
+ - **`UserData.patchReferralSubscription` takes the referrer's ID, not the code.** While a code and
30
+ a `UserID` were the same string, echoing the code back happened to be right; with a short code it
31
+ writes a code into an ID field. The activation response now carries `ReferrerUserID` separately
32
+ from `ReferralCode`.
33
+ - **A referral code is no longer a `UserID`.** `activateReferralCode` accepts only the short code
34
+ (or the invite link containing it); an internal identifier is rejected before any lookup. Nothing
35
+ had shipped on the old form, so there is no migration path and none is needed.
36
+
37
+ ### Added
38
+
39
+ - **`AcquisitionCapture`** — reads the launch URL and Telegram launch parameters when the client is
40
+ created (`utm_*`, `gclid`/`fbclid`/`ttclid`/`msclkid`/`yclid`, `?ref=`, `idos_click`,
41
+ `document.referrer`), keeps them across the login screen with a 30-day TTL, and attaches them to
42
+ whichever sign-in the player uses. Nothing to wire; nothing to call.
43
+ - **`PlaytimeTracker`** — counts playtime and reports it from login. Without it `addUsageTime` had
44
+ no caller at all, so a web title reported **zero** active users no matter how many people played:
45
+ DAU, WAU, MAU, stickiness and every retention cohort are derived from what it sends. Flush
46
+ interval and idle threshold match the Unity SDK so the two platforms count the same thing.
47
+ - **`IDosGamesSettings.appVersion`** — the build number that rides along with the acquisition
48
+ signal. The web has no `Application.version`, so the host has to pass it; without it no player is
49
+ attributed to a release and cohort-by-version is unanswerable.
50
+ - **`UserReferralState.Code`** — the player's own short invite code, minted lazily by the server on
51
+ the first `getUserState()`.
52
+ - **`UserReferralState.PendingActivationReward`** — set when the binding happened at login rather
53
+ than by typing a code; the debt is settled by the next `getUserState()`.
54
+ - **`UserReferralStateResponse.InviteUrl`** — a ready-made invite link built by the server. Do not
55
+ assemble one from a template: a client-built link is an open redirect.
56
+ - **`SpendRewardDefinition`** gains `Basis`, `MinSourceAmount`, `MaxRewardPerOperation` and
57
+ `Limits` — the anti-farm controls that existed on the server and could not be expressed here.
58
+ - **`PlatformAdapter.getReferrer?()`** — optional, so existing custom adapters keep compiling.
59
+
60
+ ### Fixed
61
+
62
+ - **Playtime was lost on every tab switch.** `visibilitychange` fires _after_ the state flips, so
63
+ the tracker asked the document, was told "hidden", and discarded the visible stretch that led up
64
+ to the switch — up to a full flush interval, every time.
65
+ - **Response schemas no longer strip unknown fields.** `zUserReferralStateResponse`,
66
+ `zActivateReferralCodeResponse`, `zClaimInviteRewardResponse` and
67
+ `zReferralDefinitionsResponse` were missing `.passthrough()`, so `InviteUrl` never reached the
68
+ client at all.
69
+
70
+ ## [0.9.0] - 2026-08-24
71
+
72
+ Signing in by e-mail becomes a **two-step flow** with a confirmation code, and a password now
73
+ belongs to a **title**, not to the platform. Friend requests gain the outgoing direction, and the
74
+ usage history is rolled up into periods. Breaking — see below.
75
+
76
+ ### Breaking
77
+
78
+ - **`registerWithEmail` no longer signs the player in.** It returns `EmailRegistrationOutcome`
79
+ (`codeTtlMinutes`, `resendCooldownSeconds`) instead of `ClientState`: the server only stores the
80
+ attempt and mails a code, and the player is created by `confirmEmailRegistration`. A screen that
81
+ waits for a session here waits forever. There is no flag to branch on — confirmation is platform
82
+ behaviour and no title can switch it off.
83
+ - **`EMAIL_ALREADY_EXISTS` is no longer returned by registration.** A free and a taken address
84
+ answer identically, so the form cannot be used to ask "does this person have an account here";
85
+ the owner of a taken address gets a "you already have an account" e-mail instead, without a code.
86
+ - **`loginWithPlatformToken` and `AuthenticationAction.LoginWithPlatformToken` removed.** The
87
+ backend stopped issuing the audience it accepted, so the endpoint was unreachable code. Use
88
+ `loginWithSsoCode` (see `beginSsoRedirect`).
89
+ - **`UserReferralState.FollowerIDs` removed.** It was an unbounded list in the hot player document
90
+ with no readers; the count that replaced it is server-sent, and the relationship itself lives on
91
+ the subscriber's side.
92
+
93
+ ### Added
94
+
95
+ - **`confirmEmailRegistration(email, code)`** — exchanges the mailed code for a session and creates
96
+ the player. Persists the credentials for `autoLogin()` exactly as `loginWithEmail` does; the
97
+ password comes from step one and is never sent again.
98
+ - **`resendVerificationCode(email)`** — mails a NEW code (only a hash is stored server-side, so the
99
+ previous one cannot be re-sent). Answers successfully whatever happened, with one exception worth
100
+ surfacing: `EMAIL_SENDER_NOT_CONFIGURED`, which is about the server's configuration and not about
101
+ the address.
102
+ - **`zEmailRegistrationResponse` / `EmailRegistrationResponse`** and
103
+ `AuthenticationRequest.VerificationCode`.
104
+ - **`client.social.getOutgoingRequests()`** + `SocialAction.GetOutgoingRequests` and the
105
+ `social:outgoingRequestsLoaded` event. Direction and status are separate axes, and the outgoing
106
+ list cannot be derived from a counter — without it the "Sent" mark on a card lived only until the
107
+ app restarted.
108
+ - **`UserSocialState.FriendsCount` / `IncomingCount` / `OutgoingCount`** — server-sent counters.
109
+ The friend lists themselves are now a local cache, filled by the matching `get*` call.
110
+ - **`UsagePeriodRecord`** — the rolled-up month/year record (`Seconds`, `Sessions`, `ActiveDays`,
111
+ `LongestSessionSeconds`, `ActiveHoursMask`, `FirstActiveDay`).
112
+ - **`BoardRollResponse.DiceValues`** — the individual dice behind the rolled total.
113
+
114
+ ### Changed
115
+
116
+ - ⚠ **A password now belongs to the TITLE.** The same address in two games means two independent
117
+ passwords, and resetting in one game does not change the login for the other. The reset e-mail
118
+ goes out through the **publisher's** SMTP, so a shared password would let one publisher's mail
119
+ server control access to every other publisher's game. The identity stays shared — Google, Apple
120
+ and the idosgames.com sign-in still recognise the same person everywhere; identity answers "who
121
+ is this", not "what proves they may sign in". Don't tell the player "the same password as in your
122
+ other game", and don't reuse stored credentials across titles.
123
+ - **Every outcome of `resetPassword` answers the same** — `INVALID_RESET_CODE` for an unknown
124
+ address, a code never requested, an expired code and a wrong code alike; an exhausted code
125
+ answers `RESET_CODE_ATTEMPTS_EXCEEDED`. The remaining-attempt count is gone from the response: it
126
+ confirmed the account existed and told an attacker how much room was left. Don't parse a number
127
+ out of `error`.
128
+
17
129
  ## [0.7.0] - 2026-08-10
18
130
 
19
131
  Real-money payment support. Every configured price becomes a **`PriceOptions` dictionary**, a
@@ -0,0 +1 @@
1
+ var n=class{getString(e){if(typeof localStorage>"u")return null;try{return localStorage.getItem(e)}catch{return null}}setString(e,t){if(!(typeof localStorage>"u"))try{localStorage.setItem(e,t);}catch{}}remove(e){if(!(typeof localStorage>"u"))try{localStorage.removeItem(e);}catch{}}},o=class{map=new Map;getString(e){return this.map.get(e)??null}setString(e,t){this.map.set(e,t);}remove(e){this.map.delete(e);}};function r(){return globalThis.Telegram?.WebApp??null}function l(){return crypto.randomUUID()}var s="idos_device_id",i=class{storage;constructor(e=new n){this.storage=e;}getPlatform(){return r()?.platform??"Web"}getFullURL(){return typeof location<"u"?location.href:""}getStartParameter(){let e=r()?.initDataUnsafe?.start_param;return typeof e=="string"?e:null}getReferrer(){if(typeof document>"u")return null;let e=document.referrer;return e&&e.length>0?e:null}getTelegramInitDataRaw(){let e=r()?.initData;return e&&e.length>0?e:null}getDeviceID(){let e=this.storage.getString(s);return e||(e=l(),this.storage.setString(s,e)),e}getDeviceModel(){return typeof navigator<"u"&&navigator.userAgent?navigator.userAgent:"Web"}shareLink(e){let t=r();if(t?.openTelegramLink){t.openTelegramLink(e);return}typeof window<"u"&&window.open(e,"_blank");}openInvoice(e){let t=r();if(t?.openInvoice){t.openInvoice(e);return}typeof window<"u"&&window.open(e,"_blank");}async showAd(e){let t=globalThis;typeof t.showAd=="function"&&await t.showAd(e);}async copyToClipboard(e){typeof navigator<"u"&&navigator.clipboard&&await navigator.clipboard.writeText(e);}};var p="idos_device_id",g=class{storage;constructor(e=new o){this.storage=e;}getPlatform(){return "Web"}getFullURL(){return ""}getStartParameter(){return null}getTelegramInitDataRaw(){return null}getDeviceID(){let e=this.storage.getString(p);return e||(e="noop-device",this.storage.setString(p,e)),e}getDeviceModel(){return "Noop"}shareLink(){}openInvoice(){}async showAd(){}async copyToClipboard(){}};export{n as a,o as b,r as c,l as d,i as e,g as f};