push notifications
This commit is contained in:
+146
-13
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
@@ -13,11 +14,13 @@ public static class SupportClient
|
||||
{
|
||||
public const string DefaultBaseUrl = "https://kkadmin.playpoolstudios.com";
|
||||
public const int MaxMessageLength = 4000;
|
||||
public const int MaxLogsLength = 2000000;
|
||||
|
||||
const string TokenPrefsKey = "WalkInvest.SupportPlayerToken";
|
||||
const string UserIdPrefsKey = "WalkInvest.SupportPlayerUserId";
|
||||
const string UsernamePrefsKey = "WalkInvest.SupportPlayerUsername";
|
||||
const string MessageLengthError = "Message must be between 1 and 4000 characters";
|
||||
const string LogsLengthError = "Logs must be at most 2000000 characters";
|
||||
const string PlayerConnectionError = "Please check your internet connection and try again.";
|
||||
const int RequestTimeoutSeconds = 20;
|
||||
|
||||
@@ -141,8 +144,6 @@ public static class SupportClient
|
||||
password = password ?? ""
|
||||
};
|
||||
string json = JsonUtility.ToJson(body);
|
||||
Debug.Log("[SupportClient] login user='" + body.username +
|
||||
"' passwordLength=" + body.password.Length);
|
||||
HttpOutcome http = null;
|
||||
yield return Request("POST", "/api/support/player/login", json, false, r => http = r);
|
||||
|
||||
@@ -157,14 +158,55 @@ public static class SupportClient
|
||||
onComplete?.Invoke(result);
|
||||
}
|
||||
|
||||
/// <summary>POST /api/support/player/devices. Stores this install's Firebase Installation ID.</summary>
|
||||
public static IEnumerator RegisterDevice(string fid, string platform, Action<SupportCallResult<SupportDeviceResponse>> onComplete)
|
||||
{
|
||||
yield return SendDevice("POST", fid, platform, onComplete);
|
||||
}
|
||||
|
||||
/// <summary>DELETE /api/support/player/devices. Drops this install's Firebase Installation ID.</summary>
|
||||
public static IEnumerator UnregisterDevice(string fid, Action<SupportCallResult<SupportDeviceResponse>> onComplete)
|
||||
{
|
||||
yield return SendDevice("DELETE", fid, "android", onComplete);
|
||||
}
|
||||
|
||||
static IEnumerator SendDevice(string method, string fid, string platform, Action<SupportCallResult<SupportDeviceResponse>> onComplete)
|
||||
{
|
||||
fid = fid != null ? fid.Trim() : "";
|
||||
if (string.IsNullOrEmpty(fid))
|
||||
{
|
||||
var invalid = Fail<SupportDeviceResponse>(400, "Invalid installation id");
|
||||
LogFailure("/api/support/player/devices", invalid);
|
||||
onComplete?.Invoke(invalid);
|
||||
yield break;
|
||||
}
|
||||
|
||||
string json = JsonUtility.ToJson(new DeviceRequest
|
||||
{
|
||||
fid = fid,
|
||||
platform = string.IsNullOrEmpty(platform) ? "android" : platform.Trim()
|
||||
});
|
||||
HttpOutcome http = null;
|
||||
yield return Request(method, "/api/support/player/devices", json, true, r => http = r);
|
||||
if (http != null && http.statusCode == 401 && IsSignedIn)
|
||||
ClearSession();
|
||||
|
||||
SupportCallResult<SupportDeviceResponse> result = ParseAck<SupportDeviceResponse>(http);
|
||||
LogFailure("/api/support/player/devices", result);
|
||||
onComplete?.Invoke(result);
|
||||
}
|
||||
|
||||
/// <summary>GET /api/support/player/tickets. Open tickets first, then newest message.</summary>
|
||||
public static IEnumerator ListTickets(Action<SupportCallResult<SupportTicketListResponse>> onComplete)
|
||||
{
|
||||
yield return SendAuthorized("/api/support/player/tickets", "GET", null, onComplete, NormalizeTickets);
|
||||
}
|
||||
|
||||
/// <summary>POST /api/support/player/tickets. Each call creates a new open ticket.</summary>
|
||||
public static IEnumerator CreateTicket(string category, string body, Action<SupportCallResult<SupportThread>> onComplete)
|
||||
/// <summary>
|
||||
/// POST /api/support/player/tickets. Each call creates a new open ticket.
|
||||
/// <paramref name="logs"/> is the Unity player log, sent on create only. Null or whitespace omits it.
|
||||
/// </summary>
|
||||
public static IEnumerator CreateTicket(string category, string body, string logs, Action<SupportCallResult<SupportThread>> onComplete)
|
||||
{
|
||||
category = category != null ? category.Trim() : "";
|
||||
if (!IsCategory(category))
|
||||
@@ -184,10 +226,71 @@ public static class SupportClient
|
||||
yield break;
|
||||
}
|
||||
|
||||
string json = JsonUtility.ToJson(new CreateTicketRequest { category = category, body = body });
|
||||
logs = string.IsNullOrWhiteSpace(logs) ? null : logs.Trim();
|
||||
if (logs != null && logs.Length > MaxLogsLength)
|
||||
{
|
||||
var invalid = Fail<SupportThread>(400, LogsLengthError);
|
||||
LogFailure("/api/support/player/tickets", invalid);
|
||||
onComplete?.Invoke(invalid);
|
||||
yield break;
|
||||
}
|
||||
|
||||
string json = JsonUtility.ToJson(new CreateTicketRequest { category = category, body = body, logs = logs });
|
||||
yield return SendAuthorized("/api/support/player/tickets", "POST", json, onComplete, NormalizeThread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads <see cref="Application.consoleLogPath"/> for a new ticket.
|
||||
/// Returns null when the log is missing or empty. Keeps the newest text when the file is longer than <see cref="MaxLogsLength"/>.
|
||||
/// </summary>
|
||||
public static string ReadUnityPlayerLog()
|
||||
{
|
||||
string path = Application.consoleLogPath;
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
bool truncated = false;
|
||||
long maxBytes = (long)MaxLogsLength * 4;
|
||||
if (stream.Length > maxBytes)
|
||||
{
|
||||
stream.Seek(-maxBytes, SeekOrigin.End);
|
||||
truncated = true;
|
||||
}
|
||||
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8, true, 4096, true))
|
||||
{
|
||||
string text;
|
||||
if (truncated)
|
||||
{
|
||||
string partial = reader.ReadLine();
|
||||
string rest = reader.ReadToEnd();
|
||||
text = string.IsNullOrEmpty(rest) ? partial : rest;
|
||||
}
|
||||
else
|
||||
{
|
||||
text = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return null;
|
||||
text = text.Trim();
|
||||
if (text.Length > MaxLogsLength)
|
||||
text = text.Substring(text.Length - MaxLogsLength);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning("[SupportClient] could not read Unity log: " + e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>GET /api/support/player/tickets/{ticketId}. Messages are oldest first.</summary>
|
||||
public static IEnumerator GetThread(string ticketId, Action<SupportCallResult<SupportThread>> onComplete)
|
||||
{
|
||||
@@ -279,17 +382,16 @@ public static class SupportClient
|
||||
}
|
||||
|
||||
string url = RootUrl() + path;
|
||||
Debug.Log("[SupportClient] " + method + " " + url);
|
||||
using (var request = new UnityWebRequest(url, method))
|
||||
{
|
||||
request.downloadHandler = new DownloadHandlerBuffer();
|
||||
request.timeout = RequestTimeoutSeconds;
|
||||
if (method == UnityWebRequest.kHttpVerbPOST)
|
||||
{
|
||||
byte[] raw = Encoding.UTF8.GetBytes(json ?? "{}");
|
||||
request.uploadHandler = new UploadHandlerRaw(raw);
|
||||
request.SetRequestHeader("Content-Type", "application/json");
|
||||
}
|
||||
request.timeout = json != null && json.Length > 64 * 1024 ? 120 : RequestTimeoutSeconds;
|
||||
if (json != null || method == UnityWebRequest.kHttpVerbPOST)
|
||||
{
|
||||
byte[] raw = Encoding.UTF8.GetBytes(json ?? "{}");
|
||||
request.uploadHandler = new UploadHandlerRaw(raw);
|
||||
request.SetRequestHeader("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
if (authorize)
|
||||
request.SetRequestHeader("Authorization", "Bearer " + Token);
|
||||
@@ -347,6 +449,23 @@ public static class SupportClient
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>2xx with an empty body still counts as success.</summary>
|
||||
static SupportCallResult<T> ParseAck<T>(HttpOutcome http) where T : class, new()
|
||||
{
|
||||
SupportCallResult<T> parsed = ParseOk<T>(http);
|
||||
if (parsed.ok || http == null || http.statusCode < 200 || http.statusCode >= 300)
|
||||
return parsed;
|
||||
if (parsed.error != "Invalid response from server")
|
||||
return parsed;
|
||||
|
||||
return new SupportCallResult<T>
|
||||
{
|
||||
ok = true,
|
||||
statusCode = http.statusCode,
|
||||
data = new T()
|
||||
};
|
||||
}
|
||||
|
||||
static SupportCallResult<T> Fail<T>(long statusCode, string error, int retryAfterSeconds = 0)
|
||||
{
|
||||
return new SupportCallResult<T>
|
||||
@@ -467,6 +586,7 @@ public static class SupportClient
|
||||
{
|
||||
public string category;
|
||||
public string body;
|
||||
public string logs;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -475,6 +595,13 @@ public static class SupportClient
|
||||
public string body;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class DeviceRequest
|
||||
{
|
||||
public string fid;
|
||||
public string platform;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class SupportErrorBody
|
||||
{
|
||||
@@ -519,6 +646,7 @@ public class SupportTicket
|
||||
public string updatedAt;
|
||||
public string lastMessageAt;
|
||||
public string lastMessagePreview;
|
||||
public bool hasLogs;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -544,3 +672,8 @@ public class SupportTicketListResponse
|
||||
{
|
||||
public SupportTicket[] tickets;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class SupportDeviceResponse
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user