From 9aef26d21595af4d230b35e495701651030e4c3e Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 01/35] feat: JS error example --- .../Assets/Scenes/2_NativeSupport.unity | 402 +++++++++++++++++- .../NativeSupport/JavaScriptPlugin.jslib | 9 + .../NativeSupport/JavaScriptPlugin.jslib.meta | 32 ++ .../NativeSupport/NativeSupportScene.cs | 4 + .../Scripts/NativeSupport/WebGLButtons.cs | 20 + .../NativeSupport/WebGLButtons.cs.meta | 11 + 6 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib create mode 100644 samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib.meta create mode 100644 samples/unity-of-bugs/Assets/Scripts/NativeSupport/WebGLButtons.cs create mode 100644 samples/unity-of-bugs/Assets/Scripts/NativeSupport/WebGLButtons.cs.meta diff --git a/samples/unity-of-bugs/Assets/Scenes/2_NativeSupport.unity b/samples/unity-of-bugs/Assets/Scenes/2_NativeSupport.unity index 7fa1fa6e8..046805f7c 100644 --- a/samples/unity-of-bugs/Assets/Scenes/2_NativeSupport.unity +++ b/samples/unity-of-bugs/Assets/Scenes/2_NativeSupport.unity @@ -887,6 +887,83 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 560000143} m_CullTransparentMesh: 0 +--- !u!1 &565393628 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 565393629} + - component: {fileID: 565393630} + - component: {fileID: 565393631} + m_Layer: 5 + m_Name: WebGL + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &565393629 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 565393628} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1519446888} + - {fileID: 1334676550} + - {fileID: 1313662201} + m_Father: {fileID: 1665572489} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -220} + m_SizeDelta: {x: 0, y: 90} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &565393630 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 565393628} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 088c449aca9f79c4b929eea17a498d7d, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &565393631 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 565393628} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 1 + m_Spacing: 0 + m_ChildForceExpandWidth: 0 + m_ChildForceExpandHeight: 0 + m_ChildControlWidth: 0 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 --- !u!1 &567661510 GameObject: m_ObjectHideFlags: 0 @@ -1811,6 +1888,214 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1180987020} m_CullTransparentMesh: 0 +--- !u!1 &1313662200 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1313662201} + - component: {fileID: 1313662204} + - component: {fileID: 1313662203} + - component: {fileID: 1313662202} + m_Layer: 5 + m_Name: ThrowJS + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1313662201 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1313662200} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 2070520013} + m_Father: {fileID: 565393629} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 200, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1313662202 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1313662200} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1313662203} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 565393630} + m_MethodName: ThrowJavaScript + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!114 &1313662203 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1313662200} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1313662204 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1313662200} + m_CullTransparentMesh: 0 +--- !u!1 &1334676549 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1334676550} + - component: {fileID: 1334676552} + - component: {fileID: 1334676551} + m_Layer: 5 + m_Name: WebGL + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1334676550 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1334676549} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 565393629} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 200, y: 20} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &1334676551 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1334676549} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: JavaScript (WebGL) +--- !u!222 &1334676552 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1334676549} + m_CullTransparentMesh: 0 --- !u!1 &1348565635 GameObject: m_ObjectHideFlags: 0 @@ -2065,6 +2350,41 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1455265211} m_CullTransparentMesh: 0 +--- !u!1 &1519446887 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1519446888} + m_Layer: 5 + m_Name: =================== + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1519446888 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1519446887} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 565393629} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 200, y: 5} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &1574187300 GameObject: m_ObjectHideFlags: 0 @@ -2135,6 +2455,7 @@ RectTransform: - {fileID: 253040315} - {fileID: 1167211827} - {fileID: 121492395} + - {fileID: 565393629} - {fileID: 2041051215464099389} m_Father: {fileID: 0} m_RootOrder: 2 @@ -2218,6 +2539,7 @@ MonoBehaviour: m_EditorClassIdentifier: _androidButtons: {fileID: 1167211826} _iosButtons: {fileID: 121492394} + _webglButtons: {fileID: 565393628} --- !u!1 &1703366541 GameObject: m_ObjectHideFlags: 0 @@ -2634,6 +2956,84 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2018121283} m_CullTransparentMesh: 0 +--- !u!1 &2070520012 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2070520013} + - component: {fileID: 2070520015} + - component: {fileID: 2070520014} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2070520013 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2070520012} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1313662201} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2070520014 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2070520012} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 147 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Throw: JavaScript' +--- !u!222 &2070520015 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2070520012} + m_CullTransparentMesh: 0 --- !u!222 &2041051214740625425 CanvasRenderer: m_ObjectHideFlags: 0 @@ -2862,7 +3262,7 @@ RectTransform: - {fileID: 2041051215832613689} - {fileID: 2041051216597842965} m_Father: {fileID: 1665572489} - m_RootOrder: 4 + m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 0} diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib new file mode 100644 index 000000000..37c5458bd --- /dev/null +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib @@ -0,0 +1,9 @@ +mergeInto(LibraryManager.library, { + + throwJavaScript: function () { + var something = undefined; + console.log("JavaScript error incoming..."); + something.do(); + }, + +}); diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib.meta b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib.meta new file mode 100644 index 000000000..a0f65ff9f --- /dev/null +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: dc7f3d07d56c4baaa9abaa89f51d5738 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeSupportScene.cs b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeSupportScene.cs index 958457036..fc31899f4 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeSupportScene.cs +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeSupportScene.cs @@ -4,6 +4,7 @@ public class NativeSupportScene : MonoBehaviour { [SerializeField] private GameObject _androidButtons; [SerializeField] private GameObject _iosButtons; + [SerializeField] private GameObject _webglButtons; private void Start() { @@ -12,6 +13,9 @@ private void Start() #endif #if UNITY_EDITOR || !PLATFORM_IOS _iosButtons.SetActive(false); +#endif +#if UNITY_EDITOR || !PLATFORM_WEBGL + _webglButtons.SetActive(false); #endif } } diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/WebGLButtons.cs b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/WebGLButtons.cs new file mode 100644 index 000000000..0f12226f1 --- /dev/null +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/WebGLButtons.cs @@ -0,0 +1,20 @@ +using UnityEngine; +using System.Runtime.InteropServices; + +public class WebGLButtons : MonoBehaviour +{ + public void ThrowJavaScript() + { +#if PLATFORM_WEBGL + throwJavaScript(); +#else + Debug.Log("Requires WebGL."); +#endif + } + +#if PLATFORM_WEBGL + // JavaScriptPlugin.jslib + [DllImport("__Internal")] + private static extern void throwJavaScript(); +#endif +} diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/WebGLButtons.cs.meta b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/WebGLButtons.cs.meta new file mode 100644 index 000000000..c63db2d94 --- /dev/null +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/WebGLButtons.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 088c449aca9f79c4b929eea17a498d7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 817ae3a6c9ac998311caa93105a3c274a6777348 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 02/35] chore: webgl sample - disable compression --- samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset b/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset index de18da7a0..93c19dfd9 100644 --- a/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset +++ b/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset @@ -775,7 +775,7 @@ PlayerSettings: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 - webGLCompressionFormat: 1 + webGLCompressionFormat: 2 webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLWasmStreaming: 0 From 1a2e322adf5892140329678b2fb4af17cff292c4 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 03/35] webgl --- package-dev/Runtime/SentryInitialization.cs | 10 ++ .../ProjectSettings/ProjectSettings.asset | 4 +- src/Sentry.Unity/WebGL/SentryWebGL.cs | 106 ++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/Sentry.Unity/WebGL/SentryWebGL.cs diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 75fac4de0..469bd6f6a 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -5,9 +5,14 @@ #define SENTRY_NATIVE_ANDROID #elif UNITY_STANDALONE_WIN && ENABLE_IL2CPP #define SENTRY_NATIVE_WINDOWS +#elif UNITY_WEBGL +#define SENTRY_WEBGL #endif #endif +using System; +using System.Threading; +using System.Threading.Tasks; using UnityEngine; using UnityEngine.Scripting; @@ -18,6 +23,9 @@ #elif SENTRY_NATIVE_WINDOWS using Sentry.Unity.Native; #endif +using Sentry.Extensibility; +using Sentry.Internal; +using Sentry.Protocol.Envelopes; [assembly: AlwaysLinkAssembly] @@ -39,6 +47,8 @@ public static void Init() SentryNativeAndroid.Configure(options, sentryUnityInfo); #elif SENTRY_NATIVE_WINDOWS SentryNative.Configure(options); +#elif SENTRY_WEBGL + SentryWebGL.Configure(options); #endif SentryUnity.Init(options); diff --git a/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset b/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset index 93c19dfd9..1419b664b 100644 --- a/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset +++ b/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset @@ -766,10 +766,10 @@ PlayerSettings: blurSplashScreenBackground: 1 spritePackerPolicy: webGLMemorySize: 16 - webGLExceptionSupport: 1 + webGLExceptionSupport: 3 webGLNameFilesAsHashes: 0 webGLDataCaching: 1 - webGLDebugSymbols: 0 + webGLDebugSymbols: 1 webGLEmscriptenArgs: webGLModulesDirectory: webGLTemplate: APPLICATION:Default diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs new file mode 100644 index 000000000..4839cfda7 --- /dev/null +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Sentry.Extensibility; +using Sentry.Infrastructure; +using Sentry.Internal; +using Sentry.Protocol.Envelopes; +using UnityEngine; +using UnityEngine.Networking; + +namespace Sentry.Unity.WebGL +{ + /// + /// Configure Sentry for WebGL + /// + public static class SentryWebGL + { + /// + /// Configures the WebGL support. + /// + /// The Sentry Unity options to use. + public static void Configure(SentryUnityOptions options) + { + // Caching transport relies on a background thread + options.CacheDirectoryPath = null; + options.BackgroundWorker = new WebBackgroundWorker(options, SentryMonoBehaviour.Instance); + + // Still cant' find out what's using Threads so: + options.AutoSessionTracking = false; + options.DetectStartupTime = StartupTimeDetectionMode.None; + options.DisableTaskUnobservedTaskExceptionCapture(); + options.DisableAppDomainUnhandledExceptionCapture(); + options.DisableAppDomainProcessExitFlush(); + options.DisableDuplicateEventDetection(); + options.ReportAssembliesMode = ReportAssembliesMode.None; + } + } + + internal class WebBackgroundWorker : IBackgroundWorker + { + private readonly SentryUnityOptions _options; + private readonly ISystemClock _clock = new SystemClock(); + + private readonly SentryMonoBehaviour _behaviour; + // private readonly ITransport _transport; + + public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) + { + _options = options; + _behaviour = behaviour; + // var composer = new SdkComposer(options); + // HTTP transport is not compatible. Need to use Unity's one. + // _transport = composer.CreateTransport(); + } + + public bool EnqueueEnvelope(Envelope envelope) + { + // _transport.SendEnvelopeAsync(envelope, CancellationToken.None) + // .ContinueWith(r => _options.DiagnosticLogger?.LogInfo("Result of envelope capture was: {0}", r.Status)); + _ = _behaviour.StartCoroutine(SendEnvelope(envelope)); + return true; + } + + private IEnumerator SendEnvelope(Envelope envelope) + { + var dsn = Dsn.Parse(_options.Dsn!); + var authHeader = + $"Sentry sentry_version={_options.SentryVersion}," + + $"sentry_client={SdkVersion.Instance.Name}/{SdkVersion.Instance.Version}," + + $"sentry_key={dsn.PublicKey}," + + (dsn.SecretKey is { } secretKey ? $"sentry_secret={secretKey}," : null) + + $"sentry_timestamp={_clock.GetUtcNow().ToUnixTimeSeconds()}"; + + var www = new UnityWebRequest(dsn.GetEnvelopeEndpointUri()); + www.method = "POST"; + www.SetRequestHeader("X-Sentry-Auth", authHeader); + var stream = new MemoryStream(); + envelope.SerializeAsync(stream, _options.DiagnosticLogger).Wait(TimeSpan.FromSeconds(2)); + stream.Flush(); + www.uploadHandler = new UploadHandlerRaw(stream.ToArray()); + www.downloadHandler = new DownloadHandlerBuffer(); + yield return www.SendWebRequest(); + + while (!www.isDone) + { + yield return null; + } + if ( + www.isNetworkError || www.isHttpError + || www.responseCode != 200) + { + _options.DiagnosticLogger?.LogWarning("error sending request to sentry: {0}", www.error); + } + { + _options.DiagnosticLogger?.LogDebug("Sentry sent back: {0}", www.downloadHandler.text); + } + } + + public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; + + public int QueuedItems { get; } + } +} \ No newline at end of file From 48090764b84680529086d2418378be85e7b1a67c Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 04/35] fix: SentryWebGL version info sources --- package-dev/Runtime/SentryInitialization.cs | 2 ++ src/Sentry.Unity/WebGL/SentryWebGL.cs | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 469bd6f6a..38240b745 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -22,6 +22,8 @@ using Sentry.Unity.Android; #elif SENTRY_NATIVE_WINDOWS using Sentry.Unity.Native; +#elif SENTRY_WEBGL +using Sentry.Unity.WebGL; #endif using Sentry.Extensibility; using Sentry.Internal; diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 4839cfda7..6cfb8dd49 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -24,6 +24,8 @@ public static class SentryWebGL /// The Sentry Unity options to use. public static void Configure(SentryUnityOptions options) { + options.DiagnosticLogger?.LogDebug("Updating configuration for Unity WebGL."); + // Caching transport relies on a background thread options.CacheDirectoryPath = null; options.BackgroundWorker = new WebBackgroundWorker(options, SentryMonoBehaviour.Instance); @@ -68,8 +70,8 @@ private IEnumerator SendEnvelope(Envelope envelope) { var dsn = Dsn.Parse(_options.Dsn!); var authHeader = - $"Sentry sentry_version={_options.SentryVersion}," + - $"sentry_client={SdkVersion.Instance.Name}/{SdkVersion.Instance.Version}," + + $"Sentry sentry_version={Sentry.Constants.ProtocolVersion}," + + $"sentry_client={UnitySdkInfo.Name}/{UnitySdkInfo.Version}," + $"sentry_key={dsn.PublicKey}," + (dsn.SecretKey is { } secretKey ? $"sentry_secret={secretKey}," : null) + $"sentry_timestamp={_clock.GetUtcNow().ToUnixTimeSeconds()}"; From 9a6a063cede1fa73be22287d94a6221f58af9efe Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 05/35] Format code --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 6cfb8dd49..450aafa7d 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -105,4 +105,4 @@ private IEnumerator SendEnvelope(Envelope envelope) public int QueuedItems { get; } } -} \ No newline at end of file +} From d233371f3e78b8395394db067ff53ee26b7b1725 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 06/35] fix: WebGL BackgroundWorker implementation & sample packages --- samples/unity-of-bugs/Packages/manifest.json | 3 +- .../unity-of-bugs/Packages/packages-lock.json | 6 +++ src/Sentry.Unity/WebGL/SentryWebGL.cs | 41 +++++++------------ 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/samples/unity-of-bugs/Packages/manifest.json b/samples/unity-of-bugs/Packages/manifest.json index 0d63ad298..44fa10c4c 100644 --- a/samples/unity-of-bugs/Packages/manifest.json +++ b/samples/unity-of-bugs/Packages/manifest.json @@ -10,6 +10,7 @@ "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.audio": "1.0.0", "com.unity.modules.screencapture": "1.0.0", - "com.unity.modules.ui": "1.0.0" + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" } } diff --git a/samples/unity-of-bugs/Packages/packages-lock.json b/samples/unity-of-bugs/Packages/packages-lock.json index 568a6ef6d..6c5a63db9 100644 --- a/samples/unity-of-bugs/Packages/packages-lock.json +++ b/samples/unity-of-bugs/Packages/packages-lock.json @@ -128,6 +128,12 @@ "depth": 0, "source": "builtin", "dependencies": {} + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} } } } diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 450aafa7d..7fd95bf58 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -7,6 +7,7 @@ using Sentry.Extensibility; using Sentry.Infrastructure; using Sentry.Internal; +using Sentry.Internal.Http; using Sentry.Protocol.Envelopes; using UnityEngine; using UnityEngine.Networking; @@ -28,6 +29,9 @@ public static void Configure(SentryUnityOptions options) // Caching transport relies on a background thread options.CacheDirectoryPath = null; + // Note: we need to use a custom background worker which actually doesn't work in the background + // because Unity doesn't support async (multithreading) yet. This may change in the future so let's watch + // https://docs.unity3d.com/2019.4/Documentation/ScriptReference/PlayerSettings.WebGL-threadsSupport.html options.BackgroundWorker = new WebBackgroundWorker(options, SentryMonoBehaviour.Instance); // Still cant' find out what's using Threads so: @@ -53,32 +57,22 @@ public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behav { _options = options; _behaviour = behaviour; - // var composer = new SdkComposer(options); - // HTTP transport is not compatible. Need to use Unity's one. - // _transport = composer.CreateTransport(); } public bool EnqueueEnvelope(Envelope envelope) { - // _transport.SendEnvelopeAsync(envelope, CancellationToken.None) - // .ContinueWith(r => _options.DiagnosticLogger?.LogInfo("Result of envelope capture was: {0}", r.Status)); _ = _behaviour.StartCoroutine(SendEnvelope(envelope)); return true; } private IEnumerator SendEnvelope(Envelope envelope) { - var dsn = Dsn.Parse(_options.Dsn!); - var authHeader = - $"Sentry sentry_version={Sentry.Constants.ProtocolVersion}," + - $"sentry_client={UnitySdkInfo.Name}/{UnitySdkInfo.Version}," + - $"sentry_key={dsn.PublicKey}," + - (dsn.SecretKey is { } secretKey ? $"sentry_secret={secretKey}," : null) + - $"sentry_timestamp={_clock.GetUtcNow().ToUnixTimeSeconds()}"; - - var www = new UnityWebRequest(dsn.GetEnvelopeEndpointUri()); - www.method = "POST"; - www.SetRequestHeader("X-Sentry-Auth", authHeader); + var builder = new HttpRequestBuilder(_options); + var www = new UnityWebRequest(); + www.url = builder.GetEnvelopeEndpointUri().ToString(); + www.method = UnityWebRequest.kHttpVerbPOST; + www.SetRequestHeader(builder.AuthHeaderName, builder.AuthHeader(_clock.GetUtcNow())); + // TODO is it OK to call .Wait() here in webGL? var stream = new MemoryStream(); envelope.SerializeAsync(stream, _options.DiagnosticLogger).Wait(TimeSpan.FromSeconds(2)); stream.Flush(); @@ -86,19 +80,12 @@ private IEnumerator SendEnvelope(Envelope envelope) www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); - while (!www.isDone) - { - yield return null; - } - if ( - www.isNetworkError || www.isHttpError - || www.responseCode != 200) + if (www.isNetworkError || www.isHttpError || www.responseCode != 200) { - _options.DiagnosticLogger?.LogWarning("error sending request to sentry: {0}", www.error); - } - { - _options.DiagnosticLogger?.LogDebug("Sentry sent back: {0}", www.downloadHandler.text); + _options.DiagnosticLogger?.LogWarning("error sending request to Sentry: {0}", www.error); } + + _options.DiagnosticLogger?.LogDebug("Sentry sent back: {0}", www.downloadHandler.text); } public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; From 82d18dfa32cfa55cf9119dcf6323ffacd3e5a03b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 07/35] feat: implement WebGL with a custom HttpMessageHandler --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 111 +++++++++++++++++++------- 1 file changed, 83 insertions(+), 28 deletions(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 7fd95bf58..14ee099ba 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -1,10 +1,14 @@ using System; using System.Collections; using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; using Sentry.Extensibility; +using Sentry.Http; using Sentry.Infrastructure; using Sentry.Internal; using Sentry.Internal.Http; @@ -47,49 +51,100 @@ public static void Configure(SentryUnityOptions options) internal class WebBackgroundWorker : IBackgroundWorker { - private readonly SentryUnityOptions _options; - private readonly ISystemClock _clock = new SystemClock(); + private readonly HttpTransport _transport; + public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) => + _transport = new HttpTransport(options, new HttpClient(new UnityWebRequestMessageHandler(options, behaviour))); + + public bool EnqueueEnvelope(Envelope envelope) + { + _ = _transport.SendEnvelopeAsync(envelope); + return true; + } + + public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; // TODO maybe we can implement this somehow? + + public int QueuedItems { get; } + } + + internal class UnityWebRequestMessageHandler : HttpMessageHandler + { private readonly SentryMonoBehaviour _behaviour; - // private readonly ITransport _transport; + private readonly SentryOptions _options; - public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) + public UnityWebRequestMessageHandler(SentryOptions options, SentryMonoBehaviour behaviour) { - _options = options; _behaviour = behaviour; + _options = options; } - public bool EnqueueEnvelope(Envelope envelope) + protected override Task SendAsync(HttpRequestMessage message, CancellationToken cancellationToken) { - _ = _behaviour.StartCoroutine(SendEnvelope(envelope)); - return true; + var tcs = new TaskCompletionSource(); + _ = _behaviour.StartCoroutine(SendInCoroutine(message, cancellationToken, tcs)); + return tcs.Task; } - private IEnumerator SendEnvelope(Envelope envelope) + private IEnumerator SendInCoroutine(HttpRequestMessage message, CancellationToken cancellationToken, TaskCompletionSource tcs) { - var builder = new HttpRequestBuilder(_options); var www = new UnityWebRequest(); - www.url = builder.GetEnvelopeEndpointUri().ToString(); - www.method = UnityWebRequest.kHttpVerbPOST; - www.SetRequestHeader(builder.AuthHeaderName, builder.AuthHeader(_clock.GetUtcNow())); - // TODO is it OK to call .Wait() here in webGL? - var stream = new MemoryStream(); - envelope.SerializeAsync(stream, _options.DiagnosticLogger).Wait(TimeSpan.FromSeconds(2)); - stream.Flush(); - www.uploadHandler = new UploadHandlerRaw(stream.ToArray()); - www.downloadHandler = new DownloadHandlerBuffer(); - yield return www.SendWebRequest(); - - if (www.isNetworkError || www.isHttpError || www.responseCode != 200) + UnityWebRequestAsyncOperation? result; + try { - _options.DiagnosticLogger?.LogWarning("error sending request to Sentry: {0}", www.error); - } + www.url = message.RequestUri.ToString(); + www.method = message.Method.Method.ToUpperInvariant(); - _options.DiagnosticLogger?.LogDebug("Sentry sent back: {0}", www.downloadHandler.text); - } + foreach (var header in message.Headers) + { + www.SetRequestHeader(header.Key, string.Join(",", header.Value)); + } + + var stream = new MemoryStream(); + _ = message.Content.CopyToAsync(stream).Wait(2000, cancellationToken); + stream.Flush(); + www.uploadHandler = new UploadHandlerRaw(stream.ToArray()); + www.downloadHandler = new DownloadHandlerBuffer(); + result = www.SendWebRequest(); + } + catch (Exception e) + { + _options.DiagnosticLogger?.Log(SentryLevel.Warning, "Error sending request to Sentry", e); + _ = tcs.TrySetException(e); + result = null; + } + yield return result; - public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; - public int QueuedItems { get; } + if (result != null) + { + try + { + if (www.isNetworkError) + { + throw new Exception(www.error); + } + else + { + var response = new HttpResponseMessage((HttpStatusCode)www.responseCode); + foreach (var header in www.GetResponseHeaders()) + { + // Unity would throw if we tried to set content-length/content-length + if (!string.Equals(header.Key, "content-length", StringComparison.InvariantCultureIgnoreCase) + && !string.Equals(header.Key, "content-type", StringComparison.InvariantCultureIgnoreCase)) + { + response.Headers.Add(header.Key, header.Value); + } + } + response.Content = new StringContent(www.downloadHandler.text); + _ = tcs.TrySetResult(response); + } + } + catch (Exception e) + { + _options.DiagnosticLogger?.Log(SentryLevel.Warning, "Error sending request to Sentry", e); + _ = tcs.TrySetException(e); + } + } + } } } From ff9a1f0497215d5aa3bd34a6bd6f77097594c6cf Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 08/35] refactor: change WebGL implementation to override HttpTransport --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 196 ++++++++++++++++++-------- 1 file changed, 137 insertions(+), 59 deletions(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 14ee099ba..c86923c49 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Sentry.Extensibility; @@ -51,14 +52,18 @@ public static void Configure(SentryUnityOptions options) internal class WebBackgroundWorker : IBackgroundWorker { - private readonly HttpTransport _transport; + private readonly SentryMonoBehaviour _behaviour; + private readonly UnityWebRequestTransport _transport; - public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) => - _transport = new HttpTransport(options, new HttpClient(new UnityWebRequestMessageHandler(options, behaviour))); + public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) + { + _behaviour = behaviour; + _transport = new UnityWebRequestTransport(options, behaviour); + } public bool EnqueueEnvelope(Envelope envelope) { - _ = _transport.SendEnvelopeAsync(envelope); + _ = _behaviour.StartCoroutine(_transport.SendEnvelopeAsync(envelope)); return true; } @@ -67,84 +72,157 @@ public bool EnqueueEnvelope(Envelope envelope) public int QueuedItems { get; } } - internal class UnityWebRequestMessageHandler : HttpMessageHandler + internal class UnityWebRequestTransport : HttpTransport { - private readonly SentryMonoBehaviour _behaviour; - private readonly SentryOptions _options; + private readonly SentryUnityOptions _options; - public UnityWebRequestMessageHandler(SentryOptions options, SentryMonoBehaviour behaviour) + public UnityWebRequestTransport(SentryUnityOptions options, SentryMonoBehaviour behaviour) + : base(options, new HttpClient(new UnityWebRequestMessageHandler())) { - _behaviour = behaviour; _options = options; } - protected override Task SendAsync(HttpRequestMessage message, CancellationToken cancellationToken) + // adapted HttpTransport.SendEnvelopeAsync() + internal IEnumerator SendEnvelopeAsync(Envelope envelope) { - var tcs = new TaskCompletionSource(); - _ = _behaviour.StartCoroutine(SendInCoroutine(message, cancellationToken, tcs)); - return tcs.Task; + var instant = DateTimeOffset.Now; + + // Apply rate limiting and re-package envelope items + using var processedEnvelope = ProcessEnvelope(envelope, instant); + if (processedEnvelope.Items.Count != 0) + { + // Send envelope to ingress + var www = CreateWebRequest(CreateRequest(processedEnvelope)); + yield return www.SendWebRequest(); + + var response = GetResponse(www); + if (response != null) + { + // Read & set rate limits for future requests + ExtractRateLimits(response, instant); + + if (response.StatusCode != HttpStatusCode.OK) + { + HandleFailure(response, processedEnvelope, www); + } + else if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Debug) is true) + { + _options.DiagnosticLogger?.LogDebug("Envelope '{0}' sent successfully. Payload:\n{1}", + envelope.TryGetEventId(), Encoding.UTF8.GetString(www.uploadHandler.data)); + } + else + { + _options.DiagnosticLogger?.LogInfo("Envelope '{0}' successfully received by Sentry.", + processedEnvelope.TryGetEventId()); + } + } + } } - private IEnumerator SendInCoroutine(HttpRequestMessage message, CancellationToken cancellationToken, TaskCompletionSource tcs) + // adapted HttpTransport.HandleFailureAsync() + private void HandleFailure(HttpResponseMessage response, Envelope processedEnvelope, UnityWebRequest www) { - var www = new UnityWebRequest(); - UnityWebRequestAsyncOperation? result; - try + // Spare the overhead if level is not enabled + if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Error) is true && response.Content is { } content) { - www.url = message.RequestUri.ToString(); - www.method = message.Method.Method.ToUpperInvariant(); - - foreach (var header in message.Headers) + var responseString = ((ExposedStringContent)response.Content).Content; + if (string.Equals(content.Headers.ContentType?.MediaType, "application/json", + StringComparison.OrdinalIgnoreCase)) + { + using var document = JsonDocument.Parse(responseString); + LogFailure(response, processedEnvelope, document.RootElement); + } + else { - www.SetRequestHeader(header.Key, string.Join(",", header.Value)); + LogFailure(response, processedEnvelope, responseString); } - var stream = new MemoryStream(); - _ = message.Content.CopyToAsync(stream).Wait(2000, cancellationToken); - stream.Flush(); - www.uploadHandler = new UploadHandlerRaw(stream.ToArray()); - www.downloadHandler = new DownloadHandlerBuffer(); - result = www.SendWebRequest(); + // If debug level, dump the whole envelope to the logger + if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Debug) is true) + { + _options.DiagnosticLogger?.LogDebug("Failed envelope '{0}' has payload:\n{1}\n", + processedEnvelope.TryGetEventId(), Encoding.UTF8.GetString(www.uploadHandler.data)); + } } - catch (Exception e) + + // SDK is in debug mode, and envelope was too large. To help troubleshoot: + // NOTE: likely no point to do this on WebGL - who would check the file (in IndexDB)? + } + + private UnityWebRequest CreateWebRequest(HttpRequestMessage message) + { + var www = new UnityWebRequest(); + www.url = message.RequestUri.ToString(); + www.method = message.Method.Method.ToUpperInvariant(); + + foreach (var header in message.Headers) { - _options.DiagnosticLogger?.Log(SentryLevel.Warning, "Error sending request to Sentry", e); - _ = tcs.TrySetException(e); - result = null; + www.SetRequestHeader(header.Key, string.Join(",", header.Value)); } - yield return result; + var stream = new MemoryStream(); + _ = message.Content.CopyToAsync(stream).Wait(2000); + stream.Flush(); + www.uploadHandler = new UploadHandlerRaw(stream.ToArray()); + www.downloadHandler = new DownloadHandlerBuffer(); + return www; + } + + private HttpResponseMessage? GetResponse(UnityWebRequest www) + { - if (result != null) + if (www.result == UnityWebRequest.Result.ConnectionError) // unity 2021+ + // if (www.isNetworkError) { - try - { - if (www.isNetworkError) - { - throw new Exception(www.error); - } - else - { - var response = new HttpResponseMessage((HttpStatusCode)www.responseCode); - foreach (var header in www.GetResponseHeaders()) - { - // Unity would throw if we tried to set content-length/content-length - if (!string.Equals(header.Key, "content-length", StringComparison.InvariantCultureIgnoreCase) - && !string.Equals(header.Key, "content-type", StringComparison.InvariantCultureIgnoreCase)) - { - response.Headers.Add(header.Key, header.Value); - } - } - response.Content = new StringContent(www.downloadHandler.text); - _ = tcs.TrySetResult(response); - } - } - catch (Exception e) + _options.DiagnosticLogger?.LogWarning("Failed to set TCS request result for: {0}", www.error); + return null; + } + + var response = new HttpResponseMessage((HttpStatusCode)www.responseCode); + foreach (var header in www.GetResponseHeaders()) + { + // Unity would throw if we tried to set content-type or content-length + if (header.Key != "content-length" && header.Key != "content-type") { - _options.DiagnosticLogger?.Log(SentryLevel.Warning, "Error sending request to Sentry", e); - _ = tcs.TrySetException(e); + response.Headers.Add(header.Key, header.Value); } } + response.Content = new ExposedStringContent(www.downloadHandler.text); + return response; + } + } + + internal class UnityWebRequestMessageHandler : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage _, CancellationToken __) + { + // if this throws, see usages of HttpTransport._httpClient + throw new InvalidOperationException("UnityWebRequestMessageHandler must be unused"); + } + } + + internal class ExposedStringContent : StringContent + { + internal readonly String Content; + public ExposedStringContent(String data) : base(data) => Content = data; + } + + internal static class JsonExtensions + { + public static JsonElement? GetPropertyOrNull(this JsonElement json, string name) + { + if (json.ValueKind != JsonValueKind.Object) + { + return null; + } + + if (json.TryGetProperty(name, out var result) && + result.ValueKind is not JsonValueKind.Undefined and not JsonValueKind.Null) + { + return result; + } + + return null; } } } From d179b836c5b6c3ea89dcf9712b772169efaab696 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 09/35] chore: update WebGL sample test code --- .../Assets/Scripts/AdditionalButtons.cs | 14 +++++++++++++- .../Scripts/NativeSupport/JavaScriptPlugin.jslib | 9 +++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/samples/unity-of-bugs/Assets/Scripts/AdditionalButtons.cs b/samples/unity-of-bugs/Assets/Scripts/AdditionalButtons.cs index 94ad43926..a1f8a5553 100644 --- a/samples/unity-of-bugs/Assets/Scripts/AdditionalButtons.cs +++ b/samples/unity-of-bugs/Assets/Scripts/AdditionalButtons.cs @@ -42,8 +42,20 @@ public void CaptureMessageWithContext() SentrySdk.ConfigureScope(scope => scope.Contexts = null); } - public void BackgroundBreadcrumb() => + public void BackgroundBreadcrumb() + { +#if PLATFORM_WEBGL + StartCoroutine(CoroutineBreadcrumb()); +#else Task.Run(() => SentrySdk.AddBreadcrumb("Breadcrumb from the background", "background task")); +#endif + } + + private IEnumerator CoroutineBreadcrumb() + { + SentrySdk.AddBreadcrumb("Breadcrumb from the background", "coroutine"); + yield return null; + } public void CaptureMessageWithScreenshot() => StartCoroutine(CaptureScreenshot()); diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib index 37c5458bd..8d284da13 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib @@ -2,8 +2,13 @@ mergeInto(LibraryManager.library, { throwJavaScript: function () { var something = undefined; - console.log("JavaScript error incoming..."); - something.do(); + // Note: if we trigger the JS error by calling `something.do();` directly here, Unity get's stuck: + // An abnormal situation has occurred: the PlayerLoop internal function has been called recursively. Please contact Customer Support with a sample project so that we can reproduce the problem and troubleshoot it. + console.log("Scheduling a JavaScript error"); + setTimeout(() => { + console.log("JavaScript error incoming..."); + something.do(); + }, 0); }, }); From cfd3697060f18f6b9aea9d6eb83effb2619a18bb Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 10/35] feat: update SmokeTest for WebGL --- .github/workflows/ci.yml | 5 +++++ Directory.Build.targets | 9 +++++++++ samples/unity-of-bugs/Assets/Editor/Builder.cs | 1 + samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs | 5 +++++ src/Sentry.Unity/WebGL/SentryWebGL.cs | 2 ++ 5 files changed, 22 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c06417c98..3ee1e8b67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -193,6 +193,11 @@ jobs: path: samples/artifacts/builds/Android if-no-files-found: error + - name: Build WebGL Player + run: | + docker exec -e TEST_DSN=http://publickey@localhost:8000/12345 unity dotnet msbuild /t:UnityConfigureSentryOptions /p:Configuration=Release /p:OutDir=other src/Sentry.Unity + docker exec unity dotnet msbuild /t:UnityBuildPlayerWebGL /p:Configuration=Release /p:OutDir=other src/Sentry.Unity + package-validation: needs: [build] name: UPM Package validation diff --git a/Directory.Build.targets b/Directory.Build.targets index 166791755..c9323ac12 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -256,6 +256,15 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on + + + + + + + + + diff --git a/samples/unity-of-bugs/Assets/Editor/Builder.cs b/samples/unity-of-bugs/Assets/Editor/Builder.cs index 748d503d4..b8641900d 100644 --- a/samples/unity-of-bugs/Assets/Editor/Builder.cs +++ b/samples/unity-of-bugs/Assets/Editor/Builder.cs @@ -67,6 +67,7 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group) public static void BuildLinuxIl2CPPPlayer() => BuildIl2CPPPlayer(BuildTarget.StandaloneLinux64, BuildTargetGroup.Standalone); public static void BuildAndroidIl2CPPPlayer() => BuildIl2CPPPlayer(BuildTarget.Android, BuildTargetGroup.Android); public static void BuildIOSPlayer() => BuildIl2CPPPlayer(BuildTarget.iOS, BuildTargetGroup.iOS); + public static void BuildWebGLPlayer() => BuildIl2CPPPlayer(BuildTarget.WebGL, BuildTargetGroup.WebGL); private static void SetupSentryOptions(Dictionary args) { diff --git a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs index d9d224342..7020fb13f 100644 --- a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs +++ b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs @@ -14,6 +14,8 @@ using Sentry.Unity.Android; #elif SENTRY_NATIVE_WINDOWS using Sentry.Unity.Native; +#elif UNITY_WEBGL +using System.Web; #endif #if UNITY_IOS @@ -50,6 +52,9 @@ public void Start() #elif UNITY_IOS // .net `Environment.GetCommandLineArgs()` doens't seem to work on iOS so we get the test arg in Objective-C arg = getTestArgObjectiveC(); +#elif UNITY_WEBGL + var uri = new Uri(Application.absoluteURL); + arg = HttpUtility.ParseQueryString(uri.Query).Get("test"); #else var args = Environment.GetCommandLineArgs(); if (args.Length > 2 && args[1] == "--test") diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index c86923c49..9d83760a5 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -39,6 +39,8 @@ public static void Configure(SentryUnityOptions options) // https://docs.unity3d.com/2019.4/Documentation/ScriptReference/PlayerSettings.WebGL-threadsSupport.html options.BackgroundWorker = new WebBackgroundWorker(options, SentryMonoBehaviour.Instance); + options.CrashedLastRun = () => false; // no way to recognize crashes in WebGL yet + // Still cant' find out what's using Threads so: options.AutoSessionTracking = false; options.DetectStartupTime = StartupTimeDetectionMode.None; From 4aba6da19e3662da4f57323b6b5f9499afda9a10 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 11/35] test: webgl smoke test script --- .../Assets/Scripts/SmokeTester.cs | 5 + scripts/smoke-test-webgl.py | 94 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 scripts/smoke-test-webgl.py diff --git a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs index 7020fb13f..28b3443f9 100644 --- a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs +++ b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs @@ -319,8 +319,13 @@ public string GetMessage(int index) public bool CheckMessage(int index, String substring) { +#if UNITY_WEBGL + // Note: we cannot use the standard checks on WebGL - it would get stuck here because of the lack of multi-threading + return true; +#else var message = GetMessage(index); return message.Contains(substring) || message.Contains(substring.Replace("'", "\"")); +#endif } public void ExpectMessage(int index, String substring) => diff --git a/scripts/smoke-test-webgl.py b/scripts/smoke-test-webgl.py new file mode 100644 index 000000000..0ef5dfaa5 --- /dev/null +++ b/scripts/smoke-test-webgl.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +import time +import datetime +import os +from http.server import BaseHTTPRequestHandler, SimpleHTTPRequestHandler, ThreadingHTTPServer +from threading import Thread +from typing import final +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +# Testing approach: +# 1. Start an API server +# 2. Start a server that serves the pre-built WebGL app directory (index.html & co) +# 3. Wait for the smoke test to complete +# 4. Check the messages received on the API server + +host = '127.0.0.1' +htmlPort = 8080 +apiPort = 8000 +scriptDir = os.path.dirname(os.path.abspath(__file__)) +appDir = os.path.join(scriptDir, '..', 'samples', + 'artifacts', 'builds', 'WebGL') +apiRequests = [] + + +class AppDirHandler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=appDir, **kwargs) + + +appServer = ThreadingHTTPServer((host, htmlPort), AppDirHandler) +appServerThread = Thread(target=appServer.serve_forever) +appServerThread.start() + + +class ApiHandler(BaseHTTPRequestHandler): + def do_GET(self): + apiRequests.add(self.requestline) + + def do_POST(self): + apiRequests.add(self.requestline) + + +apiServer = ThreadingHTTPServer((host, apiPort), ApiHandler) +apiServerThread = Thread(target=apiServer.serve_forever) +apiServerThread.start() + + +class TestDriver: + def __init__(self): + options = Options() + options.add_experimental_option('excludeSwitches', ['enable-logging']) + d = DesiredCapabilities.CHROME + d['goog:loggingPrefs'] = {'browser': 'ALL'} + self.driver = webdriver.Chrome(options=options, desired_capabilities=d) + self.driver.get('http://{}:{}?test=smoke'.format(host, htmlPort)) + self.messages = [] + + def fetchMessages(self): + for entry in self.driver.get_log('browser'): + m = entry['message'] + entry['message'] = m[m.find('"'):].replace('\\n', '').strip('" ') + self.messages.append(entry) + + def hasMessage(self, message): + self.fetchMessages() + return any(message in entry['message'] for entry in self.messages) + + def dumpMessages(self): + self.fetchMessages() + for entry in self.messages: + print("CHROME: {} {}".format(datetime.datetime.fromtimestamp( + entry['timestamp']/1000).strftime('%H:%M:%S.%f'), entry['message'])) + + +def waitUntil(condition, interval=0.1, timeout=1): + start = time.time() + while not condition(): + if time.time() - start >= timeout: + raise Exception('Waiting timed out'.format(condition)) + time.sleep(interval) + + +driver = TestDriver() +try: + waitUntil(lambda: driver.hasMessage('SMOKE TEST: PASS'), timeout=10) +finally: + driver.dumpMessages() + driver.driver.quit() + appServer.shutdown() + apiServer.shutdown() + print('API requests: {}'.format(apiRequests)) From d7340a5019a02485282ce3602d2ca1a810c544ea Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 12/35] fix: compilation on Unity 2019 --- .../Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib | 2 +- samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset | 1 + src/Sentry.Unity/WebGL/SentryWebGL.cs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib index 8d284da13..e140a5d18 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/JavaScriptPlugin.jslib @@ -5,7 +5,7 @@ mergeInto(LibraryManager.library, { // Note: if we trigger the JS error by calling `something.do();` directly here, Unity get's stuck: // An abnormal situation has occurred: the PlayerLoop internal function has been called recursively. Please contact Customer Support with a sample project so that we can reproduce the problem and troubleshoot it. console.log("Scheduling a JavaScript error"); - setTimeout(() => { + setTimeout(function(){ console.log("JavaScript error incoming..."); something.do(); }, 0); diff --git a/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset b/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset index 1419b664b..c178218cb 100644 --- a/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset +++ b/samples/unity-of-bugs/ProjectSettings/ProjectSettings.asset @@ -784,6 +784,7 @@ PlayerSettings: scriptingBackend: Android: 1 Standalone: 1 + WebGL: 1 il2cppCompilerConfiguration: Android: 1 managedStrippingLevel: {} diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 9d83760a5..176966d05 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -173,8 +173,8 @@ private UnityWebRequest CreateWebRequest(HttpRequestMessage message) private HttpResponseMessage? GetResponse(UnityWebRequest www) { - if (www.result == UnityWebRequest.Result.ConnectionError) // unity 2021+ - // if (www.isNetworkError) + // if (www.result == UnityWebRequest.Result.ConnectionError) // unity 2021+ + if (www.isNetworkError) // Unity 2019 { _options.DiagnosticLogger?.LogWarning("Failed to set TCS request result for: {0}", www.error); return null; From 6308a9dc148d4c47feb5763f440335fe7731bab6 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 13/35] test: finalize webGL test scripts --- Directory.Build.targets | 7 ++ .../Assets/Scripts/SmokeTester.cs | 13 ++- scripts/smoke-test-webgl.py | 105 ++++++++++++------ src/Sentry.Unity/WebGL/SentryWebGL.cs | 2 +- 4 files changed, 91 insertions(+), 36 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index c9323ac12..beaad19f1 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -265,6 +265,13 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on + + + + + + + diff --git a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs index 28b3443f9..37dd401b4 100644 --- a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs +++ b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs @@ -102,7 +102,9 @@ public void Start() public static void Configure(SentryUnityOptions options) { Debug.Log("SmokeTester.Configure() called"); +#if UNITY_WEBGL options.CreateHttpClientHandler = () => t; +#endif _crashedLastRun = () => { if (options.CrashedLastRun != null) @@ -126,7 +128,7 @@ public static void SmokeTest() t.ExpectMessage(currentMessage, "'type':'session'"); var guid = Guid.NewGuid().ToString(); - Debug.LogError(guid); + Debug.LogError($"LogError(GUID)={guid}"); // Skip the session init requests (there may be multiple of othem). We can't skip them by a "positive" // because they're also repeated with standard events (in an envelope). @@ -141,11 +143,11 @@ public static void SmokeTest() Debug.Log($"Done skipping non-event requests. Last one was: #{currentMessage}"); t.ExpectMessage(currentMessage, "'type':'event'"); - t.ExpectMessage(currentMessage, guid); + t.ExpectMessage(currentMessage, $"LogError(GUID)={guid}"); - SentrySdk.CaptureMessage(guid); + SentrySdk.CaptureMessage($"CaptureMessage(GUID)={guid}"); t.ExpectMessage(++currentMessage, "'type':'event'"); - t.ExpectMessage(currentMessage, guid); + t.ExpectMessage(currentMessage, $"CaptureMessage(GUID)={guid}"); var ex = new Exception("Exception & context test"); AddContext(); @@ -281,7 +283,10 @@ public void Pass() // Exit Code 200 to avoid false positive from a graceful exit unrelated to this test run exitCode = 200; + +#if !UNITY_WEBGL // We don't quit on WebGL because outgoing HTTP requests (in coroutines) would be cancelled. Application.Quit(exitCode); +#endif } } diff --git a/scripts/smoke-test-webgl.py b/scripts/smoke-test-webgl.py index 0ef5dfaa5..c0caae6fd 100644 --- a/scripts/smoke-test-webgl.py +++ b/scripts/smoke-test-webgl.py @@ -1,51 +1,76 @@ #!/usr/bin/env python3 -import time +# Testing approach: +# 1. Start an server that serves the pre-built WebGL app directory (index.html & co), as well as logs API requests +# 3. Run the smoke test using chromedriver +# 4. Check the messages received on the API server + +import binascii import datetime +import logging +import time import os -from http.server import BaseHTTPRequestHandler, SimpleHTTPRequestHandler, ThreadingHTTPServer +from http import HTTPStatus +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from threading import Thread -from typing import final from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.desired_capabilities import DesiredCapabilities -# Testing approach: -# 1. Start an API server -# 2. Start a server that serves the pre-built WebGL app directory (index.html & co) -# 3. Wait for the smoke test to complete -# 4. Check the messages received on the API server - host = '127.0.0.1' -htmlPort = 8080 -apiPort = 8000 +port = 8000 scriptDir = os.path.dirname(os.path.abspath(__file__)) appDir = os.path.join(scriptDir, '..', 'samples', 'artifacts', 'builds', 'WebGL') -apiRequests = [] -class AppDirHandler(SimpleHTTPRequestHandler): - def __init__(self, *args, **kwargs): - super().__init__(*args, directory=appDir, **kwargs) +class RequestVerifier: + __requests = [] + __testNumber = 0 + def Capture(self, info, body): + print("TEST: Recieved HTTP Request #{} = {}\n{}".format( + len(self.__requests), info, body), flush=True) + self.__requests.append({"request": info, "body": body}) -appServer = ThreadingHTTPServer((host, htmlPort), AppDirHandler) -appServerThread = Thread(target=appServer.serve_forever) -appServerThread.start() + def Expect(self, message, result): + self.__testNumber += 1 + info = "TEST | #{}. {}: {}".format(self.__testNumber, + message, "PASS" if result else "FAIL") + if result: + print(info, flush=True) + else: + raise Exception(info) + def ExpectMessage(self, index, substring): + message = self.__requests[index]["body"] + self.Expect("HTTP Request #{} contains \"{}\".".format(index, substring), + substring in message or substring.replace("'", "\"") in message) -class ApiHandler(BaseHTTPRequestHandler): - def do_GET(self): - apiRequests.add(self.requestline) - def do_POST(self): - apiRequests.add(self.requestline) +t = RequestVerifier() + +class Handler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=appDir, **kwargs) -apiServer = ThreadingHTTPServer((host, apiPort), ApiHandler) -apiServerThread = Thread(target=apiServer.serve_forever) -apiServerThread.start() + def do_POST(self): + body = "" + content = self.rfile.read(int(self.headers['Content-Length'])) + try: + body = content.decode("utf-8") + except: + logging.exception("Exception while parsing an API request") + body = binascii.hexlify(bytearray(content)) + t.Capture(self.requestline, body) + self.send_response(HTTPStatus.OK, '{'+'}') + self.end_headers() + + +appServer = ThreadingHTTPServer((host, port), Handler) +appServerThread = Thread(target=appServer.serve_forever) +appServerThread.start() class TestDriver: @@ -54,8 +79,9 @@ def __init__(self): options.add_experimental_option('excludeSwitches', ['enable-logging']) d = DesiredCapabilities.CHROME d['goog:loggingPrefs'] = {'browser': 'ALL'} - self.driver = webdriver.Chrome(options=options, desired_capabilities=d) - self.driver.get('http://{}:{}?test=smoke'.format(host, htmlPort)) + self.driver = webdriver.Chrome( + options=options, desired_capabilities=d) + self.driver.get('http://{}:{}?test=smoke'.format(host, port)) self.messages = [] def fetchMessages(self): @@ -72,7 +98,7 @@ def dumpMessages(self): self.fetchMessages() for entry in self.messages: print("CHROME: {} {}".format(datetime.datetime.fromtimestamp( - entry['timestamp']/1000).strftime('%H:%M:%S.%f'), entry['message'])) + entry['timestamp']/1000).strftime('%H:%M:%S.%f'), entry['message']), flush=True) def waitUntil(condition, interval=0.1, timeout=1): @@ -90,5 +116,22 @@ def waitUntil(condition, interval=0.1, timeout=1): driver.dumpMessages() driver.driver.quit() appServer.shutdown() - apiServer.shutdown() - print('API requests: {}'.format(apiRequests)) + + +# Verify received API requests - see SmokeTester.cs - this is a copy-paste with minimal syntax changes +currentMessage = 0 +t.ExpectMessage(currentMessage, "'type':'event'") +t.ExpectMessage(currentMessage, "LogError(GUID)") +currentMessage += 1 +t.ExpectMessage(currentMessage, "'type':'event'") +t.ExpectMessage(currentMessage, "CaptureMessage(GUID)") +currentMessage += 1 +t.ExpectMessage(currentMessage, "'type':'event'") +t.ExpectMessage( + currentMessage, "'message':'crumb','type':'error','data':{'foo':'bar'},'category':'bread','level':'critical'}") +t.ExpectMessage(currentMessage, "'message':'scope-crumb'}") +t.ExpectMessage(currentMessage, "'extra':{'extra-key':42}") +t.ExpectMessage(currentMessage, "'tags':{'tag-key':'tag-value'") +t.ExpectMessage( + currentMessage, "'user':{'email':'email@example.com','id':'user-id','ip_address':'::1','username':'username','other':{'role':'admin'}}") +print('TEST: PASS', flush=True) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 176966d05..ff6547a9d 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -176,7 +176,7 @@ private UnityWebRequest CreateWebRequest(HttpRequestMessage message) // if (www.result == UnityWebRequest.Result.ConnectionError) // unity 2021+ if (www.isNetworkError) // Unity 2019 { - _options.DiagnosticLogger?.LogWarning("Failed to set TCS request result for: {0}", www.error); + _options.DiagnosticLogger?.LogWarning("Failed to send request: {0}", www.error); return null; } From 26039fadf4105df8a728baa3ccd71c356407942e Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:45 +0200 Subject: [PATCH 14/35] ci: add WebGL smoke test to CI --- .github/workflows/ci.yml | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ee1e8b67..04a0d21a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: paths-ignore: - '**.md' - '**.txt' - workflow_dispatch: # e.g. to manually trigger on foreign PRs + workflow_dispatch: # e.g. to manually trigger on foreign PRs env: LOWEST_SUPPORTED_UNITY_VERSION: 2019 @@ -198,6 +198,13 @@ jobs: docker exec -e TEST_DSN=http://publickey@localhost:8000/12345 unity dotnet msbuild /t:UnityConfigureSentryOptions /p:Configuration=Release /p:OutDir=other src/Sentry.Unity docker exec unity dotnet msbuild /t:UnityBuildPlayerWebGL /p:Configuration=Release /p:OutDir=other src/Sentry.Unity + - name: Upload WebGL Build + uses: actions/upload-artifact@v2 + with: + name: testapp-webgl-${{ matrix.unity-version }} + path: samples/artifacts/builds/WebGL + if-no-files-found: error + package-validation: needs: [build] name: UPM Package validation @@ -454,3 +461,25 @@ jobs: $runtime = "iOS " + $runtime } ./Scripts/smoke-test-ios.ps1 Test "$runtime" + + webgl-smoke-test: + needs: [build] + name: Run WebGL Unity ${{ matrix.unity-version }} Smoke Test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + unity-version: ['2019', '2020', '2021'] + steps: + - name: Checkout + uses: actions/checkout@v2.3.3 + + - name: Download test app artifact + uses: actions/download-artifact@v2 + with: + name: testapp-webgl-${{ matrix.unity-version }} + path: samples/artifacts/builds/WebGL + + - run: pip3 install --user selenium + + - run: python3 scripts/smoke-test-webgl.py From 1462ccad5b77c12bfc2fa482702abe4e3d7ce598 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:46 +0200 Subject: [PATCH 15/35] chore: minor code cleanup --- .github/workflows/ci.yml | 2 +- scripts/smoke-test-webgl.py | 4 ++-- src/Sentry.Unity/WebGL/SentryWebGL.cs | 9 +++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04a0d21a7..a95f711e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,7 +195,7 @@ jobs: - name: Build WebGL Player run: | - docker exec -e TEST_DSN=http://publickey@localhost:8000/12345 unity dotnet msbuild /t:UnityConfigureSentryOptions /p:Configuration=Release /p:OutDir=other src/Sentry.Unity + docker exec -e TEST_DSN=http://publickey@127.0.0.1:8000/12345 unity dotnet msbuild /t:UnityConfigureSentryOptions /p:Configuration=Release /p:OutDir=other src/Sentry.Unity docker exec unity dotnet msbuild /t:UnityBuildPlayerWebGL /p:Configuration=Release /p:OutDir=other src/Sentry.Unity - name: Upload WebGL Build diff --git a/scripts/smoke-test-webgl.py b/scripts/smoke-test-webgl.py index c0caae6fd..4923db76d 100644 --- a/scripts/smoke-test-webgl.py +++ b/scripts/smoke-test-webgl.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # Testing approach: -# 1. Start an server that serves the pre-built WebGL app directory (index.html & co), as well as logs API requests +# 1. Start a web=server for pre-built WebGL app directory (index.html & co) and to collect the API requests # 3. Run the smoke test using chromedriver -# 4. Check the messages received on the API server +# 4. Check the messages received by the API server import binascii import datetime diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index ff6547a9d..beea7ed51 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -41,7 +41,7 @@ public static void Configure(SentryUnityOptions options) options.CrashedLastRun = () => false; // no way to recognize crashes in WebGL yet - // Still cant' find out what's using Threads so: + // Still can't find out what's using Threads so: options.AutoSessionTracking = false; options.DetectStartupTime = StartupTimeDetectionMode.None; options.DisableTaskUnobservedTaskExceptionCapture(); @@ -69,7 +69,7 @@ public bool EnqueueEnvelope(Envelope envelope) return true; } - public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; // TODO maybe we can implement this somehow? + public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; public int QueuedItems { get; } } @@ -146,9 +146,6 @@ private void HandleFailure(HttpResponseMessage response, Envelope processedEnvel processedEnvelope.TryGetEventId(), Encoding.UTF8.GetString(www.uploadHandler.data)); } } - - // SDK is in debug mode, and envelope was too large. To help troubleshoot: - // NOTE: likely no point to do this on WebGL - who would check the file (in IndexDB)? } private UnityWebRequest CreateWebRequest(HttpRequestMessage message) @@ -198,7 +195,7 @@ internal class UnityWebRequestMessageHandler : HttpMessageHandler { protected override Task SendAsync(HttpRequestMessage _, CancellationToken __) { - // if this throws, see usages of HttpTransport._httpClient + // if this throws, see usages of HttpTransport._httpClient - all should be overridden by UnityWebRequestTransport throw new InvalidOperationException("UnityWebRequestMessageHandler must be unused"); } } From 5c038a80508f1af1c1d47e1563dd6ca5b06a5c57 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:46 +0200 Subject: [PATCH 16/35] chore: reenable features on WebGL --- .../NativeSupport/NativeSupportScene.cs | 3 ++- scripts/smoke-test-webgl.py | 2 ++ src/Sentry.Unity/WebGL/SentryWebGL.cs | 18 ++++++------------ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeSupportScene.cs b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeSupportScene.cs index fc31899f4..2c17ed1ed 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeSupportScene.cs +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/NativeSupportScene.cs @@ -14,7 +14,8 @@ private void Start() #if UNITY_EDITOR || !PLATFORM_IOS _iosButtons.SetActive(false); #endif -#if UNITY_EDITOR || !PLATFORM_WEBGL + // TODO: webgl native buttons support is currently not available - it requires a javascript error handling +#if true || UNITY_EDITOR || !PLATFORM_WEBGL _webglButtons.SetActive(false); #endif } diff --git a/scripts/smoke-test-webgl.py b/scripts/smoke-test-webgl.py index 4923db76d..df8b661cd 100644 --- a/scripts/smoke-test-webgl.py +++ b/scripts/smoke-test-webgl.py @@ -120,6 +120,8 @@ def waitUntil(condition, interval=0.1, timeout=1): # Verify received API requests - see SmokeTester.cs - this is a copy-paste with minimal syntax changes currentMessage = 0 +t.ExpectMessage(currentMessage, "'type':'session'") +currentMessage += 1 t.ExpectMessage(currentMessage, "'type':'event'") t.ExpectMessage(currentMessage, "LogError(GUID)") currentMessage += 1 diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index beea7ed51..6403f8d92 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -32,23 +32,17 @@ public static void Configure(SentryUnityOptions options) { options.DiagnosticLogger?.LogDebug("Updating configuration for Unity WebGL."); - // Caching transport relies on a background thread - options.CacheDirectoryPath = null; // Note: we need to use a custom background worker which actually doesn't work in the background // because Unity doesn't support async (multithreading) yet. This may change in the future so let's watch // https://docs.unity3d.com/2019.4/Documentation/ScriptReference/PlayerSettings.WebGL-threadsSupport.html options.BackgroundWorker = new WebBackgroundWorker(options, SentryMonoBehaviour.Instance); - options.CrashedLastRun = () => false; // no way to recognize crashes in WebGL yet - - // Still can't find out what's using Threads so: - options.AutoSessionTracking = false; - options.DetectStartupTime = StartupTimeDetectionMode.None; - options.DisableTaskUnobservedTaskExceptionCapture(); - options.DisableAppDomainUnhandledExceptionCapture(); - options.DisableAppDomainProcessExitFlush(); - options.DisableDuplicateEventDetection(); - options.ReportAssembliesMode = ReportAssembliesMode.None; + // We may be able to do so after implementing the JS support. + // Additionally, we could recognize the situation when the unity gets stuck due to an error in JS/native: + // "An abnormal situation has occurred: the PlayerLoop internal function has been called recursively. + // Please contact Customer Support with a sample project so that we can reproduce the problem and troubleshoot it." + // Maybe we could write a file when this error occurs and recognize it on the next start. Like unity-native. + options.CrashedLastRun = () => false; } } From 5b901a9215abc7b58bdc68ab88ded03fcaf29a75 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:46 +0200 Subject: [PATCH 17/35] refactor: update WebGL to use HttpTransportBase --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 56 +++------------------------ 1 file changed, 5 insertions(+), 51 deletions(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 6403f8d92..faed59d47 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -68,12 +68,12 @@ public bool EnqueueEnvelope(Envelope envelope) public int QueuedItems { get; } } - internal class UnityWebRequestTransport : HttpTransport + internal class UnityWebRequestTransport : HttpTransportBase { private readonly SentryUnityOptions _options; public UnityWebRequestTransport(SentryUnityOptions options, SentryMonoBehaviour behaviour) - : base(options, new HttpClient(new UnityWebRequestMessageHandler())) + : base(options) { _options = options; } @@ -81,11 +81,8 @@ public UnityWebRequestTransport(SentryUnityOptions options, SentryMonoBehaviour // adapted HttpTransport.SendEnvelopeAsync() internal IEnumerator SendEnvelopeAsync(Envelope envelope) { - var instant = DateTimeOffset.Now; - - // Apply rate limiting and re-package envelope items - using var processedEnvelope = ProcessEnvelope(envelope, instant); - if (processedEnvelope.Items.Count != 0) + using var processedEnvelope = ProcessEnvelope(envelope); + if (processedEnvelope.Items.Count > 0) { // Send envelope to ingress var www = CreateWebRequest(CreateRequest(processedEnvelope)); @@ -94,50 +91,7 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope) var response = GetResponse(www); if (response != null) { - // Read & set rate limits for future requests - ExtractRateLimits(response, instant); - - if (response.StatusCode != HttpStatusCode.OK) - { - HandleFailure(response, processedEnvelope, www); - } - else if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Debug) is true) - { - _options.DiagnosticLogger?.LogDebug("Envelope '{0}' sent successfully. Payload:\n{1}", - envelope.TryGetEventId(), Encoding.UTF8.GetString(www.uploadHandler.data)); - } - else - { - _options.DiagnosticLogger?.LogInfo("Envelope '{0}' successfully received by Sentry.", - processedEnvelope.TryGetEventId()); - } - } - } - } - - // adapted HttpTransport.HandleFailureAsync() - private void HandleFailure(HttpResponseMessage response, Envelope processedEnvelope, UnityWebRequest www) - { - // Spare the overhead if level is not enabled - if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Error) is true && response.Content is { } content) - { - var responseString = ((ExposedStringContent)response.Content).Content; - if (string.Equals(content.Headers.ContentType?.MediaType, "application/json", - StringComparison.OrdinalIgnoreCase)) - { - using var document = JsonDocument.Parse(responseString); - LogFailure(response, processedEnvelope, document.RootElement); - } - else - { - LogFailure(response, processedEnvelope, responseString); - } - - // If debug level, dump the whole envelope to the logger - if (_options.DiagnosticLogger?.IsEnabled(SentryLevel.Debug) is true) - { - _options.DiagnosticLogger?.LogDebug("Failed envelope '{0}' has payload:\n{1}\n", - processedEnvelope.TryGetEventId(), Encoding.UTF8.GetString(www.uploadHandler.data)); + HandleResponse(response, processedEnvelope); } } } From 06f6d1fa9f54a2de2cf6131c276645650f4720b8 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:46 +0200 Subject: [PATCH 18/35] refactor: futer WebGL implementation cleanup --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 66 +++++++++++---------------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index faed59d47..7a615d32b 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -54,7 +54,7 @@ internal class WebBackgroundWorker : IBackgroundWorker public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) { _behaviour = behaviour; - _transport = new UnityWebRequestTransport(options, behaviour); + _transport = new UnityWebRequestTransport(options); } public bool EnqueueEnvelope(Envelope envelope) @@ -72,7 +72,7 @@ internal class UnityWebRequestTransport : HttpTransportBase { private readonly SentryUnityOptions _options; - public UnityWebRequestTransport(SentryUnityOptions options, SentryMonoBehaviour behaviour) + public UnityWebRequestTransport(SentryUnityOptions options) : base(options) { _options = options; @@ -85,7 +85,8 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope) if (processedEnvelope.Items.Count > 0) { // Send envelope to ingress - var www = CreateWebRequest(CreateRequest(processedEnvelope)); + var httpRequest = CreateRequest(processedEnvelope); + var www = CreateWebRequest(httpRequest, processedEnvelope); yield return www.SendWebRequest(); var response = GetResponse(www); @@ -96,28 +97,40 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope) } } - private UnityWebRequest CreateWebRequest(HttpRequestMessage message) + private UnityWebRequest CreateWebRequest(HttpRequestMessage message, Envelope envelope) { - var www = new UnityWebRequest(); - www.url = message.RequestUri.ToString(); - www.method = message.Method.Method.ToUpperInvariant(); + // Note: In order to use the synchronous Envelope.Serialize() we ignore the `message.Content` + // which is an `EnvelopeHttpContent` instance and use the actual envelope it wraps. + var stream = new MemoryStream(); + try + { + envelope.Serialize(stream, _options.DiagnosticLogger); + stream.Flush(); + } + catch (Exception e) + { + _options.DiagnosticLogger?.LogError("Failed to serialize Envelope into the network stream", e); + throw; + } + + var www = new UnityWebRequest + { + url = message.RequestUri.ToString(), + method = message.Method.Method.ToUpperInvariant(), + uploadHandler = new UploadHandlerRaw(stream.ToArray()), + downloadHandler = new DownloadHandlerBuffer() + }; foreach (var header in message.Headers) { www.SetRequestHeader(header.Key, string.Join(",", header.Value)); } - var stream = new MemoryStream(); - _ = message.Content.CopyToAsync(stream).Wait(2000); - stream.Flush(); - www.uploadHandler = new UploadHandlerRaw(stream.ToArray()); - www.downloadHandler = new DownloadHandlerBuffer(); return www; } private HttpResponseMessage? GetResponse(UnityWebRequest www) { - // if (www.result == UnityWebRequest.Result.ConnectionError) // unity 2021+ if (www.isNetworkError) // Unity 2019 { @@ -134,7 +147,7 @@ private UnityWebRequest CreateWebRequest(HttpRequestMessage message) response.Headers.Add(header.Key, header.Value); } } - response.Content = new ExposedStringContent(www.downloadHandler.text); + response.Content = new StringContent(www.downloadHandler.text); return response; } } @@ -147,29 +160,4 @@ protected override Task SendAsync(HttpRequestMessage _, Can throw new InvalidOperationException("UnityWebRequestMessageHandler must be unused"); } } - - internal class ExposedStringContent : StringContent - { - internal readonly String Content; - public ExposedStringContent(String data) : base(data) => Content = data; - } - - internal static class JsonExtensions - { - public static JsonElement? GetPropertyOrNull(this JsonElement json, string name) - { - if (json.ValueKind != JsonValueKind.Object) - { - return null; - } - - if (json.TryGetProperty(name, out var result) && - result.ValueKind is not JsonValueKind.Undefined and not JsonValueKind.Null) - { - return result; - } - - return null; - } - } } From ef58bd94b68447551866eba4acd82b71b7f3e029 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:46 +0200 Subject: [PATCH 19/35] refactor: WebGL - use latest sentry-dotnet APIs --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 7a615d32b..25b248696 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -86,7 +86,7 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope) { // Send envelope to ingress var httpRequest = CreateRequest(processedEnvelope); - var www = CreateWebRequest(httpRequest, processedEnvelope); + var www = CreateWebRequest(httpRequest); yield return www.SendWebRequest(); var response = GetResponse(www); @@ -97,27 +97,17 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope) } } - private UnityWebRequest CreateWebRequest(HttpRequestMessage message, Envelope envelope) + private UnityWebRequest CreateWebRequest(HttpRequestMessage message) { // Note: In order to use the synchronous Envelope.Serialize() we ignore the `message.Content` // which is an `EnvelopeHttpContent` instance and use the actual envelope it wraps. - var stream = new MemoryStream(); - try - { - envelope.Serialize(stream, _options.DiagnosticLogger); - stream.Flush(); - } - catch (Exception e) - { - _options.DiagnosticLogger?.LogError("Failed to serialize Envelope into the network stream", e); - throw; - } + using var stream = ReadStreamFromHttpContent(message.Content); var www = new UnityWebRequest { url = message.RequestUri.ToString(), method = message.Method.Method.ToUpperInvariant(), - uploadHandler = new UploadHandlerRaw(stream.ToArray()), + uploadHandler = new UploadHandlerRaw(((MemoryStream)stream).ToArray()), downloadHandler = new DownloadHandlerBuffer() }; From b0b1cb1598b551f459764377ba88c9706a552e2d Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:46 +0200 Subject: [PATCH 20/35] chore: update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5baa1056..11933d748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +### Features + +- Bump Sentry Java SDK to v6.0.0-alpha.4 ([#653](https://github.com/getsentry/sentry-unity/pull/653)) + - [changelog](https://github.com/getsentry/sentry-java/blob/6.0.0-alpha.4/CHANGELOG.md) + - [diff](https://github.com/getsentry/sentry-java/compare/5.5.0...6.0.0-alpha.4) +- WebGL - .NET support ([#657](https://github.com/getsentry/sentry-unity/pull/657)) + ### Fixes - Whitespaces no longer cause issues when uploading symbols for Windows native ([#655](https://github.com/getsentry/sentry-unity/pull/655)) From 154ce694ef6ee11cbcc2b9446f683c9752f0d077 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 10:25:46 +0200 Subject: [PATCH 21/35] fix: disable async file IO on WebGL --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 25b248696..8e0a83843 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -43,6 +43,9 @@ public static void Configure(SentryUnityOptions options) // Please contact Customer Support with a sample project so that we can reproduce the problem and troubleshoot it." // Maybe we could write a file when this error occurs and recognize it on the next start. Like unity-native. options.CrashedLastRun = () => false; + + // Disable async when accessing files (e.g. FileStream(useAsync: true)) because it throws on WebGL. + options.UseAsyncFileIO = false; } } @@ -99,15 +102,12 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope) private UnityWebRequest CreateWebRequest(HttpRequestMessage message) { - // Note: In order to use the synchronous Envelope.Serialize() we ignore the `message.Content` - // which is an `EnvelopeHttpContent` instance and use the actual envelope it wraps. - using var stream = ReadStreamFromHttpContent(message.Content); - + using var contentStream = ReadStreamFromHttpContent(message.Content); var www = new UnityWebRequest { url = message.RequestUri.ToString(), method = message.Method.Method.ToUpperInvariant(), - uploadHandler = new UploadHandlerRaw(((MemoryStream)stream).ToArray()), + uploadHandler = new UploadHandlerRaw(((MemoryStream)contentStream).ToArray()), downloadHandler = new DownloadHandlerBuffer() }; From 0c5234dec450f2b856074ae082a8a57966f6b41e Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 12:30:28 +0200 Subject: [PATCH 22/35] ci: change docker image name to the new one with WebGL --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a95f711e4..546454fdb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,7 @@ jobs: key: samples/unity-of-bugs|${{ steps.env.outputs.unityVersion }}-${{ hashFiles('samples/unity-of-bugs/Packages/packages-lock.json') }} - name: Start the Unity docker container - run: docker run -td --name unity -v ${{ github.workspace }}:/sentry-unity --workdir /sentry-unity ghcr.io/getsentry/unity-docker:editor-ubuntu-${{ steps.env.outputs.unityVersion }}-main + run: docker run -td --name unity -v ${{ github.workspace }}:/sentry-unity --workdir /sentry-unity ghcr.io/getsentry/unity-docker:editor-ubuntu-${{ steps.env.outputs.unityVersion }} - uses: vaind/download-artifact@3682b366b0e69c7108d5cf991efe7034ea0dd56d with: From 0831f9c0281e2008ba2379148ae1b1ecb2777e86 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Apr 2022 16:13:15 +0200 Subject: [PATCH 23/35] fix: compilation on Unity 2020+ --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 8e0a83843..dc1794b9d 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -121,8 +121,12 @@ private UnityWebRequest CreateWebRequest(HttpRequestMessage message) private HttpResponseMessage? GetResponse(UnityWebRequest www) { - // if (www.result == UnityWebRequest.Result.ConnectionError) // unity 2021+ - if (www.isNetworkError) // Unity 2019 + // Let's disable treating "warning:obsolete" as an error here because the alternative of putting a static + // function to user code (to be able to use #if UNITY_2019) is just ugly. +#pragma warning disable 618 + // if (www.result == UnityWebRequest.Result.ConnectionError) // unity 2021+; `.result` not present on 2019 + if (www.isNetworkError) // Unity 2019; obsolete (error) on later versions +#pragma warning restore 618 { _options.DiagnosticLogger?.LogWarning("Failed to send request: {0}", www.error); return null; From fa35fe6eb41bc553374377ed44ef76e1874ef5b0 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Mon, 11 Apr 2022 16:39:01 +0200 Subject: [PATCH 24/35] Update src/Sentry.Unity/WebGL/SentryWebGL.cs Co-authored-by: Stefan Jandl --- src/Sentry.Unity/WebGL/SentryWebGL.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index dc1794b9d..4c4282ab9 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -124,7 +124,7 @@ private UnityWebRequest CreateWebRequest(HttpRequestMessage message) // Let's disable treating "warning:obsolete" as an error here because the alternative of putting a static // function to user code (to be able to use #if UNITY_2019) is just ugly. #pragma warning disable 618 - // if (www.result == UnityWebRequest.Result.ConnectionError) // unity 2021+; `.result` not present on 2019 + // if (www.result == UnityWebRequest.Result.ConnectionError) // Unity 2020.1+; `.result` not present on 2019 if (www.isNetworkError) // Unity 2019; obsolete (error) on later versions #pragma warning restore 618 { From 5543f851425f7513c4a38c8d69fd8fda4a888461 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 12 Apr 2022 15:18:16 +0200 Subject: [PATCH 25/35] chore: cleanups --- package-dev/Runtime/SentryInitialization.cs | 6 ------ samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs | 2 -- src/Sentry.Unity/WebGL/SentryWebGL.cs | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 38240b745..2c6381e62 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -10,9 +10,6 @@ #endif #endif -using System; -using System.Threading; -using System.Threading.Tasks; using UnityEngine; using UnityEngine.Scripting; @@ -25,9 +22,6 @@ #elif SENTRY_WEBGL using Sentry.Unity.WebGL; #endif -using Sentry.Extensibility; -using Sentry.Internal; -using Sentry.Protocol.Envelopes; [assembly: AlwaysLinkAssembly] diff --git a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs index 37dd401b4..4ecb1c04d 100644 --- a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs +++ b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs @@ -102,9 +102,7 @@ public void Start() public static void Configure(SentryUnityOptions options) { Debug.Log("SmokeTester.Configure() called"); -#if UNITY_WEBGL options.CreateHttpClientHandler = () => t; -#endif _crashedLastRun = () => { if (options.CrashedLastRun != null) diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 4c4282ab9..1a65a0d39 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -37,7 +37,7 @@ public static void Configure(SentryUnityOptions options) // https://docs.unity3d.com/2019.4/Documentation/ScriptReference/PlayerSettings.WebGL-threadsSupport.html options.BackgroundWorker = new WebBackgroundWorker(options, SentryMonoBehaviour.Instance); - // We may be able to do so after implementing the JS support. + // No way to recognize crashes in WebGL yet. We may be able to do so after implementing the JS support. // Additionally, we could recognize the situation when the unity gets stuck due to an error in JS/native: // "An abnormal situation has occurred: the PlayerLoop internal function has been called recursively. // Please contact Customer Support with a sample project so that we can reproduce the problem and troubleshoot it." From 39e06370d09db94941adfaeee9137018cca20733 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Tue, 12 Apr 2022 18:31:00 +0200 Subject: [PATCH 26/35] Apply suggestions from code review Co-authored-by: Bruno Garcia --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11933d748..99a4e6c80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,6 @@ ### Features -- Bump Sentry Java SDK to v6.0.0-alpha.4 ([#653](https://github.com/getsentry/sentry-unity/pull/653)) - - [changelog](https://github.com/getsentry/sentry-java/blob/6.0.0-alpha.4/CHANGELOG.md) - - [diff](https://github.com/getsentry/sentry-java/compare/5.5.0...6.0.0-alpha.4) - WebGL - .NET support ([#657](https://github.com/getsentry/sentry-unity/pull/657)) ### Fixes From 29e1adbfbf46df56cbabf6c7c8620236bc2214c1 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 12 Apr 2022 20:14:40 +0200 Subject: [PATCH 27/35] refactor: move WebRequest transport to its own file --- src/Sentry.Unity/UnityWebRequestTransport.cs | 111 +++++++++++++++++ src/Sentry.Unity/WebGL/SentryWebGL.cs | 123 ------------------- 2 files changed, 111 insertions(+), 123 deletions(-) create mode 100644 src/Sentry.Unity/UnityWebRequestTransport.cs diff --git a/src/Sentry.Unity/UnityWebRequestTransport.cs b/src/Sentry.Unity/UnityWebRequestTransport.cs new file mode 100644 index 000000000..f23f7c231 --- /dev/null +++ b/src/Sentry.Unity/UnityWebRequestTransport.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Sentry.Extensibility; +using Sentry.Http; +using Sentry.Protocol.Envelopes; +using UnityEngine; +using UnityEngine.Networking; + +namespace Sentry.Unity +{ + internal class WebBackgroundWorker : IBackgroundWorker + { + private readonly SentryMonoBehaviour _behaviour; + private readonly UnityWebRequestTransport _transport; + + public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) + { + _behaviour = behaviour; + _transport = new UnityWebRequestTransport(options); + } + + public bool EnqueueEnvelope(Envelope envelope) + { + _ = _behaviour.StartCoroutine(_transport.SendEnvelopeAsync(envelope)); + return true; + } + + public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; + + public int QueuedItems { get; } + } + + internal class UnityWebRequestTransport : HttpTransportBase + { + private readonly SentryUnityOptions _options; + + public UnityWebRequestTransport(SentryUnityOptions options) + : base(options) + { + _options = options; + } + + // adapted HttpTransport.SendEnvelopeAsync() + internal IEnumerator SendEnvelopeAsync(Envelope envelope) + { + using var processedEnvelope = ProcessEnvelope(envelope); + if (processedEnvelope.Items.Count > 0) + { + // Send envelope to ingress + var httpRequest = CreateRequest(processedEnvelope); + var www = CreateWebRequest(httpRequest); + yield return www.SendWebRequest(); + + var response = GetResponse(www); + if (response != null) + { + HandleResponse(response, processedEnvelope); + } + } + } + + private UnityWebRequest CreateWebRequest(HttpRequestMessage message) + { + using var contentStream = ReadStreamFromHttpContent(message.Content); + var www = new UnityWebRequest + { + url = message.RequestUri.ToString(), + method = message.Method.Method.ToUpperInvariant(), + uploadHandler = new UploadHandlerRaw(((MemoryStream)contentStream).ToArray()), + downloadHandler = new DownloadHandlerBuffer() + }; + + foreach (var header in message.Headers) + { + www.SetRequestHeader(header.Key, string.Join(",", header.Value)); + } + + return www; + } + + private HttpResponseMessage? GetResponse(UnityWebRequest www) + { + // Let's disable treating "warning:obsolete" as an error here because the alternative of putting a static + // function to user code (to be able to use #if UNITY_2019) is just ugly. +#pragma warning disable 618 + // if (www.result == UnityWebRequest.Result.ConnectionError) // Unity 2020.1+; `.result` not present on 2019 + if (www.isNetworkError) // Unity 2019; obsolete (error) on later versions +#pragma warning restore 618 + { + _options.DiagnosticLogger?.LogWarning("Failed to send request: {0}", www.error); + return null; + } + + var response = new HttpResponseMessage((HttpStatusCode)www.responseCode); + foreach (var header in www.GetResponseHeaders()) + { + // Unity would throw if we tried to set content-type or content-length + if (header.Key != "content-length" && header.Key != "content-type") + { + response.Headers.Add(header.Key, header.Value); + } + } + response.Content = new StringContent(www.downloadHandler.text); + return response; + } + } +} diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 1a65a0d39..b7df85bc4 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -1,21 +1,4 @@ -using System; -using System.Collections; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; using Sentry.Extensibility; -using Sentry.Http; -using Sentry.Infrastructure; -using Sentry.Internal; -using Sentry.Internal.Http; -using Sentry.Protocol.Envelopes; -using UnityEngine; -using UnityEngine.Networking; namespace Sentry.Unity.WebGL { @@ -48,110 +31,4 @@ public static void Configure(SentryUnityOptions options) options.UseAsyncFileIO = false; } } - - internal class WebBackgroundWorker : IBackgroundWorker - { - private readonly SentryMonoBehaviour _behaviour; - private readonly UnityWebRequestTransport _transport; - - public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) - { - _behaviour = behaviour; - _transport = new UnityWebRequestTransport(options); - } - - public bool EnqueueEnvelope(Envelope envelope) - { - _ = _behaviour.StartCoroutine(_transport.SendEnvelopeAsync(envelope)); - return true; - } - - public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; - - public int QueuedItems { get; } - } - - internal class UnityWebRequestTransport : HttpTransportBase - { - private readonly SentryUnityOptions _options; - - public UnityWebRequestTransport(SentryUnityOptions options) - : base(options) - { - _options = options; - } - - // adapted HttpTransport.SendEnvelopeAsync() - internal IEnumerator SendEnvelopeAsync(Envelope envelope) - { - using var processedEnvelope = ProcessEnvelope(envelope); - if (processedEnvelope.Items.Count > 0) - { - // Send envelope to ingress - var httpRequest = CreateRequest(processedEnvelope); - var www = CreateWebRequest(httpRequest); - yield return www.SendWebRequest(); - - var response = GetResponse(www); - if (response != null) - { - HandleResponse(response, processedEnvelope); - } - } - } - - private UnityWebRequest CreateWebRequest(HttpRequestMessage message) - { - using var contentStream = ReadStreamFromHttpContent(message.Content); - var www = new UnityWebRequest - { - url = message.RequestUri.ToString(), - method = message.Method.Method.ToUpperInvariant(), - uploadHandler = new UploadHandlerRaw(((MemoryStream)contentStream).ToArray()), - downloadHandler = new DownloadHandlerBuffer() - }; - - foreach (var header in message.Headers) - { - www.SetRequestHeader(header.Key, string.Join(",", header.Value)); - } - - return www; - } - - private HttpResponseMessage? GetResponse(UnityWebRequest www) - { - // Let's disable treating "warning:obsolete" as an error here because the alternative of putting a static - // function to user code (to be able to use #if UNITY_2019) is just ugly. -#pragma warning disable 618 - // if (www.result == UnityWebRequest.Result.ConnectionError) // Unity 2020.1+; `.result` not present on 2019 - if (www.isNetworkError) // Unity 2019; obsolete (error) on later versions -#pragma warning restore 618 - { - _options.DiagnosticLogger?.LogWarning("Failed to send request: {0}", www.error); - return null; - } - - var response = new HttpResponseMessage((HttpStatusCode)www.responseCode); - foreach (var header in www.GetResponseHeaders()) - { - // Unity would throw if we tried to set content-type or content-length - if (header.Key != "content-length" && header.Key != "content-type") - { - response.Headers.Add(header.Key, header.Value); - } - } - response.Content = new StringContent(www.downloadHandler.text); - return response; - } - } - - internal class UnityWebRequestMessageHandler : HttpMessageHandler - { - protected override Task SendAsync(HttpRequestMessage _, CancellationToken __) - { - // if this throws, see usages of HttpTransport._httpClient - all should be overridden by UnityWebRequestTransport - throw new InvalidOperationException("UnityWebRequestMessageHandler must be unused"); - } - } } From 5bc3c87fbbca2afa23d7d4a1bbf2e7f87d5ff510 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 12 Apr 2022 20:32:02 +0200 Subject: [PATCH 28/35] chore: update package snapshot --- test/Scripts.Tests/package-release.zip.snapshot | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Scripts.Tests/package-release.zip.snapshot b/test/Scripts.Tests/package-release.zip.snapshot index a581308ce..75cc78366 100644 --- a/test/Scripts.Tests/package-release.zip.snapshot +++ b/test/Scripts.Tests/package-release.zip.snapshot @@ -338,6 +338,8 @@ Samples~/unity-of-bugs/Scripts/NativeSupport/CppPlugin.cpp Samples~/unity-of-bugs/Scripts/NativeSupport/CppPlugin.cpp.meta Samples~/unity-of-bugs/Scripts/NativeSupport/IosButtons.cs Samples~/unity-of-bugs/Scripts/NativeSupport/IosButtons.cs.meta +Samples~/unity-of-bugs/Scripts/NativeSupport/JavaScriptPlugin.jslib +Samples~/unity-of-bugs/Scripts/NativeSupport/JavaScriptPlugin.jslib.meta Samples~/unity-of-bugs/Scripts/NativeSupport/KotlinPlugin.kt Samples~/unity-of-bugs/Scripts/NativeSupport/KotlinPlugin.kt.meta Samples~/unity-of-bugs/Scripts/NativeSupport/NativeButtons.cs @@ -346,6 +348,8 @@ Samples~/unity-of-bugs/Scripts/NativeSupport/NativeSupportScene.cs Samples~/unity-of-bugs/Scripts/NativeSupport/NativeSupportScene.cs.meta Samples~/unity-of-bugs/Scripts/NativeSupport/ObjectiveCPlugin.m Samples~/unity-of-bugs/Scripts/NativeSupport/ObjectiveCPlugin.m.meta +Samples~/unity-of-bugs/Scripts/NativeSupport/WebGLButtons.cs +Samples~/unity-of-bugs/Scripts/NativeSupport/WebGLButtons.cs.meta CHANGELOG.md CHANGELOG.md.meta Editor.meta From dcc113739e62ed1f68d4e0fd1d712e84858f7c3b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 12 Apr 2022 20:33:54 +0200 Subject: [PATCH 29/35] refactor: improve HttpRequestMessage stream type support --- src/Sentry.Unity/UnityWebRequestTransport.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Sentry.Unity/UnityWebRequestTransport.cs b/src/Sentry.Unity/UnityWebRequestTransport.cs index f23f7c231..a26f6e30c 100644 --- a/src/Sentry.Unity/UnityWebRequestTransport.cs +++ b/src/Sentry.Unity/UnityWebRequestTransport.cs @@ -66,11 +66,19 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope) private UnityWebRequest CreateWebRequest(HttpRequestMessage message) { using var contentStream = ReadStreamFromHttpContent(message.Content); + var contentMemoryStream = contentStream as MemoryStream; + if (contentMemoryStream is null) + { + contentMemoryStream = new MemoryStream(); + contentStream.CopyTo(contentMemoryStream); + contentMemoryStream.Flush(); + } + var www = new UnityWebRequest { url = message.RequestUri.ToString(), method = message.Method.Method.ToUpperInvariant(), - uploadHandler = new UploadHandlerRaw(((MemoryStream)contentStream).ToArray()), + uploadHandler = new UploadHandlerRaw(contentMemoryStream.ToArray()), downloadHandler = new DownloadHandlerBuffer() }; From cde6f21dec43d0366bbd9d9ffb4c0af63a3db158 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 13 Apr 2022 08:21:43 +0200 Subject: [PATCH 30/35] chore: update broken python packages on CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80dc121f4..dbb9b45c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -480,6 +480,6 @@ jobs: name: testapp-webgl-${{ matrix.unity-version }} path: samples/artifacts/builds/WebGL - - run: pip3 install --user selenium + - run: pip3 install --upgrade --user selenium urllib3 requests - run: python3 scripts/smoke-test-webgl.py From 810a772461ff3f638093217c643cc456258a8276 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 13 Apr 2022 10:42:46 +0200 Subject: [PATCH 31/35] chore: fix WebGL smoke test on CI --- .github/workflows/ci.yml | 1 + scripts/smoke-test-webgl.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbb9b45c3..d4c2ca0e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -483,3 +483,4 @@ jobs: - run: pip3 install --upgrade --user selenium urllib3 requests - run: python3 scripts/smoke-test-webgl.py + timeout-minutes: 10 diff --git a/scripts/smoke-test-webgl.py b/scripts/smoke-test-webgl.py index df8b661cd..c5ba7dba7 100644 --- a/scripts/smoke-test-webgl.py +++ b/scripts/smoke-test-webgl.py @@ -77,6 +77,7 @@ class TestDriver: def __init__(self): options = Options() options.add_experimental_option('excludeSwitches', ['enable-logging']) + options.add_argument('--headless') d = DesiredCapabilities.CHROME d['goog:loggingPrefs'] = {'browser': 'ALL'} self.driver = webdriver.Chrome( From 01769a5f1972f8584cc2a62f0fe3a473fe8584ad Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 13 Apr 2022 13:06:18 +0200 Subject: [PATCH 32/35] fix: SmokeTester GetTestArg when in the editor --- samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs index 711eef4bb..280b15904 100644 --- a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs +++ b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs @@ -49,7 +49,8 @@ public void Start() private static string GetTestArg() { string arg = null; -#if UNITY_ANDROID +#if UNITY_EDITOR +#elif UNITY_ANDROID using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var currentActivity = unityPlayer.GetStatic("currentActivity")) using (var intent = currentActivity.Call("getIntent")) From bfdf9d841c63cf383397cf6bec442fa76a2d02a4 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 14 Apr 2022 09:37:33 +0200 Subject: [PATCH 33/35] chore: skip unity shader errors on webgl smoke test --- scripts/smoke-test-webgl.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/smoke-test-webgl.py b/scripts/smoke-test-webgl.py index c5ba7dba7..ddeb1e04f 100644 --- a/scripts/smoke-test-webgl.py +++ b/scripts/smoke-test-webgl.py @@ -8,6 +8,7 @@ import binascii import datetime import logging +import re import time import os from http import HTTPStatus @@ -29,7 +30,11 @@ class RequestVerifier: __testNumber = 0 def Capture(self, info, body): - print("TEST: Recieved HTTP Request #{} = {}\n{}".format( + if re.match('"exception":{"values":[{"type":"The resource [^ ]+ could not be loaded from the resource file!"', body): + print( + "TEST: Skipping the received HTTP Request because it's an unrelated unity bug:\n{}".format(body)) + + print("TEST: Received HTTP Request #{} = {}\n{}".format( len(self.__requests), info, body), flush=True) self.__requests.append({"request": info, "body": body}) From ce4cd4ce0ff16f8d8dc748870643f630d0492ea7 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 14 Apr 2022 11:53:55 +0200 Subject: [PATCH 34/35] fixup: chore: skip unity shader errors on webgl smoke test --- scripts/smoke-test-webgl.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/smoke-test-webgl.py b/scripts/smoke-test-webgl.py index ddeb1e04f..7177ade3d 100644 --- a/scripts/smoke-test-webgl.py +++ b/scripts/smoke-test-webgl.py @@ -24,15 +24,20 @@ appDir = os.path.join(scriptDir, '..', 'samples', 'artifacts', 'builds', 'WebGL') +ignoreRegex = '"exception":{"values":\[{"type":"(' + '|'.join( + ['The resource [^ ]+ could not be loaded from the resource file!', 'GL.End requires material.SetPass before!']) + ')"' + class RequestVerifier: __requests = [] __testNumber = 0 def Capture(self, info, body): - if re.match('"exception":{"values":[{"type":"The resource [^ ]+ could not be loaded from the resource file!"', body): + match = re.search(ignoreRegex, body) + if match: print( - "TEST: Skipping the received HTTP Request because it's an unrelated unity bug:\n{}".format(body)) + "TEST: Skipping the received HTTP Request because it's an unrelated unity bug:\n{}".format(match.group(0))) + return print("TEST: Received HTTP Request #{} = {}\n{}".format( len(self.__requests), info, body), flush=True) From 9f82cd5db126085b9bc93d38b3e8a810d267314a Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Tue, 19 Apr 2022 10:42:52 +0200 Subject: [PATCH 35/35] Apply suggestions from code review Co-authored-by: Bruno Garcia --- Directory.Build.targets | 4 +--- samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs | 3 ++- src/Sentry.Unity/UnityWebRequestTransport.cs | 8 +++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 291ae172e..b1619e211 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -264,9 +264,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - - - + diff --git a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs index 280b15904..937d0208c 100644 --- a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs +++ b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs @@ -306,7 +306,8 @@ public string GetMessage(int index) public bool CheckMessage(int index, String substring) { #if UNITY_WEBGL - // Note: we cannot use the standard checks on WebGL - it would get stuck here because of the lack of multi-threading + // Note: we cannot use the standard checks on WebGL - it would get stuck here because of the lack of multi-threading. + // The verification is done in the python script used for WebGL smoke test return true; #else var message = GetMessage(index); diff --git a/src/Sentry.Unity/UnityWebRequestTransport.cs b/src/Sentry.Unity/UnityWebRequestTransport.cs index a26f6e30c..534b1b409 100644 --- a/src/Sentry.Unity/UnityWebRequestTransport.cs +++ b/src/Sentry.Unity/UnityWebRequestTransport.cs @@ -40,9 +40,7 @@ internal class UnityWebRequestTransport : HttpTransportBase public UnityWebRequestTransport(SentryUnityOptions options) : base(options) - { - _options = options; - } + => _options = options; // adapted HttpTransport.SendEnvelopeAsync() internal IEnumerator SendEnvelopeAsync(Envelope envelope) @@ -56,7 +54,7 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope) yield return www.SendWebRequest(); var response = GetResponse(www); - if (response != null) + if (response is not null) { HandleResponse(response, processedEnvelope); } @@ -107,7 +105,7 @@ private UnityWebRequest CreateWebRequest(HttpRequestMessage message) foreach (var header in www.GetResponseHeaders()) { // Unity would throw if we tried to set content-type or content-length - if (header.Key != "content-length" && header.Key != "content-type") + if (header.Key is not "content-length" && header.Key is not "content-type") { response.Headers.Add(header.Key, header.Value); }