feature: Edgegap Plugin V2 (#3692)

* Edgegap Plugin V2 from https://github.com/dylanh724/edgegap-unity-plugin-v2/tree/feat/plugin-v2.2

* fix things

* fix warnings

* fix u2020 support

* fix nre

* fix logo hardcoded path

* fix clipboard image hardcoded path and remove fonts

* syntax

* fix unity 2019/2020 breaking all editor UIs

* fix NRE on first open due to old window

* fix 2019 asserts

* link to our documentation

* disable warning

* fix unity 2019 unsupported syntax error

* Mirror.Hosting.asmdef: fix missing ref

* fix: remove all fonts to avoid the errors

* readme: two click hosting

* readme

* better

* fix 2019 errors

* fix U2020 async warning

---------

Co-authored-by: mischa <16416509+vis2k@users.noreply.github.com>
This commit is contained in:
mischa 2023-12-09 11:02:01 +01:00 committed by GitHub
parent 608429c314
commit 67f32f2d90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 5956 additions and 129 deletions

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 14024ce6d2e64d5ba58ab20409ac648f
timeCreated: 1701785018

View File

@ -0,0 +1,704 @@
// MIRROR CHANGE: drop in Codice.Utils HttpUtility subset to not depend on Unity's plastic scm package
// SOURCE: Unity Plastic SCM package
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace Edgegap.Codice.Utils // MIRROR CHANGE: namespace Edgegap.* to not collide if anyone has Plastic SCM installed already
{
public class HttpEncoder
{
private static char[] hexChars = "0123456789abcdef".ToCharArray();
private static object entitiesLock = new object();
private static SortedDictionary<string, char> entities;
private static HttpEncoder defaultEncoder = new HttpEncoder();
private static HttpEncoder currentEncoder = HttpEncoder.defaultEncoder;
private static IDictionary<string, char> Entities
{
get
{
lock (HttpEncoder.entitiesLock)
{
if (HttpEncoder.entities == null)
HttpEncoder.InitEntities();
return (IDictionary<string, char>) HttpEncoder.entities;
}
}
}
public static HttpEncoder Current
{
get => HttpEncoder.currentEncoder;
set => HttpEncoder.currentEncoder = value != null ? value : throw new ArgumentNullException(nameof (value));
}
public static HttpEncoder Default => HttpEncoder.defaultEncoder;
protected internal virtual void HeaderNameValueEncode(
string headerName,
string headerValue,
out string encodedHeaderName,
out string encodedHeaderValue)
{
encodedHeaderName = !string.IsNullOrEmpty(headerName) ? HttpEncoder.EncodeHeaderString(headerName) : headerName;
if (string.IsNullOrEmpty(headerValue))
encodedHeaderValue = headerValue;
else
encodedHeaderValue = HttpEncoder.EncodeHeaderString(headerValue);
}
private static void StringBuilderAppend(string s, ref StringBuilder sb)
{
if (sb == null)
sb = new StringBuilder(s);
else
sb.Append(s);
}
private static string EncodeHeaderString(string input)
{
StringBuilder sb = (StringBuilder) null;
for (int index = 0; index < input.Length; ++index)
{
char ch = input[index];
if (ch < ' ' && ch != '\t' || ch == '\u007F')
HttpEncoder.StringBuilderAppend(string.Format("%{0:x2}", (object) (int) ch), ref sb);
}
return sb != null ? sb.ToString() : input;
}
protected internal virtual void HtmlAttributeEncode(string value, TextWriter output)
{
if (output == null)
throw new ArgumentNullException(nameof (output));
if (string.IsNullOrEmpty(value))
return;
output.Write(HttpEncoder.HtmlAttributeEncode(value));
}
protected internal virtual void HtmlDecode(string value, TextWriter output)
{
if (output == null)
throw new ArgumentNullException(nameof (output));
output.Write(HttpEncoder.HtmlDecode(value));
}
protected internal virtual void HtmlEncode(string value, TextWriter output)
{
if (output == null)
throw new ArgumentNullException(nameof (output));
output.Write(HttpEncoder.HtmlEncode(value));
}
protected internal virtual byte[] UrlEncode(byte[] bytes, int offset, int count) => HttpEncoder.UrlEncodeToBytes(bytes, offset, count);
protected internal virtual string UrlPathEncode(string value)
{
if (string.IsNullOrEmpty(value))
return value;
MemoryStream result = new MemoryStream();
int length = value.Length;
for (int index = 0; index < length; ++index)
HttpEncoder.UrlPathEncodeChar(value[index], (Stream) result);
return Encoding.ASCII.GetString(result.ToArray());
}
internal static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count)
{
int num1 = bytes != null ? bytes.Length : throw new ArgumentNullException(nameof (bytes));
if (num1 == 0)
return new byte[0];
if (offset < 0 || offset >= num1)
throw new ArgumentOutOfRangeException(nameof (offset));
if (count < 0 || count > num1 - offset)
throw new ArgumentOutOfRangeException(nameof (count));
MemoryStream result = new MemoryStream(count);
int num2 = offset + count;
for (int index = offset; index < num2; ++index)
HttpEncoder.UrlEncodeChar((char) bytes[index], (Stream) result, false);
return result.ToArray();
}
internal static string HtmlEncode(string s)
{
switch (s)
{
case "":
return string.Empty;
case null:
return (string) null;
default:
bool flag = false;
for (int index = 0; index < s.Length; ++index)
{
char ch = s[index];
if (ch == '&' || ch == '"' || ch == '<' || ch == '>' || ch > '\u009F' || ch == '\'')
{
flag = true;
break;
}
}
if (!flag)
return s;
StringBuilder stringBuilder = new StringBuilder();
int length = s.Length;
for (int index = 0; index < length; ++index)
{
char ch = s[index];
switch (ch)
{
case '"':
stringBuilder.Append("&quot;");
break;
case '&':
stringBuilder.Append("&amp;");
break;
case '\'':
stringBuilder.Append("&#39;");
break;
case '<':
stringBuilder.Append("&lt;");
break;
case '>':
stringBuilder.Append("&gt;");
break;
case '':
stringBuilder.Append("&#65308;");
break;
case '':
stringBuilder.Append("&#65310;");
break;
default:
if (ch > '\u009F' && ch < 'Ā')
{
stringBuilder.Append("&#");
stringBuilder.Append(((int) ch).ToString((IFormatProvider) CultureInfo.InvariantCulture));
stringBuilder.Append(";");
break;
}
stringBuilder.Append(ch);
break;
}
}
return stringBuilder.ToString();
}
}
internal static string HtmlAttributeEncode(string s)
{
if (string.IsNullOrEmpty(s))
return string.Empty;
bool flag = false;
for (int index = 0; index < s.Length; ++index)
{
char ch = s[index];
int num;
switch (ch)
{
case '"':
case '&':
case '<':
num = 0;
break;
default:
num = ch != '\'' ? 1 : 0;
break;
}
if (num == 0)
{
flag = true;
break;
}
}
if (!flag)
return s;
StringBuilder stringBuilder = new StringBuilder();
int length = s.Length;
for (int index = 0; index < length; ++index)
{
char ch = s[index];
switch (ch)
{
case '"':
stringBuilder.Append("&quot;");
break;
case '&':
stringBuilder.Append("&amp;");
break;
case '\'':
stringBuilder.Append("&#39;");
break;
case '<':
stringBuilder.Append("&lt;");
break;
default:
stringBuilder.Append(ch);
break;
}
}
return stringBuilder.ToString();
}
internal static string HtmlDecode(string s)
{
switch (s)
{
case "":
return string.Empty;
case null:
return (string) null;
default:
if (s.IndexOf('&') == -1)
return s;
StringBuilder stringBuilder1 = new StringBuilder();
StringBuilder stringBuilder2 = new StringBuilder();
StringBuilder stringBuilder3 = new StringBuilder();
int length = s.Length;
int num1 = 0;
int num2 = 0;
bool flag1 = false;
bool flag2 = false;
for (int index = 0; index < length; ++index)
{
char ch = s[index];
if (num1 == 0)
{
if (ch == '&')
{
stringBuilder2.Append(ch);
stringBuilder1.Append(ch);
num1 = 1;
}
else
stringBuilder3.Append(ch);
}
else if (ch == '&')
{
num1 = 1;
if (flag2)
{
stringBuilder2.Append(num2.ToString((IFormatProvider) CultureInfo.InvariantCulture));
flag2 = false;
}
stringBuilder3.Append(stringBuilder2.ToString());
stringBuilder2.Length = 0;
stringBuilder2.Append('&');
}
else
{
switch (num1)
{
case 1:
if (ch == ';')
{
num1 = 0;
stringBuilder3.Append(stringBuilder2.ToString());
stringBuilder3.Append(ch);
stringBuilder2.Length = 0;
break;
}
num2 = 0;
flag1 = false;
num1 = ch == '#' ? 3 : 2;
stringBuilder2.Append(ch);
stringBuilder1.Append(ch);
break;
case 2:
stringBuilder2.Append(ch);
if (ch == ';')
{
string str = stringBuilder2.ToString();
if (str.Length > 1 && HttpEncoder.Entities.ContainsKey(str.Substring(1, str.Length - 2)))
str = HttpEncoder.Entities[str.Substring(1, str.Length - 2)].ToString();
stringBuilder3.Append(str);
num1 = 0;
stringBuilder2.Length = 0;
stringBuilder1.Length = 0;
break;
}
break;
case 3:
if (ch == ';')
{
if (num2 == 0)
stringBuilder3.Append(stringBuilder1.ToString() + ";");
else if (num2 > (int) ushort.MaxValue)
{
stringBuilder3.Append("&#");
stringBuilder3.Append(num2.ToString((IFormatProvider) CultureInfo.InvariantCulture));
stringBuilder3.Append(";");
}
else
stringBuilder3.Append((char) num2);
num1 = 0;
stringBuilder2.Length = 0;
stringBuilder1.Length = 0;
flag2 = false;
}
else if (flag1 && Uri.IsHexDigit(ch))
{
num2 = num2 * 16 + Uri.FromHex(ch);
flag2 = true;
stringBuilder1.Append(ch);
}
else if (char.IsDigit(ch))
{
num2 = num2 * 10 + ((int) ch - 48);
flag2 = true;
stringBuilder1.Append(ch);
}
else if (num2 == 0 && (ch == 'x' || ch == 'X'))
{
flag1 = true;
stringBuilder1.Append(ch);
}
else
{
num1 = 2;
if (flag2)
{
stringBuilder2.Append(num2.ToString((IFormatProvider) CultureInfo.InvariantCulture));
flag2 = false;
}
stringBuilder2.Append(ch);
}
break;
}
}
}
if (stringBuilder2.Length > 0)
stringBuilder3.Append(stringBuilder2.ToString());
else if (flag2)
stringBuilder3.Append(num2.ToString((IFormatProvider) CultureInfo.InvariantCulture));
return stringBuilder3.ToString();
}
}
internal static bool NotEncoded(char c) => c == '!' || c == '(' || c == ')' || c == '*' || c == '-' || c == '.' || c == '_';
internal static void UrlEncodeChar(char c, Stream result, bool isUnicode)
{
if (c > 'ÿ')
{
int num = (int) c;
result.WriteByte((byte) 37);
result.WriteByte((byte) 117);
int index1 = num >> 12;
result.WriteByte((byte) HttpEncoder.hexChars[index1]);
int index2 = num >> 8 & 15;
result.WriteByte((byte) HttpEncoder.hexChars[index2]);
int index3 = num >> 4 & 15;
result.WriteByte((byte) HttpEncoder.hexChars[index3]);
int index4 = num & 15;
result.WriteByte((byte) HttpEncoder.hexChars[index4]);
}
else if (c > ' ' && HttpEncoder.NotEncoded(c))
result.WriteByte((byte) c);
else if (c == ' ')
result.WriteByte((byte) 43);
else if (c < '0' || c < 'A' && c > '9' || c > 'Z' && c < 'a' || c > 'z')
{
if (isUnicode && c > '\u007F')
{
result.WriteByte((byte) 37);
result.WriteByte((byte) 117);
result.WriteByte((byte) 48);
result.WriteByte((byte) 48);
}
else
result.WriteByte((byte) 37);
int index5 = (int) c >> 4;
result.WriteByte((byte) HttpEncoder.hexChars[index5]);
int index6 = (int) c & 15;
result.WriteByte((byte) HttpEncoder.hexChars[index6]);
}
else
result.WriteByte((byte) c);
}
internal static void UrlPathEncodeChar(char c, Stream result)
{
if (c < '!' || c > '~')
{
byte[] bytes = Encoding.UTF8.GetBytes(c.ToString());
for (int index1 = 0; index1 < bytes.Length; ++index1)
{
result.WriteByte((byte) 37);
int index2 = (int) bytes[index1] >> 4;
result.WriteByte((byte) HttpEncoder.hexChars[index2]);
int index3 = (int) bytes[index1] & 15;
result.WriteByte((byte) HttpEncoder.hexChars[index3]);
}
}
else if (c == ' ')
{
result.WriteByte((byte) 37);
result.WriteByte((byte) 50);
result.WriteByte((byte) 48);
}
else
result.WriteByte((byte) c);
}
private static void InitEntities()
{
HttpEncoder.entities = new SortedDictionary<string, char>((IComparer<string>) StringComparer.Ordinal);
HttpEncoder.entities.Add("nbsp", ' ');
HttpEncoder.entities.Add("iexcl", '¡');
HttpEncoder.entities.Add("cent", '¢');
HttpEncoder.entities.Add("pound", '£');
HttpEncoder.entities.Add("curren", '¤');
HttpEncoder.entities.Add("yen", '¥');
HttpEncoder.entities.Add("brvbar", '¦');
HttpEncoder.entities.Add("sect", '§');
HttpEncoder.entities.Add("uml", '¨');
HttpEncoder.entities.Add("copy", '©');
HttpEncoder.entities.Add("ordf", 'ª');
HttpEncoder.entities.Add("laquo", '«');
HttpEncoder.entities.Add("not", '¬');
HttpEncoder.entities.Add("shy", '\u00AD');
HttpEncoder.entities.Add("reg", '®');
HttpEncoder.entities.Add("macr", '¯');
HttpEncoder.entities.Add("deg", '°');
HttpEncoder.entities.Add("plusmn", '±');
HttpEncoder.entities.Add("sup2", '\u00B2');
HttpEncoder.entities.Add("sup3", '\u00B3');
HttpEncoder.entities.Add("acute", '´');
HttpEncoder.entities.Add("micro", 'µ');
HttpEncoder.entities.Add("para", '¶');
HttpEncoder.entities.Add("middot", '·');
HttpEncoder.entities.Add("cedil", '¸');
HttpEncoder.entities.Add("sup1", '\u00B9');
HttpEncoder.entities.Add("ordm", 'º');
HttpEncoder.entities.Add("raquo", '»');
HttpEncoder.entities.Add("frac14", '\u00BC');
HttpEncoder.entities.Add("frac12", '\u00BD');
HttpEncoder.entities.Add("frac34", '\u00BE');
HttpEncoder.entities.Add("iquest", '¿');
HttpEncoder.entities.Add("Agrave", 'À');
HttpEncoder.entities.Add("Aacute", 'Á');
HttpEncoder.entities.Add("Acirc", 'Â');
HttpEncoder.entities.Add("Atilde", 'Ã');
HttpEncoder.entities.Add("Auml", 'Ä');
HttpEncoder.entities.Add("Aring", 'Å');
HttpEncoder.entities.Add("AElig", 'Æ');
HttpEncoder.entities.Add("Ccedil", 'Ç');
HttpEncoder.entities.Add("Egrave", 'È');
HttpEncoder.entities.Add("Eacute", 'É');
HttpEncoder.entities.Add("Ecirc", 'Ê');
HttpEncoder.entities.Add("Euml", 'Ë');
HttpEncoder.entities.Add("Igrave", 'Ì');
HttpEncoder.entities.Add("Iacute", 'Í');
HttpEncoder.entities.Add("Icirc", 'Î');
HttpEncoder.entities.Add("Iuml", 'Ï');
HttpEncoder.entities.Add("ETH", 'Ð');
HttpEncoder.entities.Add("Ntilde", 'Ñ');
HttpEncoder.entities.Add("Ograve", 'Ò');
HttpEncoder.entities.Add("Oacute", 'Ó');
HttpEncoder.entities.Add("Ocirc", 'Ô');
HttpEncoder.entities.Add("Otilde", 'Õ');
HttpEncoder.entities.Add("Ouml", 'Ö');
HttpEncoder.entities.Add("times", '×');
HttpEncoder.entities.Add("Oslash", 'Ø');
HttpEncoder.entities.Add("Ugrave", 'Ù');
HttpEncoder.entities.Add("Uacute", 'Ú');
HttpEncoder.entities.Add("Ucirc", 'Û');
HttpEncoder.entities.Add("Uuml", 'Ü');
HttpEncoder.entities.Add("Yacute", 'Ý');
HttpEncoder.entities.Add("THORN", 'Þ');
HttpEncoder.entities.Add("szlig", 'ß');
HttpEncoder.entities.Add("agrave", 'à');
HttpEncoder.entities.Add("aacute", 'á');
HttpEncoder.entities.Add("acirc", 'â');
HttpEncoder.entities.Add("atilde", 'ã');
HttpEncoder.entities.Add("auml", 'ä');
HttpEncoder.entities.Add("aring", 'å');
HttpEncoder.entities.Add("aelig", 'æ');
HttpEncoder.entities.Add("ccedil", 'ç');
HttpEncoder.entities.Add("egrave", 'è');
HttpEncoder.entities.Add("eacute", 'é');
HttpEncoder.entities.Add("ecirc", 'ê');
HttpEncoder.entities.Add("euml", 'ë');
HttpEncoder.entities.Add("igrave", 'ì');
HttpEncoder.entities.Add("iacute", 'í');
HttpEncoder.entities.Add("icirc", 'î');
HttpEncoder.entities.Add("iuml", 'ï');
HttpEncoder.entities.Add("eth", 'ð');
HttpEncoder.entities.Add("ntilde", 'ñ');
HttpEncoder.entities.Add("ograve", 'ò');
HttpEncoder.entities.Add("oacute", 'ó');
HttpEncoder.entities.Add("ocirc", 'ô');
HttpEncoder.entities.Add("otilde", 'õ');
HttpEncoder.entities.Add("ouml", 'ö');
HttpEncoder.entities.Add("divide", '÷');
HttpEncoder.entities.Add("oslash", 'ø');
HttpEncoder.entities.Add("ugrave", 'ù');
HttpEncoder.entities.Add("uacute", 'ú');
HttpEncoder.entities.Add("ucirc", 'û');
HttpEncoder.entities.Add("uuml", 'ü');
HttpEncoder.entities.Add("yacute", 'ý');
HttpEncoder.entities.Add("thorn", 'þ');
HttpEncoder.entities.Add("yuml", 'ÿ');
HttpEncoder.entities.Add("fnof", 'ƒ');
HttpEncoder.entities.Add("Alpha", 'Α');
HttpEncoder.entities.Add("Beta", 'Β');
HttpEncoder.entities.Add("Gamma", 'Γ');
HttpEncoder.entities.Add("Delta", 'Δ');
HttpEncoder.entities.Add("Epsilon", 'Ε');
HttpEncoder.entities.Add("Zeta", 'Ζ');
HttpEncoder.entities.Add("Eta", 'Η');
HttpEncoder.entities.Add("Theta", 'Θ');
HttpEncoder.entities.Add("Iota", 'Ι');
HttpEncoder.entities.Add("Kappa", 'Κ');
HttpEncoder.entities.Add("Lambda", 'Λ');
HttpEncoder.entities.Add("Mu", 'Μ');
HttpEncoder.entities.Add("Nu", 'Ν');
HttpEncoder.entities.Add("Xi", 'Ξ');
HttpEncoder.entities.Add("Omicron", 'Ο');
HttpEncoder.entities.Add("Pi", 'Π');
HttpEncoder.entities.Add("Rho", 'Ρ');
HttpEncoder.entities.Add("Sigma", 'Σ');
HttpEncoder.entities.Add("Tau", 'Τ');
HttpEncoder.entities.Add("Upsilon", 'Υ');
HttpEncoder.entities.Add("Phi", 'Φ');
HttpEncoder.entities.Add("Chi", 'Χ');
HttpEncoder.entities.Add("Psi", 'Ψ');
HttpEncoder.entities.Add("Omega", 'Ω');
HttpEncoder.entities.Add("alpha", 'α');
HttpEncoder.entities.Add("beta", 'β');
HttpEncoder.entities.Add("gamma", 'γ');
HttpEncoder.entities.Add("delta", 'δ');
HttpEncoder.entities.Add("epsilon", 'ε');
HttpEncoder.entities.Add("zeta", 'ζ');
HttpEncoder.entities.Add("eta", 'η');
HttpEncoder.entities.Add("theta", 'θ');
HttpEncoder.entities.Add("iota", 'ι');
HttpEncoder.entities.Add("kappa", 'κ');
HttpEncoder.entities.Add("lambda", 'λ');
HttpEncoder.entities.Add("mu", 'μ');
HttpEncoder.entities.Add("nu", 'ν');
HttpEncoder.entities.Add("xi", 'ξ');
HttpEncoder.entities.Add("omicron", 'ο');
HttpEncoder.entities.Add("pi", 'π');
HttpEncoder.entities.Add("rho", 'ρ');
HttpEncoder.entities.Add("sigmaf", 'ς');
HttpEncoder.entities.Add("sigma", 'σ');
HttpEncoder.entities.Add("tau", 'τ');
HttpEncoder.entities.Add("upsilon", 'υ');
HttpEncoder.entities.Add("phi", 'φ');
HttpEncoder.entities.Add("chi", 'χ');
HttpEncoder.entities.Add("psi", 'ψ');
HttpEncoder.entities.Add("omega", 'ω');
HttpEncoder.entities.Add("thetasym", 'ϑ');
HttpEncoder.entities.Add("upsih", 'ϒ');
HttpEncoder.entities.Add("piv", 'ϖ');
HttpEncoder.entities.Add("bull", '•');
HttpEncoder.entities.Add("hellip", '…');
HttpEncoder.entities.Add("prime", '');
HttpEncoder.entities.Add("Prime", '″');
HttpEncoder.entities.Add("oline", '‾');
HttpEncoder.entities.Add("frasl", '');
HttpEncoder.entities.Add("weierp", '℘');
HttpEncoder.entities.Add("image", '');
HttpEncoder.entities.Add("real", '');
HttpEncoder.entities.Add("trade", '™');
HttpEncoder.entities.Add("alefsym", 'ℵ');
HttpEncoder.entities.Add("larr", '←');
HttpEncoder.entities.Add("uarr", '↑');
HttpEncoder.entities.Add("rarr", '→');
HttpEncoder.entities.Add("darr", '↓');
HttpEncoder.entities.Add("harr", '↔');
HttpEncoder.entities.Add("crarr", '↵');
HttpEncoder.entities.Add("lArr", '⇐');
HttpEncoder.entities.Add("uArr", '⇑');
HttpEncoder.entities.Add("rArr", '⇒');
HttpEncoder.entities.Add("dArr", '⇓');
HttpEncoder.entities.Add("hArr", '⇔');
HttpEncoder.entities.Add("forall", '∀');
HttpEncoder.entities.Add("part", '∂');
HttpEncoder.entities.Add("exist", '∃');
HttpEncoder.entities.Add("empty", '∅');
HttpEncoder.entities.Add("nabla", '∇');
HttpEncoder.entities.Add("isin", '∈');
HttpEncoder.entities.Add("notin", '∉');
HttpEncoder.entities.Add("ni", '∋');
HttpEncoder.entities.Add("prod", '∏');
HttpEncoder.entities.Add("sum", '∑');
HttpEncoder.entities.Add("minus", '');
HttpEncoder.entities.Add("lowast", '');
HttpEncoder.entities.Add("radic", '√');
HttpEncoder.entities.Add("prop", '∝');
HttpEncoder.entities.Add("infin", '∞');
HttpEncoder.entities.Add("ang", '∠');
HttpEncoder.entities.Add("and", '∧');
HttpEncoder.entities.Add("or", '');
HttpEncoder.entities.Add("cap", '∩');
HttpEncoder.entities.Add("cup", '');
HttpEncoder.entities.Add("int", '∫');
HttpEncoder.entities.Add("there4", '∴');
HttpEncoder.entities.Add("sim", '');
HttpEncoder.entities.Add("cong", '≅');
HttpEncoder.entities.Add("asymp", '≈');
HttpEncoder.entities.Add("ne", '≠');
HttpEncoder.entities.Add("equiv", '≡');
HttpEncoder.entities.Add("le", '≤');
HttpEncoder.entities.Add("ge", '≥');
HttpEncoder.entities.Add("sub", '⊂');
HttpEncoder.entities.Add("sup", '⊃');
HttpEncoder.entities.Add("nsub", '⊄');
HttpEncoder.entities.Add("sube", '⊆');
HttpEncoder.entities.Add("supe", '⊇');
HttpEncoder.entities.Add("oplus", '⊕');
HttpEncoder.entities.Add("otimes", '⊗');
HttpEncoder.entities.Add("perp", '⊥');
HttpEncoder.entities.Add("sdot", '⋅');
HttpEncoder.entities.Add("lceil", '⌈');
HttpEncoder.entities.Add("rceil", '⌉');
HttpEncoder.entities.Add("lfloor", '⌊');
HttpEncoder.entities.Add("rfloor", '⌋');
HttpEncoder.entities.Add("lang", '〈');
HttpEncoder.entities.Add("rang", '〉');
HttpEncoder.entities.Add("loz", '◊');
HttpEncoder.entities.Add("spades", '♠');
HttpEncoder.entities.Add("clubs", '♣');
HttpEncoder.entities.Add("hearts", '♥');
HttpEncoder.entities.Add("diams", '♦');
HttpEncoder.entities.Add("quot", '"');
HttpEncoder.entities.Add("amp", '&');
HttpEncoder.entities.Add("lt", '<');
HttpEncoder.entities.Add("gt", '>');
HttpEncoder.entities.Add("OElig", 'Œ');
HttpEncoder.entities.Add("oelig", 'œ');
HttpEncoder.entities.Add("Scaron", 'Š');
HttpEncoder.entities.Add("scaron", 'š');
HttpEncoder.entities.Add("Yuml", 'Ÿ');
HttpEncoder.entities.Add("circ", 'ˆ');
HttpEncoder.entities.Add("tilde", '˜');
HttpEncoder.entities.Add("ensp", '');
HttpEncoder.entities.Add("emsp", '');
HttpEncoder.entities.Add("thinsp", '');
HttpEncoder.entities.Add("zwnj", '\u200C');
HttpEncoder.entities.Add("zwj", '\u200D');
HttpEncoder.entities.Add("lrm", '\u200E');
HttpEncoder.entities.Add("rlm", '\u200F');
HttpEncoder.entities.Add("ndash", '');
HttpEncoder.entities.Add("mdash", '—');
HttpEncoder.entities.Add("lsquo", '');
HttpEncoder.entities.Add("rsquo", '');
HttpEncoder.entities.Add("sbquo", '');
HttpEncoder.entities.Add("ldquo", '“');
HttpEncoder.entities.Add("rdquo", '”');
HttpEncoder.entities.Add("bdquo", '„');
HttpEncoder.entities.Add("dagger", '†');
HttpEncoder.entities.Add("Dagger", '‡');
HttpEncoder.entities.Add("permil", '‰');
HttpEncoder.entities.Add("lsaquo", '');
HttpEncoder.entities.Add("rsaquo", '');
HttpEncoder.entities.Add("euro", '€');
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3c9f699c227f48e381db521abe59c2e1
timeCreated: 1701789490

View File

@ -0,0 +1,230 @@
// MIRROR CHANGE: drop in Codice.Utils HttpUtility subset to not depend on Unity's plastic scm package
// SOURCE: Unity Plastic SCM package
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
namespace Edgegap.Codice.Utils // MIRROR CHANGE: namespace Edgegap.* to not collide if anyone has Plastic SCM installed already
{
public sealed class HttpUtility
{
private static void WriteCharBytes(IList buf, char ch, Encoding e)
{
if (ch > 'ÿ')
{
Encoding encoding = e;
char[] chars = new char[1]{ ch };
foreach (byte num in encoding.GetBytes(chars))
buf.Add((object) num);
}
else
buf.Add((object) (byte) ch);
}
public static string UrlDecode(string s, Encoding e)
{
if (null == s)
return (string) null;
if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1)
return s;
if (e == null)
e = Encoding.UTF8;
long length = (long) s.Length;
List<byte> buf = new List<byte>();
for (int index = 0; (long) index < length; ++index)
{
char ch = s[index];
if (ch == '%' && (long) (index + 2) < length && s[index + 1] != '%')
{
if (s[index + 1] == 'u' && (long) (index + 5) < length)
{
int num = HttpUtility.GetChar(s, index + 2, 4);
if (num != -1)
{
HttpUtility.WriteCharBytes((IList) buf, (char) num, e);
index += 5;
}
else
HttpUtility.WriteCharBytes((IList) buf, '%', e);
}
else
{
int num;
if ((num = HttpUtility.GetChar(s, index + 1, 2)) != -1)
{
HttpUtility.WriteCharBytes((IList) buf, (char) num, e);
index += 2;
}
else
HttpUtility.WriteCharBytes((IList) buf, '%', e);
}
}
else if (ch == '+')
HttpUtility.WriteCharBytes((IList) buf, ' ', e);
else
HttpUtility.WriteCharBytes((IList) buf, ch, e);
}
byte[] array = buf.ToArray();
return e.GetString(array);
}
private static int GetInt(byte b)
{
char ch = (char) b;
if (ch >= '0' && ch <= '9')
return (int) ch - 48;
if (ch >= 'a' && ch <= 'f')
return (int) ch - 97 + 10;
return ch >= 'A' && ch <= 'F' ? (int) ch - 65 + 10 : -1;
}
private static int GetChar(string str, int offset, int length)
{
int num1 = 0;
int num2 = length + offset;
for (int index = offset; index < num2; ++index)
{
char b = str[index];
if (b > '\u007F')
return -1;
int num3 = HttpUtility.GetInt((byte) b);
if (num3 == -1)
return -1;
num1 = (num1 << 4) + num3;
}
return num1;
}
public static string UrlEncode(string str) => HttpUtility.UrlEncode(str, Encoding.UTF8);
public static string UrlEncode(string s, Encoding Enc)
{
if (s == null)
return (string) null;
if (s == string.Empty)
return string.Empty;
bool flag = false;
int length = s.Length;
for (int index = 0; index < length; ++index)
{
char c = s[index];
if ((c < '0' || c < 'A' && c > '9' || c > 'Z' && c < 'a' || c > 'z') && !HttpEncoder.NotEncoded(c))
{
flag = true;
break;
}
}
if (!flag)
return s;
byte[] bytes1 = new byte[Enc.GetMaxByteCount(s.Length)];
int bytes2 = Enc.GetBytes(s, 0, s.Length, bytes1, 0);
return Encoding.ASCII.GetString(HttpUtility.UrlEncodeToBytes(bytes1, 0, bytes2));
}
public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count) => bytes == null ? (byte[]) null : HttpEncoder.Current.UrlEncode(bytes, offset, count);
public static string HtmlDecode(string s)
{
if (s == null)
return (string) null;
using (StringWriter output = new StringWriter())
{
HttpEncoder.Current.HtmlDecode(s, (TextWriter) output);
return output.ToString();
}
}
public static NameValueCollection ParseQueryString(string query) => HttpUtility.ParseQueryString(query, Encoding.UTF8);
public static NameValueCollection ParseQueryString(
string query,
Encoding encoding)
{
if (query == null)
throw new ArgumentNullException(nameof (query));
if (encoding == null)
throw new ArgumentNullException(nameof (encoding));
if (query.Length == 0 || query.Length == 1 && query[0] == '?')
return (NameValueCollection) new HttpUtility.HttpQSCollection();
if (query[0] == '?')
query = query.Substring(1);
NameValueCollection result = (NameValueCollection) new HttpUtility.HttpQSCollection();
HttpUtility.ParseQueryString(query, encoding, result);
return result;
}
internal static void ParseQueryString(
string query,
Encoding encoding,
NameValueCollection result)
{
if (query.Length == 0)
return;
string str1 = HttpUtility.HtmlDecode(query);
int length = str1.Length;
int num1 = 0;
bool flag = true;
while (num1 <= length)
{
int startIndex = -1;
int num2 = -1;
for (int index = num1; index < length; ++index)
{
if (startIndex == -1 && str1[index] == '=')
startIndex = index + 1;
else if (str1[index] == '&')
{
num2 = index;
break;
}
}
if (flag)
{
flag = false;
if (str1[num1] == '?')
++num1;
}
string name;
if (startIndex == -1)
{
name = (string) null;
startIndex = num1;
}
else
name = HttpUtility.UrlDecode(str1.Substring(num1, startIndex - num1 - 1), encoding);
if (num2 < 0)
{
num1 = -1;
num2 = str1.Length;
}
else
num1 = num2 + 1;
string str2 = HttpUtility.UrlDecode(str1.Substring(startIndex, num2 - startIndex), encoding);
result.Add(name, str2);
if (num1 == -1)
break;
}
}
private sealed class HttpQSCollection : NameValueCollection
{
public override string ToString()
{
int count = this.Count;
if (count == 0)
return "";
StringBuilder stringBuilder = new StringBuilder();
string[] allKeys = this.AllKeys;
for (int index = 0; index < count; ++index)
stringBuilder.AppendFormat("{0}={1}&", (object) allKeys[index], (object) HttpUtility.UrlEncode(this[allKeys[index]]));
if (stringBuilder.Length > 0)
--stringBuilder.Length;
return stringBuilder.ToString();
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6f83f468a8b546fd92606db56038f9e6
timeCreated: 1701785025

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6d2a4589d6738cb4b82bb1ceebd1453f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,280 @@
using System;
using System.Collections.Specialized;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
// using Codice.Utils; // MIRROR CHANGE
using Edgegap.Codice.Utils; // MIRROR CHANGE
using UnityEngine;
namespace Edgegap.Editor.Api
{
/// <summary>
/// Handles base URL and common methods for all Edgegap APIs.
/// </summary>
public abstract class EdgegapApiBase
{
#region Vars
private readonly HttpClient _httpClient = new HttpClient(); // Base address set // MIRROR CHANGE: Unity 2020 support
protected ApiEnvironment SelectedApiEnvironment { get; }
protected EdgegapWindowMetadata.LogLevel LogLevel { get; set; }
protected bool IsLogLevelDebug => LogLevel == EdgegapWindowMetadata.LogLevel.Debug;
/// <summary>Based on SelectedApiEnvironment.</summary>
/// <returns></returns>
private string GetBaseUrl() =>
SelectedApiEnvironment == ApiEnvironment.Staging
? ApiEnvironment.Staging.GetApiUrl()
: ApiEnvironment.Console.GetApiUrl();
#endregion // Vars
/// <param name="apiEnvironment">"console" || "staging-console"?</param>
/// <param name="apiToken">Without the "token " prefix, although we'll clear this if present</param>
/// <param name="logLevel">You may want more-verbose logs other than errs</param>
protected EdgegapApiBase(
ApiEnvironment apiEnvironment,
string apiToken,
EdgegapWindowMetadata.LogLevel logLevel = EdgegapWindowMetadata.LogLevel.Error)
{
this.SelectedApiEnvironment = apiEnvironment;
this._httpClient.BaseAddress = new Uri($"{GetBaseUrl()}/");
this._httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
string cleanedApiToken = apiToken.Replace("token ", ""); // We already prefixed token below
this._httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("token", cleanedApiToken);
this.LogLevel = logLevel;
}
#region HTTP Requests
/// <summary>
/// POST | We already added "https://api.edgegap.com/" (or similar) BaseAddress via constructor.
/// </summary>
/// <param name="relativePath"></param>
/// <param name="json">Serialize to your model via Newtonsoft</param>
/// <returns>
/// - Success => returns HttpResponseMessage result
/// - Error => Catches errs => returns null (no rethrow)
/// </returns>
protected async Task<HttpResponseMessage> PostAsync(string relativePath = "", string json = "{}")
{
StringContent stringContent = CreateStringContent(json);
Uri uri = new Uri(_httpClient.BaseAddress, relativePath); // Normalize POST uri: Can't end with `/`.
if (IsLogLevelDebug)
Debug.Log($"PostAsync to: `{uri}` with json: `{json}`");
try
{
return await ExecuteRequestAsync(() => _httpClient.PostAsync(uri, stringContent));
}
catch (Exception e)
{
Debug.LogError($"Error: {e}");
throw;
}
}
/// <summary>
/// PATCH | We already added "https://api.edgegap.com/" (or similar) BaseAddress via constructor.
/// </summary>
/// <param name="relativePath"></param>
/// <param name="json">Serialize to your model via Newtonsoft</param>
/// <returns>
/// - Success => returns HttpResponseMessage result
/// - Error => Catches errs => returns null (no rethrow)
/// </returns>
protected async Task<HttpResponseMessage> PatchAsync(string relativePath = "", string json = "{}")
{
StringContent stringContent = CreateStringContent(json);
Uri uri = new Uri(_httpClient.BaseAddress, relativePath); // Normalize PATCH uri: Can't end with `/`.
if (IsLogLevelDebug)
Debug.Log($"PatchAsync to: `{uri}` with json: `{json}`");
// (!) As of 11/15/2023, .PatchAsync() is "unsupported by Unity" -- so we manually set the verb and SendAsync()
// Create the request manually
HttpRequestMessage patchRequest = new HttpRequestMessage(new HttpMethod("PATCH"), uri)
{
Content = stringContent,
};
try
{
return await ExecuteRequestAsync(() => _httpClient.SendAsync(patchRequest));
}
catch (Exception e)
{
Debug.LogError($"Error: {e}");
throw;
}
}
/// <summary>
/// GET | We already added "https://api.edgegap.com/" (or similar) BaseAddress via constructor.
/// </summary>
/// <param name="relativePath"></param>
/// <param name="customQuery">
/// To append to the URL; eg: "foo=0&bar=1"
/// (!) First query key should prefix nothing, as shown</param>
/// <returns>
/// - Success => returns HttpResponseMessage result
/// - Error => Catches errs => returns null (no rethrow)
/// </returns>
protected async Task<HttpResponseMessage> GetAsync(string relativePath = "", string customQuery = "")
{
string completeRelativeUri = prepareEdgegapUriWithQuery(
relativePath,
customQuery);
if (IsLogLevelDebug)
Debug.Log($"GetAsync to: `{completeRelativeUri} with customQuery: `{customQuery}`");
try
{
return await ExecuteRequestAsync(() => _httpClient.GetAsync(completeRelativeUri));
}
catch (Exception e)
{
Debug.LogError($"Error: {e}");
throw;
}
}
/// <summary>
/// DELETE | We already added "https://api.edgegap.com/" (or similar) BaseAddress via constructor.
/// </summary>
/// <param name="relativePath"></param>
/// <param name="customQuery">
/// To append to the URL; eg: "foo=0&bar=1"
/// (!) First query key should prefix nothing, as shown</param>
/// <returns>
/// - Success => returns HttpResponseMessage result
/// - Error => Catches errs => returns null (no rethrow)
/// </returns>
protected async Task<HttpResponseMessage> DeleteAsync(string relativePath = "", string customQuery = "")
{
string completeRelativeUri = prepareEdgegapUriWithQuery(
relativePath,
customQuery);
if (IsLogLevelDebug)
Debug.Log($"DeleteAsync to: `{completeRelativeUri} with customQuery: `{customQuery}`");
try
{
return await ExecuteRequestAsync(() => _httpClient.DeleteAsync(completeRelativeUri));
}
catch (Exception e)
{
Debug.LogError($"Error: {e}");
throw;
}
}
/// <summary>POST || GET</summary>
/// <param name="requestFunc"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private static async Task<HttpResponseMessage> ExecuteRequestAsync(
Func<Task<HttpResponseMessage>> requestFunc,
CancellationToken cancellationToken = default)
{
HttpResponseMessage response = null;
try
{
response = await requestFunc();
}
catch (HttpRequestException e)
{
Debug.LogError($"HttpRequestException: {e.Message}");
return null;
}
catch (TaskCanceledException e)
{
if (cancellationToken.IsCancellationRequested)
Debug.LogError("Task was cancelled by caller.");
else
Debug.LogError($"TaskCanceledException: Timeout - {e.Message}");
return null;
}
catch (Exception e) // Generic exception handler
{
Debug.LogError($"Unexpected error occurred: {e.Message}");
return null;
}
// Check for a successful status code
if (response == null)
{
Debug.Log("!Success (null response) - returning 500");
return CreateUnknown500Err();
}
if (!response.IsSuccessStatusCode)
{
HttpMethod httpMethod = response.RequestMessage.Method;
Debug.Log($"!Success: {(short)response.StatusCode} {response.ReasonPhrase} - " +
$"{httpMethod} | {response.RequestMessage.RequestUri}` - " +
$"{response.Content?.ReadAsStringAsync().Result}");
}
return response;
}
#endregion // HTTP Requests
#region Utils
/// <summary>Creates a UTF-8 encoded application/json + json obj</summary>
/// <param name="json">Arbitrary json obj</param>
/// <returns></returns>
private StringContent CreateStringContent(string json = "{}") =>
new StringContent(json, Encoding.UTF8, "application/json"); // MIRROR CHANGE: 'new()' not supported in Unity 2020
private static HttpResponseMessage CreateUnknown500Err() =>
new HttpResponseMessage(HttpStatusCode.InternalServerError); // 500 - Unknown // MIRROR CHANGE: 'new()' not supported in Unity 2020
/// <summary>
/// Merges Edgegap-required query params (source) -> merges with custom query -> normalizes.
/// </summary>
/// <param name="relativePath"></param>
/// <param name="customQuery"></param>
/// <returns></returns>
private string prepareEdgegapUriWithQuery(string relativePath, string customQuery)
{
// Create UriBuilder using the BaseAddress
UriBuilder uriBuilder = new UriBuilder(_httpClient.BaseAddress);
// Add the relative path to the UriBuilder's path
uriBuilder.Path += relativePath;
// Parse the existing query from the UriBuilder
NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
// Add default "source=unity" param
query["source"] = "unity";
// Parse and merge the custom query parameters
NameValueCollection customParams = HttpUtility.ParseQueryString(customQuery);
foreach (string key in customParams)
{
query[key] = customParams[key];
}
// Set the merged query back to the UriBuilder
uriBuilder.Query = query.ToString();
// Extract the complete relative URI and return it
return uriBuilder.Uri.PathAndQuery;
}
#endregion // Utils
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 50379f30f5137224aa05a5c7b6b5ebba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,148 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Edgegap.Editor.Api.Models.Requests;
using Edgegap.Editor.Api.Models.Results;
namespace Edgegap.Editor.Api
{
/// <summary>
/// Wraps the v1/app API endpoint: Applications Control API.
/// - API Doc | https://docs.edgegap.com/api/#tag/Applications
/// </summary>
public class EdgegapAppApi : EdgegapApiBase
{
public EdgegapAppApi(
ApiEnvironment apiEnvironment,
string apiToken,
EdgegapWindowMetadata.LogLevel logLevel = EdgegapWindowMetadata.LogLevel.Error)
: base(apiEnvironment, apiToken, logLevel)
{
}
#region API Methods
/// <summary>
/// POST to v1/app
/// - Create an application that will regroup application versions.
/// - API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/application-post
/// </summary>
/// <returns>
/// Http info with GetCreateAppResult data model
/// - Success: 200
/// - Fail: 409 (app already exists), 400 (reached limit)
/// </returns>
public async Task<EdgegapHttpResult<GetCreateAppResult>> CreateApp(CreateAppRequest request)
{
HttpResponseMessage response = await PostAsync("v1/app", request.ToString());
EdgegapHttpResult<GetCreateAppResult> result = new EdgegapHttpResult<GetCreateAppResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
bool isSuccess = response.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
/// <summary>
/// GET to v1/app
/// - Get an application that will regroup application versions.
/// - API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/application-post
/// </summary>
/// <returns>
/// Http info with GetCreateAppResult data model
/// - Success: 200
/// </returns>
public async Task<EdgegapHttpResult<GetCreateAppResult>> GetApp(string appName)
{
HttpResponseMessage response = await GetAsync($"v1/app/{appName}");
EdgegapHttpResult<GetCreateAppResult> result = new EdgegapHttpResult<GetCreateAppResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
bool isSuccess = response.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
/// <summary>
/// PATCH to v1/app/{app_name}/version/{version_name}
/// - Update an *existing* application version with new specifications.
/// - API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/app-versions-patch
/// </summary>
/// <returns>
/// Http info with UpdateAppVersionRequest data model
/// - Success: 200
/// </returns>
public async Task<EdgegapHttpResult<UpsertAppVersionResult>> UpdateAppVersion(UpdateAppVersionRequest request)
{
string relativePath = $"v1/app/{request.AppName}/version/{request.VersionName}";
HttpResponseMessage response = await PatchAsync(relativePath, request.ToString());
EdgegapHttpResult<UpsertAppVersionResult> result = new EdgegapHttpResult<UpsertAppVersionResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
bool isSuccess = response.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
/// <summary>
/// POST to v1/app/{app_name}/version
/// - Create an new application version with new specifications.
/// - API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/app-version-post
/// </summary>
/// <returns>
/// Http info with UpdateAppVersionRequest data model
/// - Success: 200 (no result model)
/// - Fail: 409 (app already exists), 400 (reached limit)
/// </returns>
public async Task<EdgegapHttpResult<UpsertAppVersionResult>> CreateAppVersion(CreateAppVersionRequest request)
{
string relativePath = $"v1/app/{request.AppName}/version";
HttpResponseMessage response = await PostAsync(relativePath, request.ToString());
EdgegapHttpResult<UpsertAppVersionResult> result = new EdgegapHttpResult<UpsertAppVersionResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
bool isSuccess = response.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
#endregion // API Methods
#region Chained API Methods
/// <summary>
/// PATCH and/or POST to v1/app/: Upsert an *existing* application version with new specifications.
/// - Consumes either 1 or 2 API calls: 1st tries to PATCH, then POST if PATCH fails (!exists).
/// - API POST Doc | https://docs.edgegap.com/api/#tag/Applications/operation/app-version-post
/// - API PATCH Doc | https://docs.edgegap.com/api/#tag/Applications/operation/app-versions-patch
/// </summary>
/// <returns>
/// Http info with UpdateAppVersionRequest data model
/// - Success: 200 (no result model)
/// - Fail: 409 (app already exists), 400 (reached limit)
/// </returns>
public async Task<EdgegapHttpResult<UpsertAppVersionResult>> UpsertAppVersion(UpdateAppVersionRequest request)
{
EdgegapHttpResult<UpsertAppVersionResult> result = await UpdateAppVersion(request); // PATCH
if (result.HasErr)
{
// Try to create, instead
CreateAppVersionRequest createAppVersionRequest = CreateAppVersionRequest.FromUpdateRequest(request);
result = await CreateAppVersion(createAppVersionRequest); // POST
}
bool isSuccess = result.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
#endregion // Chained API Methods
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3b0b3b865abe64b49a4000294c4e9593
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,177 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Edgegap.Editor.Api.Models.Requests;
using Edgegap.Editor.Api.Models.Results;
using UnityEngine.Assertions;
namespace Edgegap.Editor.Api
{
/// <summary>
/// Wraps the v1/[deploy | status | stop] API endpoints: Deployments Control API.
/// - API Doc | https://docs.edgegap.com/api/#tag/Deployments
/// </summary>
public class EdgegapDeploymentsApi : EdgegapApiBase
{
public EdgegapDeploymentsApi(
ApiEnvironment apiEnvironment,
string apiToken,
EdgegapWindowMetadata.LogLevel logLevel = EdgegapWindowMetadata.LogLevel.Error)
: base(apiEnvironment, apiToken, logLevel)
{
}
#region API Methods
/// <summary>
/// POST v1/deploy
/// - Create a new deployment. Deployment is a server instance of your application version.
/// - API Doc | https://docs.edgegap.com/api/#tag/Deployments
/// </summary>
/// <returns>
/// Http info with CreateDeploymentResult data model
/// - Success: 200
/// </returns>
public async Task<EdgegapHttpResult<CreateDeploymentResult>> CreateDeploymentAsync(
CreateDeploymentRequest request)
{
HttpResponseMessage response = await PostAsync("v1/deploy", request.ToString());
EdgegapHttpResult<CreateDeploymentResult> result = new EdgegapHttpResult<CreateDeploymentResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
bool isSuccess = response.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
/// <summary>
/// GET v1/status/{requestId}
/// - Retrieve the information for a deployment.
/// - API Doc | https://docs.edgegap.com/api/#tag/Deployments/operation/deployment-status-get
/// </summary>
/// <param name="requestId">
/// Unique Identifier to keep track of your request across all Arbitrium ecosystem.
/// It's included in the response of the app deploy. Ex: "93924761ccde"</param>
/// <returns>
/// Http info with GetDeploymentStatusResult data model
/// - Success: 200
/// </returns>
public async Task<EdgegapHttpResult<GetDeploymentStatusResult>> GetDeploymentStatusAsync(string requestId)
{
HttpResponseMessage response = await GetAsync($"v1/status/{requestId}");
EdgegapHttpResult<GetDeploymentStatusResult> result = new EdgegapHttpResult<GetDeploymentStatusResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
bool isSuccess = response.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
/// <summary>
/// DELETE v1/stop/{requestId}
/// - Delete an instance of deployment. It will stop the running container and all its games.
/// - API Doc | https://docs.edgegap.com/api/#tag/Deployments/operation/deployment-status-get
/// </summary>
/// <param name="requestId">
/// Unique Identifier to keep track of your request across all Arbitrium ecosystem.
/// It's included in the response of the app deploy. Ex: "93924761ccde"</param>
/// <returns>
/// Http info with GetDeploymentStatusResult data model
/// - Success: 200
/// </returns>
public async Task<EdgegapHttpResult<StopActiveDeploymentResult>> StopActiveDeploymentAsync(string requestId)
{
HttpResponseMessage response = await DeleteAsync($"v1/stop/{requestId}");
EdgegapHttpResult<StopActiveDeploymentResult> result = new EdgegapHttpResult<StopActiveDeploymentResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
bool isSuccess = response.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
#endregion // API Methods
#region Chained API Methods
/// <summary>
/// POST v1/deploy => GET v1/status/{requestId}
/// - Create a new deployment. Deployment is a server instance of your application version.
/// - Then => await READY status.
/// - API Doc | https://docs.edgegap.com/api/#tag/Deployments
/// </summary>
/// <returns>
/// Http info with CreateDeploymentResult data model (with a READY deployment status)
/// - Success: 200
/// - Error: If createResult.HasErr, returns createResult
/// </returns>
public async Task<EdgegapHttpResult<CreateDeploymentResult>> CreateDeploymentAwaitReadyStatusAsync(
CreateDeploymentRequest request, TimeSpan pollInterval)
{
EdgegapHttpResult<CreateDeploymentResult> createResponse = await CreateDeploymentAsync(request);
// Create =>
bool isCreateSuccess = createResponse.StatusCode == HttpStatusCode.OK; // 200
if (!isCreateSuccess)
return createResponse;
// Await Status READY =>
string requestId = createResponse.Data.RequestId;
_ = await AwaitReadyStatusAsync(requestId, pollInterval);
// Return no matter what the result; no need to validate
return createResponse;
}
/// <summary>If you recently deployed but want to await READY status.</summary>
/// <param name="requestId"></param>
/// <param name="pollInterval"></param>
public async Task<EdgegapHttpResult<GetDeploymentStatusResult>> AwaitReadyStatusAsync(
string requestId,
TimeSpan pollInterval)
{
Assert.IsTrue(!string.IsNullOrEmpty(requestId)); // Validate
EdgegapHttpResult<GetDeploymentStatusResult> statusResponse = null;
CancellationTokenSource cts = new CancellationTokenSource (TimeSpan.FromMinutes( // MIRROR CHANGE: 'new()' not supported in Unity 2020
EdgegapWindowMetadata.DEPLOYMENT_AWAIT_READY_STATUS_TIMEOUT_MINS));
bool isReady = false;
while (!isReady && !cts.Token.IsCancellationRequested)
{
await Task.Delay(pollInterval, cts.Token);
statusResponse = await GetDeploymentStatusAsync(requestId);
isReady = statusResponse.Data.CurrentStatus == EdgegapWindowMetadata.READY_STATUS;
}
return statusResponse;
}
/// <summary>If you recently stopped a deployment, but want to await TERMINATED (410) status.</summary>
/// <param name="requestId"></param>
/// <param name="pollInterval"></param>
public async Task<EdgegapHttpResult<StopActiveDeploymentResult>> AwaitTerminatedDeleteStatusAsync(
string requestId,
TimeSpan pollInterval)
{
EdgegapHttpResult<StopActiveDeploymentResult> deleteResponse = null;
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes( // MIRROR CHANGE: 'new()' not supported in Unity 2020
EdgegapWindowMetadata.DEPLOYMENT_AWAIT_READY_STATUS_TIMEOUT_MINS));
bool isStopped = false;
while (!isStopped && !cts.Token.IsCancellationRequested)
{
await Task.Delay(pollInterval, cts.Token);
deleteResponse = await StopActiveDeploymentAsync(requestId);
isStopped = deleteResponse.StatusCode == HttpStatusCode.Gone; // 410
}
return deleteResponse;
}
#endregion Chained API Methods
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 37ecdc6abda4402419438f2284ef2d95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,47 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Edgegap.Editor.Api.Models.Results;
namespace Edgegap.Editor.Api
{
/// <summary>
/// Wraps the v1/ip API endpoint: "IP Lookup" API.
/// - API Doc | https://docs.edgegap.com/api/#tag/IP-Lookup
/// </summary>
public class EdgegapIpApi : EdgegapApiBase
{
public EdgegapIpApi(
ApiEnvironment apiEnvironment,
string apiToken,
EdgegapWindowMetadata.LogLevel logLevel = EdgegapWindowMetadata.LogLevel.Error)
: base(apiEnvironment, apiToken, logLevel)
{
}
#region API Methods
/// <summary>
/// GET to v1/app
/// - Retrieve your public IP address.
/// - API Doc | https://docs.edgegap.com/api/#tag/IP-Lookup/operation/IP
/// </summary>
/// <returns>
/// Http info with GetCreateAppResult data model
/// - Success: 200
/// - Fail: 409 (app already exists), 400 (reached limit)
/// </returns>
public async Task<EdgegapHttpResult<GetYourPublicIpResult>> GetYourPublicIp()
{
HttpResponseMessage response = await GetAsync("v1/ip");
EdgegapHttpResult<GetYourPublicIpResult> result = new EdgegapHttpResult<GetYourPublicIpResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
bool isSuccess = response.StatusCode == HttpStatusCode.OK; // 200
if (!isSuccess)
return result;
return result;
}
#endregion // API Methods
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fac4a7425623f39488af09d60549313e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,52 @@
using System.Net.Http;
using System.Threading.Tasks;
using Edgegap.Editor.Api.Models.Results;
using Newtonsoft.Json.Linq;
namespace Edgegap.Editor.Api
{
/// <summary>Wraps the v1/wizard API endpoint. Used for internal purposes.</summary>
public class EdgegapWizardApi : EdgegapApiBase
{
/// <summary>Extended path after the base uri</summary>
public EdgegapWizardApi(
ApiEnvironment apiEnvironment,
string apiToken,
EdgegapWindowMetadata.LogLevel logLevel = EdgegapWindowMetadata.LogLevel.Error)
: base(apiEnvironment, apiToken, logLevel)
{
}
#region API Methods
/// <summary>POST to v1/wizard/init-quick-start</summary>
/// <returns>
/// Http info with no explicit data model
/// - Success: 204 (no result model)
/// </returns>
public async Task<EdgegapHttpResult> InitQuickStart()
{
string json = new JObject { ["source"] = "unity" }.ToString();
HttpResponseMessage response = await PostAsync("v1/wizard/init-quick-start", json);
EdgegapHttpResult result = new EdgegapHttpResult(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
return result;
}
/// <summary>GET to v1/wizard/registry-credentials</summary>
/// <returns>
/// - Http info with GetRegistryCredentialsResult data model
/// - Success: 200
/// - Error: Likely if called before a successful InitQuickStart(),
/// or if called in a staging env. Soon, this will be available in production.
/// </returns>
public async Task<EdgegapHttpResult<GetRegistryCredentialsResult>> GetRegistryCredentials()
{
HttpResponseMessage response = await GetAsync("v1/wizard/registry-credentials");
EdgegapHttpResult<GetRegistryCredentialsResult> result = new EdgegapHttpResult<GetRegistryCredentialsResult>(response); // MIRROR CHANGE: 'new()' not supported in Unity 2020
return result;
}
#endregion // API Methods
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6986ee67361f0b45928ccd70c7ab12c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aed107c714fce71449ef56590221c567
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models
{
/// <summary>
/// Used in `UpdateAppVersionRequest`, `CreateAppVersionRequest`.
/// For GetDeploymentStatusResult, see DeploymentPortsData
/// </summary>
public class AppPortsData
{
/// <summary>1024~49151; Default 7770</summary>
[JsonProperty("port")]
public int Port { get; set; } = EdgegapWindowMetadata.PORT_DEFAULT;
/// <summary>Default "UDP"</summary>
[JsonProperty("protocol")]
public string ProtocolStr { get; set; } = EdgegapWindowMetadata.DEFAULT_PROTOCOL_TYPE.ToString();
[JsonProperty("to_check")]
public bool ToCheck { get; set; } = true;
[JsonProperty("tls_upgrade")]
public bool TlsUpgrade { get; set; }
[JsonProperty("name")]
public string PortName { get; set; } = "Game Port";
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b6d4864ea3706574fb35920c6fab46fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models
{
/// <summary>Used in `GetDeploymentStatus`.</summary>
public class DeploymentPortsData
{
[JsonProperty("external")]
public int External { get; set; }
[JsonProperty("internal")]
public int Internal { get; set; }
[JsonProperty("protocol")]
public string Protocol { get; set; }
[JsonProperty("name")]
public string PortName { get; set; }
[JsonProperty("tls_upgrade")]
public bool TlsUpgrade { get; set; }
[JsonProperty("link")]
public string Link { get; set; }
[JsonProperty("proxy")]
public int? Proxy { get; set; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 62d51b44b8414c9f968ca607ccb06b7e
timeCreated: 1701522748

View File

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models
{
public class LocationData
{
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("continent")]
public string Continent { get; set; }
[JsonProperty("administrative_division")]
public string AdministrativeDivision { get; set; }
[JsonProperty("timezone")]
public string Timezone { get; set; }
[JsonProperty("latitude")]
public double Latitude { get; set; }
[JsonProperty("longitude")]
public double Longitude { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 57eed0dbd556e074c992cf6599a1f6bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
namespace Edgegap.Editor.Api.Models
{
/// <summary>
/// Unity default: UDP.
/// (!) UDP !works in WebGL.
/// </summary>
public enum ProtocolType
{
/// <summary>Unity default - fastest; !works in WebGL.</summary>
UDP,
/// <summary>Slower, but more reliable; works in WebGL.</summary>
TCP,
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be5acd63e783b364ebdbb783639e2d32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d1b2a5c481353934f906c30ba047df9b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,55 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Requests
{
/// <summary>
/// Request model for https://docs.edgegap.com/api/#tag/Applications/operation/application-post
/// </summary>
public class CreateAppRequest
{
#region Required
/// <summary>*The application name.</summary>
[JsonProperty("name")]
public string AppName { get; set; }
#endregion // Required
#region Optional
/// <summary>*If the application can be deployed.</summary>
[JsonProperty("is_active")]
public bool IsActive { get; set; }
/// <summary>*Image base64 string.</summary>
[JsonProperty("image")]
public string Image { get; set; }
/// <summary>If the telemetry agent is installed on the versions of this app.</summary>
[JsonProperty("is_telemetry_agent_active")]
public bool IsTelemetryAgentActive { get; set; }
#endregion // Optional
/// <summary>Used by Newtonsoft</summary>
public CreateAppRequest()
{
}
/// <summary>Init with required info</summary>
/// <param name="appName">The application name</param>
/// <param name="isActive">If the application can be deployed</param>
/// <param name="image">Image base64 string</param>
public CreateAppRequest(
string appName,
bool isActive,
string image)
{
this.AppName = appName;
this.IsActive = isActive;
this.Image = image;
}
/// <summary>Parse to json str</summary>
public override string ToString() =>
JsonConvert.SerializeObject(this);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a492d7c515b8894ea30b37db6b7efe4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,225 @@
using System;
using Newtonsoft.Json;
using UnityEngine;
namespace Edgegap.Editor.Api.Models.Requests
{
/// <summary>
/// Request model for `POST v1/app/{app_name}/version`.
/// API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/app-version-post
/// </summary>
public class CreateAppVersionRequest
{
#region Required
/// <summary>*The name of the application associated.</summary>
[JsonIgnore] // *Path var
public string AppName { get; set; }
/// <summary>*The name of the application associated.</summary>
[JsonProperty("name")]
public string VersionName { get; set; } = EdgegapWindowMetadata.DEFAULT_VERSION_TAG;
/// <summary>*The tag of your image. Default == "latest".</summary>
/// <example>"0.1.2" || "latest" (although "latest" !recommended; use actual versions in production)</example>
[JsonProperty("docker_tag")]
public string DockerTag { get; set; } = EdgegapWindowMetadata.DEFAULT_VERSION_TAG;
/// <summary>*The name of your image.</summary>
/// <example>"edgegap/demo" || "myCompany-someId/mylowercaseapp"</example>
[JsonProperty("docker_image")]
public string DockerImage { get; set; } = "";
/// <summary>*The Repository where the image is.</summary>
/// <example>"registry.edgegap.com" || "harbor.edgegap.com" || "docker.io"</example>
[JsonProperty("docker_repository")]
public string DockerRepository { get; set; } = "";
/// <summary>*Units of vCPU needed (1024 = 1vcpu)</summary>
[JsonProperty("req_cpu")]
public int ReqCpu { get; set; } = 256;
/// <summary>*Units of memory in MB needed (1024 = 1 GPU)</summary>
[JsonProperty("req_memory")]
public int ReqMemory { get; set; } = 256;
/// <summary>*Required: At least 1 { Port, ProtocolStr }.</summary>
[JsonProperty("ports")]
public AppPortsData[] Ports { get; set; } = {};
/// <summary>The username to access the docker repository</summary>
[JsonProperty("private_username")]
public string PrivateUsername { get; set; } = "";
/// <summary>The Private Password or Token of the username (We recommend to use a token)</summary>
[JsonProperty("private_token")]
public string PrivateToken { get; set; } = "";
#endregion // Required
// #region Optional
// [JsonProperty("is_active")]
// public bool IsActive { get; set; } = true;
//
// [JsonProperty("req_video")]
// public int ReqVideo { get; set; } = 256;
//
// [JsonProperty("max_duration")]
// public int MaxDuration { get; set; } = 30;
//
// [JsonProperty("use_telemetry")]
// public bool UseTelemetry { get; set; } = true;
//
// [JsonProperty("inject_context_env")]
// public bool InjectContextEnv { get; set; } = true;
//
// [JsonProperty("whitelisting_active")]
// public bool WhitelistingActive { get; set; } = true;
//
// [JsonProperty("force_cache")]
// public bool ForceCache { get; set; }
//
// [JsonProperty("cache_min_hour")]
// public int CacheMinHour { get; set; }
//
// [JsonProperty("cache_max_hour")]
// public int CacheMaxHour { get; set; }
//
// [JsonProperty("time_to_deploy")]
// public int TimeToDeploy { get; set; } = 15;
//
// [JsonProperty("enable_all_locations")]
// public bool EnableAllLocations { get; set; }
//
// [JsonProperty("termination_grace_period_seconds")]
// public int TerminationGracePeriodSeconds { get; set; } = 5;
//
// [JsonProperty("endpoint_storage")]
// public string EndpointStorage { get; set; } = "";
//
// [JsonProperty("command")]
// public string Command { get; set; }
//
// [JsonProperty("arguments")]
// public string Arguments { get; set; }
//
// [JsonProperty("verify_image")]
// public bool VerifyImage { get; set; }
//
// [JsonProperty("session_config")]
// public SessionConfigData SessionConfig { get; set; } = new();
//
// [JsonProperty("probe")]
// public ProbeData Probe { get; set; } = new();
//
// [JsonProperty("envs")]
// public EnvsData[] Envs { get; set; } = {};
//
// public class SessionConfigData
// {
// [JsonProperty("kind")]
// public string Kind { get; set; } = "Seat";
//
// [JsonProperty("sockets")]
// public int Sockets { get; set; } = 10;
//
// [JsonProperty("autodeploy")]
// public bool Autodeploy { get; set; } = true;
//
// [JsonProperty("empty_ttl")]
// public int EmptyTtl { get; set; } = 60;
//
// [JsonProperty("session_max_duration")]
// public int SessionMaxDuration { get; set; } = 60;
// }
//
//
// public class ProbeData
// {
// [JsonProperty("optimal_ping")]
// public int OptimalPing { get; set; } = 60;
//
// [JsonProperty("rejected_ping")]
// public int RejectedPing { get; set; } = 180;
// }
//
// public class EnvsData
// {
// [JsonProperty("key")]
// public string Key { get; set; }
//
// [JsonProperty("value")]
// public string Value { get; set; }
//
// [JsonProperty("is_secret")]
// public bool IsSecret { get; set; } = true;
// }
// #endregion // Optional
/// <summary>Used by Newtonsoft</summary>
public CreateAppVersionRequest()
{
}
/// <summary>
/// Init with required info.
/// (!) If looking for refs, also see FromUpdateRequest() builder below.
/// </summary>
/// <param name="appName">The name of the application.</param>
/// <param name="containerRegistryUsername"></param>
/// <param name="containerRegistryPasswordToken"></param>
/// <param name="portNum"></param>
/// <param name="protocolType"></param>
public CreateAppVersionRequest(
string appName,
string containerRegistryUsername,
string containerRegistryPasswordToken,
int portNum,
ProtocolType protocolType)
{
this.AppName = appName;
this.PrivateUsername = containerRegistryUsername;
this.PrivateToken = containerRegistryPasswordToken;
this.Ports = new AppPortsData[]
{
new AppPortsData() // MIRROR CHANGE: 'new()' not supported in Unity 2020
{
Port = portNum,
ProtocolStr = protocolType.ToString(),
},
};
}
/// <summary>
/// Port from Update PATCH model: If you tried to Update, but !exists, you probably want to create it next.
/// </summary>
/// <param name="updateRequest"></param>
public static CreateAppVersionRequest FromUpdateRequest(UpdateAppVersionRequest updateRequest)
{
// Convert the updateRequest to JSON
string json = JsonConvert.SerializeObject(updateRequest);
// Deserialize the JSON back to CreateAppVersionRequest
CreateAppVersionRequest createReq = null;
try
{
createReq = JsonConvert.DeserializeObject<CreateAppVersionRequest>(json);
createReq.AppName = updateRequest.AppName; // Normally JsonIgnored in Update
createReq.VersionName = updateRequest.VersionName; // Normally JsonIgnored in Update
createReq.PrivateUsername = updateRequest.PrivateUsername;
createReq.PrivateToken = updateRequest.PrivateToken;
}
catch (Exception e)
{
Debug.LogError($"Error (when parsing CreateAppVersionRequest from CreateAppVersionRequest): {e}");
throw;
}
return createReq;
}
/// <summary>Parse to json str</summary>
public override string ToString() =>
JsonConvert.SerializeObject(this);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0bb645e2f9d04384a85739269cc8a4e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,63 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Requests
{
/// <summary>
/// Request model for `POST v1/deploy`.
/// API Doc | https://docs.edgegap.com/api/#tag/Deployments/operation/deploy
/// </summary>
public class CreateDeploymentRequest
{
#region Required
/// <summary>*Required: The name of the App you want to deploy.</summary>
[JsonProperty("app_name")]
public string AppName { get; set; }
/// <summary>
/// *Required: The name of the App Version you want to deploy;
/// if not present, the last version created is picked.
/// </summary>
[JsonProperty("version_name")]
public string VersionName { get; set; }
/// <summary>
/// *Required: The List of IP of your user.
/// </summary>
[JsonProperty("ip_list")]
public string[] IpList { get; set; }
/// <summary>
/// *Required: The list of IP of your user with their location (latitude, longitude).
/// </summary>
[JsonProperty("geo_ip_list")]
public string[] GeoIpList { get; set; } = {};
#endregion // Required
/// <summary>Used by Newtonsoft</summary>
public CreateDeploymentRequest()
{
}
/// <summary>Init with required info; used for a single external IP address.</summary>
/// <param name="appName">The name of the application.</param>
/// <param name="versionName">
/// The name of the App Version you want to deploy, if not present,
/// the last version created is picked.
/// </param>
/// <param name="externalIp">Obtain from IpApi.</param>
public CreateDeploymentRequest(
string appName,
string versionName,
string externalIp)
{
this.AppName = appName;
this.VersionName = versionName;
this.IpList = new[] { externalIp };
}
/// <summary>Parse to json str</summary>
public override string ToString() =>
JsonConvert.SerializeObject(this);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aae7b317093230e419bc0f8be1097ea6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,175 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Requests
{
/// <summary>
/// Request model for `PATCH v1/app/{app_name}/version/{version_name}`.
/// Request model for https://docs.edgegap.com/api/#tag/Applications/operation/app-versions-patch
/// TODO: Split "Create" and "Update" into their own, separate models: CTRL+F for "(!)" for more info.
/// </summary>
public class UpdateAppVersionRequest
{
#region Required
/// <summary>*Required: The name of the application.</summary>
[JsonIgnore] // *Path var
public string AppName { get; set; }
#endregion // Required
#region Optional
/// <summary>The name of the application version.</summary>
[JsonIgnore] // *Path var
public string VersionName { get; set; } = EdgegapWindowMetadata.DEFAULT_VERSION_TAG;
/// <summary>At least 1 { Port, ProtocolStr }</summary>
[JsonProperty("ports")]
public AppPortsData[] Ports { get; set; } = {};
/// <summary>The Repository where the image is.</summary>
/// <example>"registry.edgegap.com" || "harbor.edgegap.com" || "docker.io"</example>
[JsonProperty("docker_repository")]
public string DockerRepository { get; set; } = "";
/// <summary>The name of your image.</summary>
/// <example>"edgegap/demo" || "myCompany-someId/mylowercaseapp"</example>
[JsonProperty("docker_image")]
public string DockerImage { get; set; } = "";
/// <summary>The tag of your image. Default == "latest".</summary>
/// <example>"0.1.2" || "latest" (although "latest" !recommended; use actual versions in production)</example>
[JsonProperty("docker_tag")]
public string DockerTag { get; set; } = EdgegapWindowMetadata.DEFAULT_VERSION_TAG;
[JsonProperty("is_active")]
public bool IsActive { get; set; } = true;
[JsonProperty("private_username")]
public string PrivateUsername { get; set; } = "";
[JsonProperty("private_token")]
public string PrivateToken { get; set; } = "";
#region (!) Shows in API docs for PATCH, but could be CREATE only? "Unknown Args"
// [JsonProperty("req_cpu")]
// public int ReqCpu { get; set; } = 256;
//
// [JsonProperty("req_memory")]
// public int ReqMemory { get; set; } = 256;
//
// [JsonProperty("req_video")]
// public int ReqVideo { get; set; } = 256;
#endregion // (!) Shows in API docs for PATCH, but could be CREATE only? "Unknown Args"
[JsonProperty("max_duration")]
public int MaxDuration { get; set; } = 30;
[JsonProperty("use_telemetry")]
public bool UseTelemetry { get; set; } = true;
[JsonProperty("inject_context_env")]
public bool InjectContextEnv { get; set; } = true;
[JsonProperty("whitelisting_active")]
public bool WhitelistingActive { get; set; } = true;
[JsonProperty("force_cache")]
public bool ForceCache { get; set; }
[JsonProperty("cache_min_hour")]
public int CacheMinHour { get; set; }
[JsonProperty("cache_max_hour")]
public int CacheMaxHour { get; set; }
[JsonProperty("time_to_deploy")]
public int TimeToDeploy { get; set; } = 15;
[JsonProperty("enable_all_locations")]
public bool EnableAllLocations { get; set; }
[JsonProperty("termination_grace_period_seconds")]
public int TerminationGracePeriodSeconds { get; set; } = 5;
// // (!) BUG: Expects empty string "" at minimum; however, empty string will throw server err
// [JsonProperty("endpoint_storage")]
// public string EndpointStorage { get; set; }
[JsonProperty("command")]
public string Command { get; set; }
[JsonProperty("arguments")]
public string Arguments { get; set; }
// /// <summary>
// /// (!) Setting this will trigger a very specific type of game that will affect the AppVersion.
// /// TODO: Is leaving as null the same as commenting out?
// /// </summary>
// [JsonProperty("session_config")]
// public SessionConfigData SessionConfig { get; set; }
[JsonProperty("probe")]
public ProbeData Probe { get; set; } = new ProbeData(); // MIRROR CHANGE: 'new()' not supported in Unity 2020
[JsonProperty("envs")]
public EnvsData[] Envs { get; set; } = {};
public class SessionConfigData
{
[JsonProperty("kind")]
public string Kind { get; set; } = "Seat";
[JsonProperty("sockets")]
public int Sockets { get; set; } = 10;
[JsonProperty("autodeploy")]
public bool Autodeploy { get; set; } = true;
[JsonProperty("empty_ttl")]
public int EmptyTtl { get; set; } = 60;
[JsonProperty("session_max_duration")]
public int SessionMaxDuration { get; set; } = 60;
}
public class ProbeData
{
[JsonProperty("optimal_ping")]
public int OptimalPing { get; set; } = 60;
[JsonProperty("rejected_ping")]
public int RejectedPing { get; set; } = 180;
}
public class EnvsData
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
[JsonProperty("is_secret")]
public bool IsSecret { get; set; } = true;
}
#endregion // Optional
/// <summary>Used by Newtonsoft</summary>
public UpdateAppVersionRequest()
{
}
/// <summary>
/// Init with required info. Default version/tag == "default".
/// Since we're updating, we only require the AppName.
/// </summary>
/// <param name="appName">The name of the application.</param>
public UpdateAppVersionRequest(string appName)
{
this.AppName = appName;
}
/// <summary>Parse to json str</summary>
public override string ToString() =>
JsonConvert.SerializeObject(this);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8da9712633ee1e64faca0b960d4bed31
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aa4ceffbc97b8254885a63937def2324
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,53 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Results
{
/// <summary>
/// Result model for `POST v1/deploy`.
/// </summary>
public class CreateDeploymentResult
{
[JsonProperty("request_id")]
public string RequestId { get; set; }
[JsonProperty("request_dns")]
public string RequestDns { get; set; }
[JsonProperty("request_app")]
public string RequestApp { get; set; }
[JsonProperty("request_version")]
public string RequestVersion { get; set; }
[JsonProperty("request_user_count")]
public int RequestUserCount { get; set; }
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("continent")]
public string Continent { get; set; }
[JsonProperty("administrative_division")]
public string AdministrativeDivision { get; set; }
[JsonProperty("tags")]
public string[] Tags { get; set; }
[JsonProperty("container_log_storage")]
public ContainerLogStorageData ContainerLogStorage { get; set; }
public class ContainerLogStorageData
{
[JsonProperty("enabled")]
public bool Enabled { get; set; }
[JsonProperty("endpoint_storage")]
public string EndpointStorage { get; set; }
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8361abc6f84fccd4cba26dc285d335dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Results
{
/// <summary>Edgegap error, generally just containing `message`</summary>
public class EdgegapErrorResult
{
/// <summary>Friendly, UI-facing error message from Edgegap; can be lengthy.</summary>
[JsonProperty("message")]
public string ErrorMessage { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5b29093cb10cf3040b76f4fbe77a435d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,118 @@
using System;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace Edgegap.Editor.Api.Models.Results
{
/// <summary>
/// Wraps the inner json data with outer http info.
/// This class overload contains no json-deserialiable data result.
/// </summary>
public class EdgegapHttpResult
{
/// <summary>HTTP Status code for the request.</summary>
public HttpStatusCode StatusCode { get; }
/// <summary>This could be err, success, or null.</summary>
public string Json { get; }
/// <summary>eg: "POST"</summary>
public HttpMethod HttpMethod;
/// <summary>
/// Typically is sent by servers together with the status code.
/// Useful for fallback err descriptions, often based on the status code.
/// </summary>
public string ReasonPhrase { get; }
/// <summary>Contains `message` with friendly info.</summary>
public bool HasErr => Error != null;
public EdgegapErrorResult Error { get; set; }
#region Common Shortcuts
/// <summary>OK</summary>
public bool IsResultCode200 => StatusCode == HttpStatusCode.OK;
/// <summary>NoContent</summary>
public bool IsResultCode204 => StatusCode == HttpStatusCode.NoContent;
/// <summary>Forbidden</summary>
public bool IsResultCode403 => StatusCode == HttpStatusCode.Forbidden;
/// <summary>Conflict</summary>
public bool IsResultCode409 => StatusCode == HttpStatusCode.Conflict;
/// <summary>BadRequest</summary>
public bool IsResultCode400 => StatusCode == HttpStatusCode.BadRequest;
/// <summary>Gone</summary>
public bool IsResultCode410 => StatusCode == HttpStatusCode.Gone;
#endregion // Common Shortcuts
/// <summary>
/// Constructor that initializes the class based on an HttpResponseMessage.
/// </summary>
public EdgegapHttpResult(HttpResponseMessage httpResponse)
{
this.ReasonPhrase = httpResponse.ReasonPhrase;
this.StatusCode = httpResponse.StatusCode;
try
{
// TODO: This can be read async with `await`, but can't do this in a Constructor.
// Instead, make a factory builder Task =>
this.Json = httpResponse.Content.ReadAsStringAsync().Result;
this.Error = JsonConvert.DeserializeObject<EdgegapErrorResult>(Json);
if (Error != null && string.IsNullOrEmpty(Error.ErrorMessage))
Error = null;
}
catch (Exception e)
{
Debug.LogError("Error (reading httpResponse.Content): Client expected json, " +
$"but server returned !json: {e} - ");
}
}
}
/// <summary>
/// Wraps the inner json data with outer http info.
/// This class overload contains json-deserialiable data result.
/// </summary>
public class EdgegapHttpResult<TResult> : EdgegapHttpResult
{
/// <summary>The actual result model from Json. Could be null!</summary>
public TResult Data { get; set; }
public EdgegapHttpResult(HttpResponseMessage httpResponse, bool isLogLevelDebug = false)
: base(httpResponse)
{
this.HttpMethod = httpResponse.RequestMessage.Method;
// Assuming JSON content and using Newtonsoft.Json for deserialization
bool isDeserializable = httpResponse.Content != null &&
httpResponse.Content.Headers.ContentType.MediaType == "application/json";
if (isDeserializable)
{
try
{
this.Data = JsonConvert.DeserializeObject<TResult>(Json);
}
catch (Exception e)
{
Debug.LogError($"Error (deserializing EdgegapHttpResult.Data): {e} - json: {Json}");
throw;
}
}
if (isLogLevelDebug)
UnityEngine.Debug.Log($"{typeof(TResult).Name} result: {JObject.Parse(Json)}"); // Prettified
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 888bfc2c113487b44a3103648d2c2ae3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Results
{
/// <summary>
/// Result model for `[GET | POST] v1/app`.
/// POST API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/application-post
/// GET API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/application-get
/// </summary>
public class GetCreateAppResult
{
[JsonProperty("name")]
public string AppName { get; set; }
[JsonProperty("is_active")]
public bool IsActive { get; set; }
/// <summary>Optional</summary>
[JsonProperty("is_telemetry_agent_active")]
public bool IsTelemetryAgentActive { get; set; }
[JsonProperty("image")]
public string Image { get; set; }
[JsonProperty("create_time")]
public string CreateTimeStr { get; set; }
[JsonProperty("last_updated")]
public string LastUpdatedStr { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a66c935238edd8846b1e9e9e19cfab70
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Results
{
/// <summary>
/// Result model for `GET v1/status/{request_id}`.
/// API Doc | https://docs.edgegap.com/api/#tag/Deployments/operation/deployment-status-get
/// </summary>
public class GetDeploymentStatusResult
{
[JsonProperty("request_id")]
public string RequestId { get; set; }
[JsonProperty("fqdn")]
public string Fqdn { get; set; }
[JsonProperty("app_name")]
public string AppName { get; set; }
[JsonProperty("app_version")]
public string AppVersion { get; set; }
[JsonProperty("current_status")]
public string CurrentStatus { get; set; }
[JsonProperty("running")]
public bool Running { get; set; }
[JsonProperty("whitelisting_active")]
public bool WhitelistingActive { get; set; }
[JsonProperty("start_time")]
public string StartTime { get; set; }
[JsonProperty("removal_time")]
public string RemovalTime { get; set; }
[JsonProperty("elapsed_time")]
public int? ElapsedTime { get; set; }
[JsonProperty("last_status")]
public string LastStatus { get; set; }
[JsonProperty("error")]
public bool Error { get; set; }
[JsonProperty("error_detail")]
public string ErrorDetail { get; set; }
[JsonProperty("public_ip")]
public string PublicIp { get; set; }
[JsonProperty("sessions")]
public SessionData[] Sessions { get; set; }
[JsonProperty("location")]
public LocationData Location { get; set; }
[JsonProperty("tags")]
public string[] Tags { get; set; }
[JsonProperty("sockets")]
public string Sockets { get; set; }
[JsonProperty("sockets_usage")]
public string SocketsUsage { get; set; }
[JsonProperty("command")]
public string Command { get; set; }
[JsonProperty("arguments")]
public string Arguments { get; set; }
/// <summary>
/// TODO: Server should swap `ports` to an array of DeploymentPortsData (instead of an object of dynamic unknown objects).
/// <example>
/// {
/// "7777", {}
/// },
/// {
/// "Some Port Name", {}
/// }
/// </example>
/// </summary>
[JsonProperty("ports")]
public Dictionary<string, DeploymentPortsData> PortsDict { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c658b7f5c5d5d0648934b0ae1d71de9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Results
{
/// <summary>
/// Result model for `GET v1/wizard/registry-credentials`.
/// </summary>
public class GetRegistryCredentialsResult
{
[JsonProperty("registry_url")]
public string RegistryUrl { get; set; }
[JsonProperty("project")]
public string Project { get; set; }
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("token")]
public string Token { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e6af130c329d2b43b2f4b0dc8639477
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Results
{
/// <summary>
/// Result model for `GET v1/ip`.
/// GET API Doc | https://docs.edgegap.com/api/#tag/IP-Lookup/operation/IP
/// </summary>
public class GetYourPublicIpResult
{
[JsonProperty("public_ip")]
public string PublicIp { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73c9651ef0fdfcb449ec0120016963a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Results
{
public class StopActiveDeploymentResult
{
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("deployment_summary")]
public DeploymentSummaryData DeploymentSummary { get; set; }
public class DeploymentSummaryData
{
[JsonProperty("request_id")]
public string RequestId { get; set; }
[JsonProperty("fqdn")]
public string Fqdn { get; set; }
[JsonProperty("app_name")]
public string AppName { get; set; }
[JsonProperty("app_version")]
public string AppVersion { get; set; }
[JsonProperty("current_status")]
public string CurrentStatus { get; set; }
[JsonProperty("running")]
public bool Running { get; set; }
[JsonProperty("whitelisting_active")]
public bool WhitelistingActive { get; set; }
[JsonProperty("start_time")]
public string StartTime { get; set; }
[JsonProperty("removal_time")]
public string RemovalTime { get; set; }
[JsonProperty("elapsed_time")]
public int ElapsedTime { get; set; }
[JsonProperty("last_status")]
public string LastStatus { get; set; }
[JsonProperty("error")]
public bool Error { get; set; }
[JsonProperty("error_detail")]
public string ErrorDetail { get; set; }
[JsonProperty("ports")]
public PortsData Ports { get; set; }
[JsonProperty("public_ip")]
public string PublicIp { get; set; }
[JsonProperty("sessions")]
public SessionData[] Sessions { get; set; }
[JsonProperty("location")]
public LocationData Location { get; set; }
[JsonProperty("tags")]
public string[] Tags { get; set; }
[JsonProperty("sockets")]
public string Sockets { get; set; }
[JsonProperty("sockets_usage")]
public string SocketsUsage { get; set; }
[JsonProperty("command")]
public string Command { get; set; }
[JsonProperty("arguments")]
public string Arguments { get; set; }
}
public class PortsData
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e2e4d9424ca8f7459803e631acf912f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,165 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models.Results
{
/// <summary>
/// Result model for:
/// - `POST 1/app/{app_name}/version`
/// - `PATCH v1/app/{app_name}/version/{version_name}`
/// POST API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/application-post
/// PATCH API Doc | https://docs.edgegap.com/api/#tag/Applications/operation/app-versions-patch
/// </summary>
public class UpsertAppVersionResult
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("version")]
public VersionData Version { get; set; }
public class VersionData
{
[JsonProperty("name")]
public string VersionName { get; set; }
[JsonProperty("is_active")]
public bool IsActive { get; set; }
[JsonProperty("docker_repository")]
public string DockerRepository { get; set; }
[JsonProperty("docker_image")]
public string DockerImage { get; set; }
[JsonProperty("docker_tag")]
public string DockerTag { get; set; }
[JsonProperty("private_username")]
public string PrivateUsername { get; set; }
[JsonProperty("private_token")]
public string PrivateToken { get; set; }
[JsonProperty("req_cpu")]
public int? ReqCpu { get; set; }
[JsonProperty("req_memory")]
public int? ReqMemory { get; set; }
[JsonProperty("req_video")]
public int? ReqVideo { get; set; }
[JsonProperty("max_duration")]
public int? MaxDuration { get; set; }
[JsonProperty("use_telemetry")]
public bool UseTelemetry { get; set; }
[JsonProperty("inject_context_env")]
public bool InjectContextEnv { get; set; }
[JsonProperty("whitelisting_active")]
public bool WhitelistingActive { get; set; }
[JsonProperty("force_cache")]
public bool ForceCache { get; set; }
[JsonProperty("cache_min_hour")]
public int? CacheMinHour { get; set; }
[JsonProperty("cache_max_hour")]
public int? CacheMaxHour { get; set; }
[JsonProperty("time_to_deploy")]
public int? TimeToDeploy { get; set; }
[JsonProperty("enable_all_locations")]
public bool EnableAllLocations { get; set; }
[JsonProperty("session_config")]
public SessionConfigData SessionConfig { get; set; }
[JsonProperty("ports")]
public PortsData[] Ports { get; set; }
[JsonProperty("probe")]
public ProbeData Probe { get; set; }
[JsonProperty("envs")]
public EnvsData[] Envs { get; set; }
[JsonProperty("verify_image")]
public bool VerifyImage { get; set; }
[JsonProperty("termination_grace_period_seconds")]
public int? TerminationGracePeriodSeconds { get; set; }
[JsonProperty("endpoint_storage")]
public string EndpointStorage { get; set; }
[JsonProperty("command")]
public string Command { get; set; }
[JsonProperty("arguments")]
public string Arguments { get; set; }
}
public class SessionConfigData
{
[JsonProperty("kind")]
public string Kind { get; set; }
[JsonProperty("sockets")]
public int? Sockets { get; set; }
[JsonProperty("autodeploy")]
public bool Autodeploy { get; set; }
[JsonProperty("empty_ttl")]
public int? EmptyTtl { get; set; }
[JsonProperty("session_max_duration")]
public int? SessionMaxDuration { get; set; }
}
public class PortsData
{
[JsonProperty("port")]
public int? Port { get; set; }
[JsonProperty("protocol")]
public string Protocol { get; set; }
[JsonProperty("to_check")]
public bool ToCheck { get; set; }
[JsonProperty("tls_upgrade")]
public bool TlsUpgrade { get; set; }
[JsonProperty("name")]
public string PortName { get; set; }
}
public class ProbeData
{
[JsonProperty("optimal_ping")]
public int? OptimalPing { get; set; }
[JsonProperty("rejected_ping")]
public int? RejectedPing { get; set; }
}
public class EnvsData
{
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
[JsonProperty("is_secret")]
public bool IsSecret { get; set; }
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7dde7d59c66d8c44b86af35e853f9f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Edgegap.Editor.Api.Models
{
/// <summary>
/// Shared model for `GetDeploymentStatusResult`, `StopActiveDeploymentResult`.
/// </summary>
public class SessionData
{
[JsonProperty("session_id")]
public string SessionId { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("ready")]
public bool Ready { get; set; }
[JsonProperty("linked")]
public bool Linked { get; set; }
[JsonProperty("kind")]
public string Kind { get; set; }
[JsonProperty("user_count")]
public string UserCount { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f9024e4ca5438e4788e461387313531
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,37 @@
using System.Threading.Tasks;
using UnityEngine.UIElements;
namespace Edgegap.Editor
{
/// <summary>Slightly shake a UI button to indicate attention.</summary>
public class ButtonShaker
{
const string SHAKE_START_CLASS = "shakeStart";
const string SHAKE_STOP_CLASS = "shakeEnd";
private Button targetButton;
public ButtonShaker(Button buttonToShake) =>
targetButton = buttonToShake;
/// <summary>Shake the button x times for x msDelayBetweenShakes each.
/// 1 shake = 1 bigger -> followed by 1 smaller.</summary>
/// <param name="msDelayBetweenShakes"></param>
/// <param name="iterations"># of shakes</param>
public async Task ApplyShakeAsync(int msDelayBetweenShakes = 40, int iterations = 2)
{
for (int i = 0; i < iterations; i++)
await shakeOnce(msDelayBetweenShakes);
}
private async Task shakeOnce(int msDelayBetweenShakes)
{
targetButton.AddToClassList(SHAKE_START_CLASS);
await Task.Delay(msDelayBetweenShakes); // duration of the first transition
targetButton.RemoveFromClassList(SHAKE_START_CLASS);
targetButton.AddToClassList(SHAKE_STOP_CLASS);
await Task.Delay(msDelayBetweenShakes); // duration of the second transition
targetButton.RemoveFromClassList(SHAKE_STOP_CLASS);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f963d64ffcf32ba4bba54fe1cd70b5a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -159,7 +159,7 @@ static async Task RunCommand(string command, string arguments, Action<string> ou
ConcurrentQueue<string> errors = new ConcurrentQueue<string>(); ConcurrentQueue<string> errors = new ConcurrentQueue<string>();
ConcurrentQueue<string> outputs = new ConcurrentQueue<string>(); ConcurrentQueue<string> outputs = new ConcurrentQueue<string>();
void PipeQueue(ConcurrentQueue<string> q, Action<string> opt) void pipeQueue(ConcurrentQueue<string> q, Action<string> opt)
{ {
while (!q.IsEmpty) while (!q.IsEmpty)
{ {
@ -180,12 +180,14 @@ void PipeQueue(ConcurrentQueue<string> q, Action<string> opt)
while (!proc.HasExited) while (!proc.HasExited)
{ {
await Task.Delay(100); await Task.Delay(100);
PipeQueue(errors, errorReciever); pipeQueue(errors, errorReciever);
PipeQueue(outputs, outputReciever); pipeQueue(outputs, outputReciever);
} }
PipeQueue(errors, errorReciever); pipeQueue(errors, errorReciever);
PipeQueue(outputs, outputReciever); pipeQueue(outputs, outputReciever);
} }
static void Proc_OutputDataReceived(object sender, DataReceivedEventArgs e) static void Proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
@ -229,6 +231,68 @@ RUN chmod +x /root/build/ServerBuild
ENTRYPOINT [ ""/root/build/ServerBuild"", ""-batchmode"", ""-nographics""] ENTRYPOINT [ ""/root/build/ServerBuild"", ""-batchmode"", ""-nographics""]
"; ";
/// <summary>Run a Docker cmd with streaming log response. TODO: Plugin to other Docker cmds</summary>
/// <returns>Throws if logs contain "ERROR"</returns>
///
/// <param name="registryUrl">ex: "registry.edgegap.com"</param>
/// <param name="repoUsername">ex: "robot$mycompany-asdf+client-push"</param>
/// <param name="repoPasswordToken">Different from ApiToken; sometimes called "Container Registry Password"</param>
/// <param name="onStatusUpdate">Log stream</param>
// MIRROR CHANGE: CROSS PLATFORM SUPPORT
static async Task<bool> RunCommand_DockerLogin(
string registryUrl,
string repoUsername,
string repoPasswordToken,
Action<string> outputReciever = null, Action<string> errorReciever = null)
{
// TODO: Use --password-stdin for security (!) This is no easy task for child Process | https://stackoverflow.com/q/51489359/6541639
// (!) Don't use single quotes for cross-platform support (works unexpectedly in `cmd`).
try
{
#if UNITY_EDITOR_WIN
await RunCommand("cmd.exe", $"/c docker login -u \"{repoUsername}\" --password \"{repoPasswordToken}\" \"{registryUrl}\"", outputReciever, errorReciever);
#elif UNITY_EDITOR_OSX
await RunCommand("/bin/bash", $"-c \"docker login -u \"{repoUsername}\" --password \"{repoPasswordToken}\" \"{registryUrl}\"\"", outputReciever, errorReciever);
#elif UNITY_EDITOR_LINUX
await RunCommand("/bin/bash", $"-c \"docker login -u \"{repoUsername}\" --password \"{repoPasswordToken}\" \"{registryUrl}\"\"", outputReciever, errorReciever);
#else
Debug.LogError("The platform is not supported yet.");
#endif
}
catch (Exception e)
{
Debug.LogError($"Error: {e}");
return false;
}
return true;
}
/// <summary>
/// v2: Login to Docker Registry via RunCommand(), returning streamed log messages:
/// "docker login {registryUrl} {repository} {repoUsername} {repoPasswordToken}"
/// </summary>
/// <param name="registryUrl">ex: "registry.edgegap.com"</param>
/// <param name="repoUsername">ex: "robot$mycompany-asdf+client-push"</param>
/// <param name="repoPasswordToken">Different from ApiToken; sometimes called "Container Registry Password"</param>
/// <param name="onStatusUpdate">Log stream</param>
/// <returns>isSuccess</returns>
public static async Task<bool> LoginContainerRegistry(
string registryUrl,
string repoUsername,
string repoPasswordToken,
Action<string> onStatusUpdate)
{
string error = null;
await RunCommand_DockerLogin(registryUrl, repoUsername, repoPasswordToken, onStatusUpdate, msg => error = msg); // MIRROR CHANGE
if (error.Contains("ERROR"))
{
Debug.LogError(error);
return false;
}
return true;
}
} }
} }

View File

@ -1 +1,25 @@
// MIRROR CHANGE: removed 2023-11-06 because it doesn't seem to do anything. using UnityEditor;
using UnityEngine.UIElements;
using Edgegap;
[CustomEditor(typeof(EdgegapToolScript))]
public class EdgegapPluginScriptEditor : Editor
{
VisualElement _serverDataContainer;
private void OnEnable()
{
_serverDataContainer = EdgegapServerDataManager.GetServerDataVisualTree();
EdgegapServerDataManager.RegisterServerDataContainer(_serverDataContainer);
}
private void OnDisable()
{
EdgegapServerDataManager.DeregisterServerDataContainer(_serverDataContainer);
}
public override VisualElement CreateInspectorGUI()
{
return _serverDataContainer;
}
}

View File

@ -7,20 +7,234 @@
namespace Edgegap namespace Edgegap
{ {
// MIRROR CHANGE: EdgegapServerDataManagerUtils were merged into EdgegapServerDataManager to reduce complexity & dependencies static class EdgegapServerDataManagerUtils
// static class EdgegapServerDataManagerUtils {} {
// END MIRROR CHANGE public static Label GetHeader(string text)
{
Label header = new Label(text);
header.AddToClassList("label__header");
return header;
}
public static VisualElement GetHeaderRow()
{
VisualElement row = new VisualElement();
row.AddToClassList("row__port-table");
row.AddToClassList("label__header");
row.Add(new Label("Name"));
row.Add(new Label("External"));
row.Add(new Label("Internal"));
row.Add(new Label("ProtocolStr"));
row.Add(new Label("Link"));
return row;
}
public static VisualElement GetRowFromPortResponse(PortMapping port)
{
VisualElement row = new VisualElement();
row.AddToClassList("row__port-table");
row.AddToClassList("focusable");
row.Add(new Label(port.Name));
row.Add(new Label(port.External.ToString()));
row.Add(new Label(port.Internal.ToString()));
row.Add(new Label(port.Protocol));
row.Add(GetCopyButton("Copy", port.Link));
return row;
}
public static Button GetCopyButton(string btnText, string copiedText)
{
Button copyBtn = new Button();
copyBtn.text = btnText;
copyBtn.clickable.clicked += () => GUIUtility.systemCopyBuffer = copiedText;
return copyBtn;
}
public static Button GetLinkButton(string btnText, string targetUrl)
{
Button copyBtn = new Button();
copyBtn.text = btnText;
copyBtn.clickable.clicked += () => UnityEngine.Application.OpenURL(targetUrl);
return copyBtn;
}
public static Label GetInfoText(string innerText)
{
Label infoText = new Label(innerText);
infoText.AddToClassList("label__info-text");
return infoText;
}
}
/// <summary> /// <summary>
/// Utility class to centrally manage the Edgegap server data. /// Utility class to centrally manage the Edgegap server data, and create / update the elements displaying the server info.
/// </summary> /// </summary>
//
public static class EdgegapServerDataManager public static class EdgegapServerDataManager
{ {
// MIRROR CHANGE: ServerDataManager.GetServerStatus() is still static for other scripts to access.
// However, all UI code was moved to non-static EdgegapWindow.
// this allows us to properly assign the stylesheet without hardcoding paths etc.
internal static Status _serverData; internal static Status _serverData;
private static ApiEnvironment _apiEnvironment;
// UI elements
private static readonly StyleSheet _serverDataStylesheet;
private static readonly List<VisualElement> _serverDataContainers = new List<VisualElement>();
public static Status GetServerStatus() => _serverData; public static Status GetServerStatus() => _serverData;
static EdgegapServerDataManager()
{
_serverDataStylesheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Edgegap/Editor/EdgegapServerData.uss");
}
public static void RegisterServerDataContainer(VisualElement serverDataContainer)
{
_serverDataContainers.Add(serverDataContainer);
}
public static void DeregisterServerDataContainer(VisualElement serverDataContainer)
{
_serverDataContainers.Remove(serverDataContainer);
}
public static void SetServerData(Status serverData, ApiEnvironment apiEnvironment)
{
_serverData = serverData;
_apiEnvironment = apiEnvironment;
RefreshServerDataContainers();
}
private static VisualElement GetStatusSection()
{
ServerStatus serverStatus = _serverData.GetServerStatus();
string dashboardUrl = _apiEnvironment.GetDashboardUrl();
string requestId = _serverData.RequestId;
string deploymentDashboardUrl = "";
if (!string.IsNullOrEmpty(requestId) && !string.IsNullOrEmpty(dashboardUrl))
{
deploymentDashboardUrl = $"{dashboardUrl}/arbitrium/deployment/read/{requestId}/";
}
VisualElement container = new VisualElement();
container.AddToClassList("container");
container.Add(EdgegapServerDataManagerUtils.GetHeader("Server Status"));
VisualElement row = new VisualElement();
row.AddToClassList("row__status");
// Status pill
Label statusLabel = new Label(serverStatus.GetLabelText());
statusLabel.AddToClassList(serverStatus.GetStatusBgClass());
statusLabel.AddToClassList("label__status");
row.Add(statusLabel);
// Link to dashboard
if (!string.IsNullOrEmpty(deploymentDashboardUrl))
{
row.Add(EdgegapServerDataManagerUtils.GetLinkButton("See in the dashboard", deploymentDashboardUrl));
}
else
{
row.Add(new Label("Could not resolve link to this deployment"));
}
container.Add(row);
return container;
}
private static VisualElement GetDnsSection()
{
string serverDns = _serverData.Fqdn;
VisualElement container = new VisualElement();
container.AddToClassList("container");
container.Add(EdgegapServerDataManagerUtils.GetHeader("Server DNS"));
VisualElement row = new VisualElement();
row.AddToClassList("row__dns");
row.AddToClassList("focusable");
row.Add(new Label(serverDns));
row.Add(EdgegapServerDataManagerUtils.GetCopyButton("Copy", serverDns));
container.Add(row);
return container;
}
private static VisualElement GetPortsSection()
{
List<PortMapping> serverPorts = _serverData.Ports.Values.ToList();
VisualElement container = new VisualElement();
container.AddToClassList("container");
container.Add(EdgegapServerDataManagerUtils.GetHeader("Server PortsDict"));
container.Add(EdgegapServerDataManagerUtils.GetHeaderRow());
VisualElement portList = new VisualElement();
if (serverPorts.Count > 0)
{
foreach (PortMapping port in serverPorts)
{
portList.Add(EdgegapServerDataManagerUtils.GetRowFromPortResponse(port));
}
}
else
{
portList.Add(new Label("No port configured for this app version."));
}
container.Add(portList);
return container;
}
public static VisualElement GetServerDataVisualTree()
{
VisualElement serverDataTree = new VisualElement();
serverDataTree.styleSheets.Add(_serverDataStylesheet);
bool hasServerData = _serverData != null;
bool isReady = hasServerData && _serverData.GetServerStatus().IsOneOf(ServerStatus.Ready, ServerStatus.Error);
if (hasServerData)
{
serverDataTree.Add(GetStatusSection());
if (isReady)
{
serverDataTree.Add(GetDnsSection());
serverDataTree.Add(GetPortsSection());
}
else
{
serverDataTree.Add(EdgegapServerDataManagerUtils.GetInfoText("Additional information will be displayed when the server is ready."));
}
}
else
{
serverDataTree.Add(EdgegapServerDataManagerUtils.GetInfoText("Server data will be displayed here when a server is running."));
}
return serverDataTree;
}
private static void RefreshServerDataContainers()
{
foreach (VisualElement serverDataContainer in _serverDataContainers)
{
serverDataContainer.Clear();
serverDataContainer.Add(GetServerDataVisualTree()); // Cannot reuse a same instance of VisualElement
}
}
} }
} }

View File

@ -1 +1,12 @@
// MIRROR CHANGE: removed 2023-11-06 because it doesn't seem to do anything. using UnityEngine;
using Edgegap;
using IO.Swagger.Model;
/// <summary>
/// This script acts as an interface to display and use the necessary variables from the Edgegap tool.
/// The server info can be accessed from the tool window, as well as through the public script property.
/// </summary>
public class EdgegapToolScript : MonoBehaviour
{
public Status ServerStatus => EdgegapServerDataManager.GetServerStatus();
}

View File

@ -1,3 +1,5 @@
// MIRROR CHANGE: disable this completely. otherwise InitUIElements can still throw NRE.
/*
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
@ -78,7 +80,8 @@ public class EdgegapWindow : EditorWindow
StyleSheet _serverDataStylesheet; StyleSheet _serverDataStylesheet;
List<VisualElement> _serverDataContainers = new List<VisualElement>(); List<VisualElement> _serverDataContainers = new List<VisualElement>();
[MenuItem("Edgegap/Edgegap Hosting")] // MIRROR CHANGE [Obsolete("See EdgegapWindowV2.ShowEdgegapToolWindow()")]
// [MenuItem("Edgegap/Server Management")]
public static void ShowEdgegapToolWindow() public static void ShowEdgegapToolWindow()
{ {
EdgegapWindow window = GetWindow<EdgegapWindow>(); EdgegapWindow window = GetWindow<EdgegapWindow>();
@ -634,6 +637,8 @@ void RestoreActiveDeployment()
void SyncObjectWithForm() void SyncObjectWithForm()
{ {
if (_apiKeyInput == null) return; // MIRROR CHANGE: fix NRE when this is called before UI elements were assgned
_apiKey = _apiKeyInput.value; _apiKey = _apiKeyInput.value;
_apiEnvironment = (ApiEnvironment)_apiEnvironmentSelect.value; _apiEnvironment = (ApiEnvironment)_apiEnvironmentSelect.value;
_appName = _appNameInput.value; _appName = _appNameInput.value;
@ -924,7 +929,7 @@ public VisualElement GetServerDataVisualTree()
serverDataTree.styleSheets.Add(_serverDataStylesheet); serverDataTree.styleSheets.Add(_serverDataStylesheet);
bool hasServerData = EdgegapServerDataManager._serverData != null; bool hasServerData = EdgegapServerDataManager._serverData != null;
bool isReady = hasServerData && EdgegapServerDataManager._serverData.GetServerStatus().IsOneOf(ServerStatus.Ready, ServerStatus.Error); bool isReady = hasServerData && EdgegapServerDataManager. _serverData.GetServerStatus().IsOneOf(ServerStatus.Ready, ServerStatus.Error);
if (hasServerData) if (hasServerData)
{ {
@ -978,3 +983,4 @@ void LoadToolData()
} }
} }
} }
*/

View File

@ -1,12 +1,12 @@
#serverDataContainer { #serverDataContainer {
margin: 4px 0px 0px 0px; margin: 4px 0 0 0;
} }
.content { .content {
position: relative; position: relative;
height: auto; height: auto;
overflow: hidden; overflow: hidden;
padding: 8px 0px 8px 0px; padding: 8px 0 8px 0;
} }
.background { .background {
@ -15,7 +15,9 @@
width: 400px; width: 400px;
top: -10px; top: -10px;
right: -150px; right: -150px;
background-image: url("./Images/logo_transparent_400_alpha25.png"); /* MIRROR CHANGE: disable hardcoded image path. this is inserted from the script now.
background-image: url('project://database/Assets/Edgegap/Editor/Images/logo_transparent_400_alpha25.png?fileID=21300000&guid=b7012da4ebf9008458abc3ef9a741f3c&type=3#logo_transparent_400_alpha25');
*/
-unity-background-scale-mode: scale-and-crop; -unity-background-scale-mode: scale-and-crop;
} }
@ -25,15 +27,17 @@
padding-top: 8px; padding-top: 8px;
padding-bottom: 8px; padding-bottom: 8px;
-unity-text-align: middle-center; -unity-text-align: middle-center;
-unity-font: url("./Fonts/BaronNeue.otf"); /* MIRROR CHANGE: disable hardcoded font path
-unity-font: url('project://database/Assets/Edgegap/Editor/Fonts/Src/BaronNeue.otf?fileID=12800000&guid=fb67205c672fbb04d829783b9f771fc9&type=3#BaronNeue');
*/
} }
.text--muted { .text--muted {
color: #8a8a8a; color: rgb(192, 192, 192);
} }
.text--success { .text--success {
color: #90be6d; color: rgb(144, 190, 109);
} }
.container { .container {
@ -58,3 +62,154 @@
.flex--between { .flex--between {
justify-content: space-between; justify-content: space-between;
} }
#unity-text-input {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
color: rgb(255, 255, 255);
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
height: 27px;
background-color: rgb(26, 26, 26);
}
.button-edgegap {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
border-left-color: rgba(0, 0, 0, 0.35);
border-right-color: rgba(0, 0, 0, 0.35);
border-top-color: rgba(0, 0, 0, 0.35);
border-bottom-color: rgba(0, 0, 0, 0.35);
height: 27px;
-unity-font-style: bold;
background-color: rgb(36, 76, 87);
min-width: 170px;
max-width: 200px;
}
.button-edgegap:hover {
background-color: rgb(56, 96, 107);
}
.button-purple-hover:hover {
background-color: rgb(44, 30, 210);
}
.container-row-parent {
background-color: rgb(49, 49, 49);
border-top-color: rgb(0, 0, 0);
border-bottom-color: rgb(0, 0, 0);
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
padding-top: 10px;
padding-bottom: 10px;
margin-bottom: 10px;
}
.text-edgegap {
font-size: 11px;
color: rgb(222, 222, 222);
/* MIRROR CHANGE: disable hardcoded font path
-unity-font-definition: url('project://database/Assets/Edgegap/Editor/Fonts/Spartan-Regular%20SDF.asset?fileID=11400000&guid=8b0fb2c68be09174f8ea5057b27a545c&type=2#Spartan-Regular SDF');
*/
}
.text-btnTxt {
font-size: 12px;
color: rgb(255, 255, 255);
-unity-font-style: bold;
}
Toggle > #unity-checkmark {
}
.unity-foldout > #unity-checkmark {
}
.bg-purple {
background-color: rgb(44, 30, 210);
}
.bg-purple:hover {
background-color: rgb(64, 50, 230);
}
.unity-foldout {
padding-top: 10px;
padding-bottom: 10px;
}
.unity-text-field {
min-width: auto;
width: 400px;
padding-right: 5px;
white-space: normal;
-unity-text-align: middle-left;
opacity: 1;
padding-bottom: 5px;
padding-top: 5px;
align-items: center;
}
.unity-text-field > Label {
}
.container-row {
background-color: rgb(37, 37, 37);
padding-top: 0;
padding-bottom: 0;
margin-bottom: 3px;
}
.checkmark-edgegap {
font-size: 12px;
}
.checkmark-edgegap #unity-checkmark {
margin-right: 15px;
}
.unity-text-field__input > TextElement {
color: rgb(255, 255, 255);
/* MIRROR CHANGE: disable hardcoded font path
-unity-font-definition: url('project://database/Assets/Edgegap/Editor/Fonts/UbuntuMono-R%20SDF.asset?fileID=11400000&guid=2635d61c9807d6c46bcb00a3d8645b37&type=2#UbuntuMono-R SDF');
*/
font-size: 12px;
white-space: nowrap;
text-overflow: clip;
}
.shakeStart {
left: 5px;
transition: left 50ms ease-in-out;
}
.shakeEnd {
left: -5px;
transition: left 50ms ease-in-out;
}
.customContainerChildTxt {
padding-left: 32px;
}
#DeploymentConnectionUrlReadOnlyTxt > #unity-text-input {
display: flex;
visibility: visible;
background-color: rgba(0, 0, 0, 0.13);
}
.button-purple-hover {
}

View File

@ -1,52 +1,101 @@
<?xml version="1.0" encoding="utf-8"?> <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" editor-extension-mode="True">
<engine:UXML <Style src="project://database/Assets/Edgegap/Editor/EdgegapWindow.uss?fileID=7433441132597879392&amp;guid=b1a2e4572c5de8840ac8d98377d409ae&amp;type=3#EdgegapWindow" />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <ui:VisualElement class="content" style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0;">
xmlns:engine="UnityEngine.UIElements" <ui:VisualElement name="HeaderHorizGroup" style="flex-grow: 1; flex-direction: row; height: 100px; background-color: rgb(37, 37, 37); left: 0;">
xmlns:editor="UnityEditor.UIElements" <ui:VisualElement name="header-logo-img" style="height: 67px; width: 100px; flex-direction: row; -unity-background-scale-mode: scale-to-fit; -unity-slice-left: 0; -unity-slice-top: 0; -unity-slice-right: 0; -unity-slice-bottom: 0; -unity-background-image-tint-color: rgb(255, 255, 255); -unity-slice-scale: 1px; align-self: center;" />
xmlns:uie="UnityEditor.UIElements" <ui:Label text="EDGEGAP" name="header-logo-txt" class="text__title" style="flex-direction: row; flex-grow: 1; color: rgb(255, 255, 255); font-size: 30px; -unity-text-align: middle-left; -unity-font-style: normal; -unity-font: initial; margin-left: 0; align-items: center; height: 74px;">
> <ui:Button text="DEBUG" parse-escape-sequences="true" display-tooltip-when-elided="true" name="DebugBtn" tooltip="Hide me @ EdgegapWindowMetadata.SHOW_DEBUG_BTN" style="-unity-text-align: middle-left; white-space: normal; text-overflow: clip; justify-content: flex-start; align-self: flex-end; position: absolute; right: 0; top: 0; padding-top: 3px;" />
<engine:VisualElement class="content"> </ui:Label>
<engine:VisualElement class="background" /> </ui:VisualElement>
<engine:Label class="text__title" text="Edgegap Hosting" /> </ui:VisualElement>
<ui:ScrollView name="BodyScrollView" style="background-color: rgb(37, 37, 37); height: 1132px; justify-content: flex-end;">
<engine:ScrollView> <ui:GroupBox name="ApiTokenHorizGroupBox" class="container-row-parent" style="flex-direction: row; margin-left: 0; margin-right: 0; margin-bottom: 10px; align-items: center; justify-content: flex-start; align-self: auto; padding-bottom: 0; padding-top: 0;">
<engine:VisualElement class="container"> <ui:TextField name="ApiTokenMaskedTxt" label="&lt;b&gt;API Token&lt;/b&gt;" tooltip="No token? Click the &quot;Get a Token&quot; button &gt;&gt; Click &quot;Verify&quot; after entered to unlock other features" password="true" class="text-edgegap" style="padding-left: 25px; flex-grow: 0.17;" />
<engine:TextField name="apiKey" label="API key" password="true" view-data-key="apiKey" /> <ui:Button name="ApiTokenVerifyPurpleBtn" text="Verify" tooltip="On successful validation, the remaining UI will be unlocked" class="button-edgegap text-edgegap bg-purple" style="min-width: 75px; -unity-font-style: bold; visibility: visible; display: flex; overflow: hidden;" />
<uie:EnumField name="environmentSelect" label="API environment" include-obsolete-values="false"/> <ui:Button name="ApiTokenGetBtn" text="Get a Token" class="button-edgegap text-edgegap" style="-unity-font-style: bold; width: 125px; max-width: none; min-width: auto;" />
<engine:TextField name="appName" label="App name" view-data-key="appName" /> </ui:GroupBox>
<engine:TextField name="appVersionName" label="App version" view-data-key="appVersionName" /> <ui:VisualElement name="PostAuthContainer" usage-hints="MaskContainer" style="flex-grow: 1; transition-timing-function: ease-in; transition-duration: 0.2s;">
</engine:VisualElement> <ui:Foldout text="&lt;b&gt;Application Info&lt;/b&gt;" name="ApplicationInfoFoldout" class="text-edgegap container-row-parent" style="margin-left: 0; -unity-font-style: normal;">
<ui:VisualElement name="ApplicationNameRow" class="container-row" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto;">
<engine:VisualElement class="container"> <ui:TextField name="ApplicationNameTxt" label="Application Name" tooltip="Arbitrary name to call your app" password="false" view-data-key="ApplicationNameTxt" class="text-edgegap" />
<engine:VisualElement class="flex flex--right"> <ui:Button name="AppLoadExistingBtn" text="Load Existing App" tooltip="Already have an existing application by this name?" class="button-edgegap text-edgegap bg-purple" style="min-width: 75px; -unity-font-style: bold; visibility: visible; display: flex; overflow: hidden;" />
<engine:Label name="connectionStatusLabel" class="text--muted" text="Awaiting connection" /> </ui:VisualElement>
<engine:Button name="connectionBtn" /> <ui:VisualElement name="ApplicationIconRow" class="container-row" style="flex-grow: 1;">
</engine:VisualElement> <ui:VisualElement name="ApplicationIconHorizGroup" class="text-edgegap" style="flex-grow: 1; flex-direction: row; align-items: center; justify-content: flex-start; align-self: stretch; width: 600px;">
</engine:VisualElement> <uie:ObjectField label="Application Icon" type="UnityEngine.Sprite, UnityEngine.CoreModule" name="ApplicationIconSprite" tooltip="Choose a Sprite image to use as an app icon" view-data-key="ApplicationIconSprite" style="align-items: center; justify-content: flex-start; align-self: center; width: 409px;" />
<ui:VisualElement name="SelectFileVertGroup" style="flex-grow: 0.7;">
<engine:VisualElement class="container"> <ui:VisualElement name="SelectFileHorizGroup" style="flex-grow: 1; flex-direction: row; align-items: center;">
<engine:TextField name="containerRegistry" tooltip="ex: docker.io or harbor.edgegap.net" label="Container Registry" view-data-key="containerRegistry" /> <ui:Label tabindex="-1" text="Limit 200kb" parse-escape-sequences="true" display-tooltip-when-elided="true" name="SelectFileSizeLimitTooltipLabel" class="text-edgegap" style="font-size: 11px; color: rgb(147, 147, 147); -unity-text-align: upper-left;" />
<engine:TextField name="containerImageRepo" tooltip="ex: edgegap/image" label="Image repository" view-data-key="containerImageRepo" /> </ui:VisualElement>
<engine:TextField name="tag" label="Tag" tooltip="ex: 1.2" view-data-key="tag" /> </ui:VisualElement>
<engine:Toggle name="autoIncrementTag" tooltip="Auto increment the tag on build for quicker iteration" label="Increment tag on build" /> </ui:VisualElement>
</engine:VisualElement> </ui:VisualElement>
<ui:VisualElement name="ApplicationCreateHorizRow" style="flex-grow: 1; flex-direction: row;">
<ui:Button name="ApplicationCreateBtn" text="Create Application" class="button-edgegap text-edgegap" style="flex-direction: row; -unity-text-align: middle-center; margin-left: 0;" />
<engine:VisualElement class="container" /> <ui:Label tabindex="-1" text="{ApplicationCreateResultLabel}" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ApplicationCreateResultLabel" class="text-edgegap" style="color: rgb(138, 238, 140); align-items: stretch; justify-content: flex-start; align-self: center;" />
</ui:VisualElement>
<engine:VisualElement class="container"> </ui:Foldout>
<engine:VisualElement class="flex"> <ui:VisualElement name="ContainerRegistryFoldoutHorizRow" class="container-row-parent" style="flex-grow: 1; flex-direction: row;">
<engine:Button name="buildAndPushBtn" text="Build and Push" /> <ui:Foldout text="&lt;b&gt;Container Registry&lt;/b&gt;" name="ContainerRegistryFoldout" class="text-edgegap" style="flex-grow: 1; padding-bottom: 0; padding-top: 0; padding-right: 0; padding-left: 0;">
<engine:Button name="serverActionBtn" /> <ui:VisualElement name="ContainerPortRow" class="container-row" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto;">
</engine:VisualElement> <ui:TextField name="ContainerRegistryPortNumTxt" label="Port" tooltip="1024~49151 (Default `7770`)" value="7770" view-data-key="ContainerRegistryPortNumTxt" keyboard-type="NumberPad" class="text-edgegap" style="width: 250px;" />
<ui:VisualElement name="MIRROR_CHANGE_PORT_HARDCODED" class="MIRROR_CHANGE_PORT_HARDCODED" />
</engine:VisualElement> </ui:VisualElement>
<ui:VisualElement name="ContainerNewVersionTagRow" class="container-row" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto;">
<engine:VisualElement name="serverDataContainer" /> <ui:TextField name="ContainerNewVersionTagTxt" label="New Version Tag" tooltip="eg: &quot;latest&quot; (default), &quot;v1.0.0&quot;, &quot;1.0.0&quot;" value="latest" view-data-key="ContainerNewVersionTagTxt" class="text-edgegap" />
</ui:VisualElement>
<engine:VisualElement class="container flex flex--right"> <ui:VisualElement name="ContainerUseCustomRegistryRow" view-data-key="ContainerUseCustomRegistryRow" class="container-row" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto; padding-top: 5px; padding-bottom: 5px;">
<engine:Button name="documentationBtn" text="Documentation" tooltip="Opens the documentation website. The website version changes depending on the selected API environment."/> <ui:Toggle name="ContainerUseCustomRegistryToggle" label="Use Custom Container Registry" class="text-edgegap checkmark-edgegap" style="justify-content: flex-start; align-self: center; flex-direction: row-reverse; align-items: center; padding-left: 3px;" />
</engine:VisualElement> </ui:VisualElement>
</engine:ScrollView> <ui:VisualElement name="ContainerCustomRegistryWrapper" style="flex-grow: 1;">
</engine:VisualElement> <ui:VisualElement name="ContainerRegistryUrlRow" class="container-row customContainerRegistryMember" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto;">
</engine:UXML> <ui:TextField name="ContainerRegistryUrlTxt" label="Registry URL" tooltip="eg: `registry.edgegap.com`, `docker.io`, `harbor.edgegap.net`" class="text-edgegap customContainerChildTxt" />
</ui:VisualElement>
<ui:VisualElement name="ContainerRepositoryRow" class="container-row" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto;">
<ui:TextField name="ContainerImageRepositoryTxt" label="Repository" tooltip="eg: `edgegap-public/tutorial`, `mycompany-someid/mylowercaseapp-`" class="text-edgegap customContainerChildTxt" />
</ui:VisualElement>
<ui:VisualElement name="ContainerUsernameRow" class="container-row" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto;">
<ui:TextField name="ContainerUsernameTxt" label="Username" tooltip="eg: `robot$mycompany-someid+client-push`" class="text-edgegap customContainerChildTxt" />
</ui:VisualElement>
<ui:VisualElement name="ContainerTokenRow" class="container-row" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto;">
<ui:TextField name="ContainerTokenTxt" label="Token" tooltip="Registry pasword/token/secret (different from top-level API Token)" password="true" class="text-edgegap customContainerChildTxt" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="ContainerBuildAndPushHorizRow" style="flex-grow: 1; flex-direction: row;">
<ui:Button name="ContainerBuildAndPushBtn" text="Build and Push" class="button-edgegap text-edgegap" style="margin-left: 0;" />
<ui:Label tabindex="-1" text="{ContainerBuildAndPushResultLabel}" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ContainerBuildAndPushResultLabel" class="text-edgegap" style="color: rgb(138, 238, 140); align-items: stretch; justify-content: flex-start; align-self: center; display: none;" />
</ui:VisualElement>
</ui:Foldout>
</ui:VisualElement>
<ui:Foldout text="&lt;b&gt;Deployments&lt;/b&gt;" name="DeploymentsFoldout" class="text-edgegap container-row-parent">
<ui:VisualElement name="Row" class="container-row" style="flex-grow: 1; align-items: center; flex-direction: row; justify-content: flex-start; align-self: auto;" />
<ui:GroupBox name="DeploymentsHorizGroupBox" style="flex-direction: row; justify-content: flex-start; align-items: stretch; align-self: flex-start; margin-left: 0; padding-left: 0;">
<ui:Button name="DeploymentsRefreshBtn" text="Refresh" class="button-edgegap text-edgegap" style="flex-grow: 0.5;" />
<ui:Button name="DeploymentsCreateBtn" text="Create New Deployment" class="button-edgegap text-edgegap" style="flex-grow: 0.5; max-width: 250px; min-width: 200px;" />
</ui:GroupBox>
<ui:Label tabindex="-1" text="{DeploymentsStatusLabel}" parse-escape-sequences="true" display-tooltip-when-elided="true" name="DeploymentsStatusLabel" class="text-edgegap" style="color: rgb(138, 238, 140); white-space: normal; text-overflow: ellipsis; width: 650px; display: none; margin-left: 3px;" />
<ui:GroupBox name="DeploymentsHeadersGroupBox" class="container-row-parent" style="flex-direction: row; padding-top: 6px; padding-right: 0; padding-bottom: 3px; padding-left: 0; margin-bottom: 0; align-self: auto; justify-content: flex-start; align-items: center;">
<ui:Label name="DeploymentsConnectionStatusHeaderLabel" text="Status" style="flex-grow: 0.5; -unity-text-align: upper-left; -unity-font-style: bold; width: 75px; flex-direction: column;" />
<ui:Label name="DeploymentsConnectionURLHeaderLabel" text="URL" style="flex-grow: 1; -unity-font-style: bold; align-items: auto; justify-content: flex-start; align-self: auto;" />
<ui:Label name="DeploymentsConnectionControlHeaderLabel" text="Control" style="flex-grow: 0.08; -unity-text-align: upper-left; -unity-font-style: bold; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0;" />
</ui:GroupBox>
<ui:GroupBox name="DeploymentsConnectionGroupBox" style="flex-direction: row; padding-left: 0; align-items: center; align-self: stretch; justify-content: flex-start; height: 32px;">
<ui:Label name="DeploymentsConnectionStatusLabel" text="Unknown" class="text--muted" style="flex-grow: 1; flex-direction: column; -unity-text-align: middle-left; justify-content: flex-start; align-items: center; width: 86px; margin-right: 15px;" />
<ui:VisualElement name="DeploymentConnectionUrlHorizGroup" style="flex-grow: 1; flex-direction: row; width: 304px; margin-left: 25px; margin-right: 25px; padding-right: 25px; padding-left: 25px;">
<ui:TextField picking-mode="Ignore" name="DeploymentConnectionUrlReadOnlyTxt" readonly="true" tooltip="Selectable" style="padding-left: 0; -unity-text-align: middle-center; width: 297px; align-items: center;">
<ui:Button name="DeploymentConnectionCopyUrlBtn" tooltip="Copy" class="text-edgegap bg-purple" style="min-width: 15px; visibility: visible; display: flex; overflow: hidden; width: 35px; -unity-background-scale-mode: scale-to-fit; min-height: 25px; background-color: rgb(44, 30, 210); -unity-background-image-tint-color: rgb(224, 224, 224); -unity-slice-left: 1; -unity-slice-top: 1; -unity-slice-right: 1; -unity-slice-bottom: 1; translate: -40px 0;" />
</ui:TextField>
</ui:VisualElement>
<ui:VisualElement name="DeploymentsConnectionServerStopHorizBtnHorizGroup" style="flex-grow: 1; flex-direction: row; align-items: center; justify-content: center;">
<ui:Button name="DeploymentsConnectionServerStopBtn" text="Stop Server" class="button-edgegap text-edgegap" style="min-width: auto; width: 125px; max-width: none; visibility: visible; display: flex;" />
</ui:VisualElement>
</ui:GroupBox>
</ui:Foldout>
<ui:VisualElement name="ServerDataContainer" />
</ui:VisualElement>
<ui:GroupBox name="FooterHorizGroup" class="container-row-parent" style="flex-direction: row; justify-content: center; align-items: auto; align-self: center; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-left-radius: 1px; border-top-right-radius: 1px; border-bottom-right-radius: 1px; border-bottom-left-radius: 1px; border-right-color: rgb(0, 0, 0); border-left-color: rgb(0, 0, 0); padding-right: 135px; padding-left: 150px; padding-top: 15px; padding-bottom: 15px;">
<ui:Button text="Documentation" tooltip="Opens the documentation website. The website version changes depending on the selected API environment." name="FooterDocumentationBtn" class="button-edgegap text-edgegap" />
<ui:Button text="Add More Game Servers!" tooltip="Opens the documentation website. The website version changes depending on the selected API environment." name="FooterNeedMoreGameServersBtn" class="button-edgegap text-edgegap bg-purple" style="width: 220px; max-width: none; min-width: auto;" />
</ui:GroupBox>
</ui:ScrollView>
</ui:UXML>

View File

@ -0,0 +1,172 @@
using System;
using Edgegap.Editor.Api.Models;
namespace Edgegap.Editor
{
/// <summary>
/// Contains static metadata / options for the EdgegapWindowV2 UI.
/// - Notable:
/// * SHOW_DEBUG_BTN
/// * LOG_LEVEL
/// * DEFAULT_VERSION_TAG
/// * SKIP_SERVER_BUILD_WHEN_PUSHING
/// * SKIP_DOCKER_IMAGE_BUILD_WHEN_PUSHING
/// </summary>
public static class EdgegapWindowMetadata
{
#region Debug
/// <summary>Log Debug+, or Errors only?</summary>
public enum LogLevel
{
Debug,
Error,
}
/// <summary>
/// Set to Debug to show more logs. Default `Error`.
/// - Error level includes "potentially-intentional" (!fatal) errors logged with Debug.Log
/// - TODO: Move opt to UI?
/// </summary>
public const LogLevel LOG_LEVEL = LogLevel.Error;
/// <summary>
/// Set to show a debug button at the top-right for arbitrary testing.
/// Default enables groups. Default `false`.
/// </summary>
public const bool SHOW_DEBUG_BTN = false;
/// <summary>
/// When running a Docker-based "Build & Push" flow, skip building the Unity server binary
/// (great for testing push flow). Default false.
/// </summary>
public static readonly bool SKIP_SERVER_BUILD_WHEN_PUSHING = false; // MIRROR CHANGE: 'const' changed to 'static readonly' to avoid 'unreachable code detected' warning
/// <summary>
/// When running a Docker-based "Build & Push" flow, skip building the Docker image
/// (great for testing registry login mechanics). Default false.
/// </summary>
public static readonly bool SKIP_DOCKER_IMAGE_BUILD_WHEN_PUSHING = false; // MIRROR CHANGE: 'const' changed to 'static readonly' to avoid 'unreachable code detected' warning
#endregion // Debug
/// <summary>Interval at which the server status is updated</summary>
public const int SERVER_STATUS_CRON_JOB_INTERVAL_MS = 10000;
public const int PORT_DEFAULT = 7770;
public const int PORT_MIN = 1024;
public const int PORT_MAX = 49151;
public const int DEPLOYMENT_AWAIT_READY_STATUS_TIMEOUT_MINS = 1;
public const int DEPLOYMENT_READY_STATUS_POLL_SECONDS = 2;
public const int DEPLOYMENT_STOP_STATUS_POLL_SECONDS = 2;
public const ProtocolType DEFAULT_PROTOCOL_TYPE = ProtocolType.UDP;
public const string READY_STATUS = "Status.READY";
public const string EDGEGAP_GET_A_TOKEN_URL = "https://app.edgegap.com/?oneClick=true";
public const string EDGEGAP_ADD_MORE_GAME_SERVERS_URL = "https://edgegap.com/en/resources/contact";
public const string EDGEGAP_DOC_BTN_HOW_TO_LOGIN_VIA_CLI_URL = "https://docs.edgegap.com/docs/container/edgegap-container-registry/#getting-your-credentials";
public const string DEFAULT_VERSION_TAG = "latest";
public const string LOADING_RICH_STR = "<i>Loading...</i>";
public const string PROCESSING_RICH_STR = "<i>Processing...</i>";
public const string DEPLOY_REQUEST_RICH_STR = "<i>Requesting Deploy...</i>";
#region Colors
/// <summary>Earthy lime green</summary>
public const string SUCCESS_COLOR_HEX = "#8AEE8C";
/// <summary>Calming light orange</summary>
public const string WARN_COLOR_HEX = "#EEC58A";
/// <summary>Vivid blood orange</summary>
public const string FAIL_COLOR_HEX = "#EE9A8A";
/// <summary>Corn yellow</summary>
public const string PROCESSING_COLOR_HEX = "#EEEA8A";
public enum StatusColors
{
/// <summary>CornYellow</summary>
Processing,
/// <summary>EarthyLimeGreen</summary>
Success,
/// <summary>CalmingLightOrange</summary>
Warn,
/// <summary>VividBloodOrange</summary>
Error,
}
/// <returns>Wraps string in color rich text</returns>
public static string WrapRichTextInColor(string str, StatusColors statusColor)
{
switch (statusColor)
{
case StatusColors.Processing:
return $"<color={PROCESSING_COLOR_HEX}>{str}</color>";
case StatusColors.Success:
return $"<color={SUCCESS_COLOR_HEX}>{str}</color>";
case StatusColors.Warn:
return $"<color={WARN_COLOR_HEX}>{str}</color>";
case StatusColors.Error:
return $"<color={FAIL_COLOR_HEX}>{str}</color>";
default:
throw new ArgumentOutOfRangeException(nameof(statusColor), statusColor, null);
}
}
#endregion // Colors
#region Player Pref Key Ids for persistence
/// <summary>Cached as base64</summary>
public const string API_TOKEN_KEY_STR = "ApiToken";
public const string DEPLOYMENT_REQUEST_ID_KEY_STR = "DeploymentRequestId";
public const string DEPLOYMENT_CONNECTION_URL_KEY_STR = "DeploymentConnectionUrlLabel";
public const string DEPLOYMENT_CONNECTION_STATUS_KEY_STR = "DeploymentsConnectionStatusLabel";
#endregion // Editor Pref Key Ids for persistence
#region UI Element Ids
public const string DEBUG_BTN_ID = "DebugBtn";
public const string API_TOKEN_TXT_ID = "ApiTokenMaskedTxt";
public const string API_TOKEN_VERIFY_BTN_ID = "ApiTokenVerifyPurpleBtn";
public const string API_TOKEN_GET_BTN_ID = "ApiTokenGetBtn";
public const string POST_AUTH_CONTAINER_ID = "PostAuthContainer";
public const string APP_INFO_FOLDOUT_ID = "ApplicationInfoFoldout";
public const string APP_NAME_TXT_ID = "ApplicationNameTxt";
public const string APP_LOAD_EXISTING_BTN_ID = "AppLoadExistingBtn";
public const string APP_ICON_SPRITE_OBJ_ID = "ApplicationIconSprite";
public const string APP_CREATE_BTN_ID = "ApplicationCreateBtn";
public const string APP_CREATE_RESULT_LABEL_ID = "ApplicationCreateResultLabel";
public const string CONTAINER_REGISTRY_FOLDOUT_ID = "ContainerRegistryFoldout";
public const string CONTAINER_REGISTRY_PORT_NUM_ID = "ContainerRegistryPortNumTxt";
public const string CONTAINER_REGISTRY_TRANSPORT_TYPE_ENUM_ID = "ContainerRegistryProtocolTypeEnumField";
public const string CONTAINER_NEW_TAG_VERSION_TXT_ID = "ContainerNewVersionTagTxt";
public const string CONTAINER_USE_CUSTOM_REGISTRY_TOGGLE_ID = "ContainerUseCustomRegistryToggle";
public const string CONTAINER_CUSTOM_REGISTRY_WRAPPER_ID = "ContainerCustomRegistryWrapper";
public const string CONTAINER_REGISTRY_URL_TXT_ID = "ContainerRegistryUrlTxt";
public const string CONTAINER_IMAGE_REPOSITORY_URL_TXT_ID = "ContainerImageRepositoryTxt";
public const string CONTAINER_USERNAME_TXT_ID = "ContainerUsernameTxt";
public const string CONTAINER_TOKEN_TXT_ID = "ContainerTokenTxt";
public const string CONTAINER_BUILD_AND_PUSH_BTN_ID = "ContainerBuildAndPushBtn";
public const string CONTAINER_BUILD_AND_PUSH_RESULT_LABEL_ID = "ContainerBuildAndPushResultLabel";
public const string DEPLOYMENTS_FOLDOUT_ID = "DeploymentsFoldout";
public const string DEPLOYMENTS_REFRESH_BTN_ID = "DeploymentsRefreshBtn";
public const string DEPLOYMENTS_CREATE_BTN_ID = "DeploymentsCreateBtn";
public const string DEPLOYMENTS_STATUS_LABEL_ID = "DeploymentsStatusLabel";
public const string DEPLOYMENTS_CONTAINER_ID = "DeploymentsConnectionGroupBox";
public const string DEPLOYMENTS_CONNECTION_COPY_URL_BTN_ID = "DeploymentConnectionCopyUrlBtn";
public const string DEPLOYMENTS_CONNECTION_URL_READONLY_TXT_ID = "DeploymentConnectionUrlReadOnlyTxt"; // Dynamic
public const string DEPLOYMENTS_CONNECTION_STATUS_LABEL_ID = "DeploymentsConnectionStatusLabel"; // Dynamic
public const string DEPLOYMENTS_CONNECTION_SERVER_ACTION_STOP_BTN_ID = "DeploymentsConnectionServerStopBtn";
public const string FOOTER_DOCUMENTATION_BTN_ID = "FooterDocumentationBtn";
public const string FOOTER_NEED_MORE_GAME_SERVERS_BTN_ID = "FooterNeedMoreGameServersBtn";
#endregion // UI Element Ids
//[Obsolete("Hard-coded; not from UI. TODO: Get from UI")] // MIRROR CHANGE: comment this out to avoid import warnings
public const ApiEnvironment API_ENVIRONMENT = ApiEnvironment.Console;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c17286cfb15166442920036353163fc0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 1c3d4497250ad3e4aa500d4c599b30fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- m_ViewDataDictionary: {instanceID: 0}
- LogoImage: {fileID: 2800000, guid: b7012da4ebf9008458abc3ef9a741f3c, type: 3}
- ClipboardImage: {fileID: 2800000, guid: caa516cdb721dd143bbc8000ca78d50a, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,21 +0,0 @@
fileFormatVersion: 2
guid: fb67205c672fbb04d829783b9f771fc9
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- Baron Neue
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1 @@
Removed font files to avoid all kinds of runtime errors.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 57002d60dd8ce46b081a0a0a25e5b52b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: caa516cdb721dd143bbc8000ca78d50a
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -55,10 +55,10 @@ public static string GetDocumentationUrl(this ApiEnvironment apiEnvironment)
switch (apiEnvironment) switch (apiEnvironment)
{ {
case ApiEnvironment.Staging: case ApiEnvironment.Staging:
apiUrl = "https://staging-docs.edgegap.com"; apiUrl = "https://staging-docs.edgegap.com/docs";
break; break;
case ApiEnvironment.Console: case ApiEnvironment.Console:
apiUrl = "https://docs.edgegap.com"; apiUrl = "https://docs.edgegap.com/docs";
break; break;
default: default:
apiUrl = null; apiUrl = null;

View File

@ -1,9 +1,16 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Edgegap namespace Edgegap
{ {
//[Obsolete("Use UpdateAppVersionRequest")] // MIRROR CHANGE: commented this out to avoid import warnings
public struct AppVersionUpdatePatchData public struct AppVersionUpdatePatchData
{ {
[JsonProperty("docker_repository")] [JsonProperty("docker_repository")]
public string DockerRegistry; public string DockerRegistry;
@ -12,5 +19,6 @@ public struct AppVersionUpdatePatchData
[JsonProperty("docker_tag")] [JsonProperty("docker_tag")]
public string DockerTag; public string DockerTag;
} }
} }

View File

@ -1,9 +1,7 @@
{ {
"name": "Mirror.Hosting", "name": "Mirror.Hosting",
"rootNamespace": "", "rootNamespace": "",
"references": [ "references": [],
""
],
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"
], ],

View File

@ -33,33 +33,34 @@ Mirror is **[stable](https://mirror-networking.gitbook.io/docs/general/tests)**,
Mirror comes with a wide variety of features to support all game genres.<br> Mirror comes with a wide variety of features to support all game genres.<br>
Many of our features quickly became the norm across all Unity netcodes!<br> Many of our features quickly became the norm across all Unity netcodes!<br>
| Feature | Description | Status | | Feature | Description | Status |
|-------------------------------|-------------------------------------------------------------|-----------------| |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|
| 🎛 **Transports** | UDP, TCP, Websockets, Steam, Relay and more. | **Stable** | | 🎛 **Transports** | UDP, TCP, Websockets, Steam, Relay and more. | **Stable** |
| 🪜 **Interest Management** | Spatial Hashing & Distance Checker to partition the world. | **Stable** | | 🪜 **Interest Management** | Spatial Hashing & Distance Checker to partition the world. | **Stable** |
| ↗️ **SyncDirection** | Server & Client Authority - per component with one click. | **Stable** | | ↗️ **SyncDirection** | Server & Client Authority - per component with one click. | **Stable** |
| 🐌 **Latency Simulation** | Simulate latency, packet loss & jitter locally. | **Stable** | | 🐌 **Latency Simulation** | Simulate latency, packet loss & jitter locally. | **Stable** |
| 🧲 **Batching** | Minimize message overhead via batching automatically. | **Stable** | | 🧲 **Batching** | Minimize message overhead via batching automatically. | **Stable** |
| 💌 **RPCs & SyncVars** | Synced vars and remote function calls built in & safe. | **Stable** | | 💌 **RPCs & SyncVars** | Synced vars and remote function calls built in & safe. | **Stable** |
| 🙅‍♀️**Allocation Free** | Free of runtime allocations and no GC (except Transports). | **Stable** | | 🙅‍♀️**Allocation Free** | Free of runtime allocations and no GC (except Transports). | **Stable** |
| 🛞 **Transform & Physics** | Transform & Physics sync built in. | **Stable** | | 🛞 **Transform & Physics** | Transform & Physics sync built in. | **Stable** |
| 👩‍🍼 **Child Components** | Put networked components on anything. | **Stable** | | 👩‍🍼 **Child Components** | Put networked components on anything. | **Stable** |
| 🪚️ **IL Post Processing** | Zero overhead [Rpcs] and [Commands] via IL post processing! | **Stable** | | 🪚️ **IL Post Processing** | Zero overhead [Rpcs] and [Commands] via IL post processing! | **Stable** |
| | | | | ☁️ **Two Click Hosting** | (Optional) <a href="https://mirror-networking.gitbook.io/docs/hosting/edgegap-hosting-plugin-guide">Build & Push</a> directly from Unity Editor to the Cloud. | **Preview** |
| 📏 **Snapshot Interp.** | Perfectly smooth movement for all platforms and all games. | **Stable** | | | | |
| 🔫 **Lag Compensation** | Roll back state to see what the player saw during input. | **Preview** | | 📏 **Snapshot Interp.** | Perfectly smooth movement for all platforms and all games. | **Stable** |
| 🏎 **Prediction** | Inputs are applied immediately & corrected automatically. | **Preview** | | 🔫 **Lag Compensation** | Roll back state to see what the player saw during input. | **Preview** |
| | | | | 🏎 **Prediction** | Inputs are applied immediately & corrected automatically. | **Preview** |
| 🧙‍♂️ **General Purpose** | Mirror supports all genres for all your games! | | | | | |
| 🧘‍♀️ **Stable API** | Long term (10 years) stability instead of new versions! | | 🧙‍♂️ **General Purpose** | Mirror supports all genres for all your games! | |
| 🔬 **Battle Tested** | Mirror servers over 100 million players. It just works! | | | 🧘‍♀️ **Stable API** | Long term (10 years) stability instead of new versions! |
| 💴 **Free & Open Source** | MIT licensed without any restrictions to minimize risk! | | | 🔬 **Battle Tested** | Mirror servers over 100 million players. It just works! | |
| ❤️ **Community** | Join our Discord with nearly 15.000 developers world wide! | | | 💴 **Free & Open Source** | MIT licensed without any restrictions to minimize risk! | |
| 🧜🏻‍♀️ **Long Term Support** | Maintained since 2014 with optional LTS version! | | | ❤️ **Community** | Join our Discord with nearly 15.000 developers world wide! | |
| | | | | 🧜🏻‍♀️ **Long Term Support** | Maintained since 2014 with optional LTS version! | |
| 🦖 **Deterministic Physics** | Open source deterministic physics for C# & Unity! | **Researching** | | | | |
| 📐 **Bitpacking** | Optimized compression (bools as 1 bit etc.) | **Researching** | | 🦖 **Deterministic Physics** | Open source deterministic physics for C# & Unity! | **Researching** |
| 🔒 **Encryption** | Secure communication with end-to-end encryption. | **Researching** | | 📐 **Bitpacking** | Optimized compression (bools as 1 bit etc.) | **Researching** |
| 🔒 **Encryption** | Secure communication with end-to-end encryption. | **Researching** |
--- ---
## Architecture ## Architecture