diff --git a/api/Dockerfile b/api/Dockerfile index 0d949e25e..a6077fd13 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -3,6 +3,7 @@ LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base nodejs npm tini +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main ARG envarg # Add Tini # Startup daemon diff --git a/api/chalicelib/core/assist.py b/api/chalicelib/core/assist.py index efbc7b5c6..6fc8bcd90 100644 --- a/api/chalicelib/core/assist.py +++ b/api/chalicelib/core/assist.py @@ -35,9 +35,10 @@ def get_live_sessions_ws(project_id, body: schemas.LiveSessionsSearchPayloadSche } for f in body.filters: if f.type == schemas.LiveFilterType.metadata: - data["filter"][f.source] = f.value + data["filter"][f.source] = {"values": f.value, "operator": f.operator} + else: - data["filter"][f.type.value] = f.value + data["filter"][f.type.value] = {"values": f.value, "operator": f.operator} return __get_live_sessions_ws(project_id=project_id, data=data) diff --git a/api/chalicelib/core/integration_github.py b/api/chalicelib/core/integration_github.py index a05c946f4..2de8cc518 100644 --- a/api/chalicelib/core/integration_github.py +++ b/api/chalicelib/core/integration_github.py @@ -1,8 +1,9 @@ +import schemas from chalicelib.core import integration_base from chalicelib.core.integration_github_issue import GithubIntegrationIssue from chalicelib.utils import pg_client, helper -PROVIDER = "GITHUB" +PROVIDER = schemas.IntegrationType.github class GitHubIntegration(integration_base.BaseIntegration): diff --git a/api/chalicelib/core/integration_jira_cloud.py b/api/chalicelib/core/integration_jira_cloud.py index 7d8c956cf..469723e4e 100644 --- a/api/chalicelib/core/integration_jira_cloud.py +++ b/api/chalicelib/core/integration_jira_cloud.py @@ -1,8 +1,9 @@ +import schemas from chalicelib.core import integration_base from chalicelib.core.integration_jira_cloud_issue import JIRACloudIntegrationIssue from chalicelib.utils import pg_client, helper -PROVIDER = "JIRA" +PROVIDER = schemas.IntegrationType.jira def obfuscate_string(string): diff --git a/api/chalicelib/core/integrations_global.py b/api/chalicelib/core/integrations_global.py new file mode 100644 index 000000000..5b00a28bd --- /dev/null +++ b/api/chalicelib/core/integrations_global.py @@ -0,0 +1,61 @@ +import schemas +from chalicelib.utils import pg_client + + +def get_global_integrations_status(tenant_id, user_id, project_id): + with pg_client.PostgresClient() as cur: + cur.execute( + cur.mogrify(f"""\ + SELECT EXISTS((SELECT 1 + FROM public.oauth_authentication + WHERE user_id = %(user_id)s + AND provider = 'github')) AS {schemas.IntegrationType.github}, + EXISTS((SELECT 1 + FROM public.jira_cloud + WHERE user_id = %(user_id)s)) AS {schemas.IntegrationType.jira}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='bugsnag')) AS {schemas.IntegrationType.bugsnag}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='cloudwatch')) AS {schemas.IntegrationType.cloudwatch}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='datadog')) AS {schemas.IntegrationType.datadog}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='newrelic')) AS {schemas.IntegrationType.newrelic}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='rollbar')) AS {schemas.IntegrationType.rollbar}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='sentry')) AS {schemas.IntegrationType.sentry}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='stackdriver')) AS {schemas.IntegrationType.stackdriver}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='sumologic')) AS {schemas.IntegrationType.sumologic}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='elasticsearch')) AS {schemas.IntegrationType.elasticsearch}, + EXISTS((SELECT 1 + FROM public.webhooks + WHERE type='slack')) AS {schemas.IntegrationType.slack};""", + {"user_id": user_id, "tenant_id": tenant_id, "project_id": project_id}) + ) + current_integrations = cur.fetchone() + result = [] + for k in current_integrations.keys(): + result.append({"name": k, "integrated": current_integrations[k]}) + return result diff --git a/api/routers/core.py b/api/routers/core.py index 51f0fe85c..420d44e05 100644 --- a/api/routers/core.py +++ b/api/routers/core.py @@ -12,7 +12,7 @@ from chalicelib.core import log_tool_rollbar, sourcemaps, events, sessions_assig log_tool_cloudwatch, log_tool_sentry, log_tool_sumologic, log_tools, errors, sessions, \ log_tool_newrelic, announcements, log_tool_bugsnag, weekly_report, integration_jira_cloud, integration_github, \ assist, heatmaps, mobile, signup, tenants, errors_favorite_viewed, boarding, notifications, webhook, users, \ - custom_metrics, saved_search + custom_metrics, saved_search, integrations_global from chalicelib.core.collaboration_slack import Slack from chalicelib.utils import email_helper, helper, captcha from chalicelib.utils.TimeUTC import TimeUTC @@ -173,6 +173,14 @@ def session_top_filter_values(projectId: int, context: schemas.CurrentContext = return {'data': sessions_metas.get_top_key_values(projectId)} +@app.get('/{projectId}/integrations', tags=["integrations"]) +def get_integrations_status(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): + data = integrations_global.get_global_integrations_status(tenant_id=context.tenant_id, + user_id=context.user_id, + project_id=projectId) + return {"data": data} + + @app.post('/{projectId}/integrations/{integration}/notify/{integrationId}/{source}/{sourceId}', tags=["integrations"]) @app.put('/{projectId}/integrations/{integration}/notify/{integrationId}/{source}/{sourceId}', tags=["integrations"]) def integration_notify(projectId: int, integration: str, integrationId: int, source: str, sourceId: str, diff --git a/api/schemas.py b/api/schemas.py index ae52f7540..34399af67 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -1026,13 +1026,15 @@ class LiveFilterType(str, Enum): user_UUID = "USERUUID" tracker_version = "TRACKERVERSION" user_browser_version = "USERBROWSERVERSION" - user_device_type = "USERDEVICETYPE", + user_device_type = "USERDEVICETYPE" class LiveSessionSearchFilterSchema(BaseModel): value: Union[List[str], str] = Field(...) type: LiveFilterType = Field(...) source: Optional[str] = Field(None) + operator: Literal[SearchEventOperator._is.value, + SearchEventOperator._contains.value] = Field(SearchEventOperator._contains.value) @root_validator def validator(cls, values): @@ -1068,3 +1070,18 @@ class LiveSessionsSearchPayloadSchema(_PaginatedSchema): class Config: alias_generator = attribute_to_camel_case + + +class IntegrationType(str, Enum): + github = "GITHUB" + jira = "JIRA" + slack = "SLACK" + sentry = "SENTRY" + bugsnag = "BUGSNAG" + rollbar = "ROLLBAR" + elasticsearch = "ELASTICSEARCH" + datadog = "DATADOG" + sumologic = "SUMOLOGIC" + stackdriver = "STACKDRIVER" + cloudwatch = "CLOUDWATCH" + newrelic = "NEWRELIC" diff --git a/backend/Dockerfile b/backend/Dockerfile index 28bedcb40..fe8f9e7e4 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -50,7 +50,7 @@ ENV TZ=UTC \ ASSETS_SIZE_LIMIT=6291456 \ ASSETS_HEADERS="{ \"Cookie\": \"ABv=3;\" }" \ FS_CLEAN_HRS=72 \ - FILE_SPLIT_SIZE=500000 \ + FILE_SPLIT_SIZE=1000000 \ LOG_QUEUE_STATS_INTERVAL_SEC=60 \ DB_BATCH_QUEUE_LIMIT=20 \ DB_BATCH_SIZE_LIMIT=10000000 \ diff --git a/ee/api/Dockerfile b/ee/api/Dockerfile index 2500d2bfb..2a2aef8f0 100644 --- a/ee/api/Dockerfile +++ b/ee/api/Dockerfile @@ -3,6 +3,7 @@ LABEL Maintainer="Rajesh Rajendran" LABEL Maintainer="KRAIEM Taha Yassine" RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main RUN apk add --no-cache build-base libressl libffi-dev libressl-dev libxslt-dev libxml2-dev xmlsec-dev xmlsec nodejs npm tini +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main ARG envarg ENV SOURCE_MAP_VERSION=0.7.4 \ APP_NAME=chalice \ diff --git a/ee/api/chalicelib/core/integrations_global.py b/ee/api/chalicelib/core/integrations_global.py new file mode 100644 index 000000000..b923fc5ab --- /dev/null +++ b/ee/api/chalicelib/core/integrations_global.py @@ -0,0 +1,61 @@ +import schemas +from chalicelib.utils import pg_client + + +def get_global_integrations_status(tenant_id, user_id, project_id): + with pg_client.PostgresClient() as cur: + cur.execute( + cur.mogrify(f"""\ + SELECT EXISTS((SELECT 1 + FROM public.oauth_authentication + WHERE user_id = %(user_id)s + AND provider = 'github')) AS {schemas.IntegrationType.github}, + EXISTS((SELECT 1 + FROM public.jira_cloud + WHERE user_id = %(user_id)s)) AS {schemas.IntegrationType.jira}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='bugsnag')) AS {schemas.IntegrationType.bugsnag}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='cloudwatch')) AS {schemas.IntegrationType.cloudwatch}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='datadog')) AS {schemas.IntegrationType.datadog}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='newrelic')) AS {schemas.IntegrationType.newrelic}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='rollbar')) AS {schemas.IntegrationType.rollbar}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='sentry')) AS {schemas.IntegrationType.sentry}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='stackdriver')) AS {schemas.IntegrationType.stackdriver}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='sumologic')) AS {schemas.IntegrationType.sumologic}, + EXISTS((SELECT 1 + FROM public.integrations + WHERE project_id=%(project_id)s + AND provider='elasticsearch')) AS {schemas.IntegrationType.elasticsearch}, + EXISTS((SELECT 1 + FROM public.webhooks + WHERE type='slack' AND tenant_id=%(tenant_id)s)) AS {schemas.IntegrationType.slack};""", + {"user_id": user_id, "tenant_id": tenant_id, "project_id": project_id}) + ) + current_integrations = cur.fetchone() + result = [] + for k in current_integrations.keys(): + result.append({"name": k, "integrated": current_integrations[k]}) + return result diff --git a/ee/connectors/consumer.py b/ee/connectors/consumer.py index dfa856501..8a233ecb6 100644 --- a/ee/connectors/consumer.py +++ b/ee/connectors/consumer.py @@ -49,7 +49,7 @@ def main(): elif LEVEL == 'normal': n = handle_normal_message(message) - session_id = codec.decode_key(msg.key) + session_id = decode_key(msg.key) sessions[session_id] = handle_session(sessions[session_id], message) if sessions[session_id]: sessions[session_id].sessionid = session_id @@ -116,6 +116,15 @@ def attempt_batch_insert(batch): except Exception as e: print(repr(e)) +def decode_key(b) -> int: + """ + Decode the message key (encoded with little endian) + """ + try: + decoded = int.from_bytes(b, "little", signed=False) + except Exception as e: + raise UnicodeDecodeError(f"Error while decoding message key (SessionID) from {b}\n{e}") + return decoded if __name__ == '__main__': main() diff --git a/ee/connectors/main.py b/ee/connectors/main.py index 57349f6e9..ef3a824d9 100644 --- a/ee/connectors/main.py +++ b/ee/connectors/main.py @@ -49,7 +49,7 @@ def main(): elif LEVEL == 'normal': n = handle_normal_message(message) - session_id = codec.decode_key(msg.key) + session_id = decode_key(msg.key) sessions[session_id] = handle_session(sessions[session_id], message) if sessions[session_id]: sessions[session_id].sessionid = session_id @@ -116,6 +116,15 @@ def attempt_batch_insert(batch): except Exception as e: print(repr(e)) +def decode_key(b) -> int: + """ + Decode the message key (encoded with little endian) + """ + try: + decoded = int.from_bytes(b, "little", signed=False) + except Exception as e: + raise UnicodeDecodeError(f"Error while decoding message key (SessionID) from {b}\n{e}") + return decoded if __name__ == '__main__': main() diff --git a/ee/connectors/msgcodec/codec.py b/ee/connectors/msgcodec/codec.py index 18f074a33..5aeb0e4ed 100644 --- a/ee/connectors/msgcodec/codec.py +++ b/ee/connectors/msgcodec/codec.py @@ -1,8 +1,5 @@ import io -from msgcodec.messages import * - - class Codec: """ Implements encode/decode primitives @@ -63,608 +60,3 @@ class Codec: return s.decode("utf-8", errors="replace").replace("\x00", "\uFFFD") except UnicodeDecodeError: return None - - -class MessageCodec(Codec): - - def encode(self, m: Message) -> bytes: - ... - - def decode(self, b: bytes) -> Message: - reader = io.BytesIO(b) - message_id = self.read_message_id(reader) - - if message_id == 0: - return Timestamp( - timestamp=self.read_uint(reader) - ) - if message_id == 1: - return SessionStart( - timestamp=self.read_uint(reader), - project_id=self.read_uint(reader), - tracker_version=self.read_string(reader), - rev_id=self.read_string(reader), - user_uuid=self.read_string(reader), - user_agent=self.read_string(reader), - user_os=self.read_string(reader), - user_os_version=self.read_string(reader), - user_browser=self.read_string(reader), - user_browser_version=self.read_string(reader), - user_device=self.read_string(reader), - user_device_type=self.read_string(reader), - user_device_memory_size=self.read_uint(reader), - user_device_heap_size=self.read_uint(reader), - user_country=self.read_string(reader) - ) - - if message_id == 2: - return SessionDisconnect( - timestamp=self.read_uint(reader) - ) - - if message_id == 3: - return SessionEnd( - timestamp=self.read_uint(reader) - ) - - if message_id == 4: - return SetPageLocation( - url=self.read_string(reader), - referrer=self.read_string(reader), - navigation_start=self.read_uint(reader) - ) - - if message_id == 5: - return SetViewportSize( - width=self.read_uint(reader), - height=self.read_uint(reader) - ) - - if message_id == 6: - return SetViewportScroll( - x=self.read_int(reader), - y=self.read_int(reader) - ) - - if message_id == 7: - return CreateDocument() - - if message_id == 8: - return CreateElementNode( - id=self.read_uint(reader), - parent_id=self.read_uint(reader), - index=self.read_uint(reader), - tag=self.read_string(reader), - svg=self.read_boolean(reader), - ) - - if message_id == 9: - return CreateTextNode( - id=self.read_uint(reader), - parent_id=self.read_uint(reader), - index=self.read_uint(reader) - ) - - if message_id == 10: - return MoveNode( - id=self.read_uint(reader), - parent_id=self.read_uint(reader), - index=self.read_uint(reader) - ) - - if message_id == 11: - return RemoveNode( - id=self.read_uint(reader) - ) - - if message_id == 12: - return SetNodeAttribute( - id=self.read_uint(reader), - name=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 13: - return RemoveNodeAttribute( - id=self.read_uint(reader), - name=self.read_string(reader) - ) - - if message_id == 14: - return SetNodeData( - id=self.read_uint(reader), - data=self.read_string(reader) - ) - - if message_id == 15: - return SetCSSData( - id=self.read_uint(reader), - data=self.read_string(reader) - ) - - if message_id == 16: - return SetNodeScroll( - id=self.read_uint(reader), - x=self.read_int(reader), - y=self.read_int(reader), - ) - - if message_id == 17: - return SetInputTarget( - id=self.read_uint(reader), - label=self.read_string(reader) - ) - - if message_id == 18: - return SetInputValue( - id=self.read_uint(reader), - value=self.read_string(reader), - mask=self.read_int(reader), - ) - - if message_id == 19: - return SetInputChecked( - id=self.read_uint(reader), - checked=self.read_boolean(reader) - ) - - if message_id == 20: - return MouseMove( - x=self.read_uint(reader), - y=self.read_uint(reader) - ) - - if message_id == 21: - return MouseClick( - id=self.read_uint(reader), - hesitation_time=self.read_uint(reader), - label=self.read_string(reader) - ) - - if message_id == 22: - return ConsoleLog( - level=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 23: - return PageLoadTiming( - request_start=self.read_uint(reader), - response_start=self.read_uint(reader), - response_end=self.read_uint(reader), - dom_content_loaded_event_start=self.read_uint(reader), - dom_content_loaded_event_end=self.read_uint(reader), - load_event_start=self.read_uint(reader), - load_event_end=self.read_uint(reader), - first_paint=self.read_uint(reader), - first_contentful_paint=self.read_uint(reader) - ) - - if message_id == 24: - return PageRenderTiming( - speed_index=self.read_uint(reader), - visually_complete=self.read_uint(reader), - time_to_interactive=self.read_uint(reader), - ) - - if message_id == 25: - return JSException( - name=self.read_string(reader), - message=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 26: - return RawErrorEvent( - timestamp=self.read_uint(reader), - source=self.read_string(reader), - name=self.read_string(reader), - message=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 27: - return RawCustomEvent( - name=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 28: - return UserID( - id=self.read_string(reader) - ) - - if message_id == 29: - return UserAnonymousID( - id=self.read_string(reader) - ) - - if message_id == 30: - return Metadata( - key=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 31: - return PageEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - url=self.read_string(reader), - referrer=self.read_string(reader), - loaded=self.read_boolean(reader), - request_start=self.read_uint(reader), - response_start=self.read_uint(reader), - response_end=self.read_uint(reader), - dom_content_loaded_event_start=self.read_uint(reader), - dom_content_loaded_event_end=self.read_uint(reader), - load_event_start=self.read_uint(reader), - load_event_end=self.read_uint(reader), - first_paint=self.read_uint(reader), - first_contentful_paint=self.read_uint(reader), - speed_index=self.read_uint(reader), - visually_complete=self.read_uint(reader), - time_to_interactive=self.read_uint(reader) - ) - - if message_id == 32: - return InputEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - value=self.read_string(reader), - value_masked=self.read_boolean(reader), - label=self.read_string(reader), - ) - - if message_id == 33: - return ClickEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - hesitation_time=self.read_uint(reader), - label=self.read_string(reader) - ) - - if message_id == 34: - return ErrorEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - source=self.read_string(reader), - name=self.read_string(reader), - message=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 35: - - message_id = self.read_uint(reader) - ts = self.read_uint(reader) - if ts > 9999999999999: - ts = None - return ResourceEvent( - message_id=message_id, - timestamp=ts, - duration=self.read_uint(reader), - ttfb=self.read_uint(reader), - header_size=self.read_uint(reader), - encoded_body_size=self.read_uint(reader), - decoded_body_size=self.read_uint(reader), - url=self.read_string(reader), - type=self.read_string(reader), - success=self.read_boolean(reader), - method=self.read_string(reader), - status=self.read_uint(reader) - ) - - if message_id == 36: - return CustomEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - name=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 37: - return CSSInsertRule( - id=self.read_uint(reader), - rule=self.read_string(reader), - index=self.read_uint(reader) - ) - - if message_id == 38: - return CSSDeleteRule( - id=self.read_uint(reader), - index=self.read_uint(reader) - ) - - if message_id == 39: - return Fetch( - method=self.read_string(reader), - url=self.read_string(reader), - request=self.read_string(reader), - response=self.read_string(reader), - status=self.read_uint(reader), - timestamp=self.read_uint(reader), - duration=self.read_uint(reader) - ) - - if message_id == 40: - return Profiler( - name=self.read_string(reader), - duration=self.read_uint(reader), - args=self.read_string(reader), - result=self.read_string(reader) - ) - - if message_id == 41: - return OTable( - key=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 42: - return StateAction( - type=self.read_string(reader) - ) - - if message_id == 43: - return StateActionEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - type=self.read_string(reader) - ) - - if message_id == 44: - return Redux( - action=self.read_string(reader), - state=self.read_string(reader), - duration=self.read_uint(reader) - ) - - if message_id == 45: - return Vuex( - mutation=self.read_string(reader), - state=self.read_string(reader), - ) - - if message_id == 46: - return MobX( - type=self.read_string(reader), - payload=self.read_string(reader), - ) - - if message_id == 47: - return NgRx( - action=self.read_string(reader), - state=self.read_string(reader), - duration=self.read_uint(reader) - ) - - if message_id == 48: - return GraphQL( - operation_kind=self.read_string(reader), - operation_name=self.read_string(reader), - variables=self.read_string(reader), - response=self.read_string(reader) - ) - - if message_id == 49: - return PerformanceTrack( - frames=self.read_int(reader), - ticks=self.read_int(reader), - total_js_heap_size=self.read_uint(reader), - used_js_heap_size=self.read_uint(reader) - ) - - if message_id == 50: - return GraphQLEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - name=self.read_string(reader) - ) - - if message_id == 52: - return DomDrop( - timestamp=self.read_uint(reader) - ) - - if message_id == 53: - return ResourceTiming( - timestamp=self.read_uint(reader), - duration=self.read_uint(reader), - ttfb=self.read_uint(reader), - header_size=self.read_uint(reader), - encoded_body_size=self.read_uint(reader), - decoded_body_size=self.read_uint(reader), - url=self.read_string(reader), - initiator=self.read_string(reader) - ) - - if message_id == 54: - return ConnectionInformation( - downlink=self.read_uint(reader), - type=self.read_string(reader) - ) - - if message_id == 55: - return SetPageVisibility( - hidden=self.read_boolean(reader) - ) - - if message_id == 56: - return PerformanceTrackAggr( - timestamp_start=self.read_uint(reader), - timestamp_end=self.read_uint(reader), - min_fps=self.read_uint(reader), - avg_fps=self.read_uint(reader), - max_fps=self.read_uint(reader), - min_cpu=self.read_uint(reader), - avg_cpu=self.read_uint(reader), - max_cpu=self.read_uint(reader), - min_total_js_heap_size=self.read_uint(reader), - avg_total_js_heap_size=self.read_uint(reader), - max_total_js_heap_size=self.read_uint(reader), - min_used_js_heap_size=self.read_uint(reader), - avg_used_js_heap_size=self.read_uint(reader), - max_used_js_heap_size=self.read_uint(reader) - ) - - if message_id == 59: - return LongTask( - timestamp=self.read_uint(reader), - duration=self.read_uint(reader), - context=self.read_uint(reader), - container_type=self.read_uint(reader), - container_src=self.read_string(reader), - container_id=self.read_string(reader), - container_name=self.read_string(reader) - ) - - if message_id == 60: - return SetNodeURLBasedAttribute( - id=self.read_uint(reader), - name=self.read_string(reader), - value=self.read_string(reader), - base_url=self.read_string(reader) - ) - - if message_id == 61: - return SetStyleData( - id=self.read_uint(reader), - data=self.read_string(reader), - base_url=self.read_string(reader) - ) - - if message_id == 62: - return IssueEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - type=self.read_string(reader), - context_string=self.read_string(reader), - context=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 63: - return TechnicalInfo( - type=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 64: - return CustomIssue( - name=self.read_string(reader), - payload=self.read_string(reader) - ) - - if message_id == 65: - return PageClose() - - if message_id == 90: - return IOSSessionStart( - timestamp=self.read_uint(reader), - project_id=self.read_uint(reader), - tracker_version=self.read_string(reader), - rev_id=self.read_string(reader), - user_uuid=self.read_string(reader), - user_os=self.read_string(reader), - user_os_version=self.read_string(reader), - user_device=self.read_string(reader), - user_device_type=self.read_string(reader), - user_country=self.read_string(reader) - ) - - if message_id == 91: - return IOSSessionEnd( - timestamp=self.read_uint(reader) - ) - - if message_id == 92: - return IOSMetadata( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - key=self.read_string(reader), - value=self.read_string(reader) - ) - - if message_id == 94: - return IOSUserID( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - value=self.read_string(reader) - ) - - if message_id == 95: - return IOSUserAnonymousID( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - value=self.read_string(reader) - ) - - if message_id == 99: - return IOSScreenLeave( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - title=self.read_string(reader), - view_name=self.read_string(reader) - ) - - if message_id == 103: - return IOSLog( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - severity=self.read_string(reader), - content=self.read_string(reader) - ) - - if message_id == 104: - return IOSInternalError( - timestamp=self.read_uint(reader), - length=self.read_uint(reader), - content=self.read_string(reader) - ) - - if message_id == 110: - return IOSPerformanceAggregated( - timestamp_start=self.read_uint(reader), - timestamp_end=self.read_uint(reader), - min_fps=self.read_uint(reader), - avg_fps=self.read_uint(reader), - max_fps=self.read_uint(reader), - min_cpu=self.read_uint(reader), - avg_cpu=self.read_uint(reader), - max_cpu=self.read_uint(reader), - min_memory=self.read_uint(reader), - avg_memory=self.read_uint(reader), - max_memory=self.read_uint(reader), - min_battery=self.read_uint(reader), - avg_battery=self.read_uint(reader), - max_battery=self.read_uint(reader) - ) - - def read_message_id(self, reader: io.BytesIO) -> int: - """ - Read and return the first byte where the message id is encoded - """ - id_ = self.read_uint(reader) - return id_ - - @staticmethod - def check_message_id(b: bytes) -> int: - """ - todo: make it static and without reader. It's just the first byte - Read and return the first byte where the message id is encoded - """ - reader = io.BytesIO(b) - id_ = Codec.read_uint(reader) - - return id_ - - @staticmethod - def decode_key(b) -> int: - """ - Decode the message key (encoded with little endian) - """ - try: - decoded = int.from_bytes(b, "little", signed=False) - except Exception as e: - raise UnicodeDecodeError(f"Error while decoding message key (SessionID) from {b}\n{e}") - return decoded diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index c6e53b445..b500424b7 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -1,13 +1,20 @@ -""" -Representations of Kafka messages -""" -from abc import ABC +# Auto-generated, do not edit +from abc import ABC class Message(ABC): pass +class BatchMeta(Message): + __id__ = 80 + + def __init__(self, page_no, first_index, timestamp): + self.page_no = page_no + self.first_index = first_index + self.timestamp = timestamp + + class Timestamp(Message): __id__ = 0 @@ -18,10 +25,7 @@ class Timestamp(Message): class SessionStart(Message): __id__ = 1 - def __init__(self, timestamp, project_id, tracker_version, rev_id, user_uuid, - user_agent, user_os, user_os_version, user_browser, user_browser_version, - user_device, user_device_type, user_device_memory_size, user_device_heap_size, - user_country): + def __init__(self, timestamp, project_id, tracker_version, rev_id, user_uuid, user_agent, user_os, user_os_version, user_browser, user_browser_version, user_device, user_device_type, user_device_memory_size, user_device_heap_size, user_country, user_id): self.timestamp = timestamp self.project_id = project_id self.tracker_version = tracker_version @@ -37,6 +41,7 @@ class SessionStart(Message): self.user_device_memory_size = user_device_memory_size self.user_device_heap_size = user_device_heap_size self.user_country = user_country + self.user_id = user_id class SessionDisconnect(Message): @@ -48,7 +53,6 @@ class SessionDisconnect(Message): class SessionEnd(Message): __id__ = 3 - __name__ = 'SessionEnd' def __init__(self, timestamp): self.timestamp = timestamp @@ -82,13 +86,16 @@ class SetViewportScroll(Message): class CreateDocument(Message): __id__ = 7 + def __init__(self, ): + + class CreateElementNode(Message): __id__ = 8 def __init__(self, id, parent_id, index, tag, svg): self.id = id - self.parent_id = parent_id, + self.parent_id = parent_id self.index = index self.tag = tag self.svg = svg @@ -122,7 +129,7 @@ class RemoveNode(Message): class SetNodeAttribute(Message): __id__ = 12 - def __init__(self, id, name: str, value: str): + def __init__(self, id, name, value): self.id = id self.name = name self.value = value @@ -131,7 +138,7 @@ class SetNodeAttribute(Message): class RemoveNodeAttribute(Message): __id__ = 13 - def __init__(self, id, name: str): + def __init__(self, id, name): self.id = id self.name = name @@ -139,7 +146,7 @@ class RemoveNodeAttribute(Message): class SetNodeData(Message): __id__ = 14 - def __init__(self, id, data: str): + def __init__(self, id, data): self.id = id self.data = data @@ -147,7 +154,7 @@ class SetNodeData(Message): class SetCSSData(Message): __id__ = 15 - def __init__(self, id, data: str): + def __init__(self, id, data): self.id = id self.data = data @@ -155,7 +162,7 @@ class SetCSSData(Message): class SetNodeScroll(Message): __id__ = 16 - def __init__(self, id, x: int, y: int): + def __init__(self, id, x, y): self.id = id self.x = x self.y = y @@ -164,7 +171,7 @@ class SetNodeScroll(Message): class SetInputTarget(Message): __id__ = 17 - def __init__(self, id, label: str): + def __init__(self, id, label): self.id = id self.label = label @@ -172,7 +179,7 @@ class SetInputTarget(Message): class SetInputValue(Message): __id__ = 18 - def __init__(self, id, value: str, mask: int): + def __init__(self, id, value, mask): self.id = id self.value = value self.mask = mask @@ -181,7 +188,7 @@ class SetInputValue(Message): class SetInputChecked(Message): __id__ = 19 - def __init__(self, id, checked: bool): + def __init__(self, id, checked): self.id = id self.checked = checked @@ -194,10 +201,10 @@ class MouseMove(Message): self.y = y -class MouseClick(Message): +class MouseClickDepricated(Message): __id__ = 21 - def __init__(self, id, hesitation_time, label: str): + def __init__(self, id, hesitation_time, label): self.id = id self.hesitation_time = hesitation_time self.label = label @@ -206,7 +213,7 @@ class MouseClick(Message): class ConsoleLog(Message): __id__ = 22 - def __init__(self, level: str, value: str): + def __init__(self, level, value): self.level = level self.value = value @@ -214,9 +221,7 @@ class ConsoleLog(Message): class PageLoadTiming(Message): __id__ = 23 - def __init__(self, request_start, response_start, response_end, dom_content_loaded_event_start, - dom_content_loaded_event_end, load_event_start, load_event_end, - first_paint, first_contentful_paint): + def __init__(self, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint): self.request_start = request_start self.response_start = response_start self.response_end = response_end @@ -236,20 +241,20 @@ class PageRenderTiming(Message): self.visually_complete = visually_complete self.time_to_interactive = time_to_interactive + class JSException(Message): __id__ = 25 - def __init__(self, name: str, message: str, payload: str): + def __init__(self, name, message, payload): self.name = name self.message = message self.payload = payload -class RawErrorEvent(Message): +class IntegrationEvent(Message): __id__ = 26 - def __init__(self, timestamp, source: str, name: str, message: str, - payload: str): + def __init__(self, timestamp, source, name, message, payload): self.timestamp = timestamp self.source = source self.name = name @@ -260,7 +265,7 @@ class RawErrorEvent(Message): class RawCustomEvent(Message): __id__ = 27 - def __init__(self, name: str, payload: str): + def __init__(self, name, payload): self.name = name self.payload = payload @@ -268,44 +273,29 @@ class RawCustomEvent(Message): class UserID(Message): __id__ = 28 - def __init__(self, id: str): + def __init__(self, id): self.id = id class UserAnonymousID(Message): __id__ = 29 - def __init__(self, id: str): + def __init__(self, id): self.id = id class Metadata(Message): __id__ = 30 - def __init__(self, key: str, value: str): + def __init__(self, key, value): self.key = key self.value = value -class PerformanceTrack(Message): - __id__ = 49 - - def __init__(self, frames: int, ticks: int, total_js_heap_size, - used_js_heap_size): - self.frames = frames - self.ticks = ticks - self.total_js_heap_size = total_js_heap_size - self.used_js_heap_size = used_js_heap_size - - class PageEvent(Message): __id__ = 31 - def __init__(self, message_id, timestamp, url: str, referrer: str, - loaded: bool, request_start, response_start, response_end, - dom_content_loaded_event_start, dom_content_loaded_event_end, - load_event_start, load_event_end, first_paint, first_contentful_paint, - speed_index, visually_complete, time_to_interactive): + def __init__(self, message_id, timestamp, url, referrer, loaded, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint, speed_index, visually_complete, time_to_interactive): self.message_id = message_id self.timestamp = timestamp self.url = url @@ -328,7 +318,7 @@ class PageEvent(Message): class InputEvent(Message): __id__ = 32 - def __init__(self, message_id, timestamp, value: str, value_masked: bool, label: str): + def __init__(self, message_id, timestamp, value, value_masked, label): self.message_id = message_id self.timestamp = timestamp self.value = value @@ -339,18 +329,18 @@ class InputEvent(Message): class ClickEvent(Message): __id__ = 33 - def __init__(self, message_id, timestamp, hesitation_time, label: str): + def __init__(self, message_id, timestamp, hesitation_time, label, selector): self.message_id = message_id self.timestamp = timestamp self.hesitation_time = hesitation_time self.label = label + self.selector = selector class ErrorEvent(Message): __id__ = 34 - def __init__(self, message_id, timestamp, source: str, name: str, message: str, - payload: str): + def __init__(self, message_id, timestamp, source, name, message, payload): self.message_id = message_id self.timestamp = timestamp self.source = source @@ -362,8 +352,7 @@ class ErrorEvent(Message): class ResourceEvent(Message): __id__ = 35 - def __init__(self, message_id, timestamp, duration, ttfb, header_size, encoded_body_size, - decoded_body_size, url: str, type: str, success: bool, method: str, status): + def __init__(self, message_id, timestamp, duration, ttfb, header_size, encoded_body_size, decoded_body_size, url, type, success, method, status): self.message_id = message_id self.timestamp = timestamp self.duration = duration @@ -381,7 +370,7 @@ class ResourceEvent(Message): class CustomEvent(Message): __id__ = 36 - def __init__(self, message_id, timestamp, name: str, payload: str): + def __init__(self, message_id, timestamp, name, payload): self.message_id = message_id self.timestamp = timestamp self.name = name @@ -391,7 +380,7 @@ class CustomEvent(Message): class CSSInsertRule(Message): __id__ = 37 - def __init__(self, id, rule: str, index): + def __init__(self, id, rule, index): self.id = id self.rule = rule self.index = index @@ -408,8 +397,7 @@ class CSSDeleteRule(Message): class Fetch(Message): __id__ = 39 - def __init__(self, method: str, url: str, request: str, response: str, status, - timestamp, duration): + def __init__(self, method, url, request, response, status, timestamp, duration): self.method = method self.url = url self.request = request @@ -422,7 +410,7 @@ class Fetch(Message): class Profiler(Message): __id__ = 40 - def __init__(self, name: str, duration, args: str, result: str): + def __init__(self, name, duration, args, result): self.name = name self.duration = duration self.args = args @@ -432,7 +420,7 @@ class Profiler(Message): class OTable(Message): __id__ = 41 - def __init__(self, key: str, value: str): + def __init__(self, key, value): self.key = key self.value = value @@ -440,14 +428,14 @@ class OTable(Message): class StateAction(Message): __id__ = 42 - def __init__(self, type: str): + def __init__(self, type): self.type = type class StateActionEvent(Message): __id__ = 43 - def __init__(self, message_id, timestamp, type: str): + def __init__(self, message_id, timestamp, type): self.message_id = message_id self.timestamp = timestamp self.type = type @@ -456,7 +444,7 @@ class StateActionEvent(Message): class Redux(Message): __id__ = 44 - def __init__(self, action: str, state: str, duration): + def __init__(self, action, state, duration): self.action = action self.state = state self.duration = duration @@ -465,7 +453,7 @@ class Redux(Message): class Vuex(Message): __id__ = 45 - def __init__(self, mutation: str, state: str): + def __init__(self, mutation, state): self.mutation = mutation self.state = state @@ -473,7 +461,7 @@ class Vuex(Message): class MobX(Message): __id__ = 46 - def __init__(self, type: str, payload: str): + def __init__(self, type, payload): self.type = type self.payload = payload @@ -481,7 +469,7 @@ class MobX(Message): class NgRx(Message): __id__ = 47 - def __init__(self, action: str, state: str, duration): + def __init__(self, action, state, duration): self.action = action self.state = state self.duration = duration @@ -490,8 +478,7 @@ class NgRx(Message): class GraphQL(Message): __id__ = 48 - def __init__(self, operation_kind: str, operation_name: str, - variables: str, response: str): + def __init__(self, operation_kind, operation_name, variables, response): self.operation_kind = operation_kind self.operation_name = operation_name self.variables = variables @@ -501,8 +488,7 @@ class GraphQL(Message): class PerformanceTrack(Message): __id__ = 49 - def __init__(self, frames: int, ticks: int, - total_js_heap_size, used_js_heap_size): + def __init__(self, frames, ticks, total_js_heap_size, used_js_heap_size): self.frames = frames self.ticks = ticks self.total_js_heap_size = total_js_heap_size @@ -512,13 +498,30 @@ class PerformanceTrack(Message): class GraphQLEvent(Message): __id__ = 50 - def __init__(self, message_id, timestamp, name: str): + def __init__(self, message_id, timestamp, operation_kind, operation_name, variables, response): self.message_id = message_id self.timestamp = timestamp - self.name = name + self.operation_kind = operation_kind + self.operation_name = operation_name + self.variables = variables + self.response = response -class DomDrop(Message): +class FetchEvent(Message): + __id__ = 51 + + def __init__(self, message_id, timestamp, method, url, request, response, status, duration): + self.message_id = message_id + self.timestamp = timestamp + self.method = method + self.url = url + self.request = request + self.response = response + self.status = status + self.duration = duration + + +class DOMDrop(Message): __id__ = 52 def __init__(self, timestamp): @@ -528,8 +531,7 @@ class DomDrop(Message): class ResourceTiming(Message): __id__ = 53 - def __init__(self, timestamp, duration, ttfb, header_size, encoded_body_size, - decoded_body_size, url, initiator): + def __init__(self, timestamp, duration, ttfb, header_size, encoded_body_size, decoded_body_size, url, initiator): self.timestamp = timestamp self.duration = duration self.ttfb = ttfb @@ -543,7 +545,7 @@ class ResourceTiming(Message): class ConnectionInformation(Message): __id__ = 54 - def __init__(self, downlink, type: str): + def __init__(self, downlink, type): self.downlink = downlink self.type = type @@ -551,19 +553,14 @@ class ConnectionInformation(Message): class SetPageVisibility(Message): __id__ = 55 - def __init__(self, hidden: bool): + def __init__(self, hidden): self.hidden = hidden class PerformanceTrackAggr(Message): __id__ = 56 - def __init__(self, timestamp_start, timestamp_end, min_fps, avg_fps, - max_fps, min_cpu, avg_cpu, max_cpu, - min_total_js_heap_size, avg_total_js_heap_size, - max_total_js_heap_size, min_used_js_heap_size, - avg_used_js_heap_size, max_used_js_heap_size - ): + def __init__(self, timestamp_start, timestamp_end, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size): self.timestamp_start = timestamp_start self.timestamp_end = timestamp_end self.min_fps = min_fps @@ -583,8 +580,7 @@ class PerformanceTrackAggr(Message): class LongTask(Message): __id__ = 59 - def __init__(self, timestamp, duration, context, container_type, container_src: str, - container_id: str, container_name: str): + def __init__(self, timestamp, duration, context, container_type, container_src, container_id, container_name): self.timestamp = timestamp self.duration = duration self.context = context @@ -594,20 +590,20 @@ class LongTask(Message): self.container_name = container_name -class SetNodeURLBasedAttribute(Message): +class SetNodeAttributeURLBased(Message): __id__ = 60 - def __init__(self, id, name: str, value: str, base_url: str): + def __init__(self, id, name, value, base_url): self.id = id self.name = name self.value = value self.base_url = base_url -class SetStyleData(Message): +class SetCSSDataURLBased(Message): __id__ = 61 - def __init__(self, id, data: str, base_url: str): + def __init__(self, id, data, base_url): self.id = id self.data = data self.base_url = base_url @@ -616,8 +612,7 @@ class SetStyleData(Message): class IssueEvent(Message): __id__ = 62 - def __init__(self, message_id, timestamp, type: str, context_string: str, - context: str, payload: str): + def __init__(self, message_id, timestamp, type, context_string, context, payload): self.message_id = message_id self.timestamp = timestamp self.type = type @@ -629,7 +624,7 @@ class IssueEvent(Message): class TechnicalInfo(Message): __id__ = 63 - def __init__(self, type: str, value: str): + def __init__(self, type, value): self.type = type self.value = value @@ -637,7 +632,7 @@ class TechnicalInfo(Message): class CustomIssue(Message): __id__ = 64 - def __init__(self, name: str, payload: str): + def __init__(self, name, payload): self.name = name self.payload = payload @@ -645,13 +640,58 @@ class CustomIssue(Message): class PageClose(Message): __id__ = 65 + def __init__(self, ): + + + +class AssetCache(Message): + __id__ = 66 + + def __init__(self, url): + self.url = url + + +class CSSInsertRuleURLBased(Message): + __id__ = 67 + + def __init__(self, id, rule, index, base_url): + self.id = id + self.rule = rule + self.index = index + self.base_url = base_url + + +class MouseClick(Message): + __id__ = 69 + + def __init__(self, id, hesitation_time, label, selector): + self.id = id + self.hesitation_time = hesitation_time + self.label = label + self.selector = selector + + +class CreateIFrameDocument(Message): + __id__ = 70 + + def __init__(self, frame_id, id): + self.frame_id = frame_id + self.id = id + + +class IOSBatchMeta(Message): + __id__ = 107 + + def __init__(self, timestamp, length, first_index): + self.timestamp = timestamp + self.length = length + self.first_index = first_index + class IOSSessionStart(Message): __id__ = 90 - def __init__(self, timestamp, project_id, tracker_version: str, - rev_id: str, user_uuid: str, user_os: str, user_os_version: str, - user_device: str, user_device_type: str, user_country: str): + def __init__(self, timestamp, project_id, tracker_version, rev_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country): self.timestamp = timestamp self.project_id = project_id self.tracker_version = tracker_version @@ -674,17 +714,27 @@ class IOSSessionEnd(Message): class IOSMetadata(Message): __id__ = 92 - def __init__(self, timestamp, length, key: str, value: str): + def __init__(self, timestamp, length, key, value): self.timestamp = timestamp self.length = length self.key = key self.value = value +class IOSCustomEvent(Message): + __id__ = 93 + + def __init__(self, timestamp, length, name, payload): + self.timestamp = timestamp + self.length = length + self.name = name + self.payload = payload + + class IOSUserID(Message): __id__ = 94 - def __init__(self, timestamp, length, value: str): + def __init__(self, timestamp, length, value): self.timestamp = timestamp self.length = length self.value = value @@ -693,26 +743,91 @@ class IOSUserID(Message): class IOSUserAnonymousID(Message): __id__ = 95 - def __init__(self, timestamp, length, value: str): + def __init__(self, timestamp, length, value): self.timestamp = timestamp self.length = length self.value = value -class IOSScreenLeave(Message): - __id__ = 99 +class IOSScreenChanges(Message): + __id__ = 96 - def __init__(self, timestamp, length, title: str, view_name: str): + def __init__(self, timestamp, length, x, y, width, height): + self.timestamp = timestamp + self.length = length + self.x = x + self.y = y + self.width = width + self.height = height + + +class IOSCrash(Message): + __id__ = 97 + + def __init__(self, timestamp, length, name, reason, stacktrace): + self.timestamp = timestamp + self.length = length + self.name = name + self.reason = reason + self.stacktrace = stacktrace + + +class IOSScreenEnter(Message): + __id__ = 98 + + def __init__(self, timestamp, length, title, view_name): self.timestamp = timestamp self.length = length self.title = title self.view_name = view_name +class IOSScreenLeave(Message): + __id__ = 99 + + def __init__(self, timestamp, length, title, view_name): + self.timestamp = timestamp + self.length = length + self.title = title + self.view_name = view_name + + +class IOSClickEvent(Message): + __id__ = 100 + + def __init__(self, timestamp, length, label, x, y): + self.timestamp = timestamp + self.length = length + self.label = label + self.x = x + self.y = y + + +class IOSInputEvent(Message): + __id__ = 101 + + def __init__(self, timestamp, length, value, value_masked, label): + self.timestamp = timestamp + self.length = length + self.value = value + self.value_masked = value_masked + self.label = label + + +class IOSPerformanceEvent(Message): + __id__ = 102 + + def __init__(self, timestamp, length, name, value): + self.timestamp = timestamp + self.length = length + self.name = name + self.value = value + + class IOSLog(Message): __id__ = 103 - def __init__(self, timestamp, length, severity: str, content: str): + def __init__(self, timestamp, length, severity, content): self.timestamp = timestamp self.length = length self.severity = severity @@ -722,20 +837,31 @@ class IOSLog(Message): class IOSInternalError(Message): __id__ = 104 - def __init__(self, timestamp, length, content: str): + def __init__(self, timestamp, length, content): self.timestamp = timestamp self.length = length self.content = content +class IOSNetworkCall(Message): + __id__ = 105 + + def __init__(self, timestamp, length, duration, headers, body, url, success, method, status): + self.timestamp = timestamp + self.length = length + self.duration = duration + self.headers = headers + self.body = body + self.url = url + self.success = success + self.method = method + self.status = status + + class IOSPerformanceAggregated(Message): __id__ = 110 - def __init__(self, timestamp_start, timestamp_end, min_fps, avg_fps, - max_fps, min_cpu, avg_cpu, max_cpu, - min_memory, avg_memory, max_memory, - min_battery, avg_battery, max_battery - ): + def __init__(self, timestamp_start, timestamp_end, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_memory, avg_memory, max_memory, min_battery, avg_battery, max_battery): self.timestamp_start = timestamp_start self.timestamp_end = timestamp_end self.min_fps = min_fps @@ -750,3 +876,16 @@ class IOSPerformanceAggregated(Message): self.min_battery = min_battery self.avg_battery = avg_battery self.max_battery = max_battery + + +class IOSIssueEvent(Message): + __id__ = 111 + + def __init__(self, timestamp, type, context_string, context, payload): + self.timestamp = timestamp + self.type = type + self.context_string = context_string + self.context = context + self.payload = payload + + diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py new file mode 100644 index 000000000..3bd74499c --- /dev/null +++ b/ee/connectors/msgcodec/msgcodec.py @@ -0,0 +1,728 @@ +# Auto-generated, do not edit + +from msgcodec.codec import Codec +from msgcodec.messages import * + +class MessageCodec(Codec): + + def read_message_id(self, reader: io.BytesIO) -> int: + """ + Read and return the first byte where the message id is encoded + """ + id_ = self.read_uint(reader) + return id_ + + def encode(self, m: Message) -> bytes: + ... + + def decode(self, b: bytes) -> Message: + reader = io.BytesIO(b) + message_id = self.read_message_id(reader) + + if message_id == 80: + return BatchMeta( + page_no=self.read_uint(reader), + first_index=self.read_uint(reader), + timestamp=self.read_int(reader) + ) + + if message_id == 0: + return Timestamp( + timestamp=self.read_uint(reader) + ) + + if message_id == 1: + return SessionStart( + timestamp=self.read_uint(reader), + project_id=self.read_uint(reader), + tracker_version=self.read_string(reader), + rev_id=self.read_string(reader), + user_uuid=self.read_string(reader), + user_agent=self.read_string(reader), + user_os=self.read_string(reader), + user_os_version=self.read_string(reader), + user_browser=self.read_string(reader), + user_browser_version=self.read_string(reader), + user_device=self.read_string(reader), + user_device_type=self.read_string(reader), + user_device_memory_size=self.read_uint(reader), + user_device_heap_size=self.read_uint(reader), + user_country=self.read_string(reader), + user_id=self.read_string(reader) + ) + + if message_id == 2: + return SessionDisconnect( + timestamp=self.read_uint(reader) + ) + + if message_id == 3: + return SessionEnd( + timestamp=self.read_uint(reader) + ) + + if message_id == 4: + return SetPageLocation( + url=self.read_string(reader), + referrer=self.read_string(reader), + navigation_start=self.read_uint(reader) + ) + + if message_id == 5: + return SetViewportSize( + width=self.read_uint(reader), + height=self.read_uint(reader) + ) + + if message_id == 6: + return SetViewportScroll( + x=self.read_int(reader), + y=self.read_int(reader) + ) + + if message_id == 7: + return CreateDocument( + + ) + + if message_id == 8: + return CreateElementNode( + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader), + tag=self.read_string(reader), + svg=self.read_boolean(reader) + ) + + if message_id == 9: + return CreateTextNode( + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader) + ) + + if message_id == 10: + return MoveNode( + id=self.read_uint(reader), + parent_id=self.read_uint(reader), + index=self.read_uint(reader) + ) + + if message_id == 11: + return RemoveNode( + id=self.read_uint(reader) + ) + + if message_id == 12: + return SetNodeAttribute( + id=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 13: + return RemoveNodeAttribute( + id=self.read_uint(reader), + name=self.read_string(reader) + ) + + if message_id == 14: + return SetNodeData( + id=self.read_uint(reader), + data=self.read_string(reader) + ) + + if message_id == 15: + return SetCSSData( + id=self.read_uint(reader), + data=self.read_string(reader) + ) + + if message_id == 16: + return SetNodeScroll( + id=self.read_uint(reader), + x=self.read_int(reader), + y=self.read_int(reader) + ) + + if message_id == 17: + return SetInputTarget( + id=self.read_uint(reader), + label=self.read_string(reader) + ) + + if message_id == 18: + return SetInputValue( + id=self.read_uint(reader), + value=self.read_string(reader), + mask=self.read_int(reader) + ) + + if message_id == 19: + return SetInputChecked( + id=self.read_uint(reader), + checked=self.read_boolean(reader) + ) + + if message_id == 20: + return MouseMove( + x=self.read_uint(reader), + y=self.read_uint(reader) + ) + + if message_id == 21: + return MouseClickDepricated( + id=self.read_uint(reader), + hesitation_time=self.read_uint(reader), + label=self.read_string(reader) + ) + + if message_id == 22: + return ConsoleLog( + level=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 23: + return PageLoadTiming( + request_start=self.read_uint(reader), + response_start=self.read_uint(reader), + response_end=self.read_uint(reader), + dom_content_loaded_event_start=self.read_uint(reader), + dom_content_loaded_event_end=self.read_uint(reader), + load_event_start=self.read_uint(reader), + load_event_end=self.read_uint(reader), + first_paint=self.read_uint(reader), + first_contentful_paint=self.read_uint(reader) + ) + + if message_id == 24: + return PageRenderTiming( + speed_index=self.read_uint(reader), + visually_complete=self.read_uint(reader), + time_to_interactive=self.read_uint(reader) + ) + + if message_id == 25: + return JSException( + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 26: + return IntegrationEvent( + timestamp=self.read_uint(reader), + source=self.read_string(reader), + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 27: + return RawCustomEvent( + name=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 28: + return UserID( + id=self.read_string(reader) + ) + + if message_id == 29: + return UserAnonymousID( + id=self.read_string(reader) + ) + + if message_id == 30: + return Metadata( + key=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 31: + return PageEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + url=self.read_string(reader), + referrer=self.read_string(reader), + loaded=self.read_boolean(reader), + request_start=self.read_uint(reader), + response_start=self.read_uint(reader), + response_end=self.read_uint(reader), + dom_content_loaded_event_start=self.read_uint(reader), + dom_content_loaded_event_end=self.read_uint(reader), + load_event_start=self.read_uint(reader), + load_event_end=self.read_uint(reader), + first_paint=self.read_uint(reader), + first_contentful_paint=self.read_uint(reader), + speed_index=self.read_uint(reader), + visually_complete=self.read_uint(reader), + time_to_interactive=self.read_uint(reader) + ) + + if message_id == 32: + return InputEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + value=self.read_string(reader), + value_masked=self.read_boolean(reader), + label=self.read_string(reader) + ) + + if message_id == 33: + return ClickEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + hesitation_time=self.read_uint(reader), + label=self.read_string(reader), + selector=self.read_string(reader) + ) + + if message_id == 34: + return ErrorEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + source=self.read_string(reader), + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 35: + return ResourceEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + ttfb=self.read_uint(reader), + header_size=self.read_uint(reader), + encoded_body_size=self.read_uint(reader), + decoded_body_size=self.read_uint(reader), + url=self.read_string(reader), + type=self.read_string(reader), + success=self.read_boolean(reader), + method=self.read_string(reader), + status=self.read_uint(reader) + ) + + if message_id == 36: + return CustomEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + name=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 37: + return CSSInsertRule( + id=self.read_uint(reader), + rule=self.read_string(reader), + index=self.read_uint(reader) + ) + + if message_id == 38: + return CSSDeleteRule( + id=self.read_uint(reader), + index=self.read_uint(reader) + ) + + if message_id == 39: + return Fetch( + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + timestamp=self.read_uint(reader), + duration=self.read_uint(reader) + ) + + if message_id == 40: + return Profiler( + name=self.read_string(reader), + duration=self.read_uint(reader), + args=self.read_string(reader), + result=self.read_string(reader) + ) + + if message_id == 41: + return OTable( + key=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 42: + return StateAction( + type=self.read_string(reader) + ) + + if message_id == 43: + return StateActionEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + type=self.read_string(reader) + ) + + if message_id == 44: + return Redux( + action=self.read_string(reader), + state=self.read_string(reader), + duration=self.read_uint(reader) + ) + + if message_id == 45: + return Vuex( + mutation=self.read_string(reader), + state=self.read_string(reader) + ) + + if message_id == 46: + return MobX( + type=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 47: + return NgRx( + action=self.read_string(reader), + state=self.read_string(reader), + duration=self.read_uint(reader) + ) + + if message_id == 48: + return GraphQL( + operation_kind=self.read_string(reader), + operation_name=self.read_string(reader), + variables=self.read_string(reader), + response=self.read_string(reader) + ) + + if message_id == 49: + return PerformanceTrack( + frames=self.read_int(reader), + ticks=self.read_int(reader), + total_js_heap_size=self.read_uint(reader), + used_js_heap_size=self.read_uint(reader) + ) + + if message_id == 50: + return GraphQLEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + operation_kind=self.read_string(reader), + operation_name=self.read_string(reader), + variables=self.read_string(reader), + response=self.read_string(reader) + ) + + if message_id == 51: + return FetchEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + method=self.read_string(reader), + url=self.read_string(reader), + request=self.read_string(reader), + response=self.read_string(reader), + status=self.read_uint(reader), + duration=self.read_uint(reader) + ) + + if message_id == 52: + return DOMDrop( + timestamp=self.read_uint(reader) + ) + + if message_id == 53: + return ResourceTiming( + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + ttfb=self.read_uint(reader), + header_size=self.read_uint(reader), + encoded_body_size=self.read_uint(reader), + decoded_body_size=self.read_uint(reader), + url=self.read_string(reader), + initiator=self.read_string(reader) + ) + + if message_id == 54: + return ConnectionInformation( + downlink=self.read_uint(reader), + type=self.read_string(reader) + ) + + if message_id == 55: + return SetPageVisibility( + hidden=self.read_boolean(reader) + ) + + if message_id == 56: + return PerformanceTrackAggr( + timestamp_start=self.read_uint(reader), + timestamp_end=self.read_uint(reader), + min_fps=self.read_uint(reader), + avg_fps=self.read_uint(reader), + max_fps=self.read_uint(reader), + min_cpu=self.read_uint(reader), + avg_cpu=self.read_uint(reader), + max_cpu=self.read_uint(reader), + min_total_js_heap_size=self.read_uint(reader), + avg_total_js_heap_size=self.read_uint(reader), + max_total_js_heap_size=self.read_uint(reader), + min_used_js_heap_size=self.read_uint(reader), + avg_used_js_heap_size=self.read_uint(reader), + max_used_js_heap_size=self.read_uint(reader) + ) + + if message_id == 59: + return LongTask( + timestamp=self.read_uint(reader), + duration=self.read_uint(reader), + context=self.read_uint(reader), + container_type=self.read_uint(reader), + container_src=self.read_string(reader), + container_id=self.read_string(reader), + container_name=self.read_string(reader) + ) + + if message_id == 60: + return SetNodeAttributeURLBased( + id=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_string(reader), + base_url=self.read_string(reader) + ) + + if message_id == 61: + return SetCSSDataURLBased( + id=self.read_uint(reader), + data=self.read_string(reader), + base_url=self.read_string(reader) + ) + + if message_id == 62: + return IssueEvent( + message_id=self.read_uint(reader), + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 63: + return TechnicalInfo( + type=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 64: + return CustomIssue( + name=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 65: + return PageClose( + + ) + + if message_id == 66: + return AssetCache( + url=self.read_string(reader) + ) + + if message_id == 67: + return CSSInsertRuleURLBased( + id=self.read_uint(reader), + rule=self.read_string(reader), + index=self.read_uint(reader), + base_url=self.read_string(reader) + ) + + if message_id == 69: + return MouseClick( + id=self.read_uint(reader), + hesitation_time=self.read_uint(reader), + label=self.read_string(reader), + selector=self.read_string(reader) + ) + + if message_id == 70: + return CreateIFrameDocument( + frame_id=self.read_uint(reader), + id=self.read_uint(reader) + ) + + if message_id == 107: + return IOSBatchMeta( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + first_index=self.read_uint(reader) + ) + + if message_id == 90: + return IOSSessionStart( + timestamp=self.read_uint(reader), + project_id=self.read_uint(reader), + tracker_version=self.read_string(reader), + rev_id=self.read_string(reader), + user_uuid=self.read_string(reader), + user_os=self.read_string(reader), + user_os_version=self.read_string(reader), + user_device=self.read_string(reader), + user_device_type=self.read_string(reader), + user_country=self.read_string(reader) + ) + + if message_id == 91: + return IOSSessionEnd( + timestamp=self.read_uint(reader) + ) + + if message_id == 92: + return IOSMetadata( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + key=self.read_string(reader), + value=self.read_string(reader) + ) + + if message_id == 93: + return IOSCustomEvent( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + payload=self.read_string(reader) + ) + + if message_id == 94: + return IOSUserID( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + value=self.read_string(reader) + ) + + if message_id == 95: + return IOSUserAnonymousID( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + value=self.read_string(reader) + ) + + if message_id == 96: + return IOSScreenChanges( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + x=self.read_uint(reader), + y=self.read_uint(reader), + width=self.read_uint(reader), + height=self.read_uint(reader) + ) + + if message_id == 97: + return IOSCrash( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + reason=self.read_string(reader), + stacktrace=self.read_string(reader) + ) + + if message_id == 98: + return IOSScreenEnter( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + title=self.read_string(reader), + view_name=self.read_string(reader) + ) + + if message_id == 99: + return IOSScreenLeave( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + title=self.read_string(reader), + view_name=self.read_string(reader) + ) + + if message_id == 100: + return IOSClickEvent( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + label=self.read_string(reader), + x=self.read_uint(reader), + y=self.read_uint(reader) + ) + + if message_id == 101: + return IOSInputEvent( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + value=self.read_string(reader), + value_masked=self.read_boolean(reader), + label=self.read_string(reader) + ) + + if message_id == 102: + return IOSPerformanceEvent( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + name=self.read_string(reader), + value=self.read_uint(reader) + ) + + if message_id == 103: + return IOSLog( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + severity=self.read_string(reader), + content=self.read_string(reader) + ) + + if message_id == 104: + return IOSInternalError( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + content=self.read_string(reader) + ) + + if message_id == 105: + return IOSNetworkCall( + timestamp=self.read_uint(reader), + length=self.read_uint(reader), + duration=self.read_uint(reader), + headers=self.read_string(reader), + body=self.read_string(reader), + url=self.read_string(reader), + success=self.read_boolean(reader), + method=self.read_string(reader), + status=self.read_uint(reader) + ) + + if message_id == 110: + return IOSPerformanceAggregated( + timestamp_start=self.read_uint(reader), + timestamp_end=self.read_uint(reader), + min_fps=self.read_uint(reader), + avg_fps=self.read_uint(reader), + max_fps=self.read_uint(reader), + min_cpu=self.read_uint(reader), + avg_cpu=self.read_uint(reader), + max_cpu=self.read_uint(reader), + min_memory=self.read_uint(reader), + avg_memory=self.read_uint(reader), + max_memory=self.read_uint(reader), + min_battery=self.read_uint(reader), + avg_battery=self.read_uint(reader), + max_battery=self.read_uint(reader) + ) + + if message_id == 111: + return IOSIssueEvent( + timestamp=self.read_uint(reader), + type=self.read_string(reader), + context_string=self.read_string(reader), + context=self.read_string(reader), + payload=self.read_string(reader) + ) + diff --git a/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql b/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql index e4f709585..dd5c380da 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql @@ -661,24 +661,24 @@ $$ ); CREATE UNIQUE INDEX IF NOT EXISTS autocomplete_unique_project_id_md5value_type_idx ON autocomplete (project_id, md5(value), type); - CREATE index IF NOT EXISTS autocomplete_project_id_idx ON autocomplete (project_id); + CREATE INDEX IF NOT EXISTS autocomplete_project_id_idx ON autocomplete (project_id); CREATE INDEX IF NOT EXISTS autocomplete_type_idx ON public.autocomplete (type); - CREATE INDEX autocomplete_value_clickonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'CLICK'; - CREATE INDEX autocomplete_value_customonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'CUSTOM'; - CREATE INDEX autocomplete_value_graphqlonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'GRAPHQL'; - CREATE INDEX autocomplete_value_inputonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'INPUT'; - CREATE INDEX autocomplete_value_locationonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'LOCATION'; - CREATE INDEX autocomplete_value_referreronly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REFERRER'; - CREATE INDEX autocomplete_value_requestonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REQUEST'; - CREATE INDEX autocomplete_value_revidonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REVID'; - CREATE INDEX autocomplete_value_stateactiononly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'STATEACTION'; - CREATE INDEX autocomplete_value_useranonymousidonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERANONYMOUSID'; - CREATE INDEX autocomplete_value_userbrowseronly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERBROWSER'; - CREATE INDEX autocomplete_value_usercountryonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERCOUNTRY'; - CREATE INDEX autocomplete_value_userdeviceonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERDEVICE'; - CREATE INDEX autocomplete_value_useridonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERID'; - CREATE INDEX autocomplete_value_userosonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USEROS'; + CREATE INDEX IF NOT EXISTS autocomplete_value_clickonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'CLICK'; + CREATE INDEX IF NOT EXISTS autocomplete_value_customonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'CUSTOM'; + CREATE INDEX IF NOT EXISTS autocomplete_value_graphqlonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'GRAPHQL'; + CREATE INDEX IF NOT EXISTS autocomplete_value_inputonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'INPUT'; + CREATE INDEX IF NOT EXISTS autocomplete_value_locationonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'LOCATION'; + CREATE INDEX IF NOT EXISTS autocomplete_value_referreronly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REFERRER'; + CREATE INDEX IF NOT EXISTS autocomplete_value_requestonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REQUEST'; + CREATE INDEX IF NOT EXISTS autocomplete_value_revidonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'REVID'; + CREATE INDEX IF NOT EXISTS autocomplete_value_stateactiononly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'STATEACTION'; + CREATE INDEX IF NOT EXISTS autocomplete_value_useranonymousidonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERANONYMOUSID'; + CREATE INDEX IF NOT EXISTS autocomplete_value_userbrowseronly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERBROWSER'; + CREATE INDEX IF NOT EXISTS autocomplete_value_usercountryonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERCOUNTRY'; + CREATE INDEX IF NOT EXISTS autocomplete_value_userdeviceonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERDEVICE'; + CREATE INDEX IF NOT EXISTS autocomplete_value_useridonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USERID'; + CREATE INDEX IF NOT EXISTS autocomplete_value_userosonly_gin_idx ON public.autocomplete USING GIN (value gin_trgm_ops) WHERE type = 'USEROS'; BEGIN IF NOT EXISTS(SELECT * diff --git a/ee/utilities/package-lock.json b/ee/utilities/package-lock.json index 8aa6c9196..6b9dbdf1c 100644 --- a/ee/utilities/package-lock.json +++ b/ee/utilities/package-lock.json @@ -112,9 +112,9 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, "node_modules/@types/node": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", - "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==" + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", + "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" }, "node_modules/accepts": { "version": "1.3.8", @@ -1179,9 +1179,9 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, "@types/node": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", - "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==" + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", + "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" }, "accepts": { "version": "1.3.8", diff --git a/ee/utilities/utils/helper-ee.js b/ee/utilities/utils/helper-ee.js index 50b414b7a..86997f0c4 100644 --- a/ee/utilities/utils/helper-ee.js +++ b/ee/utilities/utils/helper-ee.js @@ -91,6 +91,7 @@ const extractPayloadFromRequest = async function (req, res) { return helper.extractPayloadFromRequest(req); } filters.filter = helper.objectToObjectOfArrays(filters.filter); + filters.filter = helper.transformFilters(filters.filter); debug && console.log("payload/filters:" + JSON.stringify(filters)) return Object.keys(filters).length > 0 ? filters : undefined; } diff --git a/frontend/app/Router.js b/frontend/app/Router.js index f5dc4c593..acbdd9cbb 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -31,7 +31,7 @@ const LiveSessionPure = lazy(() => import('Components/Session/LiveSession')); const OnboardingPure = lazy(() => import('Components/Onboarding/Onboarding')); const ClientPure = lazy(() => import('Components/Client/Client')); const AssistPure = lazy(() => import('Components/Assist')); -const BugFinderPure = lazy(() => import('Components/BugFinder/BugFinder')); +const BugFinderPure = lazy(() => import('Components/Overview')); const DashboardPure = lazy(() => import('Components/Dashboard/NewDashboard')); const ErrorsPure = lazy(() => import('Components/Errors/Errors')); const FunnelDetailsPure = lazy(() => import('Components/Funnels/FunnelDetails')); diff --git a/frontend/app/components/Assist/components/SessionList/SessionList.tsx b/frontend/app/components/Assist/components/SessionList/SessionList.tsx index ed0af5625..dc8b54025 100644 --- a/frontend/app/components/Assist/components/SessionList/SessionList.tsx +++ b/frontend/app/components/Assist/components/SessionList/SessionList.tsx @@ -24,7 +24,7 @@ function SessionList(props: Props) { return (
-
+
{props.userId}'s Live Sessions{' '} diff --git a/frontend/app/components/BugFinder/SessionList/SessionListHeader.js b/frontend/app/components/BugFinder/SessionList/SessionListHeader.js index 5e2702639..606ecaf67 100644 --- a/frontend/app/components/BugFinder/SessionList/SessionListHeader.js +++ b/frontend/app/components/BugFinder/SessionList/SessionListHeader.js @@ -26,7 +26,7 @@ function SessionListHeader({ activeTab, count, applyFilter, filter }) { }, [label]); const { startDate, endDate, rangeValue } = filter; - const period = new Record({ start: startDate, end: endDate, rangeName: rangeValue }); + const period = new Record({ start: startDate, end: endDate, rangeName: rangeValue, timezoneOffset: getTimeZoneOffset() }); const onDateChange = (e) => { const dateValues = e.toJSON(); @@ -36,10 +36,12 @@ function SessionListHeader({ activeTab, count, applyFilter, filter }) { }; React.useEffect(() => { - const dateValues = period.toJSON(); - dateValues.startDate = moment(dateValues.startDate).startOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); - dateValues.endDate = moment(dateValues.endDate).endOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); - applyFilter(dateValues); + if (label) { + const dateValues = period.toJSON(); + dateValues.startDate = moment(dateValues.startDate).startOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); + dateValues.endDate = moment(dateValues.endDate).endOf('day').utcOffset(getTimeZoneOffset(), true).valueOf(); + applyFilter(dateValues); + } }, [label]); return ( diff --git a/frontend/app/components/Client/Integrations/IntegrationForm.js b/frontend/app/components/Client/Integrations/IntegrationForm.js index a26576fc3..aeb28fe31 100644 --- a/frontend/app/components/Client/Integrations/IntegrationForm.js +++ b/frontend/app/components/Client/Integrations/IntegrationForm.js @@ -35,9 +35,9 @@ export default class IntegrationForm extends React.PureComponent { onChangeSelect = ({ value }) => { const { sites, list, name } = this.props; - const site = sites.find(s => s.id === value); + const site = sites.find(s => s.id === value.value); this.setState({ currentSiteId: site.id }) - this.init(value); + this.init(value.value); } init = (siteId) => { diff --git a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx index d2df0431d..477e42d8f 100644 --- a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx +++ b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx @@ -153,6 +153,7 @@ function UserForm(props: Props) { )); } -export default connect(state => ({ +export default connect((state: any) => ({ isEnterprise: state.getIn([ 'user', 'account', 'edition' ]) === 'ee', + isSmtp: state.getIn([ 'user', 'account', 'smtp' ]), }))(UserForm); \ No newline at end of file diff --git a/frontend/app/components/Overview/Overview.tsx b/frontend/app/components/Overview/Overview.tsx new file mode 100644 index 000000000..78b4bfe2b --- /dev/null +++ b/frontend/app/components/Overview/Overview.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import withPageTitle from 'HOCs/withPageTitle'; +import NoSessionsMessage from 'Shared/NoSessionsMessage'; +import MainSearchBar from 'Shared/MainSearchBar'; +import SessionSearch from 'Shared/SessionSearch'; +import SessionListContainer from 'Shared/SessionListContainer/SessionListContainer'; + +function Overview() { + return ( +
+
+
+ + +
+ + + +
+ +
+
+
+
+ ); +} + +export default withPageTitle('Sessions - OpenReplay')(Overview); diff --git a/frontend/app/components/Overview/index.ts b/frontend/app/components/Overview/index.ts new file mode 100644 index 000000000..44bcc2216 --- /dev/null +++ b/frontend/app/components/Overview/index.ts @@ -0,0 +1 @@ +export { default } from './Overview'; \ No newline at end of file diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index f4bb1f45d..fae2e40cf 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { Fragment, useEffect } from 'react'; import { connect } from 'react-redux'; import { NoContent, Loader, Pagination } from 'UI'; import { List } from 'immutable'; diff --git a/frontend/app/components/shared/Select/Select.tsx b/frontend/app/components/shared/Select/Select.tsx index b209eddc8..cc9e2cbbc 100644 --- a/frontend/app/components/shared/Select/Select.tsx +++ b/frontend/app/components/shared/Select/Select.tsx @@ -104,7 +104,7 @@ export default function({ placeholder='Select', name const opacity = state.isDisabled ? 0.5 : 1; const transition = 'opacity 300ms'; - return { ...provided, opacity, transition }; + return { ...provided, opacity, transition, fontWeight: 'bold' }; }, input: (provided: any) => ({ ...provided, diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index da8a940a5..7eaabc252 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -39,7 +39,7 @@ function SelectDateRange(props: Props) { }; const isCustomRange = period.rangeName === CUSTOM_RANGE; - const customRange = isCustomRange ? period.rangeFormatted(undefined, timezone) : ''; + const customRange = isCustomRange ? period.rangeFormatted() : ''; return (
; +} + +export default connect( + (state: any) => ({ + filter: state.getIn(['search', 'instance']), + }), + { sort, applyFilter } +)(SessionSort); diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionSort/index.ts b/frontend/app/components/shared/SessionListContainer/components/SessionSort/index.ts new file mode 100644 index 000000000..b0c0489be --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionSort/index.ts @@ -0,0 +1 @@ +export { default } from './SessionSort'; \ No newline at end of file diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionSort/sortDropdown.module.css b/frontend/app/components/shared/SessionListContainer/components/SessionSort/sortDropdown.module.css new file mode 100644 index 000000000..87e26bc68 --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionSort/sortDropdown.module.css @@ -0,0 +1,23 @@ +.dropdown { + display: flex !important; + padding: 4px 6px; + border-radius: 3px; + color: $gray-darkest; + font-weight: 500; + &:hover { + background-color: $gray-light; + } +} + +.dropdownTrigger { + padding: 4px 8px; + border-radius: 3px; + &:hover { + background-color: $gray-light; + } +} + +.dropdownIcon { + margin-top: 2px; + margin-left: 3px; +} \ No newline at end of file diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionTags/SessionTags.tsx b/frontend/app/components/shared/SessionListContainer/components/SessionTags/SessionTags.tsx new file mode 100644 index 000000000..1c8f5c926 --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionTags/SessionTags.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { setActiveTab } from 'Duck/search'; +import { connect } from 'react-redux'; +import { issues_types } from 'Types/session/issue'; +import { Icon } from 'UI'; +import cn from 'classnames'; + +interface Props { + setActiveTab: typeof setActiveTab; + activeTab: any; + tags: any; + total: number; +} +function SessionTags(props: Props) { + const { activeTab, tags, total } = props; + const disable = activeTab.type === 'all' && total === 0; + + return ( +
+ {tags && + tags.map((tag: any, index: any) => ( +
+ props.setActiveTab(tag)} + label={tag.name} + isActive={activeTab.type === tag.type} + icon={tag.icon} + disabled={disable && tag.type !== 'all'} + /> +
+ ))} +
+ ); +} + +export default connect( + (state: any) => { + const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee'; + return { + activeTab: state.getIn(['search', 'activeTab']), + tags: issues_types.filter((tag: any) => (isEnterprise ? tag.type !== 'bookmark' : tag.type !== 'vault')), + total: state.getIn(['sessions', 'total']) || 0, + }; + }, + { + setActiveTab, + } +)(SessionTags); + +function TagItem({ isActive, onClick, label, icon = '', disabled = false }: any) { + return ( +
+ +
+ ); +} diff --git a/frontend/app/components/shared/SessionListContainer/components/SessionTags/index.ts b/frontend/app/components/shared/SessionListContainer/components/SessionTags/index.ts new file mode 100644 index 000000000..4f5e62f6c --- /dev/null +++ b/frontend/app/components/shared/SessionListContainer/components/SessionTags/index.ts @@ -0,0 +1 @@ +export { default } from './SessionTags'; \ No newline at end of file diff --git a/frontend/app/components/shared/SessionListContainer/index.ts b/frontend/app/components/shared/SessionListContainer/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/ui/NoContent/NoContent.js b/frontend/app/components/ui/NoContent/NoContent.js deleted file mode 100644 index a82b8b99b..000000000 --- a/frontend/app/components/ui/NoContent/NoContent.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Icon } from 'UI'; -import styles from './noContent.module.css'; - -export default ({ - title =
No data available.
, - subtext, - icon, - iconSize = 100, - size, - show = true, - children = null, - empty = false, - image = null, - style = {}, -}) => (!show ? children : -
- { - icon && - } - { title &&
{ title }
} - { - subtext && -
{ subtext }
- } - { - image &&
{ image }
- } -
-); diff --git a/frontend/app/components/ui/NoContent/NoContent.tsx b/frontend/app/components/ui/NoContent/NoContent.tsx new file mode 100644 index 000000000..10a7e72e7 --- /dev/null +++ b/frontend/app/components/ui/NoContent/NoContent.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Icon } from 'UI'; +import styles from './noContent.module.css'; + +interface Props { + title?: any; + subtext?: any; + icon?: string; + iconSize?: number; + size?: number; + show?: boolean; + children?: any; + image?: any; + style?: any; +} +export default function NoContent(props: Props) { + const { title = '', subtext = '', icon, iconSize, size, show, children, image, style } = props; + + return !show ? ( + children + ) : ( +
+ {icon && } + {title &&
{title}
} + {subtext &&
{subtext}
} + {image &&
{image}
} +
+ ); +} diff --git a/frontend/app/components/ui/NoContent/index.js b/frontend/app/components/ui/NoContent/index.ts similarity index 100% rename from frontend/app/components/ui/NoContent/index.js rename to frontend/app/components/ui/NoContent/index.ts diff --git a/frontend/app/components/ui/NoContent/noContent.module.css b/frontend/app/components/ui/NoContent/noContent.module.css index 5cf7a0d24..33c63c916 100644 --- a/frontend/app/components/ui/NoContent/noContent.module.css +++ b/frontend/app/components/ui/NoContent/noContent.module.css @@ -45,15 +45,3 @@ height: 166px; margin-bottom: 20px; } - -.empty-state { - display: block; - margin: auto; - background-image: svg-load(empty-state.svg, fill=#CCC); - background-repeat: no-repeat; - background-size: contain; - background-position: center center; - width: 166px; - height: 166px; - margin-bottom: 20px; -} diff --git a/frontend/app/date.ts b/frontend/app/date.ts index e043f33fd..897c4db90 100644 --- a/frontend/app/date.ts +++ b/frontend/app/date.ts @@ -13,7 +13,7 @@ export const durationFormatted = (duration: Duration):string => { duration = duration.toFormat('h\'h\'m\'m'); } else if (duration.as('months') < 1) { // show in days and hours duration = duration.toFormat('d\'d\'h\'h'); - } else { // + } else { duration = duration.toFormat('m\'m\'s\'s\''); } @@ -133,4 +133,4 @@ export const countDaysFrom = (timestamp: number): number => { const date = DateTime.fromMillis(timestamp); const d = new Date(); return Math.round(Math.abs(d.getTime() - date.toJSDate().getTime()) / (1000 * 3600 * 24)); -} \ No newline at end of file +} diff --git a/frontend/app/dateRange.js b/frontend/app/dateRange.js index 0a1ae3410..70f674665 100644 --- a/frontend/app/dateRange.js +++ b/frontend/app/dateRange.js @@ -2,6 +2,7 @@ import origMoment from "moment"; import { extendMoment } from "moment-range"; export const moment = extendMoment(origMoment); import { DateTime } from "luxon"; +import { TIMEZONE } from 'App/constants/storageKeys'; export const CUSTOM_RANGE = "CUSTOM_RANGE"; @@ -42,39 +43,42 @@ export function getDateRangeLabel(value) { } export function getDateRangeFromValue(value) { + const tz = JSON.parse(localStorage.getItem(TIMEZONE)); + const offset = tz ? tz.label.slice(-6) : 0; + switch (value) { case DATE_RANGE_VALUES.LAST_30_MINUTES: return moment.range( - moment().startOf("hour").subtract(30, "minutes"), - moment().startOf("hour") + moment().utcOffset(offset, true).startOf("hour").subtract(30, "minutes"), + moment().utcOffset(offset, true).startOf("hour") ); + case DATE_RANGE_VALUES.YESTERDAY: + return moment.range( + moment().utcOffset(offset, true).subtract(1, "days").startOf("day"), + moment().utcOffset(offset, true).subtract(1, "days").endOf("day") + ); case DATE_RANGE_VALUES.TODAY: - return moment.range(moment().startOf("day"), moment().endOf("day")); - case DATE_RANGE_VALUES.YESTERDAY: - return moment.range( - moment().subtract(1, "days").startOf("day"), - moment().subtract(1, "days").endOf("day") - ); + return moment.range(moment().utcOffset(offset, true).startOf("day"), moment().utcOffset(offset, true).endOf("day")); case DATE_RANGE_VALUES.LAST_24_HOURS: - return moment.range(moment().subtract(24, "hours"), moment()); + return moment.range(moment().utcOffset(offset, true).subtract(24, "hours"), moment().utcOffset(offset, true)); case DATE_RANGE_VALUES.LAST_7_DAYS: return moment.range( - moment().subtract(7, "days").startOf("day"), - moment().endOf("day") + moment().utcOffset(offset, true).subtract(7, "days").startOf("day"), + moment().utcOffset(offset, true).endOf("day") ); case DATE_RANGE_VALUES.LAST_30_DAYS: return moment.range( - moment().subtract(30, "days").startOf("day"), - moment().endOf("day") + moment().utcOffset(offset, true).subtract(30, "days").startOf("day"), + moment().utcOffset(offset, true).endOf("day") ); case DATE_RANGE_VALUES.THIS_MONTH: - return moment().range("month"); + return moment().utcOffset(offset, true).range("month"); case DATE_RANGE_VALUES.LAST_MONTH: - return moment().subtract(1, "months").range("month"); + return moment().utcOffset(offset, true).subtract(1, "months").range("month"); case DATE_RANGE_VALUES.THIS_YEAR: - return moment().range("year"); + return moment().utcOffset(offset, true).range("year"); case DATE_RANGE_VALUES.CUSTOM_RANGE: - return moment.range(moment(), moment()); + return moment.range(moment().utcOffset(offset, true), moment().utcOffset(offset, true)); } return null; } diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 71361da14..4acb83313 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -137,13 +137,13 @@ export const reduceThenFetchResource = const filter = getState().getIn(['search', 'instance']).toData(); const activeTab = getState().getIn(['search', 'activeTab']); - if (activeTab.type !== 'all' && activeTab.type !== 'bookmark') { + if (activeTab.type !== 'all' && activeTab.type !== 'bookmark' && activeTab.type !== 'vault') { const tmpFilter = filtersMap[FilterKey.ISSUE]; tmpFilter.value = [activeTab.type]; filter.filters = filter.filters.concat(tmpFilter); } - if (activeTab.type === 'bookmark') { + if (activeTab.type === 'bookmark' || activeTab.type === 'vault') { filter.bookmarked = true; } diff --git a/frontend/app/player/MessageDistributor/messages/MStreamReader.ts b/frontend/app/player/MessageDistributor/messages/MStreamReader.ts index 1cc30dcec..02e765a67 100644 --- a/frontend/app/player/MessageDistributor/messages/MStreamReader.ts +++ b/frontend/app/player/MessageDistributor/messages/MStreamReader.ts @@ -9,9 +9,11 @@ import type { RawCssInsertRule, } from './raw' import RawMessageReader from './RawMessageReader' -import type { RawMessageReaderI } from './RawMessageReader' import { resolveURL, resolveCSS } from './urlResolve' +interface RawMessageReaderI { + readMessage(): RawMessage | null +} const resolveMsg = { "set_node_attribute_url_based": (msg: RawSetNodeAttributeURLBased): RawSetNodeAttribute => diff --git a/frontend/app/player/MessageDistributor/messages/RawMessageReader.ts b/frontend/app/player/MessageDistributor/messages/RawMessageReader.ts index 501a85a7b..2c1f8de2f 100644 --- a/frontend/app/player/MessageDistributor/messages/RawMessageReader.ts +++ b/frontend/app/player/MessageDistributor/messages/RawMessageReader.ts @@ -3,9 +3,6 @@ import PrimitiveReader from './PrimitiveReader' import type { RawMessage } from './raw' -export interface RawMessageReaderI { - readMessage(): RawMessage | null -} export default class RawMessageReader extends PrimitiveReader { readMessage(): RawMessage | null { diff --git a/frontend/app/player/MessageDistributor/messages/message.ts b/frontend/app/player/MessageDistributor/messages/message.ts index 1c21bbfd2..3726dfed8 100644 --- a/frontend/app/player/MessageDistributor/messages/message.ts +++ b/frontend/app/player/MessageDistributor/messages/message.ts @@ -2,7 +2,8 @@ import type { Timed } from './timed' import type { RawMessage } from './raw' -import type { RawBatchMeta, +import type { + RawBatchMeta, RawTimestamp, RawSessionDisconnect, RawSetPageLocation, @@ -60,7 +61,8 @@ import type { RawBatchMeta, RawIosClickEvent, RawIosPerformanceEvent, RawIosLog, - RawIosNetworkCall, } from './raw' + RawIosNetworkCall, +} from './raw' export type Message = RawMessage & Timed diff --git a/frontend/app/player/MessageDistributor/messages/raw.ts b/frontend/app/player/MessageDistributor/messages/raw.ts index e86181b2d..9dc368bf8 100644 --- a/frontend/app/player/MessageDistributor/messages/raw.ts +++ b/frontend/app/player/MessageDistributor/messages/raw.ts @@ -1,7 +1,7 @@ // Auto-generated, do not edit export const TP_MAP = { - 80: "batch_meta", + 80: "batch_meta", 0: "timestamp", 2: "session_disconnect", 4: "set_page_location", diff --git a/frontend/app/routes.js b/frontend/app/routes.js index b1e241292..627095f86 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -86,7 +86,7 @@ export const sessions = params => queried('/sessions', params); export const assist = params => queried('/assist', params); export const session = (sessionId = ':sessionId', hash) => hashed(`/session/${ sessionId }`, hash); -export const liveSession = (sessionId = ':sessionId', hash) => hashed(`/assist/${ sessionId }`, hash); +export const liveSession = (sessionId = ':sessionId', params, hash) => hashed(queried(`/assist/${ sessionId }`, params), hash); // export const liveSession = (sessionId = ':sessionId', hash) => hashed(`/live/session/${ sessionId }`, hash); export const errors = params => queried('/errors', params); diff --git a/frontend/app/store.js b/frontend/app/store.js index f907a579c..dd1434a0c 100644 --- a/frontend/app/store.js +++ b/frontend/app/store.js @@ -1,4 +1,4 @@ -import { createStore, applyMiddleware } from 'redux'; +import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; import { Map } from 'immutable'; import indexReducer from './duck'; @@ -9,13 +9,16 @@ const storage = new LocalStorage({ jwt: String, }); +const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && window.env.NODE_ENV === "development" + ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose; + const storageState = storage.state(); const initialState = Map({ jwt: storageState.jwt, // TODO: store user }); -const store = createStore(indexReducer, initialState, applyMiddleware(thunk, apiMiddleware)); +const store = createStore(indexReducer, initialState, composeEnhancers(applyMiddleware(thunk, apiMiddleware))); store.subscribe(() => { const state = store.getState(); storage.sync({ diff --git a/frontend/app/styles/global.scss b/frontend/app/styles/global.scss index 040831c9e..014cfce5f 100644 --- a/frontend/app/styles/global.scss +++ b/frontend/app/styles/global.scss @@ -11,4 +11,8 @@ input.no-focus:focus { outline: none !important; border: solid thin transparent !important; +} + +.widget-wrapper { + @apply rounded border bg-white; } \ No newline at end of file diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index 027e324ce..da69519b0 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -26,43 +26,43 @@ const RANGE_LABELS = { [THIS_YEAR]: "This Year", }; -function getRange(rangeName) { +function getRange(rangeName, offset) { switch (rangeName) { case TODAY: return moment.range(moment().startOf("day"), moment().endOf("day")); case YESTERDAY: return moment.range( - moment().subtract(1, "days").startOf("day"), - moment().subtract(1, "days").endOf("day") + moment().utcOffset(offset).subtract(1, "days").startOf("day"), + moment().utcOffset(offset).subtract(1, "days").endOf("day") ); case LAST_24_HOURS: return moment.range( // moment().startOf("hour").subtract(24, "hours"), // moment().startOf("hour") - moment().subtract(24, 'hours'), - moment(), + moment().utcOffset(offset).subtract(24, 'hours'), + moment().utcOffset(offset), ); case LAST_30_MINUTES: return moment.range( - moment().startOf("hour").subtract(30, "minutes"), - moment().startOf("hour") + moment().utcOffset(offset).startOf("hour").subtract(30, "minutes"), + moment().utcOffset(offset).startOf("hour") ); case LAST_7_DAYS: return moment.range( - moment().subtract(7, "days").startOf("day"), - moment().endOf("day") + moment().utcOffset(offset).subtract(7, "days").startOf("day"), + moment().utcOffset(offset).endOf("day") ); case LAST_30_DAYS: return moment.range( - moment().subtract(30, "days").startOf("day"), - moment().endOf("day") + moment().utcOffset(offset).subtract(30, "days").startOf("day"), + moment().utcOffset(offset).endOf("day") ); case THIS_MONTH: - return moment().range("month"); + return moment().utcOffset(offset).range("month"); case LAST_MONTH: - return moment().subtract(1, "months").range("month"); + return moment().utcOffset(offset).subtract(1, "months").range("month"); case THIS_YEAR: - return moment().range("year"); + return moment().utcOffset(offset).range("year"); default: return moment.range(); } @@ -77,10 +77,11 @@ export default Record( }, { fromJS: (period) => { + const offset = period.timezoneOffset || 0 if (!period.rangeName || period.rangeName === CUSTOM_RANGE) { const range = moment.range( - moment(period.start || 0), - moment(period.end || 0) + moment(period.start || 0).utcOffset(offset), + moment(period.end || 0).utcOffset(offset) ); return { ...period, @@ -89,7 +90,7 @@ export default Record( end: range.end.unix() * 1000, }; } - const range = getRange(period.rangeName); + const range = getRange(period.rangeName, offset); return { ...period, range, @@ -97,14 +98,6 @@ export default Record( end: range.end.unix() * 1000, }; }, - // fromFilter: filter => { - // const range = getRange(filter.rangeName); - // return { - // start: range.start.unix() * 1000, - // end: range.end.unix() * 1000, - // rangeName: filter.rangeName, - // } - // }, methods: { toJSON() { return { diff --git a/frontend/app/types/session/issue.js b/frontend/app/types/session/issue.js index 31214ae3e..db220f8cc 100644 --- a/frontend/app/types/session/issue.js +++ b/frontend/app/types/session/issue.js @@ -3,15 +3,18 @@ import { List } from 'immutable'; import Watchdog from 'Types/watchdog' export const issues_types = List([ - { 'type': 'js_exception', 'visible': true, 'order': 0, 'name': 'Errors', 'icon': 'funnel/exclamation-circle' }, - { 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' }, - { 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' }, - { 'type': 'click_rage', 'visible': true, 'order': 3, 'name': 'Click Rage', 'icon': 'funnel/emoji-angry' }, - { 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' }, - { 'type': 'memory', 'visible': true, 'order': 5, 'name': 'High Memory', 'icon': 'funnel/sd-card' }, - { 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' }, - { 'type': 'crash', 'visible': true, 'order': 7, 'name': 'Crashes', 'icon': 'funnel/file-earmark-break' }, - { 'type': 'custom', 'visible': false, 'order': 8, 'name': 'Custom', 'icon': 'funnel/exclamation-circle' } + { 'type': 'all', 'visible': true, 'order': 0, 'name': 'All', 'icon': '' }, + { 'type': 'js_exception', 'visible': true, 'order': 1, 'name': 'Errors', 'icon': 'funnel/exclamation-circle' }, + { 'type': 'click_rage', 'visible': true, 'order': 2, 'name': 'Click Rage', 'icon': 'funnel/emoji-angry' }, + { 'type': 'crash', 'visible': true, 'order': 3, 'name': 'Crashes', 'icon': 'funnel/file-earmark-break' }, + { 'type': 'memory', 'visible': true, 'order': 4, 'name': 'High Memory', 'icon': 'funnel/sd-card' }, + { 'type': 'vault', 'visible': true, 'order': 5, 'name': 'Vault', 'icon': 'safe' }, + { 'type': 'bookmark', 'visible': true, 'order': 5, 'name': 'Bookmarks', 'icon': 'safe' }, + // { 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' }, + // { 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' }, + // { 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' }, + // { 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' }, + // { 'type': 'custom', 'visible': false, 'order': 8, 'name': 'Custom', 'icon': 'funnel/exclamation-circle' } ]).map(Watchdog) export const issues_types_map = {} diff --git a/frontend/env.js b/frontend/env.js deleted file mode 100644 index e8332bd82..000000000 --- a/frontend/env.js +++ /dev/null @@ -1,29 +0,0 @@ -require('dotenv').config() - -// TODO: (the problem is during the build time the frontend is isolated) -//const trackerInfo = require('../tracker/tracker/package.json'); - -const oss = { - name: 'oss', - PRODUCTION: true, - SENTRY_ENABLED: false, - SENTRY_URL: "", - CAPTCHA_ENABLED: process.env.CAPTCHA_ENABLED === 'true', - CAPTCHA_SITE_KEY: process.env.CAPTCHA_SITE_KEY, - ORIGIN: () => 'window.location.origin', - API_EDP: () => 'window.location.origin + "/api"', - ASSETS_HOST: () => 'window.location.origin + "/assets"', - VERSION: '1.7.0', - SOURCEMAP: true, - MINIO_ENDPOINT: process.env.MINIO_ENDPOINT, - MINIO_PORT: process.env.MINIO_PORT, - MINIO_USE_SSL: process.env.MINIO_USE_SSL, - MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, - MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, - ICE_SERVERS: process.env.ICE_SERVERS, - TRACKER_VERSION: '3.5.15' // trackerInfo.version, -} - -module.exports = { - oss, -}; diff --git a/mobs/README.md b/mobs/README.md new file mode 100644 index 000000000..beee0030c --- /dev/null +++ b/mobs/README.md @@ -0,0 +1,16 @@ +# Message Object Binary Schema and Code Generator from Templates + + +To generate all necessary files for the project: + +```sh +ruby run.rb + +``` + +In order generated .go file to fit the go formatting style: +```sh +gofmt -w ../backend/pkg/messages/messages.go + +``` +(Otherwise there will be changes in stage) diff --git a/mobs/ios_messages.rb b/mobs/ios_messages.rb new file mode 100644 index 000000000..57e737714 --- /dev/null +++ b/mobs/ios_messages.rb @@ -0,0 +1,172 @@ +message 107, 'IOSBatchMeta', :replayer => false do + uint 'Timestamp' + uint 'Length' + uint 'FirstIndex' +end + +message 90, 'IOSSessionStart', :replayer => true do + uint 'Timestamp' + # uint 'Length' + + uint 'ProjectID' + string 'TrackerVersion' + string 'RevID' + string 'UserUUID' + # string 'UserAgent' + string 'UserOS' + string 'UserOSVersion' + # string 'UserBrowser' + # string 'UserBrowserVersion' + string 'UserDevice' + string 'UserDeviceType' + # uint 'UserDeviceMemorySize' + # uint 'UserDeviceHeapSize' + string 'UserCountry' +end + +message 91, 'IOSSessionEnd' do + uint 'Timestamp' +end + +message 92, 'IOSMetadata' do + uint 'Timestamp' + uint 'Length' + string 'Key' + string 'Value' +end + +message 93, 'IOSCustomEvent', :seq_index => true, :replayer => true do + uint 'Timestamp' + uint 'Length' + string 'Name' + string 'Payload' +end + +message 94, 'IOSUserID' do + uint 'Timestamp' + uint 'Length' + string 'Value' +end + +message 95, 'IOSUserAnonymousID' do + uint 'Timestamp' + uint 'Length' + string 'Value' +end + +message 96, 'IOSScreenChanges', :replayer => true do + uint 'Timestamp' + uint 'Length' + uint 'X' + uint 'Y' + uint 'Width' + uint 'Height' +end + +message 97, 'IOSCrash', :seq_index => true do + uint 'Timestamp' + uint 'Length' + string 'Name' + string 'Reason' + string 'Stacktrace' +end + +message 98, 'IOSScreenEnter', :seq_index => true do + uint 'Timestamp' + uint 'Length' + string 'Title' + string 'ViewName' +end + +message 99, 'IOSScreenLeave' do + uint 'Timestamp' + uint 'Length' + string 'Title' + string 'ViewName' +end + +message 100, 'IOSClickEvent', :seq_index => true, :replayer => true do + uint 'Timestamp' + uint 'Length' + string 'Label' + uint 'X' + uint 'Y' +end + +message 101, 'IOSInputEvent', :seq_index => true do + uint 'Timestamp' + uint 'Length' + string 'Value' + boolean 'ValueMasked' + string 'Label' +end + +=begin +Name/Value may be : +"physicalMemory": Total memory in bytes +"processorCount": Total processors in device +?"activeProcessorCount": Number of currently used processors +"systemUptime": Elapsed time (in seconds) since last boot +?"isLowPowerModeEnabled": Possible values (1 or 0) +2/3!"thermalState": Possible values (0:nominal 1:fair 2:serious 3:critical) +!"batteryLevel": Possible values (0 .. 100) +"batteryState": Possible values (0:unknown 1:unplugged 2:charging 3:full) +"orientation": Possible values (0unknown 1:portrait 2:portraitUpsideDown 3:landscapeLeft 4:landscapeRight 5:faceUp 6:faceDown) +"mainThreadCPU": Possible values (0 .. 100) +"memoryUsage": Used memory in bytes +=end +message 102, 'IOSPerformanceEvent', :replayer => true, :seq_index => true do + uint 'Timestamp' + uint 'Length' + string 'Name' + uint 'Value' +end + +message 103, 'IOSLog', :replayer => true do + uint 'Timestamp' + uint 'Length' + string 'Severity' # Possible values ("info", "error") + string 'Content' +end + +message 104, 'IOSInternalError' do + uint 'Timestamp' + uint 'Length' + string 'Content' +end + +message 105, 'IOSNetworkCall', :replayer => true, :seq_index => true do + uint 'Timestamp' + uint 'Length' + uint 'Duration' + string 'Headers' + string 'Body' + string 'URL' + boolean 'Success' + string 'Method' + uint 'Status' +end +message 110, 'IOSPerformanceAggregated', :swift => false do + uint 'TimestampStart' + uint 'TimestampEnd' + uint 'MinFPS' + uint 'AvgFPS' + uint 'MaxFPS' + uint 'MinCPU' + uint 'AvgCPU' + uint 'MaxCPU' + uint 'MinMemory' + uint 'AvgMemory' + uint 'MaxMemory' + uint 'MinBattery' + uint 'AvgBattery' + uint 'MaxBattery' +end + +message 111, 'IOSIssueEvent', :seq_index => true do + uint 'Timestamp' + string 'Type' + string 'ContextString' + string 'Context' + string 'Payload' +end diff --git a/mobs/messages.rb b/mobs/messages.rb new file mode 100644 index 000000000..0f97f3db5 --- /dev/null +++ b/mobs/messages.rb @@ -0,0 +1,409 @@ +# Special one for Batch Meta. Message id could define the version +message 80, 'BatchMeta', :replayer => false do + uint 'PageNo' + uint 'FirstIndex' + int 'Timestamp' +end +message 0, 'Timestamp' do + uint 'Timestamp' +end +message 1, 'SessionStart', :js => false, :replayer => false do + uint 'Timestamp' + uint 'ProjectID' + string 'TrackerVersion' + string 'RevID' + string 'UserUUID' + string 'UserAgent' + string 'UserOS' + string 'UserOSVersion' + string 'UserBrowser' + string 'UserBrowserVersion' + string 'UserDevice' + string 'UserDeviceType' + uint 'UserDeviceMemorySize' + uint 'UserDeviceHeapSize' + string 'UserCountry' + string 'UserID' +end +# Depricated (not used) since OpenReplay tracker 3.0.0 +message 2, 'SessionDisconnect', :js => false do + uint 'Timestamp' +end +message 3, 'SessionEnd', :js => false, :replayer => false do + uint 'Timestamp' +end +message 4, 'SetPageLocation' do + string 'URL' + string 'Referrer' + uint 'NavigationStart' +end +message 5, 'SetViewportSize' do + uint 'Width' + uint 'Height' +end +message 6, 'SetViewportScroll' do + int 'X' + int 'Y' +end +message 7, 'CreateDocument' do +end +message 8, 'CreateElementNode' do + uint 'ID' + uint 'ParentID' + uint 'index' + string 'Tag' + boolean 'SVG' +end +message 9, 'CreateTextNode' do + uint 'ID' + uint 'ParentID' + uint 'Index' +end +message 10, 'MoveNode' do + uint 'ID' + uint 'ParentID' + uint 'Index' +end +message 11, 'RemoveNode' do + uint 'ID' +end +message 12, 'SetNodeAttribute' do + uint 'ID' + string 'Name' + string 'Value' +end +message 13, 'RemoveNodeAttribute' do + uint 'ID' + string 'Name' +end +message 14, 'SetNodeData' do + uint 'ID' + string 'Data' +end +# Depricated starting from 5.5.11 in favor of SetStyleData +message 15, 'SetCSSData', :js => false do + uint 'ID' + string 'Data' +end +message 16, 'SetNodeScroll' do + uint 'ID' + int 'X' + int 'Y' +end +message 17, 'SetInputTarget', :replayer => false do + uint 'ID' + string 'Label' +end +message 18, 'SetInputValue' do + uint 'ID' + string 'Value' + int 'Mask' +end +message 19, 'SetInputChecked' do + uint 'ID' + boolean 'Checked' +end +message 20, 'MouseMove' do + uint 'X' + uint 'Y' +end +# Depricated since OpenReplay 1.2.0 +message 21, 'MouseClickDepricated', :js => false, :replayer => false do + uint 'ID' + uint 'HesitationTime' + string 'Label' +end +message 22, 'ConsoleLog' do + string 'Level' + string 'Value' +end +message 23, 'PageLoadTiming', :replayer => false do + uint 'RequestStart' + uint 'ResponseStart' + uint 'ResponseEnd' + uint 'DomContentLoadedEventStart' + uint 'DomContentLoadedEventEnd' + uint 'LoadEventStart' + uint 'LoadEventEnd' + uint 'FirstPaint' + uint 'FirstContentfulPaint' +end +message 24, 'PageRenderTiming', :replayer => false do + uint 'SpeedIndex' + uint 'VisuallyComplete' + uint 'TimeToInteractive' +end +message 25, 'JSException', :replayer => false do + string 'Name' + string 'Message' + string 'Payload' +end +message 26, 'IntegrationEvent', :js => false, :replayer => false do + uint 'Timestamp' + string 'Source' + string 'Name' + string 'Message' + string 'Payload' +end +message 27, 'RawCustomEvent', :replayer => false do + string 'Name' + string 'Payload' +end +message 28, 'UserID', :replayer => false do + string 'ID' +end +message 29, 'UserAnonymousID', :replayer => false do + string 'ID' +end +message 30, 'Metadata', :replayer => false do + string 'Key' + string 'Value' +end +message 31, 'PageEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + string 'URL' + string 'Referrer' + boolean 'Loaded' + uint 'RequestStart' + uint 'ResponseStart' + uint 'ResponseEnd' + uint 'DomContentLoadedEventStart' + uint 'DomContentLoadedEventEnd' + uint 'LoadEventStart' + uint 'LoadEventEnd' + uint 'FirstPaint' + uint 'FirstContentfulPaint' + uint 'SpeedIndex' + uint 'VisuallyComplete' + uint 'TimeToInteractive' +end +message 32, 'InputEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + string 'Value' + boolean 'ValueMasked' + string 'Label' +end +message 33, 'ClickEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + uint 'HesitationTime' + string 'Label' + string 'Selector' +end +message 34, 'ErrorEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + string 'Source' + string 'Name' + string 'Message' + string 'Payload' +end +message 35, 'ResourceEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + uint 'Duration' + uint 'TTFB' + uint 'HeaderSize' + uint 'EncodedBodySize' + uint 'DecodedBodySize' + string 'URL' + string 'Type' + boolean 'Success' + string 'Method' + uint 'Status' +end +message 36, 'CustomEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + string 'Name' + string 'Payload' +end + + +message 37, 'CSSInsertRule' do + uint 'ID' + string 'Rule' + uint 'Index' +end +message 38, 'CSSDeleteRule' do + uint 'ID' + uint 'Index' +end + +message 39, 'Fetch' do + string 'Method' + string 'URL' + string 'Request' + string 'Response' + uint 'Status' + uint 'Timestamp' + uint 'Duration' +end +message 40, 'Profiler' do + string 'Name' + uint 'Duration' + string 'Args' + string 'Result' +end + +message 41, 'OTable' do + string 'Key' + string 'Value' +end +message 42, 'StateAction', :replayer => false do + string 'Type' +end +message 43, 'StateActionEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + string 'Type' +end + +message 44, 'Redux' do + string 'Action' + string 'State' + uint 'Duration' +end +message 45, 'Vuex' do + string 'Mutation' + string 'State' +end +message 46, 'MobX' do + string 'Type' + string 'Payload' +end +message 47, 'NgRx' do + string 'Action' + string 'State' + uint 'Duration' +end +message 48, 'GraphQL' do + string 'OperationKind' + string 'OperationName' + string 'Variables' + string 'Response' +end +message 49, 'PerformanceTrack' do + int 'Frames' + int 'Ticks' + uint 'TotalJSHeapSize' + uint 'UsedJSHeapSize' +end +message 50, 'GraphQLEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + string 'OperationKind' + string 'OperationName' + string 'Variables' + string 'Response' +end +message 51, 'FetchEvent', :js => false, :replayer => false do + uint 'MessageID' + uint 'Timestamp' + string 'Method' + string 'URL' + string 'Request' + string 'Response' + uint 'Status' + uint 'Duration' +end +message 52, 'DOMDrop', :js => false, :replayer => false do + uint 'Timestamp' +end +message 53, 'ResourceTiming', :replayer => false do + uint 'Timestamp' + uint 'Duration' + uint 'TTFB' + uint 'HeaderSize' + uint 'EncodedBodySize' + uint 'DecodedBodySize' + string 'URL' + string 'Initiator' +end +message 54, 'ConnectionInformation' do + uint 'Downlink' + string 'Type' +end +message 55, 'SetPageVisibility' do + boolean 'hidden' +end +message 56, 'PerformanceTrackAggr', :js => false, :replayer => false do + uint 'TimestampStart' + uint 'TimestampEnd' + uint 'MinFPS' + uint 'AvgFPS' + uint 'MaxFPS' + uint 'MinCPU' + uint 'AvgCPU' + uint 'MaxCPU' + uint 'MinTotalJSHeapSize' + uint 'AvgTotalJSHeapSize' + uint 'MaxTotalJSHeapSize' + uint 'MinUsedJSHeapSize' + uint 'AvgUsedJSHeapSize' + uint 'MaxUsedJSHeapSize' +end +message 59, 'LongTask' do + uint 'Timestamp' + uint 'Duration' + uint 'Context' + uint 'ContainerType' + string 'ContainerSrc' + string 'ContainerId' + string 'ContainerName' +end +message 60, 'SetNodeAttributeURLBased', :replayer => false do + uint 'ID' + string 'Name' + string 'Value' + string 'BaseURL' +end +# Might replace SetCSSData (although BaseURL is useless after rewriting) +message 61, 'SetCSSDataURLBased', :replayer => false do + uint 'ID' + string 'Data' + string 'BaseURL' +end +message 62, 'IssueEvent', :replayer => false, :js => false do + uint 'MessageID' + uint 'Timestamp' + string 'Type' + string 'ContextString' + string 'Context' + string 'Payload' +end +message 63, 'TechnicalInfo', :replayer => false do + string 'Type' + string 'Value' +end +message 64, 'CustomIssue', :replayer => false do + string 'Name' + string 'Payload' +end +# Since 5.6.6; only for websocket (might be probably replaced with ws.close()) +# Depricated +message 65, 'PageClose', :replayer => false do +end +message 66, 'AssetCache', :replayer => false, :js => false do + string 'URL' +end +message 67, 'CSSInsertRuleURLBased', :replayer => false do + uint 'ID' + string 'Rule' + uint 'Index' + string 'BaseURL' +end +message 69, 'MouseClick' do + uint 'ID' + uint 'HesitationTime' + string 'Label' + string 'Selector' +end + +# Since 3.4.0 +message 70, 'CreateIFrameDocument' do + uint 'FrameID' + uint 'ID' +end \ No newline at end of file diff --git a/mobs/primitives/primitives.go b/mobs/primitives/primitives.go new file mode 100644 index 000000000..2603d31f5 --- /dev/null +++ b/mobs/primitives/primitives.go @@ -0,0 +1,124 @@ +package messages + +import ( + "errors" + "io" +) + +func ReadByte(reader io.Reader) (byte, error) { + p := make([]byte, 1) + _, err := io.ReadFull(reader, p) + if err != nil { + return 0, err + } + return p[0], nil +} + +func SkipBytes(reader io.ReadSeeker) error { + n, err := ReadUint(reader) + if err != nil { + return err + } + _, err := reader.Seek(n, io.SeekCurrent); + return err +} + +func ReadData(reader io.Reader) ([]byte, error) { + n, err := ReadUint(reader) + if err != nil { + return nil, err + } + p := make([]byte, n) + _, err := io.ReadFull(reader, p) + if err != nil { + return nil, err + } + return p, nil +} + +func ReadUint(reader io.Reader) (uint64, error) { + var x uint64 + var s uint + i := 0 + for { + b, err := ReadByte(reader) + if err != nil { + return x, err + } + if b < 0x80 { + if i > 9 || i == 9 && b > 1 { + return x, errors.New("overflow") + } + return x | uint64(b)<> 1) + if err != nil { + return x, err + } + if ux&1 != 0 { + x = ^x + } + return x, err +} + +func ReadBoolean(reader io.Reader) (bool, error) { + p := make([]byte, 1) + _, err := io.ReadFull(reader, p) + if err != nil { + return false, err + } + return p[0] == 1, nil +} + +func ReadString(reader io.Reader) (string, error) { + l, err := ReadUint(reader) + if err != nil { + return "", err + } + buf := make([]byte, l) + _, err = io.ReadFull(reader, buf) + if err != nil { + return "", err + } + return string(buf), nil +} + +func WriteUint(v uint64, buf []byte, p int) int { + for v >= 0x80 { + buf[p] = byte(v) | 0x80 + v >>= 7 + p++ + } + buf[p] = byte(v) + return p + 1 +} + +func WriteInt(v int64, buf []byte, p int) int { + uv := uint64(v) << 1 + if v < 0 { + uv = ^uv + } + return WriteUint(uv, buf, p) +} + +func WriteBoolean(v bool, buf []byte, p int) int { + if v { + buf[p] = 1 + } else { + buf[p] = 0 + } + return p + 1 +} + +func WriteString(str string, buf []byte, p int) int { + p = WriteUint(uint64(len(str)), buf, p) + return p + copy(buf[p:], str) +} diff --git a/mobs/primitives/primitives.py b/mobs/primitives/primitives.py new file mode 100644 index 000000000..5aeb0e4ed --- /dev/null +++ b/mobs/primitives/primitives.py @@ -0,0 +1,62 @@ +import io + +class Codec: + """ + Implements encode/decode primitives + """ + + @staticmethod + def read_boolean(reader: io.BytesIO): + b = reader.read(1) + return b == 1 + + @staticmethod + def read_uint(reader: io.BytesIO): + """ + The ending "big" doesn't play any role here, + since we're dealing with data per one byte + """ + x = 0 # the result + s = 0 # the shift (our result is big-ending) + i = 0 # n of byte (max 9 for uint64) + while True: + b = reader.read(1) + num = int.from_bytes(b, "big", signed=False) + # print(i, x) + + if num < 0x80: + if i > 9 | i == 9 & num > 1: + raise OverflowError() + return int(x | num << s) + x |= (num & 0x7f) << s + s += 7 + i += 1 + + @staticmethod + def read_int(reader: io.BytesIO) -> int: + """ + ux, err := ReadUint(reader) + x := int64(ux >> 1) + if err != nil { + return x, err + } + if ux&1 != 0 { + x = ^x + } + return x, err + """ + ux = Codec.read_uint(reader) + x = int(ux >> 1) + + if ux & 1 != 0: + x = - x - 1 + return x + + @staticmethod + def read_string(reader: io.BytesIO) -> str: + length = Codec.read_uint(reader) + s = reader.read(length) + try: + return s.decode("utf-8", errors="replace").replace("\x00", "\uFFFD") + except UnicodeDecodeError: + return None diff --git a/mobs/primitives/primitives.swift b/mobs/primitives/primitives.swift new file mode 100644 index 000000000..b7d66d0f9 --- /dev/null +++ b/mobs/primitives/primitives.swift @@ -0,0 +1,60 @@ +extension Data { + func readByte(offset: inout Int) -> UInt8 { + if offset >= self.count { + fatalError(">>> Error reading Byte") + } + let b = self[offset] + offset += 1 + return b + } + func readUint(offset: inout Int) -> UInt64 { + var x: UInt64 = 0 + var s: Int = 0 + var i: Int = 0 + while true { + let b = readByte(offset: &offset) + if b < 0x80 { + if i > 9 || i == 9 && b > 1 { + fatalError(">>> Error reading UInt") + } + return x | UInt64(b)< Int64 { + let ux = readUint(offset: &offset) + var x = Int64(ux >> 1) + if ux&1 != 0 { + x = ~x + } + return x + } + func readBoolean(offset: inout Int) -> Bool { + return readByte(offset: &offset) == 1 + } + mutating func writeUint(_ input: UInt64) { + var v = input + while v >= 0x80 { + append(UInt8(v.littleEndian & 0x7F) | 0x80) // v.littleEndian ? + v >>= 7 + } + append(UInt8(v)) + } + mutating func writeInt(_ v: Int64) { + var uv = UInt64(v) << 1 + if v < 0 { + uv = ~uv + } + writeUint(uv) + } + mutating func writeBoolean(_ v: Bool) { + if v { + append(1) + } else { + append(0) + } + } +} \ No newline at end of file diff --git a/mobs/run.rb b/mobs/run.rb new file mode 100644 index 000000000..ed9999e88 --- /dev/null +++ b/mobs/run.rb @@ -0,0 +1,136 @@ +require 'erb' + + +# TODO: change method names to correct (CapitalCase and camelCase, not CamalCase and firstLower) +class String + def camel_case + return self if self !~ /_/ && self =~ /[A-Z]+.*/ + split('_').map{|e| e.capitalize}.join.upperize + end + + def camel_case_lower + self.split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join.upperize + end + + def upperize + self.sub('Id', 'ID').sub('Url', 'URL') + end + + def first_lower + self.sub(/^[A-Z]+/) {|f| f.downcase } + end + + def underscore + self.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end +end + +class Attribute + attr_reader :name, :type + def initialize(name:, type:) + @name = name + @type = type + end + + def type_js + case @type + when :int + "number" + when :uint + "number" + when :json + # TODO + # raise "Unexpected attribute type: data type attribute #{@name} found in JS template" + "string" + when :data + raise "Unexpected attribute type: data type attribute #{@name} found in JS template" + else + @type + end + end + + def type_go + case @type + when :int + 'int64' + when :uint + 'uint64' + when :string + 'string' + when :data + '[]byte' + when :boolean + 'bool' + when :json + 'interface{}' + end + end + + def lengh_encoded + case @type + when :string, :data + true + else + false + end + end + +end + + +$context = :web + +class Message + attr_reader :id, :name, :js, :replayer, :swift, :seq_index, :attributes, :context + def initialize(name:, id:, js: $context == :web, replayer: $context == :web, swift: $context == :ios, seq_index: false, &block) + @id = id + @name = name + @js = js + @replayer = replayer + @swift = swift + @seq_index = seq_index + @context = $context + @attributes = [] + instance_eval &block + end + + %i(int uint boolean string data).each do |type| + define_method type do |name, opts = {}| + opts.merge!( + name: name, + type: type, + ) + @attributes << Attribute.new(opts) + end + end +end + +$ids = [] +$messages = [] +def message(id, name, opts = {}, &block) + raise "id duplicated #{name}" if $ids.include? id + raise "id is too big #{name}" if id > 120 + $ids << id + opts[:id] = id + opts[:name] = name + msg = Message.new(opts, &block) + $messages << msg +end + +require './messages.rb' + +$context = :ios +require './ios_messages.rb' + +Dir["templates/*.erb"].each do |tpl| + e = ERB.new(File.read(tpl)) + path = tpl.split '/' + t = '../' + path[1].gsub('~', '/') + t = t[0..-5] + File.write(t, e.result) + puts tpl + ' --> ' + t +end diff --git a/mobs/templates/backend~pkg~messages~filters.go.erb b/mobs/templates/backend~pkg~messages~filters.go.erb new file mode 100644 index 000000000..ac4ba9cba --- /dev/null +++ b/mobs/templates/backend~pkg~messages~filters.go.erb @@ -0,0 +1,10 @@ +// Auto-generated, do not edit +package messages + +func IsReplayerType(id int) bool { + return <%= $messages.select { |msg| msg.replayer }.map{ |msg| "#{msg.id} == id" }.join(' || ') %> +} + +func IsIOSType(id int) bool { + return <%= $messages.select { |msg| msg.context == :ios }.map{ |msg| "#{msg.id} == id"}.join(' || ') %> +} diff --git a/mobs/templates/backend~pkg~messages~get-timestamp.go.erb b/mobs/templates/backend~pkg~messages~get-timestamp.go.erb new file mode 100644 index 000000000..f4a634dea --- /dev/null +++ b/mobs/templates/backend~pkg~messages~get-timestamp.go.erb @@ -0,0 +1,12 @@ +// Auto-generated, do not edit +package messages + +func GetTimestamp(message Message) uint64 { + switch msg := message.(type) { +<% $messages.select { |msg| msg.swift }.each do |msg| %> + case *<%= msg.name %>: + return msg.Timestamp +<% end %> + } + return uint64(message.Meta().Timestamp) +} diff --git a/mobs/templates/backend~pkg~messages~messages.go.erb b/mobs/templates/backend~pkg~messages~messages.go.erb new file mode 100644 index 000000000..81fb18800 --- /dev/null +++ b/mobs/templates/backend~pkg~messages~messages.go.erb @@ -0,0 +1,22 @@ +// Auto-generated, do not edit +package messages +<% $messages.each do |msg| %> +type <%= msg.name %> struct { + message +<%= msg.attributes.map { |attr| +" #{attr.name} #{attr.type_go}" }.join "\n" %> +} + +func (msg *<%= msg.name %>) Encode() []byte { + buf := make([]byte, <%= msg.attributes.count * 10 + 1 %><%= msg.attributes.map { |attr| "+len(msg.#{attr.name})" if attr.lengh_encoded }.compact.join %>) + buf[0] = <%= msg.id %> + p := 1 +<%= msg.attributes.map { |attr| +" p = Write#{attr.type.to_s.camel_case}(msg.#{attr.name}, buf, p)" }.join "\n" %> + return buf[:p] +} + +func (msg *<%= msg.name %>) TypeID() int { + return <%= msg.id %> +} +<% end %> diff --git a/mobs/templates/backend~pkg~messages~read-message.go.erb b/mobs/templates/backend~pkg~messages~read-message.go.erb new file mode 100644 index 000000000..2e8920747 --- /dev/null +++ b/mobs/templates/backend~pkg~messages~read-message.go.erb @@ -0,0 +1,26 @@ +// Auto-generated, do not edit +package messages + +import ( + "fmt" + "io" +) + +func ReadMessage(reader io.Reader) (Message, error) { + t, err := ReadUint(reader) + if err != nil { + return nil, err + } + switch t { +<% $messages.each do |msg| %> + case <%= msg.id %>: + msg := &<%= msg.name %>{} +<%= msg.attributes.map { |attr| +" if msg.#{attr.name}, err = Read#{attr.type.to_s.camel_case}(reader); err != nil { + return nil, err + }" }.join "\n" %> + return msg, nil +<% end %> + } + return nil, fmt.Errorf("Unknown message code: %v", t) +} diff --git a/mobs/templates/ee~connectors~msgcodec~messages.py.erb b/mobs/templates/ee~connectors~msgcodec~messages.py.erb new file mode 100644 index 000000000..88acfa282 --- /dev/null +++ b/mobs/templates/ee~connectors~msgcodec~messages.py.erb @@ -0,0 +1,16 @@ +# Auto-generated, do not edit + +from abc import ABC + +class Message(ABC): + pass + +<% $messages.each do |msg| %> +class <%= msg.name %>(Message): + __id__ = <%= msg.id %> + + def __init__(self, <%= msg.attributes.map { |attr| "#{attr.name.underscore}" }.join ", " %>): + <%= msg.attributes.map { |attr| "self.#{attr.name.underscore} = #{attr.name.underscore}" }.join "\n " + %> + +<% end %> diff --git a/mobs/templates/ee~connectors~msgcodec~msgcodec.py.erb b/mobs/templates/ee~connectors~msgcodec~msgcodec.py.erb new file mode 100644 index 000000000..08d94ba8b --- /dev/null +++ b/mobs/templates/ee~connectors~msgcodec~msgcodec.py.erb @@ -0,0 +1,29 @@ +# Auto-generated, do not edit + +from msgcodec.codec import Codec +from msgcodec.messages import * + +class MessageCodec(Codec): + + def read_message_id(self, reader: io.BytesIO) -> int: + """ + Read and return the first byte where the message id is encoded + """ + id_ = self.read_uint(reader) + return id_ + + def encode(self, m: Message) -> bytes: + ... + + def decode(self, b: bytes) -> Message: + reader = io.BytesIO(b) + message_id = self.read_message_id(reader) +<% $messages.each do |msg| %> + if message_id == <%= msg.id %>: + return <%= msg.name %>( + <%= msg.attributes.map { |attr| + "#{attr.name.underscore}=self.read_#{attr.type.to_s}(reader)" } + .join ",\n " + %> + ) +<% end %> diff --git a/mobs/templates/frontend~app~player~MessageDistributor~messages~RawMessageReader.ts.erb b/mobs/templates/frontend~app~player~MessageDistributor~messages~RawMessageReader.ts.erb new file mode 100644 index 000000000..337dfb803 --- /dev/null +++ b/mobs/templates/frontend~app~player~MessageDistributor~messages~RawMessageReader.ts.erb @@ -0,0 +1,35 @@ +// Auto-generated, do not edit + +import PrimitiveReader from './PrimitiveReader' +import type { RawMessage } from './raw' + + +export default class RawMessageReader extends PrimitiveReader { + readMessage(): RawMessage | null { + const p = this.p + const resetPointer = () => { + this.p = p + return null + } + + const tp = this.readUint() + if (tp === null) { return resetPointer() } + + switch (tp) { + <% $messages.select { |msg| msg.js || msg.replayer }.each do |msg| %> + case <%= msg.id %>: { +<%= msg.attributes.map { |attr| +" const #{attr.name.first_lower} = this.read#{attr.type.to_s.camel_case}(); if (#{attr.name.first_lower} === null) { return resetPointer() }" }.join "\n" %> + return { + tp: "<%= msg.name.underscore %>", +<%= msg.attributes.map { |attr| +" #{attr.name.first_lower}," }.join "\n" %> + }; + } + <% end %> + default: + throw new Error(`Unrecognizable message type: ${ tp }; Pointer at the position ${this.p} of ${this.buf.length}`) + return null; + } + } +} diff --git a/mobs/templates/frontend~app~player~MessageDistributor~messages~message.ts.erb b/mobs/templates/frontend~app~player~MessageDistributor~messages~message.ts.erb new file mode 100644 index 000000000..e81bddd7c --- /dev/null +++ b/mobs/templates/frontend~app~player~MessageDistributor~messages~message.ts.erb @@ -0,0 +1,13 @@ +// Auto-generated, do not edit + +import type { Timed } from './timed' +import type { RawMessage } from './raw' +import type { +<%= $messages.select { |msg| msg.js || msg.replayer }.map { |msg| " Raw#{msg.name.underscore.camel_case}," }.join "\n" %> +} from './raw' + +export type Message = RawMessage & Timed + +<% $messages.select { |msg| msg.js || msg.replayer }.each do |msg| %> +export type <%= msg.name.underscore.camel_case %> = Raw<%= msg.name.underscore.camel_case %> & Timed +<% end %> \ No newline at end of file diff --git a/mobs/templates/frontend~app~player~MessageDistributor~messages~raw.ts.erb b/mobs/templates/frontend~app~player~MessageDistributor~messages~raw.ts.erb new file mode 100644 index 000000000..bb441f810 --- /dev/null +++ b/mobs/templates/frontend~app~player~MessageDistributor~messages~raw.ts.erb @@ -0,0 +1,14 @@ +// Auto-generated, do not edit + +export const TP_MAP = { +<%= $messages.select { |msg| msg.js || msg.replayer }.map { |msg| " #{msg.id}: \"#{msg.name.underscore}\"," }.join "\n" %> +} + +<% $messages.select { |msg| msg.js || msg.replayer }.each do |msg| %> +export interface Raw<%= msg.name.underscore.camel_case %> { + tp: "<%= msg.name.underscore %>", +<%= msg.attributes.map { |attr| " #{attr.name.first_lower}: #{attr.type_js}," }.join "\n" %> +} +<% end %> + +export type RawMessage = <%= $messages.select { |msg| msg.js || msg.replayer }.map { |msg| "Raw#{msg.name.underscore.camel_case}" }.join " | " %>; diff --git a/mobs/templates/ios/ASMessage.swift b/mobs/templates/ios/ASMessage.swift new file mode 100644 index 000000000..664a65643 --- /dev/null +++ b/mobs/templates/ios/ASMessage.swift @@ -0,0 +1,36 @@ +// Auto-generated, do not edit +import UIKit + +enum ASMessageType: UInt64 { +<%= $messages.map { |msg| " case #{msg.name.first_lower} = #{msg.id}" }.join "\n" %> +} +<% $messages.each do |msg| %> +class AS<%= msg.name.to_s.camel_case %>: ASMessage { +<%= msg.attributes[2..-1].map { |attr| " let #{attr.property}: #{attr.type_swift}" }.join "\n" %> + + init(<%= msg.attributes[2..-1].map { |attr| "#{attr.property}: #{attr.type_swift}" }.join ", " %>) { +<%= msg.attributes[2..-1].map { |attr| " self.#{attr.property} = #{attr.property}" }.join "\n" %> + super.init(messageType: .<%= "#{msg.name.first_lower}" %>) + } + + override init?(genericMessage: GenericMessage) { + <% if msg.attributes.length > 2 %> do { + var offset = 0 +<%= msg.attributes[2..-1].map { |attr| " self.#{attr.property} = try genericMessage.body.read#{attr.type_swift_read}(offset: &offset)" }.join "\n" %> + super.init(genericMessage: genericMessage) + } catch { + return nil + } + <% else %> + super.init(genericMessage: genericMessage) + <% end %>} + + override func contentData() -> Data { + return Data(values: UInt64(<%= "#{msg.id}"%>), timestamp<% if msg.attributes.length > 2 %>, Data(values: <%= msg.attributes[2..-1].map { |attr| attr.property }.join ", "%>)<% end %>) + } + + override var description: String { + return "-->> <%= msg.name.to_s.camel_case %>(<%= "#{msg.id}"%>): timestamp:\(timestamp) <%= msg.attributes[2..-1].map { |attr| "#{attr.property}:\\(#{attr.property})" }.join " "%>"; + } +} +<% end %> diff --git a/mobs/templates/tracker~tracker~src~common~messages.ts.erb b/mobs/templates/tracker~tracker~src~common~messages.ts.erb new file mode 100644 index 000000000..3b3849370 --- /dev/null +++ b/mobs/templates/tracker~tracker~src~common~messages.ts.erb @@ -0,0 +1,31 @@ +// Auto-generated, do not edit +import type { Writer, Message }from "./types.js"; +export default Message + +function bindNew( + Class: C & { new(...args: A): T } +): C & ((...args: A) => T) { + function _Class(...args: A) { + return new Class(...args); + } + _Class.prototype = Class.prototype; + return T)>_Class; +} + +export const classes: Map = new Map(); + +<% $messages.select { |msg| msg.js }.each do |msg| %> +class _<%= msg.name %> implements Message { + readonly _id: number = <%= msg.id %>; + constructor( + <%= msg.attributes.map { |attr| "public #{attr.name.first_lower}: #{attr.type_js}" }.join ",\n " %> + ) {} + encode(writer: Writer): boolean { + return writer.uint(<%= msg.id %>)<%= " &&" if msg.attributes.length() > 0 %> + <%= msg.attributes.map { |attr| "writer.#{attr.type}(this.#{attr.name.first_lower})" }.join " &&\n " %>; + } +} +export const <%= msg.name %> = bindNew(_<%= msg.name %>); +classes.set(<%= msg.id %>, <%= msg.name %>); + +<% end %> diff --git a/scripts/dockerfiles/alpine/Dockerfile b/scripts/dockerfiles/alpine/Dockerfile new file mode 100644 index 000000000..db49d3c3d --- /dev/null +++ b/scripts/dockerfiles/alpine/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine +# Fix busybox vulnerability +RUN apk upgrade busybox --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/CHANGELOG.md b/scripts/helmcharts/openreplay/charts/ingress-nginx/CHANGELOG.md index f3f44c336..bedb9f720 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/CHANGELOG.md +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/CHANGELOG.md @@ -2,39 +2,104 @@ This file documents all notable changes to [ingress-nginx](https://github.com/kubernetes/ingress-nginx) Helm Chart. The release numbering uses [semantic versioning](http://semver.org). +### 4.2.0 + +- Support for Kubernetes v1.19.0 was removed +- "[8810](https://github.com/kubernetes/ingress-nginx/pull/8810) Prepare for v1.3.0" +- "[8808](https://github.com/kubernetes/ingress-nginx/pull/8808) revert arch var name" +- "[8805](https://github.com/kubernetes/ingress-nginx/pull/8805) Bump k8s.io/klog/v2 from 2.60.1 to 2.70.1" +- "[8803](https://github.com/kubernetes/ingress-nginx/pull/8803) Update to nginx base with alpine v3.16" +- "[8802](https://github.com/kubernetes/ingress-nginx/pull/8802) chore: start v1.3.0 release process" +- "[8798](https://github.com/kubernetes/ingress-nginx/pull/8798) Add v1.24.0 to test matrix" +- "[8796](https://github.com/kubernetes/ingress-nginx/pull/8796) fix: add MAC_OS variable for static-check" +- "[8793](https://github.com/kubernetes/ingress-nginx/pull/8793) changed to alpine-v3.16" +- "[8781](https://github.com/kubernetes/ingress-nginx/pull/8781) Bump github.com/stretchr/testify from 1.7.5 to 1.8.0" +- "[8778](https://github.com/kubernetes/ingress-nginx/pull/8778) chore: remove stable.txt from release process" +- "[8775](https://github.com/kubernetes/ingress-nginx/pull/8775) Remove stable" +- "[8773](https://github.com/kubernetes/ingress-nginx/pull/8773) Bump github/codeql-action from 2.1.14 to 2.1.15" +- "[8772](https://github.com/kubernetes/ingress-nginx/pull/8772) Bump ossf/scorecard-action from 1.1.1 to 1.1.2" +- "[8771](https://github.com/kubernetes/ingress-nginx/pull/8771) fix bullet md format" +- "[8770](https://github.com/kubernetes/ingress-nginx/pull/8770) Add condition for monitoring.coreos.com/v1 API" +- "[8769](https://github.com/kubernetes/ingress-nginx/pull/8769) Fix typos and add links to developer guide" +- "[8767](https://github.com/kubernetes/ingress-nginx/pull/8767) change v1.2.0 to v1.2.1 in deploy doc URLs" +- "[8765](https://github.com/kubernetes/ingress-nginx/pull/8765) Bump github/codeql-action from 1.0.26 to 2.1.14" +- "[8752](https://github.com/kubernetes/ingress-nginx/pull/8752) Bump github.com/spf13/cobra from 1.4.0 to 1.5.0" +- "[8751](https://github.com/kubernetes/ingress-nginx/pull/8751) Bump github.com/stretchr/testify from 1.7.2 to 1.7.5" +- "[8750](https://github.com/kubernetes/ingress-nginx/pull/8750) added announcement" +- "[8740](https://github.com/kubernetes/ingress-nginx/pull/8740) change sha e2etestrunner and echoserver" +- "[8738](https://github.com/kubernetes/ingress-nginx/pull/8738) Update docs to make it easier for noobs to follow step by step" +- "[8737](https://github.com/kubernetes/ingress-nginx/pull/8737) updated baseimage sha" +- "[8736](https://github.com/kubernetes/ingress-nginx/pull/8736) set ld-musl-path" +- "[8733](https://github.com/kubernetes/ingress-nginx/pull/8733) feat: migrate leaderelection lock to leases" +- "[8726](https://github.com/kubernetes/ingress-nginx/pull/8726) prometheus metric: upstream_latency_seconds" +- "[8720](https://github.com/kubernetes/ingress-nginx/pull/8720) Ci pin deps" +- "[8719](https://github.com/kubernetes/ingress-nginx/pull/8719) Working OpenTelemetry sidecar (base nginx image)" +- "[8714](https://github.com/kubernetes/ingress-nginx/pull/8714) Create Openssf scorecard" +- "[8708](https://github.com/kubernetes/ingress-nginx/pull/8708) Bump github.com/prometheus/common from 0.34.0 to 0.35.0" +- "[8703](https://github.com/kubernetes/ingress-nginx/pull/8703) Bump actions/dependency-review-action from 1 to 2" +- "[8701](https://github.com/kubernetes/ingress-nginx/pull/8701) Fix several typos" +- "[8699](https://github.com/kubernetes/ingress-nginx/pull/8699) fix the gosec test and a make target for it" +- "[8698](https://github.com/kubernetes/ingress-nginx/pull/8698) Bump actions/upload-artifact from 2.3.1 to 3.1.0" +- "[8697](https://github.com/kubernetes/ingress-nginx/pull/8697) Bump actions/setup-go from 2.2.0 to 3.2.0" +- "[8695](https://github.com/kubernetes/ingress-nginx/pull/8695) Bump actions/download-artifact from 2 to 3" +- "[8694](https://github.com/kubernetes/ingress-nginx/pull/8694) Bump crazy-max/ghaction-docker-buildx from 1.6.2 to 3.3.1" + +### 4.1.2 + +- "[8587](https://github.com/kubernetes/ingress-nginx/pull/8587) Add CAP_SYS_CHROOT to DS/PSP when needed" +- "[8458](https://github.com/kubernetes/ingress-nginx/pull/8458) Add portNamePreffix Helm chart parameter" +- "[8522](https://github.com/kubernetes/ingress-nginx/pull/8522) Add documentation for controller.service.loadBalancerIP in Helm chart" + +### 4.1.0 + +- "[8481](https://github.com/kubernetes/ingress-nginx/pull/8481) Fix log creation in chroot script" +- "[8479](https://github.com/kubernetes/ingress-nginx/pull/8479) changed nginx base img tag to img built with alpine3.14.6" +- "[8478](https://github.com/kubernetes/ingress-nginx/pull/8478) update base images and protobuf gomod" +- "[8468](https://github.com/kubernetes/ingress-nginx/pull/8468) Fallback to ngx.var.scheme for redirectScheme with use-forward-headers when X-Forwarded-Proto is empty" +- "[8456](https://github.com/kubernetes/ingress-nginx/pull/8456) Implement object deep inspector" +- "[8455](https://github.com/kubernetes/ingress-nginx/pull/8455) Update dependencies" +- "[8454](https://github.com/kubernetes/ingress-nginx/pull/8454) Update index.md" +- "[8447](https://github.com/kubernetes/ingress-nginx/pull/8447) typo fixing" +- "[8446](https://github.com/kubernetes/ingress-nginx/pull/8446) Fix suggested annotation-value-word-blocklist" +- "[8444](https://github.com/kubernetes/ingress-nginx/pull/8444) replace deprecated topology key in example with current one" +- "[8443](https://github.com/kubernetes/ingress-nginx/pull/8443) Add dependency review enforcement" +- "[8434](https://github.com/kubernetes/ingress-nginx/pull/8434) added new auth-tls-match-cn annotation" +- "[8426](https://github.com/kubernetes/ingress-nginx/pull/8426) Bump github.com/prometheus/common from 0.32.1 to 0.33.0" + ### 4.0.18 -"[8291](https://github.com/kubernetes/ingress-nginx/pull/8291) remove git tag env from cloud build" -"[8286](https://github.com/kubernetes/ingress-nginx/pull/8286) Fix OpenTelemetry sidecar image build" -"[8277](https://github.com/kubernetes/ingress-nginx/pull/8277) Add OpenSSF Best practices badge" -"[8273](https://github.com/kubernetes/ingress-nginx/pull/8273) Issue#8241" -"[8267](https://github.com/kubernetes/ingress-nginx/pull/8267) Add fsGroup value to admission-webhooks/job-patch charts" -"[8262](https://github.com/kubernetes/ingress-nginx/pull/8262) Updated confusing error" -"[8256](https://github.com/kubernetes/ingress-nginx/pull/8256) fix: deny locations with invalid auth-url annotation" -"[8253](https://github.com/kubernetes/ingress-nginx/pull/8253) Add a certificate info metric" -"[8236](https://github.com/kubernetes/ingress-nginx/pull/8236) webhook: remove useless code." -"[8227](https://github.com/kubernetes/ingress-nginx/pull/8227) Update libraries in webhook image" -"[8225](https://github.com/kubernetes/ingress-nginx/pull/8225) fix inconsistent-label-cardinality for prometheus metrics: nginx_ingress_controller_requests" -"[8221](https://github.com/kubernetes/ingress-nginx/pull/8221) Do not validate ingresses with unknown ingress class in admission webhook endpoint" -"[8210](https://github.com/kubernetes/ingress-nginx/pull/8210) Bump github.com/prometheus/client_golang from 1.11.0 to 1.12.1" -"[8209](https://github.com/kubernetes/ingress-nginx/pull/8209) Bump google.golang.org/grpc from 1.43.0 to 1.44.0" -"[8204](https://github.com/kubernetes/ingress-nginx/pull/8204) Add Artifact Hub lint" -"[8203](https://github.com/kubernetes/ingress-nginx/pull/8203) Fix Indentation of example and link to cert-manager tutorial" -"[8201](https://github.com/kubernetes/ingress-nginx/pull/8201) feat(metrics): add path and method labels to requests countera" -"[8199](https://github.com/kubernetes/ingress-nginx/pull/8199) use functional options to reduce number of methods creating an EchoDeployment" -"[8196](https://github.com/kubernetes/ingress-nginx/pull/8196) docs: fix inconsistent controller annotation" -"[8191](https://github.com/kubernetes/ingress-nginx/pull/8191) Using Go install for misspell" -"[8186](https://github.com/kubernetes/ingress-nginx/pull/8186) prometheus+grafana using servicemonitor" -"[8185](https://github.com/kubernetes/ingress-nginx/pull/8185) Append elements on match, instead of removing for cors-annotations" -"[8179](https://github.com/kubernetes/ingress-nginx/pull/8179) Bump github.com/opencontainers/runc from 1.0.3 to 1.1.0" -"[8173](https://github.com/kubernetes/ingress-nginx/pull/8173) Adding annotations to the controller service account" -"[8163](https://github.com/kubernetes/ingress-nginx/pull/8163) Update the $req_id placeholder description" -"[8162](https://github.com/kubernetes/ingress-nginx/pull/8162) Versioned static manifests" -"[8159](https://github.com/kubernetes/ingress-nginx/pull/8159) Adding some geoip variables and default values" -"[8155](https://github.com/kubernetes/ingress-nginx/pull/8155) #7271 feat: avoid-pdb-creation-when-default-backend-disabled-and-replicas-gt-1" -"[8151](https://github.com/kubernetes/ingress-nginx/pull/8151) Automatically generate helm docs" -"[8143](https://github.com/kubernetes/ingress-nginx/pull/8143) Allow to configure delay before controller exits" -"[8136](https://github.com/kubernetes/ingress-nginx/pull/8136) add ingressClass option to helm chart - back compatibility with ingress.class annotations" -"[8126](https://github.com/kubernetes/ingress-nginx/pull/8126) Example for JWT" + +- "[8291](https://github.com/kubernetes/ingress-nginx/pull/8291) remove git tag env from cloud build" +- "[8286](https://github.com/kubernetes/ingress-nginx/pull/8286) Fix OpenTelemetry sidecar image build" +- "[8277](https://github.com/kubernetes/ingress-nginx/pull/8277) Add OpenSSF Best practices badge" +- "[8273](https://github.com/kubernetes/ingress-nginx/pull/8273) Issue#8241" +- "[8267](https://github.com/kubernetes/ingress-nginx/pull/8267) Add fsGroup value to admission-webhooks/job-patch charts" +- "[8262](https://github.com/kubernetes/ingress-nginx/pull/8262) Updated confusing error" +- "[8256](https://github.com/kubernetes/ingress-nginx/pull/8256) fix: deny locations with invalid auth-url annotation" +- "[8253](https://github.com/kubernetes/ingress-nginx/pull/8253) Add a certificate info metric" +- "[8236](https://github.com/kubernetes/ingress-nginx/pull/8236) webhook: remove useless code." +- "[8227](https://github.com/kubernetes/ingress-nginx/pull/8227) Update libraries in webhook image" +- "[8225](https://github.com/kubernetes/ingress-nginx/pull/8225) fix inconsistent-label-cardinality for prometheus metrics: nginx_ingress_controller_requests" +- "[8221](https://github.com/kubernetes/ingress-nginx/pull/8221) Do not validate ingresses with unknown ingress class in admission webhook endpoint" +- "[8210](https://github.com/kubernetes/ingress-nginx/pull/8210) Bump github.com/prometheus/client_golang from 1.11.0 to 1.12.1" +- "[8209](https://github.com/kubernetes/ingress-nginx/pull/8209) Bump google.golang.org/grpc from 1.43.0 to 1.44.0" +- "[8204](https://github.com/kubernetes/ingress-nginx/pull/8204) Add Artifact Hub lint" +- "[8203](https://github.com/kubernetes/ingress-nginx/pull/8203) Fix Indentation of example and link to cert-manager tutorial" +- "[8201](https://github.com/kubernetes/ingress-nginx/pull/8201) feat(metrics): add path and method labels to requests countera" +- "[8199](https://github.com/kubernetes/ingress-nginx/pull/8199) use functional options to reduce number of methods creating an EchoDeployment" +- "[8196](https://github.com/kubernetes/ingress-nginx/pull/8196) docs: fix inconsistent controller annotation" +- "[8191](https://github.com/kubernetes/ingress-nginx/pull/8191) Using Go install for misspell" +- "[8186](https://github.com/kubernetes/ingress-nginx/pull/8186) prometheus+grafana using servicemonitor" +- "[8185](https://github.com/kubernetes/ingress-nginx/pull/8185) Append elements on match, instead of removing for cors-annotations" +- "[8179](https://github.com/kubernetes/ingress-nginx/pull/8179) Bump github.com/opencontainers/runc from 1.0.3 to 1.1.0" +- "[8173](https://github.com/kubernetes/ingress-nginx/pull/8173) Adding annotations to the controller service account" +- "[8163](https://github.com/kubernetes/ingress-nginx/pull/8163) Update the $req_id placeholder description" +- "[8162](https://github.com/kubernetes/ingress-nginx/pull/8162) Versioned static manifests" +- "[8159](https://github.com/kubernetes/ingress-nginx/pull/8159) Adding some geoip variables and default values" +- "[8155](https://github.com/kubernetes/ingress-nginx/pull/8155) #7271 feat: avoid-pdb-creation-when-default-backend-disabled-and-replicas-gt-1" +- "[8151](https://github.com/kubernetes/ingress-nginx/pull/8151) Automatically generate helm docs" +- "[8143](https://github.com/kubernetes/ingress-nginx/pull/8143) Allow to configure delay before controller exits" +- "[8136](https://github.com/kubernetes/ingress-nginx/pull/8136) add ingressClass option to helm chart - back compatibility with ingress.class annotations" +- "[8126](https://github.com/kubernetes/ingress-nginx/pull/8126) Example for JWT" ### 4.0.15 @@ -119,11 +184,11 @@ This file documents all notable changes to [ingress-nginx](https://github.com/ku - [7707] https://github.com/kubernetes/ingress-nginx/pull/7707 Release v1.0.2 of ingress-nginx -### 4.0.2 +### 4.0.2 - [7681] https://github.com/kubernetes/ingress-nginx/pull/7681 Release v1.0.1 of ingress-nginx -### 4.0.1 +### 4.0.1 - [7535] https://github.com/kubernetes/ingress-nginx/pull/7535 Release v1.0.0 ingress-nginx diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/Chart.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/Chart.yaml index 6445231d5..7040a29cd 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/Chart.yaml @@ -1,13 +1,46 @@ annotations: artifacthub.io/changes: | - - "#8253 Add a certificate info metric" - - "#8225 fix inconsistent-label-cardinality for prometheus metrics: nginx_ingress_controller_requests" - - "#8162 Versioned static manifests" - - "#8159 Adding some geoip variables and default values" - - "#8221 Do not validate ingresses with unknown ingress class in admission webhook endpoint" + - "[8810](https://github.com/kubernetes/ingress-nginx/pull/8810) Prepare for v1.3.0" + - "[8808](https://github.com/kubernetes/ingress-nginx/pull/8808) revert arch var name" + - "[8805](https://github.com/kubernetes/ingress-nginx/pull/8805) Bump k8s.io/klog/v2 from 2.60.1 to 2.70.1" + - "[8803](https://github.com/kubernetes/ingress-nginx/pull/8803) Update to nginx base with alpine v3.16" + - "[8802](https://github.com/kubernetes/ingress-nginx/pull/8802) chore: start v1.3.0 release process" + - "[8798](https://github.com/kubernetes/ingress-nginx/pull/8798) Add v1.24.0 to test matrix" + - "[8796](https://github.com/kubernetes/ingress-nginx/pull/8796) fix: add MAC_OS variable for static-check" + - "[8793](https://github.com/kubernetes/ingress-nginx/pull/8793) changed to alpine-v3.16" + - "[8781](https://github.com/kubernetes/ingress-nginx/pull/8781) Bump github.com/stretchr/testify from 1.7.5 to 1.8.0" + - "[8778](https://github.com/kubernetes/ingress-nginx/pull/8778) chore: remove stable.txt from release process" + - "[8775](https://github.com/kubernetes/ingress-nginx/pull/8775) Remove stable" + - "[8773](https://github.com/kubernetes/ingress-nginx/pull/8773) Bump github/codeql-action from 2.1.14 to 2.1.15" + - "[8772](https://github.com/kubernetes/ingress-nginx/pull/8772) Bump ossf/scorecard-action from 1.1.1 to 1.1.2" + - "[8771](https://github.com/kubernetes/ingress-nginx/pull/8771) fix bullet md format" + - "[8770](https://github.com/kubernetes/ingress-nginx/pull/8770) Add condition for monitoring.coreos.com/v1 API" + - "[8769](https://github.com/kubernetes/ingress-nginx/pull/8769) Fix typos and add links to developer guide" + - "[8767](https://github.com/kubernetes/ingress-nginx/pull/8767) change v1.2.0 to v1.2.1 in deploy doc URLs" + - "[8765](https://github.com/kubernetes/ingress-nginx/pull/8765) Bump github/codeql-action from 1.0.26 to 2.1.14" + - "[8752](https://github.com/kubernetes/ingress-nginx/pull/8752) Bump github.com/spf13/cobra from 1.4.0 to 1.5.0" + - "[8751](https://github.com/kubernetes/ingress-nginx/pull/8751) Bump github.com/stretchr/testify from 1.7.2 to 1.7.5" + - "[8750](https://github.com/kubernetes/ingress-nginx/pull/8750) added announcement" + - "[8740](https://github.com/kubernetes/ingress-nginx/pull/8740) change sha e2etestrunner and echoserver" + - "[8738](https://github.com/kubernetes/ingress-nginx/pull/8738) Update docs to make it easier for noobs to follow step by step" + - "[8737](https://github.com/kubernetes/ingress-nginx/pull/8737) updated baseimage sha" + - "[8736](https://github.com/kubernetes/ingress-nginx/pull/8736) set ld-musl-path" + - "[8733](https://github.com/kubernetes/ingress-nginx/pull/8733) feat: migrate leaderelection lock to leases" + - "[8726](https://github.com/kubernetes/ingress-nginx/pull/8726) prometheus metric: upstream_latency_seconds" + - "[8720](https://github.com/kubernetes/ingress-nginx/pull/8720) Ci pin deps" + - "[8719](https://github.com/kubernetes/ingress-nginx/pull/8719) Working OpenTelemetry sidecar (base nginx image)" + - "[8714](https://github.com/kubernetes/ingress-nginx/pull/8714) Create Openssf scorecard" + - "[8708](https://github.com/kubernetes/ingress-nginx/pull/8708) Bump github.com/prometheus/common from 0.34.0 to 0.35.0" + - "[8703](https://github.com/kubernetes/ingress-nginx/pull/8703) Bump actions/dependency-review-action from 1 to 2" + - "[8701](https://github.com/kubernetes/ingress-nginx/pull/8701) Fix several typos" + - "[8699](https://github.com/kubernetes/ingress-nginx/pull/8699) fix the gosec test and a make target for it" + - "[8698](https://github.com/kubernetes/ingress-nginx/pull/8698) Bump actions/upload-artifact from 2.3.1 to 3.1.0" + - "[8697](https://github.com/kubernetes/ingress-nginx/pull/8697) Bump actions/setup-go from 2.2.0 to 3.2.0" + - "[8695](https://github.com/kubernetes/ingress-nginx/pull/8695) Bump actions/download-artifact from 2 to 3" + - "[8694](https://github.com/kubernetes/ingress-nginx/pull/8694) Bump crazy-max/ghaction-docker-buildx from 1.6.2 to 3.3.1" artifacthub.io/prerelease: "false" apiVersion: v2 -appVersion: 1.1.2 +appVersion: 1.3.0 description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer home: https://github.com/kubernetes/ingress-nginx @@ -15,11 +48,13 @@ icon: https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Nginx_logo.svg/5 keywords: - ingress - nginx -kubeVersion: '>=1.19.0-0' +kubeVersion: '>=1.20.0-0' maintainers: -- name: ChiefAlexander +- name: rikatz +- name: strongjz +- name: tao12345666333 name: ingress-nginx sources: - https://github.com/kubernetes/ingress-nginx type: application -version: 4.0.18 +version: 4.2.0 diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md b/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md index ad641f726..942cf0467 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md @@ -2,7 +2,7 @@ [ingress-nginx](https://github.com/kubernetes/ingress-nginx) Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer -![Version: 4.0.18](https://img.shields.io/badge/Version-4.0.18-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.1.2](https://img.shields.io/badge/AppVersion-1.1.2-informational?style=flat-square) +![Version: 4.2.0](https://img.shields.io/badge/Version-4.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.3.0](https://img.shields.io/badge/AppVersion-1.3.0-informational?style=flat-square) To use, add `ingressClassName: nginx` spec field or the `kubernetes.io/ingress.class: nginx` annotation to your Ingress resources. @@ -111,7 +111,7 @@ controller: ### AWS L7 ELB with SSL Termination -Annotate the controller as shown in the [nginx-ingress l7 patch](https://github.com/kubernetes/ingress-nginx/blob/main/deploy/aws/l7/service-l7.yaml): +Annotate the controller as shown in the [nginx-ingress l7 patch](https://github.com/kubernetes/ingress-nginx/blob/ab3a789caae65eec4ad6e3b46b19750b481b6bce/deploy/aws/l7/service-l7.yaml): ```yaml controller: @@ -128,7 +128,7 @@ controller: ### AWS route53-mapper -To configure the LoadBalancer service with the [route53-mapper addon](https://github.com/kubernetes/kops/tree/master/addons/route53-mapper), add the `domainName` annotation and `dns` label: +To configure the LoadBalancer service with the [route53-mapper addon](https://github.com/kubernetes/kops/blob/be63d4f1a7a46daaf1c4c482527328236850f111/addons/route53-mapper/README.md), add the `domainName` annotation and `dns` label: ```yaml controller: @@ -231,7 +231,7 @@ As of version `1.26.0` of this chart, by simply not providing any clusterIP valu ## Requirements -Kubernetes: `>=1.19.0-0` +Kubernetes: `>=1.20.0-0` ## Values @@ -244,7 +244,8 @@ Kubernetes: `>=1.19.0-0` | controller.admissionWebhooks.createSecretJob.resources | object | `{}` | | | controller.admissionWebhooks.enabled | bool | `true` | | | controller.admissionWebhooks.existingPsp | string | `""` | Use an existing PSP instead of creating one | -| controller.admissionWebhooks.failurePolicy | string | `"Fail"` | | +| controller.admissionWebhooks.extraEnvs | list | `[]` | Additional environment variables to set | +| controller.admissionWebhooks.failurePolicy | string | `"Fail"` | Admission Webhook failure policy to use | | controller.admissionWebhooks.key | string | `"/usr/local/certificates/key"` | | | controller.admissionWebhooks.labels | object | `{}` | Labels to be added to admission webhooks | | controller.admissionWebhooks.namespaceSelector | object | `{}` | | @@ -254,7 +255,7 @@ Kubernetes: `>=1.19.0-0` | controller.admissionWebhooks.patch.image.digest | string | `"sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660"` | | | controller.admissionWebhooks.patch.image.image | string | `"ingress-nginx/kube-webhook-certgen"` | | | controller.admissionWebhooks.patch.image.pullPolicy | string | `"IfNotPresent"` | | -| controller.admissionWebhooks.patch.image.registry | string | `"k8s.gcr.io"` | | +| controller.admissionWebhooks.patch.image.registry | string | `"registry.k8s.io"` | | | controller.admissionWebhooks.patch.image.tag | string | `"v1.1.1"` | | | controller.admissionWebhooks.patch.labels | object | `{}` | Labels to be added to patch job resources | | controller.admissionWebhooks.patch.nodeSelector."kubernetes.io/os" | string | `"linux"` | | @@ -306,12 +307,14 @@ Kubernetes: `>=1.19.0-0` | controller.hostPort.ports.https | int | `443` | 'hostPort' https port | | controller.hostname | object | `{}` | Optionally customize the pod hostname. | | controller.image.allowPrivilegeEscalation | bool | `true` | | -| controller.image.digest | string | `"sha256:28b11ce69e57843de44e3db6413e98d09de0f6688e33d4bd384002a44f78405c"` | | +| controller.image.chroot | bool | `false` | | +| controller.image.digest | string | `"sha256:d1707ca76d3b044ab8a28277a2466a02100ee9f58a86af1535a3edf9323ea1b5"` | | +| controller.image.digestChroot | string | `"sha256:0fcb91216a22aae43b374fc2e6a03b8afe9e8c78cbf07a09d75636dc4ea3c191"` | | | controller.image.image | string | `"ingress-nginx/controller"` | | | controller.image.pullPolicy | string | `"IfNotPresent"` | | -| controller.image.registry | string | `"k8s.gcr.io"` | | +| controller.image.registry | string | `"registry.k8s.io"` | | | controller.image.runAsUser | int | `101` | | -| controller.image.tag | string | `"v1.1.2"` | | +| controller.image.tag | string | `"v1.3.0"` | | | controller.ingressClass | string | `"nginx"` | For backwards compatibility with ingress.class annotation, use ingressClass. Algorithm is as follows, first ingressClassName is considered, if not present, controller looks for ingress.class annotation | | controller.ingressClassByName | bool | `false` | Process IngressClass per name (additionally as per spec.controller). | | controller.ingressClassResource.controllerValue | string | `"k8s.io/ingress-nginx"` | Controller-value of the controller that is processing this ingressClass | @@ -399,6 +402,7 @@ Kubernetes: `>=1.19.0-0` | controller.service.ipFamilies | list | `["IPv4"]` | List of IP families (e.g. IPv4, IPv6) assigned to the service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. | | controller.service.ipFamilyPolicy | string | `"SingleStack"` | Represents the dual-stack-ness requested or required by this Service. Possible values are SingleStack, PreferDualStack or RequireDualStack. The ipFamilies and clusterIPs fields depend on the value of this field. | | controller.service.labels | object | `{}` | | +| controller.service.loadBalancerIP | string | `""` | Used by cloud providers to connect the resulting `LoadBalancer` to a pre-existing static IP according to https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer | | controller.service.loadBalancerSourceRanges | list | `[]` | | | controller.service.nodePorts.http | string | `""` | | | controller.service.nodePorts.https | string | `""` | | @@ -409,6 +413,7 @@ Kubernetes: `>=1.19.0-0` | controller.service.targetPorts.http | string | `"http"` | | | controller.service.targetPorts.https | string | `"https"` | | | controller.service.type | string | `"LoadBalancer"` | | +| controller.shareProcessNamespace | bool | `false` | | | controller.sysctls | object | `{}` | See https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ for notes on enabling and using sysctls | | controller.tcp.annotations | object | `{}` | Annotations to be added to the tcp config configmap | | controller.tcp.configMapNamespace | string | `""` | Allows customization of the tcp-services-configmap; defaults to $(POD_NAMESPACE) | @@ -437,7 +442,7 @@ Kubernetes: `>=1.19.0-0` | defaultBackend.image.image | string | `"defaultbackend-amd64"` | | | defaultBackend.image.pullPolicy | string | `"IfNotPresent"` | | | defaultBackend.image.readOnlyRootFilesystem | bool | `true` | | -| defaultBackend.image.registry | string | `"k8s.gcr.io"` | | +| defaultBackend.image.registry | string | `"registry.k8s.io"` | | | defaultBackend.image.runAsNonRoot | bool | `true` | | | defaultBackend.image.runAsUser | int | `65534` | | | defaultBackend.image.tag | string | `"1.5"` | | @@ -474,6 +479,7 @@ Kubernetes: `>=1.19.0-0` | dhParam | string | `nil` | A base64-encoded Diffie-Hellman parameter. This can be generated with: `openssl dhparam 4096 2> /dev/null | base64` | | imagePullSecrets | list | `[]` | Optional array of imagePullSecrets containing private registry credentials | | podSecurityPolicy.enabled | bool | `false` | | +| portNamePrefix | string | `""` | Prefix for TCP and UDP ports names in ingress controller service | | rbac.create | bool | `true` | | | rbac.scope | bool | `false` | | | revisionHistoryLimit | int | `10` | Rollback limit | @@ -481,6 +487,6 @@ Kubernetes: `>=1.19.0-0` | serviceAccount.automountServiceAccountToken | bool | `true` | | | serviceAccount.create | bool | `true` | | | serviceAccount.name | string | `""` | | -| tcp | object | `{}` | TCP service key:value pairs | -| udp | object | `{}` | UDP service key:value pairs | +| tcp | object | `{}` | TCP service key-value pairs | +| udp | object | `{}` | UDP service key-value pairs | diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md.gotmpl b/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md.gotmpl index 5cd9e59e1..895996111 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md.gotmpl +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/README.md.gotmpl @@ -110,7 +110,7 @@ controller: ### AWS L7 ELB with SSL Termination -Annotate the controller as shown in the [nginx-ingress l7 patch](https://github.com/kubernetes/ingress-nginx/blob/main/deploy/aws/l7/service-l7.yaml): +Annotate the controller as shown in the [nginx-ingress l7 patch](https://github.com/kubernetes/ingress-nginx/blob/ab3a789caae65eec4ad6e3b46b19750b481b6bce/deploy/aws/l7/service-l7.yaml): ```yaml controller: @@ -127,7 +127,7 @@ controller: ### AWS route53-mapper -To configure the LoadBalancer service with the [route53-mapper addon](https://github.com/kubernetes/kops/tree/master/addons/route53-mapper), add the `domainName` annotation and `dns` label: +To configure the LoadBalancer service with the [route53-mapper addon](https://github.com/kubernetes/kops/blob/be63d4f1a7a46daaf1c4c482527328236850f111/addons/route53-mapper/README.md), add the `domainName` annotation and `dns` label: ```yaml controller: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/daemonset-tcp-udp-portNamePrefix-values.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/daemonset-tcp-udp-portNamePrefix-values.yaml new file mode 100644 index 000000000..90b0f57a5 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/daemonset-tcp-udp-portNamePrefix-values.yaml @@ -0,0 +1,18 @@ +controller: + kind: DaemonSet + image: + repository: ingress-controller/controller + tag: 1.0.0-dev + digest: null + admissionWebhooks: + enabled: false + service: + type: ClusterIP + +tcp: + 9000: "default/test:8080" + +udp: + 9001: "default/test:8080" + +portNamePrefix: "port" diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-tcp-udp-portNamePrefix-values.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-tcp-udp-portNamePrefix-values.yaml new file mode 100644 index 000000000..56323c5ee --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-tcp-udp-portNamePrefix-values.yaml @@ -0,0 +1,17 @@ +controller: + image: + repository: ingress-controller/controller + tag: 1.0.0-dev + digest: null + admissionWebhooks: + enabled: false + service: + type: ClusterIP + +tcp: + 9000: "default/test:8080" + +udp: + 9001: "default/test:8080" + +portNamePrefix: "port" diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-webhook-extraEnvs-values.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-webhook-extraEnvs-values.yaml new file mode 100644 index 000000000..95487b071 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/ci/deployment-webhook-extraEnvs-values.yaml @@ -0,0 +1,12 @@ +controller: + service: + type: ClusterIP + admissionWebhooks: + enabled: true + extraEnvs: + - name: FOO + value: foo + - name: TEST + value: test + patch: + enabled: true diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_helpers.tpl b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_helpers.tpl index a72af5d9d..e69de0c41 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_helpers.tpl +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/_helpers.tpl @@ -43,11 +43,40 @@ capabilities: - ALL add: - NET_BIND_SERVICE + {{- if .Values.controller.image.chroot }} + - SYS_CHROOT + {{- end }} runAsUser: {{ .Values.controller.image.runAsUser }} allowPrivilegeEscalation: {{ .Values.controller.image.allowPrivilegeEscalation }} {{- end }} {{- end -}} +{{/* +Get specific image +*/}} +{{- define "ingress-nginx.image" -}} +{{- if .chroot -}} +{{- printf "%s-chroot" .image -}} +{{- else -}} +{{- printf "%s" .image -}} +{{- end }} +{{- end -}} + +{{/* +Get specific image digest +*/}} +{{- define "ingress-nginx.imageDigest" -}} +{{- if .chroot -}} +{{- if .digestChroot -}} +{{- printf "@%s" .digestChroot -}} +{{- end }} +{{- else -}} +{{ if .digest -}} +{{- printf "@%s" .digest -}} +{{- end -}} +{{- end -}} +{{- end -}} + {{/* Create a default fully qualified controller name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml index f20e247f9..72c17eae4 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml @@ -56,6 +56,9 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + {{- if .Values.controller.admissionWebhooks.extraEnvs }} + {{- toYaml .Values.controller.admissionWebhooks.extraEnvs | nindent 12 }} + {{- end }} securityContext: allowPrivilegeEscalation: false {{- if .Values.controller.admissionWebhooks.createSecretJob.resources }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml index 8583685fa..3a1637a64 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml @@ -58,6 +58,9 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + {{- if .Values.controller.admissionWebhooks.extraEnvs }} + {{- toYaml .Values.controller.admissionWebhooks.extraEnvs | nindent 12 }} + {{- end }} securityContext: allowPrivilegeEscalation: false {{- if .Values.controller.admissionWebhooks.patchWebhookJob.resources }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/clusterrole.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/clusterrole.yaml index c093f048a..0e725ec06 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/clusterrole.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/clusterrole.yaml @@ -29,6 +29,13 @@ rules: verbs: - list - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - list + - watch {{- if and .Values.controller.scope.enabled .Values.controller.scope.namespace }} - apiGroups: - "" diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-daemonset.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-daemonset.yaml index 72811fbe4..2dca8e5c1 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-daemonset.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-daemonset.yaml @@ -67,11 +67,14 @@ spec: - name: {{ $sysctl | quote }} value: {{ $value | quote }} {{- end }} + {{- end }} + {{- if .Values.controller.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.controller.shareProcessNamespace }} {{- end }} containers: - name: {{ .Values.controller.containerName }} {{- with .Values.controller.image }} - image: "{{- if .repository -}}{{ .repository }}{{ else }}{{ .registry }}/{{ .image }}{{- end -}}:{{ .tag }}{{- if (.digest) -}} @{{.digest}} {{- end -}}" + image: "{{- if .repository -}}{{ .repository }}{{ else }}{{ .registry }}/{{ include "ingress-nginx.image" . }}{{- end -}}:{{ .tag }}{{ include "ingress-nginx.imageDigest" . }}" {{- end }} imagePullPolicy: {{ .Values.controller.image.pullPolicy }} {{- if .Values.controller.lifecycle }} @@ -79,14 +82,7 @@ spec: {{- end }} args: {{- include "ingress-nginx.params" . | nindent 12 }} - securityContext: - capabilities: - drop: - - ALL - add: - - NET_BIND_SERVICE - runAsUser: {{ .Values.controller.image.runAsUser }} - allowPrivilegeEscalation: {{ .Values.controller.image.allowPrivilegeEscalation }} + securityContext: {{ include "controller.containerSecurityContext" . | nindent 12 }} env: - name: POD_NAME valueFrom: @@ -128,7 +124,7 @@ spec: protocol: TCP {{- end }} {{- range $key, $value := .Values.tcp }} - - name: {{ $key }}-tcp + - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-tcp containerPort: {{ $key }} protocol: TCP {{- if $.Values.controller.hostPort.enabled }} @@ -136,7 +132,7 @@ spec: {{- end }} {{- end }} {{- range $key, $value := .Values.udp }} - - name: {{ $key }}-udp + - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-udp containerPort: {{ $key }} protocol: UDP {{- if $.Values.controller.hostPort.enabled }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-deployment.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-deployment.yaml index a1943cd91..5b781f8de 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-deployment.yaml @@ -71,11 +71,14 @@ spec: - name: {{ $sysctl | quote }} value: {{ $value | quote }} {{- end }} + {{- end }} + {{- if .Values.controller.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.controller.shareProcessNamespace }} {{- end }} containers: - name: {{ .Values.controller.containerName }} {{- with .Values.controller.image }} - image: "{{- if .repository -}}{{ .repository }}{{ else }}{{ .registry }}/{{ .image }}{{- end -}}:{{ .tag }}{{- if (.digest) -}} @{{.digest}} {{- end -}}" + image: "{{- if .repository -}}{{ .repository }}{{ else }}{{ .registry }}/{{ include "ingress-nginx.image" . }}{{- end -}}:{{ .tag }}{{ include "ingress-nginx.imageDigest" . }}" {{- end }} imagePullPolicy: {{ .Values.controller.image.pullPolicy }} {{- if .Values.controller.lifecycle }} @@ -125,7 +128,7 @@ spec: protocol: TCP {{- end }} {{- range $key, $value := .Values.tcp }} - - name: {{ $key }}-tcp + - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-tcp containerPort: {{ $key }} protocol: TCP {{- if $.Values.controller.hostPort.enabled }} @@ -133,7 +136,7 @@ spec: {{- end }} {{- end }} {{- range $key, $value := .Values.udp }} - - name: {{ $key }}-udp + - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-udp containerPort: {{ $key }} protocol: UDP {{- if $.Values.controller.hostPort.enabled }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-prometheusrules.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-prometheusrules.yaml index ca5427523..78b5362e8 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-prometheusrules.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-prometheusrules.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.controller.metrics.enabled .Values.controller.metrics.prometheusRule.enabled -}} +{{- if and ( .Values.controller.metrics.enabled ) ( .Values.controller.metrics.prometheusRule.enabled ) ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) -}} apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-psp.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-psp.yaml index a859594d1..fe34408c8 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-psp.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-psp.yaml @@ -12,6 +12,9 @@ metadata: spec: allowedCapabilities: - NET_BIND_SERVICE + {{- if .Values.controller.image.chroot }} + - SYS_CHROOT + {{- end }} {{- if .Values.controller.sysctls }} allowedUnsafeSysctls: {{- range $sysctl, $value := .Values.controller.sysctls }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-role.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-role.yaml index 47bbc32d0..8e5f8a0d7 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-role.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-role.yaml @@ -73,6 +73,21 @@ rules: - configmaps verbs: - create + - apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - {{ .Values.controller.electionID }} + verbs: + - get + - update + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create - apiGroups: - "" resources: diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service-internal.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service-internal.yaml index 599449836..aae3e155e 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service-internal.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service-internal.yaml @@ -52,10 +52,10 @@ spec: {{- end }} {{- end }} {{- range $key, $value := .Values.tcp }} - - name: {{ $key }}-tcp + - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-tcp port: {{ $key }} protocol: TCP - targetPort: {{ $key }}-tcp + targetPort: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-tcp {{- if $.Values.controller.service.nodePorts.tcp }} {{- if index $.Values.controller.service.nodePorts.tcp $key }} nodePort: {{ index $.Values.controller.service.nodePorts.tcp $key }} @@ -63,10 +63,10 @@ spec: {{- end }} {{- end }} {{- range $key, $value := .Values.udp }} - - name: {{ $key }}-udp + - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-udp port: {{ $key }} protocol: UDP - targetPort: {{ $key }}-udp + targetPort: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-udp {{- if $.Values.controller.service.nodePorts.udp }} {{- if index $.Values.controller.service.nodePorts.udp $key }} nodePort: {{ index $.Values.controller.service.nodePorts.udp $key }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service.yaml index 05fb2041e..2b28196de 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/templates/controller-service.yaml @@ -37,12 +37,12 @@ spec: {{- if .Values.controller.service.healthCheckNodePort }} healthCheckNodePort: {{ .Values.controller.service.healthCheckNodePort }} {{- end }} -{{- if semverCompare ">=1.20.0-0" .Capabilities.KubeVersion.Version -}} +{{- if semverCompare ">=1.21.0-0" .Capabilities.KubeVersion.Version -}} {{- if .Values.controller.service.ipFamilyPolicy }} ipFamilyPolicy: {{ .Values.controller.service.ipFamilyPolicy }} {{- end }} {{- end }} -{{- if semverCompare ">=1.20.0-0" .Capabilities.KubeVersion.Version -}} +{{- if semverCompare ">=1.21.0-0" .Capabilities.KubeVersion.Version -}} {{- if .Values.controller.service.ipFamilies }} ipFamilies: {{ toYaml .Values.controller.service.ipFamilies | nindent 4 }} {{- end }} @@ -74,10 +74,10 @@ spec: {{- end }} {{- end }} {{- range $key, $value := .Values.tcp }} - - name: {{ $key }}-tcp + - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-tcp port: {{ $key }} protocol: TCP - targetPort: {{ $key }}-tcp + targetPort: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-tcp {{- if $.Values.controller.service.nodePorts.tcp }} {{- if index $.Values.controller.service.nodePorts.tcp $key }} nodePort: {{ index $.Values.controller.service.nodePorts.tcp $key }} @@ -85,10 +85,10 @@ spec: {{- end }} {{- end }} {{- range $key, $value := .Values.udp }} - - name: {{ $key }}-udp + - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-udp port: {{ $key }} protocol: UDP - targetPort: {{ $key }}-udp + targetPort: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-udp {{- if $.Values.controller.service.nodePorts.udp }} {{- if index $.Values.controller.service.nodePorts.udp $key }} nodePort: {{ index $.Values.controller.service.nodePorts.udp $key }} diff --git a/scripts/helmcharts/openreplay/charts/ingress-nginx/values.yaml b/scripts/helmcharts/openreplay/charts/ingress-nginx/values.yaml index c887dc051..cddc2c742 100644 --- a/scripts/helmcharts/openreplay/charts/ingress-nginx/values.yaml +++ b/scripts/helmcharts/openreplay/charts/ingress-nginx/values.yaml @@ -16,13 +16,16 @@ commonLabels: {} controller: name: controller image: - registry: k8s.gcr.io + ## Keep false as default for now! + chroot: false + registry: registry.k8s.io image: ingress-nginx/controller ## for backwards compatibility consider setting the full image url via the repository value below ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail ## repository: - tag: "v1.1.2" - digest: sha256:28b11ce69e57843de44e3db6413e98d09de0f6688e33d4bd384002a44f78405c + tag: "v1.3.0" + digest: sha256:d1707ca76d3b044ab8a28277a2466a02100ee9f58a86af1535a3edf9323ea1b5 + digestChroot: sha256:0fcb91216a22aae43b374fc2e6a03b8afe9e8c78cbf07a09d75636dc4ea3c191 pullPolicy: IfNotPresent # www-data -> uid 101 runAsUser: 101 @@ -272,7 +275,7 @@ controller: ## topologySpreadConstraints: [] # - maxSkew: 1 - # topologyKey: failure-domain.beta.kubernetes.io/zone + # topologyKey: topology.kubernetes.io/zone # whenUnsatisfiable: DoNotSchedule # labelSelector: # matchLabels: @@ -457,7 +460,8 @@ controller: ## externalIPs: [] - # loadBalancerIP: "" + # -- Used by cloud providers to connect the resulting `LoadBalancer` to a pre-existing static IP according to https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer + loadBalancerIP: "" loadBalancerSourceRanges: [] enableHttp: true @@ -529,6 +533,10 @@ controller: ## Ref: https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer # externalTrafficPolicy: "" + # shareProcessNamespace enables process namespace sharing within the pod. + # This can be used for example to signal log rotation using `kill -USR1` from a sidecar. + shareProcessNamespace: false + # -- Additional containers to be added to the controller pod. # See https://github.com/lemonldap-ng-controller/lemonldap-ng-controller as example. extraContainers: [] @@ -572,7 +580,7 @@ controller: extraModules: [] ## Modules, which are mounted into the core nginx image # - name: opentelemetry - # image: busybox + # image: registry.k8s.io/ingress-nginx/opentelemetry:v20220415-controller-v1.2.0-beta.0-2-g81c2afd97@sha256:ce61e2cf0b347dffebb2dcbf57c33891d2217c1bad9c0959c878e5be671ef941 # # The image must contain a `/usr/local/bin/init_module.sh` executable, which # will be executed as initContainers, to move its config files within the @@ -586,6 +594,15 @@ controller: ## These annotations will be added to the ValidatingWebhookConfiguration and ## the Jobs Spec of the admission webhooks. enabled: true + # -- Additional environment variables to set + extraEnvs: [] + # extraEnvs: + # - name: FOO + # valueFrom: + # secretKeyRef: + # key: FOO + # name: secret-resource + # -- Admission Webhook failure policy to use failurePolicy: Fail # timeoutSeconds: 10 port: 8443 @@ -623,7 +640,7 @@ controller: patch: enabled: true image: - registry: k8s.gcr.io + registry: registry.k8s.io image: ingress-nginx/kube-webhook-certgen ## for backwards compatibility consider setting the full image url via the repository value below ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail @@ -750,7 +767,7 @@ defaultBackend: name: defaultbackend image: - registry: k8s.gcr.io + registry: registry.k8s.io image: defaultbackend-amd64 ## for backwards compatibility consider setting the full image url via the repository value below ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail @@ -901,18 +918,22 @@ serviceAccount: imagePullSecrets: [] # - name: secretName -# -- TCP service key:value pairs +# -- TCP service key-value pairs ## Ref: https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/exposing-tcp-udp-services.md ## tcp: {} # 8080: "default/example-tcp-svc:9000" -# -- UDP service key:value pairs +# -- UDP service key-value pairs ## Ref: https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/exposing-tcp-udp-services.md ## udp: {} # 53: "kube-system/kube-dns:53" +# -- Prefix for TCP and UDP ports names in ingress controller service +## Some cloud providers, like Yandex Cloud may have a requirements for a port name regex to support cloud load balancer integration +portNamePrefix: "" + # -- (string) A base64-encoded Diffie-Hellman parameter. # This can be generated with: `openssl dhparam 4096 2> /dev/null | base64` ## Ref: https://github.com/kubernetes/ingress-nginx/tree/main/docs/examples/customization/ssl-dh-param diff --git a/tracker/.husky/pre-commit b/tracker/.husky/pre-commit new file mode 100755 index 000000000..9471928e2 --- /dev/null +++ b/tracker/.husky/pre-commit @@ -0,0 +1,25 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +if git diff --cached --name-only | grep --quiet '^tracker/tracker/' +then + echo "tracker" + pwd + cd tracker/tracker + + npm run lint-front + + cd ../../ +fi + +if git diff --cached --name-only | grep --quiet '^tracker/tracker-assist/' +then + echo "tracker-assist" + cd tracker/tracker-assist + + npm run lint-front + + cd ../../ +fi + +exit 0 diff --git a/tracker/tracker-assist/.eslintignore b/tracker/tracker-assist/.eslintignore new file mode 100644 index 000000000..94b2f339c --- /dev/null +++ b/tracker/tracker-assist/.eslintignore @@ -0,0 +1,8 @@ +node_modules +npm-debug.log +lib +cjs +build +.cache +.eslintrc.cjs +src/common/messages.ts diff --git a/tracker/tracker-assist/.eslintrc.cjs b/tracker/tracker-assist/.eslintrc.cjs new file mode 100644 index 000000000..01d5c5bc0 --- /dev/null +++ b/tracker/tracker-assist/.eslintrc.cjs @@ -0,0 +1,49 @@ +/* eslint-disable */ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + project: ['./tsconfig.json'], + tsconfigRootDir: __dirname, + }, + plugins: ['@typescript-eslint', 'prettier'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'prettier', + ], + rules: { + 'no-empty': [ + 'error', + { + allowEmptyCatch: true, + }, + ], + '@typescript-eslint/camelcase': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/explicit-function-return-type': 'warn', + '@typescript-eslint/prefer-readonly': 'warn', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/restrict-plus-operands': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + 'no-useless-escape': 'warn', + 'no-control-regex': 'warn', + '@typescript-eslint/restrict-template-expressions': 'warn', + '@typescript-eslint/no-useless-constructor': 'warn', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'warn', + '@typescript-eslint/no-useless-constructor': 'warn', + 'semi': ["error", "never"], + 'quotes': ["error", "single"], + 'comma-dangle': ["error", "always"] + }, +}; diff --git a/tracker/tracker-assist/package.json b/tracker/tracker-assist/package.json index 3d363cffd..4efae850e 100644 --- a/tracker/tracker-assist/package.json +++ b/tracker/tracker-assist/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-assist", "description": "Tracker plugin for screen assistance through the WebRTC", - "version": "3.5.15", + "version": "3.5.16", "keywords": [ "WebRTC", "assistance", @@ -13,7 +13,7 @@ "type": "module", "main": "./lib/index.js", "scripts": { - "lint": "prettier --write 'src/**/*.ts' README.md && tsc --noEmit", + "lint": "eslint src --ext .ts,.js --fix --quiet", "build": "npm run build-es && npm run build-cjs", "build-es": "rm -Rf lib && tsc && npm run replace-versions", "build-cjs": "rm -Rf cjs && tsc --project tsconfig-cjs.json && echo '{ \"type\": \"commonjs\" }' > cjs/package.json && npm run replace-paths && npm run replace-versions", @@ -21,7 +21,9 @@ "replace-versions": "npm run replace-pkg-version && npm run replace-req-version", "replace-pkg-version": "replace-in-files lib/* cjs/* --string='PACKAGE_VERSION' --replacement=$npm_package_version", "replace-req-version": "replace-in-files lib/* cjs/* --string='REQUIRED_TRACKER_VERSION' --replacement='3.5.14'", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "prepare": "cd ../../ && husky install tracker/.husky/", + "lint-front": "lint-staged" }, "dependencies": { "csstype": "^3.0.10", @@ -32,9 +34,26 @@ "@openreplay/tracker": "^3.5.3" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.30.0", + "@typescript-eslint/parser": "^5.30.0", + "eslint": "^7.8.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "prettier": "^2.7.1", "@openreplay/tracker": "file:../tracker", - "prettier": "^2.7.0", "replace-in-files-cli": "^1.0.0", "typescript": "^4.6.0-dev.20211126" + }, + "husky": { + "hooks": { + "pre-commit": "sh lint.sh" + } + }, + "lint-staged": { + "*.{js,mjs,cjs,jsx,ts,tsx}": [ + "eslint --fix --quiet" + ] } } diff --git a/tracker/tracker-assist/src/AnnotationCanvas.ts b/tracker/tracker-assist/src/AnnotationCanvas.ts index 11a06a781..1341045f6 100644 --- a/tracker/tracker-assist/src/AnnotationCanvas.ts +++ b/tracker/tracker-assist/src/AnnotationCanvas.ts @@ -1,14 +1,14 @@ export default class AnnotationCanvas { private canvas: HTMLCanvasElement private ctx: CanvasRenderingContext2D | null = null - private painting: boolean = false + private painting = false constructor() { this.canvas = document.createElement('canvas') Object.assign(this.canvas.style, { - position: "fixed", + position: 'fixed', left: 0, top: 0, - pointerEvents: "none", + pointerEvents: 'none', zIndex: 2147483647 - 2, }) } @@ -18,7 +18,7 @@ export default class AnnotationCanvas { this.canvas.height = window.innerHeight } - private lastPosition: [number, number] = [0,0] + private lastPosition: [number, number] = [0,0,] start = (p: [number, number]) => { this.painting = true this.clrTmID && clearTimeout(this.clrTmID) @@ -38,9 +38,9 @@ export default class AnnotationCanvas { this.ctx.moveTo(this.lastPosition[0], this.lastPosition[1]) this.ctx.lineTo(p[0], p[1]) this.ctx.lineWidth = 8 - this.ctx.lineCap = "round" - this.ctx.lineJoin = "round" - this.ctx.strokeStyle = "red" + this.ctx.lineCap = 'round' + this.ctx.lineJoin = 'round' + this.ctx.strokeStyle = 'red' this.ctx.stroke() this.lastPosition = p } @@ -51,7 +51,7 @@ export default class AnnotationCanvas { const fadeStep = () => { if (!this.ctx || this.painting ) { return } this.ctx.globalCompositeOperation = 'destination-out' - this.ctx.fillStyle = "rgba(255, 255, 255, 0.1)" + this.ctx.fillStyle = 'rgba(255, 255, 255, 0.1)' this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) this.ctx.globalCompositeOperation = 'source-over' timeoutID = setTimeout(fadeStep,100) @@ -67,8 +67,8 @@ export default class AnnotationCanvas { mount() { document.body.appendChild(this.canvas) - this.ctx = this.canvas.getContext("2d") - window.addEventListener("resize", this.resizeCanvas) + this.ctx = this.canvas.getContext('2d') + window.addEventListener('resize', this.resizeCanvas) this.resizeCanvas() } @@ -76,6 +76,6 @@ export default class AnnotationCanvas { if (this.canvas.parentNode){ this.canvas.parentNode.removeChild(this.canvas) } - window.removeEventListener("resize", this.resizeCanvas) + window.removeEventListener('resize', this.resizeCanvas) } } \ No newline at end of file diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index a36c307db..eec9a66d7 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -1,20 +1,21 @@ -import type { Socket } from 'socket.io-client'; -import { connect } from 'socket.io-client'; -import Peer from 'peerjs'; -import type { Properties } from 'csstype'; -import { App } from '@openreplay/tracker'; +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { Socket, } from 'socket.io-client' +import { connect, } from 'socket.io-client' +import Peer from 'peerjs' +import type { Properties, } from 'csstype' +import { App, } from '@openreplay/tracker' -import RequestLocalStream from './LocalStream.js'; -import RemoteControl from './RemoteControl.js'; -import CallWindow from './CallWindow.js'; -import AnnotationCanvas from './AnnotationCanvas.js'; -import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js'; -import { callConfirmDefault } from './ConfirmWindow/defaults.js'; -import type { Options as ConfirmOptions } from './ConfirmWindow/defaults.js'; +import RequestLocalStream from './LocalStream.js' +import RemoteControl from './RemoteControl.js' +import CallWindow from './CallWindow.js' +import AnnotationCanvas from './AnnotationCanvas.js' +import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js' +import { callConfirmDefault, } from './ConfirmWindow/defaults.js' +import type { Options as ConfirmOptions, } from './ConfirmWindow/defaults.js' // TODO: fully specified strict check (everywhere) -type StartEndCallback = () => ((()=>{}) | void) +type StartEndCallback = () => ((()=>Record) | void) export interface Options { onAgentConnect: StartEndCallback, @@ -40,7 +41,7 @@ enum CallingState { // TODO typing???? -type OptionalCallback = (()=>{}) | void +type OptionalCallback = (()=>Record) | void type Agent = { onDisconnect?: OptionalCallback, onControlReleased?: OptionalCallback, @@ -49,11 +50,11 @@ type Agent = { } export default class Assist { - readonly version = "PACKAGE_VERSION" + readonly version = 'PACKAGE_VERSION' private socket: Socket | null = null private peer: Peer | null = null - private assistDemandedRestart: boolean = false + private assistDemandedRestart = false private callingState: CallingState = CallingState.False private agents: Record = {} @@ -64,8 +65,8 @@ export default class Assist { private readonly noSecureMode: boolean = false, ) { this.options = Object.assign({ - session_calling_peer_key: "__openreplay_calling_peer", - session_control_peer_key: "__openreplay_control_peer", + session_calling_peer_key: '__openreplay_calling_peer', + session_control_peer_key: '__openreplay_control_peer', config: null, onCallStart: ()=>{}, onAgentConnect: ()=>{}, @@ -74,10 +75,10 @@ export default class Assist { controlConfirm: {}, // TODO: clear options passing/merging/overriting }, options, - ); + ) if (document.hidden !== undefined) { - const sendActivityState = () => this.emit("UPDATE_SESSION", { active: !document.hidden }) + const sendActivityState = () => this.emit('UPDATE_SESSION', { active: !document.hidden, }) app.attachEventListener( document, 'visibilitychange', @@ -88,15 +89,15 @@ export default class Assist { } const titleNode = document.querySelector('title') const observer = titleNode && new MutationObserver(() => { - this.emit("UPDATE_SESSION", { pageTitle: document.title }) + this.emit('UPDATE_SESSION', { pageTitle: document.title, }) }) app.attachStartCallback(() => { - if (this.assistDemandedRestart) { return; } + if (this.assistDemandedRestart) { return } this.onStart() - observer && observer.observe(titleNode, { subtree: true, characterData: true, childList: true }) + observer && observer.observe(titleNode, { subtree: true, characterData: true, childList: true, }) }) app.attachStopCallback(() => { - if (this.assistDemandedRestart) { return; } + if (this.assistDemandedRestart) { return } this.clean() observer && observer.disconnect() }) @@ -104,10 +105,10 @@ export default class Assist { if (this.agentsConnected) { // @ts-ignore No need in statistics messages. TODO proper filter if (messages.length === 2 && messages[0]._id === 0 && messages[1]._id === 49) { return } - this.emit("messages", messages) + this.emit('messages', messages) } }) - app.session.attachUpdateCallback(sessInfo => this.emit("UPDATE_SESSION", sessInfo)) + app.session.attachUpdateCallback(sessInfo => this.emit('UPDATE_SESSION', sessInfo)) } private emit(ev: string, ...args) { @@ -119,7 +120,7 @@ export default class Assist { } private notifyCallEnd() { - this.emit("call_end"); + this.emit('call_end') } private onRemoteCallEnd = () => {} @@ -131,17 +132,17 @@ export default class Assist { const socket = this.socket = connect(app.getHost(), { path: '/ws-assist/socket', query: { - "peerId": peerID, - "identity": "session", - "sessionInfo": JSON.stringify({ + 'peerId': peerID, + 'identity': 'session', + 'sessionInfo': JSON.stringify({ pageTitle: document.title, active: true, - ...this.app.getSessionInfo() + ...this.app.getSessionInfo(), }), }, - transports: ["websocket"], + transports: ['websocket',], }) - socket.onAny((...args) => app.debug.log("Socket:", ...args)) + socket.onAny((...args) => app.debug.log('Socket:', ...args)) @@ -149,15 +150,15 @@ export default class Assist { this.options, id => { this.agents[id].onControlReleased = this.options.onRemoteControlStart() - this.emit("control_granted", id) + this.emit('control_granted', id) annot = new AnnotationCanvas() annot.mount() }, id => { const cb = this.agents[id].onControlReleased delete this.agents[id].onControlReleased - typeof cb === "function" && cb() - this.emit("control_rejected", id) + typeof cb === 'function' && cb() + this.emit('control_rejected', id) if (annot != null) { annot.remove() annot = null @@ -166,41 +167,41 @@ export default class Assist { ) // TODO: check incoming args - socket.on("request_control", remoteControl.requestControl) - socket.on("release_control", remoteControl.releaseControl) - socket.on("scroll", remoteControl.scroll) - socket.on("click", remoteControl.click) - socket.on("move", remoteControl.move) - socket.on("focus", (clientID, nodeID) => { + socket.on('request_control', remoteControl.requestControl) + socket.on('release_control', remoteControl.releaseControl) + socket.on('scroll', remoteControl.scroll) + socket.on('click', remoteControl.click) + socket.on('move', remoteControl.move) + socket.on('focus', (clientID, nodeID) => { const el = app.nodes.getNode(nodeID) if (el instanceof HTMLElement) { remoteControl.focus(clientID, el) } }) - socket.on("input", remoteControl.input) + socket.on('input', remoteControl.input) let annot: AnnotationCanvas | null = null - socket.on("moveAnnotation", (_, p) => annot && annot.move(p)) // TODO: restrict by id - socket.on("startAnnotation", (_, p) => annot && annot.start(p)) - socket.on("stopAnnotation", () => annot && annot.stop()) + socket.on('moveAnnotation', (_, p) => annot && annot.move(p)) // TODO: restrict by id + socket.on('startAnnotation', (_, p) => annot && annot.start(p)) + socket.on('stopAnnotation', () => annot && annot.stop()) - socket.on("NEW_AGENT", (id: string, info) => { + socket.on('NEW_AGENT', (id: string, info) => { this.agents[id] = { onDisconnect: this.options.onAgentConnect && this.options.onAgentConnect(), ...info, // TODO } this.assistDemandedRestart = true - this.app.stop(); + this.app.stop() this.app.start().then(() => { this.assistDemandedRestart = false }) }) - socket.on("AGENTS_CONNECTED", (ids: string[]) => { + socket.on('AGENTS_CONNECTED', (ids: string[]) => { ids.forEach(id =>{ this.agents[id] = { onDisconnect: this.options.onAgentConnect && this.options.onAgentConnect(), } }) this.assistDemandedRestart = true - this.app.stop(); + this.app.stop() this.app.start().then(() => { this.assistDemandedRestart = false }) remoteControl.reconnect(ids) @@ -208,7 +209,7 @@ export default class Assist { let confirmCall:ConfirmWindow | null = null - socket.on("AGENT_DISCONNECTED", (id) => { + socket.on('AGENT_DISCONNECTED', (id) => { remoteControl.releaseControl(id) // close the call also @@ -221,15 +222,15 @@ export default class Assist { this.agents[id] && this.agents[id].onDisconnect != null && this.agents[id].onDisconnect() delete this.agents[id] }) - socket.on("NO_AGENT", () => { + socket.on('NO_AGENT', () => { this.agents = {} }) - socket.on("call_end", () => this.onRemoteCallEnd()) // TODO: check if agent calling id + socket.on('call_end', () => this.onRemoteCallEnd()) // TODO: check if agent calling id // TODO: fix the code - let agentName = "" - let callingAgent = "" - socket.on("_agent_name",(id, name) => { agentName = name; callingAgent = id }) + let agentName = '' + let callingAgent = '' + socket.on('_agent_name',(id, name) => { agentName = name; callingAgent = id }) // PeerJS call (todo: use native WebRTC) @@ -242,27 +243,27 @@ export default class Assist { if (this.options.config) { peerOptions['config'] = this.options.config } - const peer = this.peer = new Peer(peerID, peerOptions); + const peer = this.peer = new Peer(peerID, peerOptions) // app.debug.log('Peer created: ', peer) // @ts-ignore - peer.on('error', e => app.debug.warn("Peer error: ", e.type, e)) + peer.on('error', e => app.debug.warn('Peer error: ', e.type, e)) peer.on('disconnected', () => peer.reconnect()) peer.on('call', (call) => { - app.debug.log("Call: ", call) + app.debug.log('Call: ', call) if (this.callingState !== CallingState.False) { call.close() //this.notifyCallEnd() // TODO: strictly connect calling peer with agent socket.id - app.debug.warn("Call closed instantly bacause line is busy. CallingState: ", this.callingState) - return; + app.debug.warn('Call closed instantly bacause line is busy. CallingState: ', this.callingState) + return } const setCallingState = (newState: CallingState) => { if (newState === CallingState.True) { - sessionStorage.setItem(this.options.session_calling_peer_key, call.peer); + sessionStorage.setItem(this.options.session_calling_peer_key, call.peer) } else if (newState === CallingState.False) { - sessionStorage.removeItem(this.options.session_calling_peer_key); + sessionStorage.removeItem(this.options.session_calling_peer_key) } - this.callingState = newState; + this.callingState = newState } let confirmAnswer: Promise @@ -278,7 +279,7 @@ export default class Assist { confirmAnswer = confirmCall.mount() this.playNotificationSound() this.onRemoteCallEnd = () => { // if call cancelled by a caller before confirmation - app.debug.log("Received call_end during confirm window opened") + app.debug.log('Received call_end during confirm window opened') confirmCall?.remove() setCallingState(CallingState.False) call.close() @@ -307,7 +308,7 @@ export default class Assist { const onCallEnd = this.options.onCallStart() const handleCallEnd = () => { - app.debug.log("Handle Call End") + app.debug.log('Handle Call End') call.close() callUI.remove() annot && annot.remove() @@ -322,27 +323,27 @@ export default class Assist { this.onRemoteCallEnd = handleCallEnd call.on('error', e => { - app.debug.warn("Call error:", e) + app.debug.warn('Call error:', e) initiateCallEnd() - }); + }) RequestLocalStream().then(lStream => { call.on('stream', function(rStream) { - callUI.setRemoteStream(rStream); + callUI.setRemoteStream(rStream) const onInteraction = () => { // only if hidden? callUI.playRemote() - document.removeEventListener("click", onInteraction) + document.removeEventListener('click', onInteraction) } - document.addEventListener("click", onInteraction) - }); + document.addEventListener('click', onInteraction) + }) lStream.onVideoTrack(vTrack => { - const sender = call.peerConnection.getSenders().find(s => s.track?.kind === "video") + const sender = call.peerConnection.getSenders().find(s => s.track?.kind === 'video') if (!sender) { - app.debug.warn("No video sender found") + app.debug.warn('No video sender found') return } - app.debug.log("sender found:", sender) + app.debug.log('sender found:', sender) sender.replaceTrack(vTrack) }) @@ -352,16 +353,16 @@ export default class Assist { setCallingState(CallingState.True) }) .catch(e => { - app.debug.warn("Audio mediadevice request error:", e) + app.debug.warn('Audio mediadevice request error:', e) initiateCallEnd() - }); - }).catch(); // in case of Confirm.remove() without any confirmation/decline - }); + }) + }).catch() // in case of Confirm.remove() without any confirmation/decline + }) } private playNotificationSound() { if ('Audio' in window) { - new Audio("https://static.openreplay.com/tracker-assist/notification.mp3") + new Audio('https://static.openreplay.com/tracker-assist/notification.mp3') .play() .catch(e => { this.app.debug.warn(e) @@ -372,11 +373,11 @@ export default class Assist { private clean() { if (this.peer) { this.peer.destroy() - this.app.debug.log("Peer destroyed") + this.app.debug.log('Peer destroyed') } if (this.socket) { this.socket.disconnect() - this.app.debug.log("Socket disconnected") + this.app.debug.log('Socket disconnected') } } } diff --git a/tracker/tracker-assist/src/CallWindow.ts b/tracker/tracker-assist/src/CallWindow.ts index ae2bdd3fa..8804ffa3e 100644 --- a/tracker/tracker-assist/src/CallWindow.ts +++ b/tracker/tracker-assist/src/CallWindow.ts @@ -1,7 +1,7 @@ -import type { LocalStream } from './LocalStream.js'; -import attachDND from './dnd.js'; +import type { LocalStream, } from './LocalStream.js' +import attachDND from './dnd.js' -const SS_START_TS_KEY = "__openreplay_assist_call_start_ts" +const SS_START_TS_KEY = '__openreplay_assist_call_start_ts' export default class CallWindow { private iframe: HTMLIFrameElement @@ -21,66 +21,66 @@ export default class CallWindow { constructor() { const iframe = this.iframe = document.createElement('iframe') Object.assign(iframe.style, { - position: "fixed", + position: 'fixed', zIndex: 2147483647 - 1, - border: "none", - bottom: "10px", - right: "10px", - height: "200px", - width: "200px", + border: 'none', + bottom: '10px', + right: '10px', + height: '200px', + width: '200px', }) // TODO: find the best attribute name for the ignoring iframes - iframe.setAttribute("data-openreplay-obscured", "") - iframe.setAttribute("data-openreplay-hidden", "") - iframe.setAttribute("data-openreplay-ignore", "") + iframe.setAttribute('data-openreplay-obscured', '') + iframe.setAttribute('data-openreplay-hidden', '') + iframe.setAttribute('data-openreplay-ignore', '') document.body.appendChild(iframe) - const doc = iframe.contentDocument; + const doc = iframe.contentDocument if (!doc) { - console.error("OpenReplay: CallWindow iframe document is not reachable.") - return; + console.error('OpenReplay: CallWindow iframe document is not reachable.') + return } //const baseHref = "https://static.openreplay.com/tracker-assist/test" - const baseHref = "https://static.openreplay.com/tracker-assist/3.4.4" - this.load = fetch(baseHref + "/index.html") + const baseHref = 'https://static.openreplay.com/tracker-assist/3.4.4' + this.load = fetch(baseHref + '/index.html') .then(r => r.text()) .then((text) => { iframe.onload = () => { - const assistSection = doc.getElementById("or-assist") - assistSection?.classList.remove("status-connecting") + const assistSection = doc.getElementById('or-assist') + assistSection?.classList.remove('status-connecting') //iframe.style.height = doc.body.scrollHeight + 'px'; //iframe.style.width = doc.body.scrollWidth + 'px'; this.adjustIframeSize() - iframe.onload = null; + iframe.onload = null } // ? text = text.replace(/href="css/g, `href="${baseHref}/css`) - doc.open(); - doc.write(text); - doc.close(); + doc.open() + doc.write(text) + doc.close() - this.vLocal = doc.getElementById("video-local") as (HTMLVideoElement | null); - this.vRemote = doc.getElementById("video-remote") as (HTMLVideoElement | null); - this.videoContainer = doc.getElementById("video-container"); + this.vLocal = doc.getElementById('video-local') as (HTMLVideoElement | null) + this.vRemote = doc.getElementById('video-remote') as (HTMLVideoElement | null) + this.videoContainer = doc.getElementById('video-container') - this.audioBtn = doc.getElementById("audio-btn"); + this.audioBtn = doc.getElementById('audio-btn') if (this.audioBtn) { - this.audioBtn.onclick = () => this.toggleAudio(); + this.audioBtn.onclick = () => this.toggleAudio() } - this.videoBtn = doc.getElementById("video-btn"); + this.videoBtn = doc.getElementById('video-btn') if (this.videoBtn) { - this.videoBtn.onclick = () => this.toggleVideo(); + this.videoBtn.onclick = () => this.toggleVideo() } - this.endCallBtn = doc.getElementById("end-call-btn"); + this.endCallBtn = doc.getElementById('end-call-btn') - this.agentNameElem = doc.getElementById("agent-name"); - this.vPlaceholder = doc.querySelector("#remote-stream p") + this.agentNameElem = doc.getElementById('agent-name') + this.vPlaceholder = doc.querySelector('#remote-stream p') - const tsElem = doc.getElementById("duration"); + const tsElem = doc.getElementById('duration') if (tsElem) { const startTs = Number(sessionStorage.getItem(SS_START_TS_KEY)) || Date.now() sessionStorage.setItem(SS_START_TS_KEY, startTs.toString()) @@ -90,15 +90,15 @@ export default class CallWindow { const mins = ~~(secsFull / 60) const secs = secsFull - mins * 60 tsElem.innerText = `${mins}:${secs < 10 ? 0 : ''}${secs}` - }, 500); + }, 500) } - const dragArea = doc.querySelector(".drag-area") + const dragArea = doc.querySelector('.drag-area') if (dragArea) { // TODO: save coordinates on the new page attachDND(iframe, dragArea, doc.documentElement) } - }); + }) //this.toggleVideoUI(false) //this.toggleRemoteVideoUI(false) @@ -107,8 +107,8 @@ export default class CallWindow { private adjustIframeSize() { const doc = this.iframe.contentDocument if (!doc) { return } - this.iframe.style.height = doc.body.scrollHeight + 'px'; - this.iframe.style.width = doc.body.scrollWidth + 'px'; + this.iframe.style.height = doc.body.scrollHeight + 'px' + this.iframe.style.width = doc.body.scrollWidth + 'px' } setCallEndAction(endCall: () => void) { @@ -124,16 +124,16 @@ export default class CallWindow { setRemoteStream(rStream: MediaStream) { this.load.then(() => { if (this.vRemote && !this.vRemote.srcObject) { - this.vRemote.srcObject = rStream; + this.vRemote.srcObject = rStream if (this.vPlaceholder) { - this.vPlaceholder.innerText = "Video has been paused. Click anywhere to resume."; + this.vPlaceholder.innerText = 'Video has been paused. Click anywhere to resume.' } // Hack for audio. Doesen't work inside the iframe because of some magical reasons (check if it is connected to autoplay?) - this.aRemote = document.createElement("audio"); - this.aRemote.autoplay = true; - this.aRemote.style.display = "none" - this.aRemote.srcObject = rStream; + this.aRemote = document.createElement('audio') + this.aRemote.autoplay = true + this.aRemote.style.display = 'none' + this.aRemote.srcObject = rStream document.body.appendChild(this.aRemote) } @@ -156,9 +156,9 @@ export default class CallWindow { this.load.then(() => { if (this.videoContainer) { if (enable) { - this.videoContainer.classList.add("remote") + this.videoContainer.classList.add('remote') } else { - this.videoContainer.classList.remove("remote") + this.videoContainer.classList.remove('remote') } this.adjustIframeSize() } @@ -186,11 +186,11 @@ export default class CallWindow { private toggleAudioUI(enabled: boolean) { - if (!this.audioBtn) { return; } + if (!this.audioBtn) { return } if (enabled) { - this.audioBtn.classList.remove("muted") + this.audioBtn.classList.remove('muted') } else { - this.audioBtn.classList.add("muted") + this.audioBtn.classList.add('muted') } } @@ -200,18 +200,18 @@ export default class CallWindow { } private toggleVideoUI(enabled: boolean) { - if (!this.videoBtn || !this.videoContainer) { return; } + if (!this.videoBtn || !this.videoContainer) { return } if (enabled) { - this.videoContainer.classList.add("local") - this.videoBtn.classList.remove("off"); + this.videoContainer.classList.add('local') + this.videoBtn.classList.remove('off') } else { - this.videoContainer.classList.remove("local") - this.videoBtn.classList.add("off"); + this.videoContainer.classList.remove('local') + this.videoBtn.classList.add('off') } this.adjustIframeSize() } - private videoRequested: boolean = false + private videoRequested = false private toggleVideo() { this.localStream?.toggleVideo() .then(enabled => { diff --git a/tracker/tracker-assist/src/ConfirmWindow/ConfirmWindow.ts b/tracker/tracker-assist/src/ConfirmWindow/ConfirmWindow.ts index 02d9dd9c6..fd7209689 100644 --- a/tracker/tracker-assist/src/ConfirmWindow/ConfirmWindow.ts +++ b/tracker/tracker-assist/src/ConfirmWindow/ConfirmWindow.ts @@ -1,4 +1,5 @@ -import type { Properties } from 'csstype'; +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { Properties, } from 'csstype' export type ButtonOptions = | HTMLButtonElement @@ -19,45 +20,45 @@ export interface ConfirmWindowOptions { function makeButton(options: ButtonOptions, defaultStyle?: Properties): HTMLButtonElement { if (options instanceof HTMLButtonElement) { - return options; + return options } - const btn = document.createElement("button"); + const btn = document.createElement('button') Object.assign(btn.style, { - padding: "10px 14px", - fontSize: "14px", - borderRadius: "3px", - border: "none", - cursor: "pointer", - display: "flex", - alignItems: "center", - textTransform: "uppercase", - marginRight: "10px" - }, defaultStyle); - if (typeof options === "string") { - btn.innerHTML = options; + padding: '10px 14px', + fontSize: '14px', + borderRadius: '3px', + border: 'none', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + textTransform: 'uppercase', + marginRight: '10px', + }, defaultStyle) + if (typeof options === 'string') { + btn.innerHTML = options } else { - btn.innerHTML = options.innerHTML; - Object.assign(btn.style, options.style); + btn.innerHTML = options.innerHTML + Object.assign(btn.style, options.style) } - return btn; + return btn } export default class ConfirmWindow { private wrapper: HTMLDivElement; constructor(options: ConfirmWindowOptions) { - const wrapper = document.createElement("div"); - const popup = document.createElement("div"); - const p = document.createElement("p"); - p.innerText = options.text; - const buttons = document.createElement("div"); + const wrapper = document.createElement('div') + const popup = document.createElement('div') + const p = document.createElement('p') + p.innerText = options.text + const buttons = document.createElement('div') const confirmBtn = makeButton(options.confirmBtn, { - background: "rgba(0, 167, 47, 1)", - color: "white" + background: 'rgba(0, 167, 47, 1)', + color: 'white', }) const declineBtn = makeButton(options.declineBtn, { - background: "#FFE9E9", - color: "#CC0000" + background: '#FFE9E9', + color: '#CC0000', }) buttons.appendChild(confirmBtn) buttons.appendChild(declineBtn) @@ -66,78 +67,78 @@ export default class ConfirmWindow { Object.assign(buttons.style, { - marginTop: "10px", - display: "flex", - alignItems: "center", + marginTop: '10px', + display: 'flex', + alignItems: 'center', // justifyContent: "space-evenly", - backgroundColor: "white", - padding: "10px", - boxShadow: "0px 0px 3.99778px 1.99889px rgba(0, 0, 0, 0.1)", - borderRadius: "6px" - }); + backgroundColor: 'white', + padding: '10px', + boxShadow: '0px 0px 3.99778px 1.99889px rgba(0, 0, 0, 0.1)', + borderRadius: '6px', + }) Object.assign( popup.style, { - font: "14px 'Roboto', sans-serif", - position: "relative", - pointerEvents: "auto", - margin: "4em auto", - width: "90%", - maxWidth: "fit-content", - padding: "20px", - background: "#F3F3F3", + font: '14px \'Roboto\', sans-serif', + position: 'relative', + pointerEvents: 'auto', + margin: '4em auto', + width: '90%', + maxWidth: 'fit-content', + padding: '20px', + background: '#F3F3F3', //opacity: ".75", - color: "black", - borderRadius: "3px", - boxShadow: "0px 0px 3.99778px 1.99889px rgba(0, 0, 0, 0.1)" + color: 'black', + borderRadius: '3px', + boxShadow: '0px 0px 3.99778px 1.99889px rgba(0, 0, 0, 0.1)', }, options.style - ); + ) Object.assign(wrapper.style, { - position: "fixed", + position: 'fixed', left: 0, top: 0, - height: "100%", - width: "100%", - pointerEvents: "none", - zIndex: 2147483647 - 1 - }); + height: '100%', + width: '100%', + pointerEvents: 'none', + zIndex: 2147483647 - 1, + }) - wrapper.appendChild(popup); - this.wrapper = wrapper; + wrapper.appendChild(popup) + this.wrapper = wrapper confirmBtn.onclick = () => { - this._remove(); - this.resolve(true); - }; + this._remove() + this.resolve(true) + } declineBtn.onclick = () => { - this._remove(); - this.resolve(false); - }; + this._remove() + this.resolve(false) + } } private resolve: (result: boolean) => void = () => {}; private reject: () => void = () => {}; mount(): Promise { - document.body.appendChild(this.wrapper); + document.body.appendChild(this.wrapper) return new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); + this.resolve = resolve + this.reject = reject + }) } private _remove() { if (!this.wrapper.parentElement) { - return; + return } - document.body.removeChild(this.wrapper); + document.body.removeChild(this.wrapper) } remove() { - this._remove(); - this.reject(); + this._remove() + this.reject() } } diff --git a/tracker/tracker-assist/src/ConfirmWindow/defaults.ts b/tracker/tracker-assist/src/ConfirmWindow/defaults.ts index 8f84cbe89..d6d5430c4 100644 --- a/tracker/tracker-assist/src/ConfirmWindow/defaults.ts +++ b/tracker/tracker-assist/src/ConfirmWindow/defaults.ts @@ -1,10 +1,10 @@ -import { declineCall, acceptCall, cross, remoteControl } from '../icons.js' -import type { ButtonOptions, ConfirmWindowOptions } from './ConfirmWindow.js' +import { declineCall, acceptCall, cross, remoteControl, } from '../icons.js' +import type { ButtonOptions, ConfirmWindowOptions, } from './ConfirmWindow.js' -const TEXT_GRANT_REMORTE_ACCESS = "Grant Remote Control"; -const TEXT_REJECT = "Reject"; -const TEXT_ANSWER_CALL = `${acceptCall}   Answer`; +const TEXT_GRANT_REMORTE_ACCESS = 'Grant Remote Control' +const TEXT_REJECT = 'Reject' +const TEXT_ANSWER_CALL = `${acceptCall}   Answer` export type Options = string | Partial; @@ -14,15 +14,15 @@ function confirmDefault( declineBtn: ButtonOptions, text: string ): ConfirmWindowOptions { - const isStr = typeof opts === "string"; + const isStr = typeof opts === 'string' return Object.assign( { text: isStr ? opts : text, confirmBtn, - declineBtn + declineBtn, }, isStr ? undefined : opts - ); + ) } export const callConfirmDefault = (opts: Options) => @@ -30,7 +30,7 @@ export const callConfirmDefault = (opts: Options) => opts, TEXT_ANSWER_CALL, TEXT_REJECT, - "You have an incoming call. Do you want to answer?" + 'You have an incoming call. Do you want to answer?' ) export const controlConfirmDefault = (opts: Options) => @@ -38,5 +38,5 @@ export const controlConfirmDefault = (opts: Options) => opts, TEXT_GRANT_REMORTE_ACCESS, TEXT_REJECT, - "Agent requested remote control. Allow?" + 'Agent requested remote control. Allow?' ) diff --git a/tracker/tracker-assist/src/LocalStream.ts b/tracker/tracker-assist/src/LocalStream.ts index 63f01ad58..7f233108a 100644 --- a/tracker/tracker-assist/src/LocalStream.ts +++ b/tracker/tracker-assist/src/LocalStream.ts @@ -5,44 +5,44 @@ declare global { } function dummyTrack(): MediaStreamTrack { - const canvas = document.createElement("canvas")//, { width: 0, height: 0}) + const canvas = document.createElement('canvas')//, { width: 0, height: 0}) canvas.width=canvas.height=2 // Doesn't work when 1 (?!) - const ctx = canvas.getContext('2d'); - ctx?.fillRect(0, 0, canvas.width, canvas.height); + const ctx = canvas.getContext('2d') + ctx?.fillRect(0, 0, canvas.width, canvas.height) requestAnimationFrame(function draw(){ ctx?.fillRect(0,0, canvas.width, canvas.height) - requestAnimationFrame(draw); - }); + requestAnimationFrame(draw) + }) // Also works. Probably it should be done once connected. //setTimeout(() => { ctx?.fillRect(0,0, canvas.width, canvas.height) }, 4000) - return canvas.captureStream(60).getTracks()[0]; + return canvas.captureStream(60).getTracks()[0] } export default function RequestLocalStream(): Promise { - return navigator.mediaDevices.getUserMedia({ audio:true }) + return navigator.mediaDevices.getUserMedia({ audio:true, }) .then(aStream => { const aTrack = aStream.getAudioTracks()[0] - if (!aTrack) { throw new Error("No audio tracks provided") } + if (!aTrack) { throw new Error('No audio tracks provided') } return new _LocalStream(aTrack) }) } class _LocalStream { - private mediaRequested: boolean = false + private mediaRequested = false readonly stream: MediaStream private readonly vdTrack: MediaStreamTrack constructor(aTrack: MediaStreamTrack) { this.vdTrack = dummyTrack() - this.stream = new MediaStream([ aTrack, this.vdTrack ]) + this.stream = new MediaStream([ aTrack, this.vdTrack, ]) } toggleVideo(): Promise { if (!this.mediaRequested) { - return navigator.mediaDevices.getUserMedia({video:true}) + return navigator.mediaDevices.getUserMedia({video:true,}) .then(vStream => { const vTrack = vStream.getVideoTracks()[0] if (!vTrack) { - throw new Error("No video track provided") + throw new Error('No video track provided') } this.stream.addTrack(vTrack) this.stream.removeTrack(this.vdTrack) diff --git a/tracker/tracker-assist/src/Mouse.ts b/tracker/tracker-assist/src/Mouse.ts index 52858ccf6..a6164e153 100644 --- a/tracker/tracker-assist/src/Mouse.ts +++ b/tracker/tracker-assist/src/Mouse.ts @@ -3,24 +3,24 @@ type XY = [number, number] export default class Mouse { private mouse: HTMLDivElement - private position: [number,number] = [0,0] + private position: [number,number] = [0,0,] constructor() { - this.mouse = document.createElement('div'); + this.mouse = document.createElement('div') Object.assign(this.mouse.style, { - width: "20px", - height: "20px", - opacity: ".4", - borderRadius: "50%", - position: "absolute", - zIndex: "999998", - background: "radial-gradient(red, transparent)", - }); + width: '20px', + height: '20px', + opacity: '.4', + borderRadius: '50%', + position: 'absolute', + zIndex: '999998', + background: 'radial-gradient(red, transparent)', + }) } mount() { document.body.appendChild(this.mouse) - window.addEventListener("scroll", this.handleWScroll) - window.addEventListener("resize", this.resetLastScrEl) + window.addEventListener('scroll', this.handleWScroll) + window.addEventListener('resize', this.resetLastScrEl) } move(pos: XY) { @@ -28,16 +28,16 @@ export default class Mouse { this.resetLastScrEl() } - this.position = pos; + this.position = pos Object.assign(this.mouse.style, { left: `${pos[0] || 0}px`, - top: `${pos[1] || 0}px` + top: `${pos[1] || 0}px`, }) } getPosition(): XY { - return this.position; + return this.position } click(pos: XY) { @@ -51,18 +51,18 @@ export default class Mouse { } private readonly pScrEl = document.scrollingElement || document.documentElement // Is it always correct - private lastScrEl: Element | "window" | null = null + private lastScrEl: Element | 'window' | null = null private resetLastScrEl = () => { this.lastScrEl = null } private handleWScroll = e => { if (e.target !== this.lastScrEl && - this.lastScrEl !== "window") { + this.lastScrEl !== 'window') { this.resetLastScrEl() } } scroll(delta: XY) { // what would be the browser-like logic? - const [mouseX, mouseY] = this.position - const [dX, dY] = delta + const [mouseX, mouseY,] = this.position + const [dX, dY,] = delta let el = this.lastScrEl @@ -72,7 +72,7 @@ export default class Mouse { el.scrollTop += dY return // TODO: if not scrolled } - if (el === "window") { + if (el === 'window') { window.scroll(this.pScrEl.scrollLeft + dX, this.pScrEl.scrollTop + dY) return } @@ -85,7 +85,7 @@ export default class Mouse { // el.scrollTopMax > 0 // available in firefox if (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth) { const styles = getComputedStyle(el) - if (styles.overflow.indexOf("scroll") >= 0 || styles.overflow.indexOf("auto") >= 0) { // returns true for body in habr.com but it's not scrollable + if (styles.overflow.indexOf('scroll') >= 0 || styles.overflow.indexOf('auto') >= 0) { // returns true for body in habr.com but it's not scrollable const esl = el.scrollLeft const est = el.scrollTop el.scrollLeft += dX @@ -101,14 +101,14 @@ export default class Mouse { // If not scrolled window.scroll(this.pScrEl.scrollLeft + dX, this.pScrEl.scrollTop + dY) - this.lastScrEl = "window" + this.lastScrEl = 'window' } remove() { if (this.mouse.parentElement) { - document.body.removeChild(this.mouse); + document.body.removeChild(this.mouse) } - window.removeEventListener("scroll", this.handleWScroll) - window.removeEventListener("resize", this.resetLastScrEl) + window.removeEventListener('scroll', this.handleWScroll) + window.removeEventListener('resize', this.resetLastScrEl) } } \ No newline at end of file diff --git a/tracker/tracker-assist/src/RemoteControl.ts b/tracker/tracker-assist/src/RemoteControl.ts index 50cb717c8..4cbc785f3 100644 --- a/tracker/tracker-assist/src/RemoteControl.ts +++ b/tracker/tracker-assist/src/RemoteControl.ts @@ -1,7 +1,7 @@ -import Mouse from './Mouse.js'; -import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js'; -import { controlConfirmDefault } from './ConfirmWindow/defaults.js'; -import type { Options as AssistOptions } from './Assist'; +import Mouse from './Mouse.js' +import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js' +import { controlConfirmDefault, } from './ConfirmWindow/defaults.js' +import type { Options as AssistOptions, } from './Assist' enum RCStatus { Disabled, @@ -11,7 +11,7 @@ enum RCStatus { let setInputValue = function(this: HTMLInputElement | HTMLTextAreaElement, value: string) { this.value = value } -const nativeInputValueDescriptor = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value") +const nativeInputValueDescriptor = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value') if (nativeInputValueDescriptor && nativeInputValueDescriptor.set) { setInputValue = nativeInputValueDescriptor.set } @@ -93,7 +93,7 @@ export default class RemoteControl { if (this.focused instanceof HTMLTextAreaElement || this.focused instanceof HTMLInputElement) { setInputValue.call(this.focused, value) - const ev = new Event('input', { bubbles: true}) + const ev = new Event('input', { bubbles: true,}) this.focused.dispatchEvent(ev) } else if (this.focused.isContentEditable) { this.focused.innerText = value diff --git a/tracker/tracker-assist/src/_slim.ts b/tracker/tracker-assist/src/_slim.ts index ce86863be..760a9b8d2 100644 --- a/tracker/tracker-assist/src/_slim.ts +++ b/tracker/tracker-assist/src/_slim.ts @@ -5,4 +5,4 @@ */ // @ts-ignore -typeof window !== "undefined" && (window.parcelRequire = window.parcelRequire || undefined); +typeof window !== 'undefined' && (window.parcelRequire = window.parcelRequire || undefined) diff --git a/tracker/tracker-assist/src/dnd.ts b/tracker/tracker-assist/src/dnd.ts index 818bb0b89..622d86b94 100644 --- a/tracker/tracker-assist/src/dnd.ts +++ b/tracker/tracker-assist/src/dnd.ts @@ -9,7 +9,7 @@ export default function attachDND( dropArea: Element, ) { - dragArea.addEventListener('pointerdown', userPressed, { passive: true }) + dragArea.addEventListener('pointerdown', userPressed, { passive: true, }) let bbox, startX, startY, @@ -20,9 +20,9 @@ export default function attachDND( startX = event.clientX startY = event.clientY bbox = movingEl.getBoundingClientRect() - dropArea.addEventListener('pointermove', userMoved, { passive: true }) - dropArea.addEventListener('pointerup', userReleased, { passive: true }) - dropArea.addEventListener('pointercancel', userReleased, { passive: true }) + dropArea.addEventListener('pointermove', userMoved, { passive: true, }) + dropArea.addEventListener('pointerup', userReleased, { passive: true, }) + dropArea.addEventListener('pointercancel', userReleased, { passive: true, }) }; /* @@ -46,8 +46,8 @@ export default function attachDND( } function userMovedRaf() { - movingEl.style.transform = "translate3d("+deltaX+"px,"+deltaY+"px, 0px)"; - raf = null; + movingEl.style.transform = 'translate3d('+deltaX+'px,'+deltaY+'px, 0px)' + raf = null } function userReleased() { @@ -58,9 +58,9 @@ export default function attachDND( cancelAnimationFrame(raf) raf = null } - movingEl.style.left = bbox.left + deltaX + "px" - movingEl.style.top = bbox.top + deltaY + "px" - movingEl.style.transform = "translate3d(0px,0px,0px)" + movingEl.style.left = bbox.left + deltaX + 'px' + movingEl.style.top = bbox.top + deltaY + 'px' + movingEl.style.transform = 'translate3d(0px,0px,0px)' deltaX = deltaY = 0 } } \ No newline at end of file diff --git a/tracker/tracker-assist/src/icons.ts b/tracker/tracker-assist/src/icons.ts index 763b015b9..c844812f4 100644 --- a/tracker/tracker-assist/src/icons.ts +++ b/tracker/tracker-assist/src/icons.ts @@ -4,7 +4,7 @@ export const declineCall = ` -`; +` export const acceptCall = declineCall.replace('fill="#ef5261"', 'fill="green"') @@ -13,4 +13,4 @@ export const cross = ` ` -export const remoteControl = `` +export const remoteControl = '' diff --git a/tracker/tracker-assist/src/index.ts b/tracker/tracker-assist/src/index.ts index 2a3161089..32af38d3f 100644 --- a/tracker/tracker-assist/src/index.ts +++ b/tracker/tracker-assist/src/index.ts @@ -1,7 +1,7 @@ -import './_slim.js'; +import './_slim.js' -import type { App } from '@openreplay/tracker'; -import type { Options } from './Assist.js' +import type { App, } from '@openreplay/tracker' +import type { Options, } from './Assist.js' import Assist from './Assist.js' @@ -9,13 +9,13 @@ export default function(opts?: Partial) { return function(app: App | null, appOptions: { __DISABLE_SECURE_MODE?: boolean } = {}) { // @ts-ignore if (app === null || !navigator?.mediaDevices?.getUserMedia) { // 93.04% browsers - return; - } - if (!app.checkRequiredVersion || !app.checkRequiredVersion("REQUIRED_TRACKER_VERSION")) { - console.warn("OpenReplay Assist: couldn't load. The minimum required version of @openreplay/tracker@REQUIRED_TRACKER_VERSION is not met") return } - app.notify.log("OpenReplay Assist initializing.") + if (!app.checkRequiredVersion || !app.checkRequiredVersion('REQUIRED_TRACKER_VERSION')) { + console.warn('OpenReplay Assist: couldn\'t load. The minimum required version of @openreplay/tracker@REQUIRED_TRACKER_VERSION is not met') + return + } + app.notify.log('OpenReplay Assist initializing.') const assist = new Assist(app, opts, appOptions.__DISABLE_SECURE_MODE) app.debug.log(assist) return assist diff --git a/tracker/tracker-axios/src/index.ts b/tracker/tracker-axios/src/index.ts index e7abce84e..14d3387dc 100644 --- a/tracker/tracker-axios/src/index.ts +++ b/tracker/tracker-axios/src/index.ts @@ -216,4 +216,4 @@ export default function(opts: Partial = {}) { return Promise.reject(error); }); } -} \ No newline at end of file +} diff --git a/tracker/tracker/.eslintignore b/tracker/tracker/.eslintignore new file mode 100644 index 000000000..94b2f339c --- /dev/null +++ b/tracker/tracker/.eslintignore @@ -0,0 +1,8 @@ +node_modules +npm-debug.log +lib +cjs +build +.cache +.eslintrc.cjs +src/common/messages.ts diff --git a/tracker/tracker/.eslintrc.cjs b/tracker/tracker/.eslintrc.cjs index b0c6b42eb..1e2170eee 100644 --- a/tracker/tracker/.eslintrc.cjs +++ b/tracker/tracker/.eslintrc.cjs @@ -1,8 +1,10 @@ +/* eslint-disable */ module.exports = { root: true, parser: '@typescript-eslint/parser', parserOptions: { - project: ['./tsconfig.json'], + project: ['./tsconfig-base.json', './src/main/tsconfig-cjs.json'], + tsconfigRootDir: __dirname, }, plugins: ['prettier', '@typescript-eslint'], extends: [ @@ -11,7 +13,7 @@ module.exports = { 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', - 'prettier/@typescript-eslint', + 'prettier', ], rules: { 'prettier/prettier': ['error', require('./.prettierrc.json')], @@ -27,8 +29,21 @@ module.exports = { '@typescript-eslint/unbound-method': 'off', '@typescript-eslint/explicit-function-return-type': 'warn', '@typescript-eslint/prefer-readonly': 'warn', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/restrict-plus-operands': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + 'no-useless-escape': 'warn', + 'no-control-regex': 'warn', + '@typescript-eslint/restrict-template-expressions': 'warn', + '@typescript-eslint/no-useless-constructor': 'warn', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'warn', '@typescript-eslint/no-useless-constructor': 'warn', }, -}; +}; diff --git a/tracker/tracker/.prettierrc.json b/tracker/tracker/.prettierrc.json index a20502b7f..5e2863a11 100644 --- a/tracker/tracker/.prettierrc.json +++ b/tracker/tracker/.prettierrc.json @@ -1,4 +1,5 @@ { + "printWidth": 100, "singleQuote": true, "trailingComma": "all" } diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 183006c05..90cf3e8d9 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "3.5.15", + "version": "3.5.16", "keywords": [ "logging", "replay" @@ -14,33 +14,50 @@ "type": "module", "main": "./lib/index.js", "scripts": { - "lint": "eslint src --ext .ts,.js --fix && tsc --noEmit", + "lint": "eslint src --ext .ts,.js --fix --quiet", "clean": "rm -Rf build && rm -Rf lib && rm -Rf cjs", - "tsc": "tsc -b src/main && tsc -b src/webworker && tsc --project src/main/tsconfig-cjs.json", + "tscRun": "tsc -b src/main && tsc -b src/webworker && tsc --project src/main/tsconfig-cjs.json", "rollup": "rollup --config rollup.config.js", "compile": "node --experimental-modules --experimental-json-modules scripts/compile.cjs", - "build": "npm run clean && npm run tsc && npm run rollup && npm run compile", - "prepare": "node scripts/checkver.cjs && npm run build" + "build": "npm run clean && npm run tscRun && npm run rollup && npm run compile", + "prepare": "cd ../../ && husky install tracker/.husky/", + "lint-front": "lint-staged" }, "devDependencies": { "@babel/core": "^7.10.2", "@rollup/plugin-babel": "^5.0.3", "@rollup/plugin-node-resolve": "^10.0.0", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", + "@typescript-eslint/eslint-plugin": "^5.30.0", + "@typescript-eslint/parser": "^5.30.0", "eslint": "^7.8.0", - "eslint-plugin-prettier": "^4.1.4", - "prettier": "^2.0.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "prettier": "^2.7.1", "replace-in-files": "^2.0.3", "rollup": "^2.17.0", "rollup-plugin-terser": "^6.1.0", "semver": "^6.3.0", - "typescript": "^4.6.0-dev.20211126" + "typescript": "4.6.0-dev.20211126" }, "dependencies": { "error-stack-parser": "^2.0.6" }, "engines": { "node": ">=14.0" + }, + "husky": { + "hooks": { + "pre-commit": "sh lint.sh" + } + }, + "lint-staged": { + "*.{js,mjs,jsx,ts,tsx}": [ + "eslint --fix --quiet" + ], + "*.{json,md,html,js,jsx,ts,tsx}": [ + "prettier --write" + ] } } diff --git a/tracker/tracker/scripts/compile.cjs b/tracker/tracker/scripts/compile.cjs index ec30ac21e..998ef3eca 100644 --- a/tracker/tracker/scripts/compile.cjs +++ b/tracker/tracker/scripts/compile.cjs @@ -12,7 +12,7 @@ async function main() { await replaceInFiles({ files: 'build/**/*', from: 'WEBWORKER_BODY', - to: webworker.replace(/'/g, "\\'"), + to: webworker.replace(/'/g, "\\'").replace(/\n/g, ""), }); await fs.rename('build/main', 'lib'); await fs.rename('build/common', 'lib/common'); diff --git a/tracker/tracker/src/common/webworker.ts b/tracker/tracker/src/common/webworker.ts index 3f6de9c35..5abf9b8fc 100644 --- a/tracker/tracker/src/common/webworker.ts +++ b/tracker/tracker/src/common/webworker.ts @@ -1,19 +1,19 @@ export interface Options { - connAttemptCount?: number - connAttemptGap?: number + connAttemptCount?: number; + connAttemptGap?: number; } type Start = { - type: "start", - ingestPoint: string - pageNo: number - timestamp: number -} & Options + type: 'start'; + ingestPoint: string; + pageNo: number; + timestamp: number; +} & Options; type Auth = { - type: "auth" - token: string - beaconSizeLimit?: number -} + type: 'auth'; + token: string; + beaconSizeLimit?: number; +}; -export type WorkerMessageData = null | "stop" | Start | Auth | Array<{ _id: number }> +export type WorkerMessageData = null | 'stop' | Start | Auth | Array<{ _id: number }>; diff --git a/tracker/tracker/src/main/app/guards.ts b/tracker/tracker/src/main/app/guards.ts index c3f36d398..5304950de 100644 --- a/tracker/tracker/src/main/app/guards.ts +++ b/tracker/tracker/src/main/app/guards.ts @@ -3,31 +3,32 @@ export function isSVGElement(node: Element): node is SVGElement { } export function isElementNode(node: Node): node is Element { - return node.nodeType === Node.ELEMENT_NODE + return node.nodeType === Node.ELEMENT_NODE; } export function isTextNode(node: Node): node is Text { - return node.nodeType === Node.TEXT_NODE + return node.nodeType === Node.TEXT_NODE; } export function isRootNode(node: Node): boolean { - return node.nodeType === Node.DOCUMENT_NODE || - node.nodeType === Node.DOCUMENT_FRAGMENT_NODE + return node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE; } - type TagTypeMap = { - HTML: HTMLHtmlElement - IMG: HTMLImageElement - INPUT: HTMLInputElement - TEXTAREA: HTMLTextAreaElement - SELECT: HTMLSelectElement - LABEL: HTMLLabelElement - IFRAME: HTMLIFrameElement - STYLE: HTMLStyleElement - style: SVGStyleElement - LINK: HTMLLinkElement -} -export function hasTag(el: Node, tagName: T): el is TagTypeMap[typeof tagName] { - return el.nodeName === tagName + HTML: HTMLHtmlElement; + IMG: HTMLImageElement; + INPUT: HTMLInputElement; + TEXTAREA: HTMLTextAreaElement; + SELECT: HTMLSelectElement; + LABEL: HTMLLabelElement; + IFRAME: HTMLIFrameElement; + STYLE: HTMLStyleElement; + style: SVGStyleElement; + LINK: HTMLLinkElement; +}; +export function hasTag( + el: Node, + tagName: T, +): el is TagTypeMap[typeof tagName] { + return el.nodeName === tagName; } diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index be03a968c..71e2d3e30 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -1,45 +1,45 @@ -import type Message from "../../common/messages.js"; -import { Timestamp, Metadata, UserID } from "../../common/messages.js"; -import { timestamp, deprecationWarn } from "../utils.js"; -import Nodes from "./nodes.js"; -import Observer from "./observer/top_observer.js"; -import Sanitizer from "./sanitizer.js"; -import Ticker from "./ticker.js"; -import Logger, { LogLevel } from "./logger.js"; -import Session from "./session.js"; +import type Message from '../../common/messages.js'; +import { Timestamp, Metadata, UserID } from '../../common/messages.js'; +import { timestamp, deprecationWarn } from '../utils.js'; +import Nodes from './nodes.js'; +import Observer from './observer/top_observer.js'; +import Sanitizer from './sanitizer.js'; +import Ticker from './ticker.js'; +import Logger, { LogLevel } from './logger.js'; +import Session from './session.js'; -import { deviceMemory, jsHeapSizeLimit } from "../modules/performance.js"; +import { deviceMemory, jsHeapSizeLimit } from '../modules/performance.js'; -import type { Options as ObserverOptions } from "./observer/top_observer.js"; -import type { Options as SanitizerOptions } from "./sanitizer.js"; -import type { Options as LoggerOptions } from "./logger.js" -import type { Options as WebworkerOptions, WorkerMessageData } from "../../common/webworker.js"; +import type { Options as ObserverOptions } from './observer/top_observer.js'; +import type { Options as SanitizerOptions } from './sanitizer.js'; +import type { Options as LoggerOptions } from './logger.js'; +import type { Options as WebworkerOptions, WorkerMessageData } from '../../common/webworker.js'; // TODO: Unify and clearly describe options logic export interface StartOptions { - userID?: string, - metadata?: Record, - forceNew?: boolean, + userID?: string; + metadata?: Record; + forceNew?: boolean; } interface OnStartInfo { - sessionID: string, - sessionToken: string, - userUUID: string, + sessionID: string; + sessionToken: string; + userUUID: string; } -const CANCELED = "canceled" as const -const START_ERROR = ":(" as const -type SuccessfulStart = OnStartInfo & { success: true } +const CANCELED = 'canceled' as const; +const START_ERROR = ':(' as const; +type SuccessfulStart = OnStartInfo & { success: true }; type UnsuccessfulStart = { - reason: typeof CANCELED | string - success: false -} -const UnsuccessfulStart = (reason: string): UnsuccessfulStart => ({ reason, success: false}) -const SuccessfulStart = (body: OnStartInfo): SuccessfulStart => ({ ...body, success: true}) -export type StartPromiseReturn = SuccessfulStart | UnsuccessfulStart + reason: typeof CANCELED | string; + success: false; +}; +const UnsuccessfulStart = (reason: string): UnsuccessfulStart => ({ reason, success: false }); +const SuccessfulStart = (body: OnStartInfo): SuccessfulStart => ({ ...body, success: true }); +export type StartPromiseReturn = SuccessfulStart | UnsuccessfulStart; -type StartCallback = (i: OnStartInfo) => void -type CommitCallback = (messages: Array) => void +type StartCallback = (i: OnStartInfo) => void; +type CommitCallback = (messages: Array) => void; enum ActivityState { NotActive, Starting, @@ -54,7 +54,7 @@ type AppOptions = { session_reset_key: string; local_uuid_key: string; ingestPoint: string; - resourceBaseHref: string | null, // resourceHref? + resourceBaseHref: string | null; // resourceHref? //resourceURLRewriter: (url: string) => string | boolean, verbose: boolean; __is_snippet: boolean; @@ -67,8 +67,7 @@ type AppOptions = { onStart?: StartCallback; } & WebworkerOptions; -export type Options = AppOptions & ObserverOptions & SanitizerOptions - +export type Options = AppOptions & ObserverOptions & SanitizerOptions; // TODO: use backendHost only export const DEFAULT_INGEST_POINT = 'https://api.openreplay.com/ingest'; @@ -86,19 +85,18 @@ export default class App { private readonly messages: Array = []; private readonly observer: Observer; private readonly startCallbacks: Array = []; - private readonly stopCallbacks: Array = []; + private readonly stopCallbacks: Array<() => any> = []; private readonly commitCallbacks: Array = []; private readonly options: AppOptions; private readonly revID: string; private activityState: ActivityState = ActivityState.NotActive; - private version = 'TRACKER_VERSION'; // TODO: version compatability check inside each plugin. + private readonly version = 'TRACKER_VERSION'; // TODO: version compatability check inside each plugin. private readonly worker?: Worker; constructor( projectKey: string, sessionToken: string | null | undefined, options: Partial, ) { - // if (options.onStart !== undefined) { // deprecationWarn("'onStart' option", "tracker.start().then(/* handle session info */)") // } ?? maybe onStart is good @@ -133,13 +131,14 @@ export default class App { this.notify = new Logger(this.options.verbose ? LogLevel.Warnings : LogLevel.Silent); this.session = new Session(); this.session.attachUpdateCallback(({ userID, metadata }) => { - if (userID != null) { // TODO: nullable userID - this.send(new UserID(userID)) + if (userID != null) { + // TODO: nullable userID + this.send(new UserID(userID)); } if (metadata != null) { - Object.entries(metadata).forEach(([key, value]) => this.send(new Metadata(key, value))) + Object.entries(metadata).forEach(([key, value]) => this.send(new Metadata(key, value))); } - }) + }); this.localStorage = this.options.localStorage; this.sessionStorage = this.options.sessionStorage; @@ -149,57 +148,57 @@ export default class App { try { this.worker = new Worker( - URL.createObjectURL( - new Blob([`WEBWORKER_BODY`], { type: 'text/javascript' }), - ), + URL.createObjectURL(new Blob(['WEBWORKER_BODY'], { type: 'text/javascript' })), ); - this.worker.onerror = e => { - this._debug("webworker_error", e) - } + this.worker.onerror = (e) => { + this._debug('webworker_error', e); + }; this.worker.onmessage = ({ data }: MessageEvent) => { - if (data === "failed") { + if (data === 'failed') { this.stop(); - this._debug("worker_failed", {}) // add context (from worker) - } else if (data === "restart") { + this._debug('worker_failed', {}); // add context (from worker) + } else if (data === 'restart') { this.stop(); this.start({ forceNew: true }); } - } + }; const alertWorker = () => { if (this.worker) { this.worker.postMessage(null); } - } + }; // keep better tactics, discard others? this.attachEventListener(window, 'beforeunload', alertWorker, false); this.attachEventListener(document.body, 'mouseleave', alertWorker, false, false); // TODO: stop session after inactivity timeout (make configurable) this.attachEventListener(document, 'visibilitychange', alertWorker, false); - } catch (e) { - this._debug("worker_start", e); + } catch (e) { + this._debug('worker_start', e); } } private _debug(context: string, e: any) { - if(this.options.__debug_report_edp !== null) { + if (this.options.__debug_report_edp !== null) { fetch(this.options.__debug_report_edp, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ context, - error: `${e}` - }) + error: `${e}`, + }), }); } - this.debug.error("OpenReplay error: ", context, e) + this.debug.error('OpenReplay error: ', context, e); } send(message: Message, urgent = false): void { - if (this.activityState === ActivityState.NotActive) { return } + if (this.activityState === ActivityState.NotActive) { + return; + } this.messages.push(message); - // TODO: commit on start if there were `urgent` sends; + // TODO: commit on start if there were `urgent` sends; // Clearify where urgent can be used for; - // Clearify workflow for each type of message in case it was sent before start + // Clearify workflow for each type of message in case it was sent before start // (like Fetch before start; maybe add an option "preCapture: boolean" or sth alike) if (this.activityState === ActivityState.Active && urgent) { this.commit(); @@ -209,7 +208,7 @@ export default class App { if (this.worker && this.messages.length) { this.messages.unshift(new Timestamp(timestamp())); this.worker.postMessage(this.messages); - this.commitCallbacks.forEach(cb => cb(this.messages)); + this.commitCallbacks.forEach((cb) => cb(this.messages)); this.messages.length = 0; } } @@ -220,22 +219,22 @@ export default class App { try { fn.apply(this, args); } catch (e) { - app._debug("safe_fn_call", e) + app._debug('safe_fn_call', e); // time: timestamp(), // name: e.name, // message: e.message, // stack: e.stack } - } as any // TODO: correct typing + } as any; // TODO: correct typing } attachCommitCallback(cb: CommitCallback): void { - this.commitCallbacks.push(cb) + this.commitCallbacks.push(cb); } attachStartCallback(cb: StartCallback): void { this.startCallbacks.push(cb); } - attachStopCallback(cb: Function): void { + attachStopCallback(cb: () => any): void { this.stopCallbacks.push(cb); } attachEventListener( @@ -248,24 +247,20 @@ export default class App { if (useSafe) { listener = this.safe(listener); } - this.attachStartCallback(() => - target.addEventListener(type, listener, useCapture), - ); - this.attachStopCallback(() => - target.removeEventListener(type, listener, useCapture), - ); + this.attachStartCallback(() => target.addEventListener(type, listener, useCapture)); + this.attachStopCallback(() => target.removeEventListener(type, listener, useCapture)); } // TODO: full correct semantic checkRequiredVersion(version: string): boolean { - const reqVer = version.split(/[.-]/) - const ver = this.version.split(/[.-]/) + const reqVer = version.split(/[.-]/); + const ver = this.version.split(/[.-]/); for (let i = 0; i < 3; i++) { if (Number(ver[i]) < Number(reqVer[i]) || isNaN(Number(ver[i])) || isNaN(Number(reqVer[i]))) { - return false + return false; } } - return true + return true; } private getStartInfo() { @@ -276,13 +271,13 @@ export default class App { timestamp: timestamp(), // shouldn't it be set once? trackerVersion: this.version, isSnippet: this.options.__is_snippet, - } + }; } getSessionInfo() { return { ...this.session.getInfo(), - ...this.getStartInfo() - } + ...this.getStartInfo(), + }; } getSessionToken(): string | undefined { const token = this.sessionStorage.getItem(this.options.session_token_key); @@ -294,38 +289,39 @@ export default class App { return this.session.getInfo().sessionID || undefined; } getHost(): string { - return new URL(this.options.ingestPoint).hostname + return new URL(this.options.ingestPoint).hostname; } getProjectKey(): string { - return this.projectKey + return this.projectKey; } getBaseHref(): string { if (typeof this.options.resourceBaseHref === 'string') { - return this.options.resourceBaseHref + return this.options.resourceBaseHref; } else if (typeof this.options.resourceBaseHref === 'object') { //switch between types } if (document.baseURI) { - return document.baseURI + return document.baseURI; } // IE only - return document.head - ?.getElementsByTagName("base")[0] - ?.getAttribute("href") || location.origin + location.pathname + return ( + document.head?.getElementsByTagName('base')[0]?.getAttribute('href') || + location.origin + location.pathname + ); } resolveResourceURL(resourceURL: string): string { - const base = new URL(this.getBaseHref()) - base.pathname += "/" + new URL(resourceURL).pathname - base.pathname.replace(/\/+/g, "/") - return base.toString() + const base = new URL(this.getBaseHref()); + base.pathname += '/' + new URL(resourceURL).pathname; + base.pathname.replace(/\/+/g, '/'); + return base.toString(); } isServiceURL(url: string): boolean { - return url.startsWith(this.options.ingestPoint) + return url.startsWith(this.options.ingestPoint); } active(): boolean { - return this.activityState === ActivityState.Active + return this.activityState === ActivityState.Active; } resetNextPageSession(flag: boolean) { @@ -337,14 +333,18 @@ export default class App { } private _start(startOpts: StartOptions): Promise { if (!this.worker) { - return Promise.resolve(UnsuccessfulStart("No worker found: perhaps, CSP is not set.")) + return Promise.resolve(UnsuccessfulStart('No worker found: perhaps, CSP is not set.')); } - if (this.activityState !== ActivityState.NotActive) { - return Promise.resolve(UnsuccessfulStart("OpenReplay: trying to call `start()` on the instance that has been started already.")) + if (this.activityState !== ActivityState.NotActive) { + return Promise.resolve( + UnsuccessfulStart( + 'OpenReplay: trying to call `start()` on the instance that has been started already.', + ), + ); } this.activityState = ActivityState.Starting; - let pageNo: number = 0; + let pageNo = 0; const pageNoStr = this.sessionStorage.getItem(this.options.session_pageno_key); if (pageNoStr != null) { pageNo = parseInt(pageNoStr); @@ -352,95 +352,104 @@ export default class App { } this.sessionStorage.setItem(this.options.session_pageno_key, pageNo.toString()); - const startInfo = this.getStartInfo() + const startInfo = this.getStartInfo(); const startWorkerMsg: WorkerMessageData = { - type: "start", + type: 'start', pageNo, ingestPoint: this.options.ingestPoint, timestamp: startInfo.timestamp, connAttemptCount: this.options.connAttemptCount, connAttemptGap: this.options.connAttemptGap, - } - this.worker.postMessage(startWorkerMsg) + }; + this.worker.postMessage(startWorkerMsg); - this.session.update({ // TODO: transparent "session" module logic AND explicit internal api for plugins. - // "updating" with old metadata in order to trigger session's UpdateCallbacks. + this.session.update({ + // TODO: transparent "session" module logic AND explicit internal api for plugins. + // "updating" with old metadata in order to trigger session's UpdateCallbacks. // (for the case of internal .start() calls, like on "restart" webworker signal or assistent connection in tracker-assist ) metadata: startOpts.metadata || this.session.getInfo().metadata, userID: startOpts.userID, - }) + }); const sReset = this.sessionStorage.getItem(this.options.session_reset_key); this.sessionStorage.removeItem(this.options.session_reset_key); - return window.fetch(this.options.ingestPoint + '/v1/web/start', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - ...startInfo, - userID: this.session.getInfo().userID, - token: this.sessionStorage.getItem(this.options.session_token_key), - deviceMemory, - jsHeapSizeLimit, - reset: startOpts.forceNew || sReset !== null, - }), - }) - .then(r => { - if (r.status === 200) { - return r.json() - } else { - return r.text().then(text => text === CANCELED - ? Promise.reject(CANCELED) - : Promise.reject(`Server error: ${r.status}. ${text}`) - ); - } - }) - .then(r => { - if (!this.worker) { - return Promise.reject("no worker found after start request (this might not happen)"); - } - const { token, userUUID, sessionID, beaconSizeLimit } = r; - if (typeof token !== 'string' || + return window + .fetch(this.options.ingestPoint + '/v1/web/start', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...startInfo, + userID: this.session.getInfo().userID, + token: this.sessionStorage.getItem(this.options.session_token_key), + deviceMemory, + jsHeapSizeLimit, + reset: startOpts.forceNew || sReset !== null, + }), + }) + .then((r) => { + if (r.status === 200) { + return r.json(); + } else { + return r + .text() + .then((text) => + text === CANCELED + ? Promise.reject(CANCELED) + : Promise.reject(`Server error: ${r.status}. ${text}`), + ); + } + }) + .then((r) => { + if (!this.worker) { + return Promise.reject('no worker found after start request (this might not happen)'); + } + const { token, userUUID, sessionID, beaconSizeLimit } = r; + if ( + typeof token !== 'string' || typeof userUUID !== 'string' || - (typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined')) { - return Promise.reject(`Incorrect server response: ${ JSON.stringify(r) }`); - } - this.sessionStorage.setItem(this.options.session_token_key, token); - this.localStorage.setItem(this.options.local_uuid_key, userUUID); - this.session.update({ sessionID }) // TODO: no no-explicit 'any' - const startWorkerMsg: WorkerMessageData = { - type: "auth", - token, - beaconSizeLimit - } - this.worker.postMessage(startWorkerMsg) + (typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined') + ) { + return Promise.reject(`Incorrect server response: ${JSON.stringify(r)}`); + } + this.sessionStorage.setItem(this.options.session_token_key, token); + this.localStorage.setItem(this.options.local_uuid_key, userUUID); + this.session.update({ sessionID }); // TODO: no no-explicit 'any' + const startWorkerMsg: WorkerMessageData = { + type: 'auth', + token, + beaconSizeLimit, + }; + this.worker.postMessage(startWorkerMsg); - this.activityState = ActivityState.Active - - const onStartInfo = { sessionToken: token, userUUID, sessionID }; + this.activityState = ActivityState.Active; - this.startCallbacks.forEach((cb) => cb(onStartInfo)); // TODO: start as early as possible (before receiving the token) - this.observer.observe(); - this.ticker.start(); + const onStartInfo = { sessionToken: token, userUUID, sessionID }; - this.notify.log("OpenReplay tracking started."); - // get rid of onStart ? - if (typeof this.options.onStart === 'function') { - this.options.onStart(onStartInfo) - } - return SuccessfulStart(onStartInfo) - }) - .catch(reason => { - this.sessionStorage.removeItem(this.options.session_token_key) - this.stop() - if (reason === CANCELED) { return UnsuccessfulStart(CANCELED) } + this.startCallbacks.forEach((cb) => cb(onStartInfo)); // TODO: start as early as possible (before receiving the token) + this.observer.observe(); + this.ticker.start(); - this.notify.log("OpenReplay was unable to start. ", reason) - this._debug("session_start", reason) - return UnsuccessfulStart(START_ERROR) - }) + this.notify.log('OpenReplay tracking started.'); + // get rid of onStart ? + if (typeof this.options.onStart === 'function') { + this.options.onStart(onStartInfo); + } + return SuccessfulStart(onStartInfo); + }) + .catch((reason) => { + this.sessionStorage.removeItem(this.options.session_token_key); + this.stop(); + if (reason === CANCELED) { + return UnsuccessfulStart(CANCELED); + } + + this.notify.log('OpenReplay was unable to start. ', reason); + this._debug('session_start', reason); + return UnsuccessfulStart(START_ERROR); + }); } start(options: StartOptions = {}): Promise { @@ -450,32 +459,36 @@ export default class App { return new Promise((resolve) => { const onVisibilityChange = () => { if (!document.hidden) { - document.removeEventListener("visibilitychange", onVisibilityChange); - resolve(this._start(options)) + document.removeEventListener('visibilitychange', onVisibilityChange); + resolve(this._start(options)); } - } - document.addEventListener("visibilitychange", onVisibilityChange); - }) + }; + document.addEventListener('visibilitychange', onVisibilityChange); + }); } } - stop(calledFromAPI = false): void { + stop(calledFromAPI = false, restarting = false): void { if (this.activityState !== ActivityState.NotActive) { try { - this.sanitizer.clear() - this.observer.disconnect() - this.nodes.clear() - this.ticker.stop() - this.stopCallbacks.forEach((cb) => cb()) + this.sanitizer.clear(); + this.observer.disconnect(); + this.nodes.clear(); + this.ticker.stop(); + this.stopCallbacks.forEach((cb) => cb()); if (calledFromAPI) { - this.session.reset() + this.session.reset(); } - this.notify.log("OpenReplay tracking stopped.") - if (this.worker) { - this.worker.postMessage("stop") + this.notify.log('OpenReplay tracking stopped.'); + if (this.worker && !restarting) { + this.worker.postMessage('stop'); } } finally { - this.activityState = ActivityState.NotActive + this.activityState = ActivityState.NotActive; } } } + restart() { + this.stop(false, true); + this.start({ forceNew: false }); + } } diff --git a/tracker/tracker/src/main/app/logger.ts b/tracker/tracker/src/main/app/logger.ts index 34d8c12c6..c810cb4ce 100644 --- a/tracker/tracker/src/main/app/logger.ts +++ b/tracker/tracker/src/main/app/logger.ts @@ -1,4 +1,3 @@ - export const LogLevel = { Verbose: 5, Log: 4, @@ -6,54 +5,60 @@ export const LogLevel = { Errors: 2, Silent: 0, } as const; -type LogLevel = typeof LogLevel[keyof typeof LogLevel] - +type LogLevel = typeof LogLevel[keyof typeof LogLevel]; type CustomLevel = { - error: boolean - warn: boolean - log: boolean -} + error: boolean; + warn: boolean; + log: boolean; +}; function IsCustomLevel(l: LogLevel | CustomLevel): l is CustomLevel { - return typeof l === 'object' + return typeof l === 'object'; } - interface _Options { - level: LogLevel | CustomLevel, - messages?: number[], + level: LogLevel | CustomLevel; + messages?: number[]; } - -export type Options = true | _Options | LogLevel +export type Options = true | _Options | LogLevel; export default class Logger { private readonly options: _Options; constructor(options: Options = LogLevel.Silent) { - this.options = options === true - ? { level: LogLevel.Verbose } - : typeof options === "number" ? { level: options } : options; + this.options = + options === true + ? { level: LogLevel.Verbose } + : typeof options === 'number' + ? { level: options } + : options; } log(...args: any) { - if (IsCustomLevel(this.options.level) - ? this.options.level.log - : this.options.level >= LogLevel.Log) { - console.log(...args) + if ( + IsCustomLevel(this.options.level) + ? this.options.level.log + : this.options.level >= LogLevel.Log + ) { + console.log(...args); } } warn(...args: any) { - if (IsCustomLevel(this.options.level) - ? this.options.level.warn - : this.options.level >= LogLevel.Warnings) { - console.warn(...args) + if ( + IsCustomLevel(this.options.level) + ? this.options.level.warn + : this.options.level >= LogLevel.Warnings + ) { + console.warn(...args); } } error(...args: any) { - if (IsCustomLevel(this.options.level) - ? this.options.level.error - : this.options.level >= LogLevel.Errors) { - console.error(...args) + if ( + IsCustomLevel(this.options.level) + ? this.options.level.error + : this.options.level >= LogLevel.Errors + ) { + console.error(...args); } } } diff --git a/tracker/tracker/src/main/app/nodes.ts b/tracker/tracker/src/main/app/nodes.ts index a1c87b6e2..f7b167192 100644 --- a/tracker/tracker/src/main/app/nodes.ts +++ b/tracker/tracker/src/main/app/nodes.ts @@ -2,22 +2,16 @@ type NodeCallback = (node: Node, isStart: boolean) => void; type ElementListener = [string, EventListener]; export default class Nodes { - private readonly nodes: Array; - private readonly nodeCallbacks: Array; - private readonly elementListeners: Map>; - constructor(private readonly node_id: string) { - this.nodes = []; - this.nodeCallbacks = []; - this.elementListeners = new Map(); - } + private nodes: Array = []; + private readonly nodeCallbacks: Array = []; + private readonly elementListeners: Map> = new Map(); + + constructor(private readonly node_id: string) {} + attachNodeCallback(nodeCallback: NodeCallback): void { this.nodeCallbacks.push(nodeCallback); } - attachElementListener( - type: string, - node: Element, - elementListener: EventListener, - ): void { + attachElementListener(type: string, node: Element, elementListener: EventListener): void { const id = this.getID(node); if (id === undefined) { return; @@ -46,24 +40,34 @@ export default class Nodes { const id = (node as any)[this.node_id]; if (id !== undefined) { delete (node as any)[this.node_id]; - this.nodes[id] = undefined; + delete this.nodes[id]; const listeners = this.elementListeners.get(id); if (listeners !== undefined) { this.elementListeners.delete(id); - listeners.forEach((listener) => - node.removeEventListener(listener[0], listener[1]), - ); + listeners.forEach((listener) => node.removeEventListener(listener[0], listener[1])); } } return id; } + cleanTree() { + // sadly we keep empty items in array here resulting in some memory still being used + // but its still better than keeping dead nodes or undef elements + // plus we keep our index positions for new/alive nodes + // performance test: 3ms for 30k nodes with 17k dead ones + for (let i = 0; i < this.nodes.length; i++) { + const node = this.nodes[i]; + if (node && !document.contains(node)) { + this.unregisterNode(node); + } + } + } callNodeCallbacks(node: Node, isStart: boolean): void { this.nodeCallbacks.forEach((cb) => cb(node, isStart)); } getID(node: Node): number | undefined { return (node as any)[this.node_id]; } - getNode(id: number): Node | undefined { + getNode(id: number) { return this.nodes[id]; } diff --git a/tracker/tracker/src/main/app/observer/iframe_observer.ts b/tracker/tracker/src/main/app/observer/iframe_observer.ts index 1f50e588a..faea6a5e6 100644 --- a/tracker/tracker/src/main/app/observer/iframe_observer.ts +++ b/tracker/tracker/src/main/app/observer/iframe_observer.ts @@ -1,19 +1,20 @@ -import Observer from "./observer.js"; -import { CreateIFrameDocument } from "../../../common/messages.js"; +import Observer from './observer.js'; +import { CreateIFrameDocument } from '../../../common/messages.js'; export default class IFrameObserver extends Observer { observe(iframe: HTMLIFrameElement) { const doc = iframe.contentDocument; const hostID = this.app.nodes.getID(iframe); - if (!doc || hostID === undefined) { return } //log TODO common app.logger + if (!doc || hostID === undefined) { + return; + } //log TODO common app.logger // Have to observe document, because the inner might be changed this.observeRoot(doc, (docID) => { if (docID === undefined) { - console.log("OpenReplay: Iframe document not bound") + console.log('OpenReplay: Iframe document not bound'); return; } - this.app.send(CreateIFrameDocument(hostID, docID)); + this.app.send(CreateIFrameDocument(hostID, docID)); }); } - -} \ No newline at end of file +} diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index 940a7eefb..6d11a704d 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -8,63 +8,66 @@ import { CreateElementNode, MoveNode, RemoveNode, -} from "../../../common/messages.js"; -import App from "../index.js"; -import { - isRootNode, - isTextNode, - isElementNode, - isSVGElement, - hasTag, -} from "../guards.js"; +} from '../../../common/messages.js'; +import App from '../index.js'; +import { isRootNode, isTextNode, isElementNode, isSVGElement, hasTag } from '../guards.js'; function isIgnored(node: Node): boolean { - if (isTextNode(node)) { return false } - if (!isElementNode(node)) { return true } + if (isTextNode(node)) { + return false; + } + if (!isElementNode(node)) { + return true; + } const tag = node.tagName.toUpperCase(); if (tag === 'LINK') { const rel = node.getAttribute('rel'); const as = node.getAttribute('as'); - return !(rel?.includes('stylesheet') || as === "style" || as === "font"); + return !(rel?.includes('stylesheet') || as === 'style' || as === 'font'); } return ( - tag === 'SCRIPT' || - tag === 'NOSCRIPT' || - tag === 'META' || - tag === 'TITLE' || - tag === 'BASE' + tag === 'SCRIPT' || tag === 'NOSCRIPT' || tag === 'META' || tag === 'TITLE' || tag === 'BASE' ); } function isObservable(node: Node): boolean { - if (isRootNode(node)) { return true } - return !isIgnored(node) + if (isRootNode(node)) { + return true; + } + return !isIgnored(node); } - /* TODO: - fix unbinding logic + send all removals first (ensure sequence is correct) - use document as a 0-node in the upper context (should be updated in player at first) */ +/* + Nikita: + - rn we only send unbind event for parent (all child nodes will be cut in the live replay anyways) + to prevent sending 1k+ unbinds for child nodes and making replay file bigger than it should be +*/ + enum RecentsType { New, Removed, Changed, + RemovedChild, } export default abstract class Observer { private readonly observer: MutationObserver; private readonly commited: Array = []; - private readonly recents: Map = new Map() + private readonly recents: Map = new Map(); private readonly indexes: Array = []; private readonly attributesMap: Map> = new Map(); private readonly textSet: Set = new Set(); constructor(protected readonly app: App, protected readonly isTopContext = false) { this.observer = new MutationObserver( this.app.safe((mutations) => { - for (const mutation of mutations) { // mutations order is sequential + for (const mutation of mutations) { + // mutations order is sequential const target = mutation.target; const type = mutation.type; @@ -73,7 +76,7 @@ export default abstract class Observer { } if (type === 'childList') { for (let i = 0; i < mutation.removedNodes.length; i++) { - this.bindTree(mutation.removedNodes[i]); + this.bindTree(mutation.removedNodes[i], true); } for (let i = 0; i < mutation.addedNodes.length; i++) { this.bindTree(mutation.addedNodes[i]); @@ -85,16 +88,16 @@ export default abstract class Observer { continue; } if (!this.recents.has(id)) { - this.recents.set(id, RecentsType.Changed) // TODO only when altered + this.recents.set(id, RecentsType.Changed); // TODO only when altered } if (type === 'attributes') { const name = mutation.attributeName; if (name === null) { continue; } - let attr = this.attributesMap.get(id) + let attr = this.attributesMap.get(id); if (attr === undefined) { - this.attributesMap.set(id, attr = new Set()) + this.attributesMap.set(id, (attr = new Set())); } attr.add(name); continue; @@ -110,18 +113,13 @@ export default abstract class Observer { } private clear(): void { this.commited.length = 0; - this.recents.clear() + this.recents.clear(); this.indexes.length = 1; this.attributesMap.clear(); this.textSet.clear(); } - private sendNodeAttribute( - id: number, - node: Element, - name: string, - value: string | null, - ): void { + private sendNodeAttribute(id: number, node: Element, name: string, value: string | null): void { if (isSVGElement(node)) { if (name.substr(0, 6) === 'xlink:') { name = name.substr(6); @@ -150,7 +148,7 @@ export default abstract class Observer { } if ( name === 'value' && - hasTag(node, "INPUT") && + hasTag(node, 'INPUT') && node.type !== 'button' && node.type !== 'reset' && node.type !== 'submit' @@ -161,7 +159,7 @@ export default abstract class Observer { this.app.send(new RemoveNodeAttribute(id, name)); return; } - if (name === 'style' || name === 'href' && hasTag(node, "LINK")) { + if (name === 'style' || (name === 'href' && hasTag(node, 'LINK'))) { this.app.send(new SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref())); return; } @@ -172,26 +170,31 @@ export default abstract class Observer { } private sendNodeData(id: number, parentElement: Element, data: string): void { - if (hasTag(parentElement, "STYLE") || hasTag(parentElement, "style")) { + if (hasTag(parentElement, 'STYLE') || hasTag(parentElement, 'style')) { this.app.send(new SetCSSDataURLBased(id, data, this.app.getBaseHref())); return; } - data = this.app.sanitizer.sanitize(id, data) + data = this.app.sanitizer.sanitize(id, data); this.app.send(new SetNodeData(id, data)); } private bindNode(node: Node): void { - const [ id, isNew ]= this.app.nodes.registerNode(node); - if (isNew){ - this.recents.set(id, RecentsType.New) - } else if (this.recents.get(id) !== RecentsType.New) { // can we do just `else` here? - this.recents.set(id, RecentsType.Removed) + const [id, isNew] = this.app.nodes.registerNode(node); + if (isNew) { + this.recents.set(id, RecentsType.New); + } else if (this.recents.get(id) !== RecentsType.New) { + // can we do just `else` here? + this.recents.set(id, RecentsType.Removed); } } + private unbindChildNode(node: Node): void { + const [id] = this.app.nodes.registerNode(node); + this.recents.set(id, RecentsType.RemovedChild); + } - private bindTree(node: Node): void { + private bindTree(node: Node, isChildUnbinding = false): void { if (!isObservable(node)) { - return + return; } this.bindNode(node); const walker = document.createTreeWalker( @@ -199,7 +202,7 @@ export default abstract class Observer { NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, { acceptNode: (node) => - isIgnored(node) || this.app.nodes.getID(node) !== undefined + isIgnored(node) || (this.app.nodes.getID(node) !== undefined && !isChildUnbinding) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT, }, @@ -207,11 +210,15 @@ export default abstract class Observer { false, ); while (walker.nextNode()) { - this.bindNode(walker.currentNode); + if (isChildUnbinding) { + this.unbindChildNode(walker.currentNode); + } else { + this.bindNode(walker.currentNode); + } } } - private unbindNode(node: Node): void { + private unbindNode(node: Node) { const id = this.app.nodes.unregisterNode(node); if (id !== undefined && this.recents.get(id) === RecentsType.Removed) { this.app.send(new RemoveNode(id)); @@ -229,7 +236,7 @@ export default abstract class Observer { // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before) // TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though) // TODO: Clean the logic (though now it workd fine) - if (!hasTag(node, "HTML") || !this.isTopContext) { + if (!hasTag(node, 'HTML') || !this.isTopContext) { if (parent === null) { // Sometimes one observation contains attribute mutations for the removimg node, which gets ignored here. // That shouldn't affect the visual rendering ( should it? ) @@ -264,15 +271,15 @@ export default abstract class Observer { if (sibling === null) { this.indexes[id] = 0; } - const recentsType = this.recents.get(id) - const isNew = recentsType === RecentsType.New - const index = this.indexes[id] + const recentsType = this.recents.get(id); + const isNew = recentsType === RecentsType.New; + const index = this.indexes[id]; if (index === undefined) { throw 'commitNode: missing node index'; } if (isNew) { if (isElementNode(node)) { - let el: Element = node + let el: Element = node; if (parentID !== undefined) { if (this.app.sanitizer.isMaskedContainer(id)) { const width = el.clientWidth; @@ -282,15 +289,7 @@ export default abstract class Observer { (el as HTMLElement | SVGElement).style.height = height + 'px'; } - this.app.send(new - CreateElementNode( - id, - parentID, - index, - el.tagName, - isSVGElement(node), - ), - ); + this.app.send(new CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node))); } for (let i = 0; i < el.attributes.length; i++) { const attr = el.attributes[i]; @@ -335,19 +334,23 @@ export default abstract class Observer { } return (this.commited[id] = this._commitNode(id, node)); } - private commitNodes(isStart: boolean = false): void { + private commitNodes(isStart = false): void { let node; this.recents.forEach((type, id) => { this.commitNode(id); if (type === RecentsType.New && (node = this.app.nodes.getNode(id))) { - this.app.nodes.callNodeCallbacks(node, isStart) + this.app.nodes.callNodeCallbacks(node, isStart); } - }) + }); this.clear(); } // ISSSUE - protected observeRoot(node: Node, beforeCommit: (id?: number) => unknown, nodeToBind: Node = node) { + protected observeRoot( + node: Node, + beforeCommit: (id?: number) => unknown, + nodeToBind: Node = node, + ) { this.observer.observe(node, { childList: true, attributes: true, @@ -357,8 +360,8 @@ export default abstract class Observer { characterDataOldValue: false, }); this.bindTree(nodeToBind); - beforeCommit(this.app.nodes.getID(node)) - this.commitNodes(true) + beforeCommit(this.app.nodes.getID(node)); + this.commitNodes(true); } disconnect(): void { diff --git a/tracker/tracker/src/main/app/observer/shadow_root_observer.ts b/tracker/tracker/src/main/app/observer/shadow_root_observer.ts index ea37bb30f..f6059e2f5 100644 --- a/tracker/tracker/src/main/app/observer/shadow_root_observer.ts +++ b/tracker/tracker/src/main/app/observer/shadow_root_observer.ts @@ -1,18 +1,19 @@ -import Observer from "./observer.js"; -import { CreateIFrameDocument } from "../../../common/messages.js"; +import Observer from './observer.js'; +import { CreateIFrameDocument } from '../../../common/messages.js'; export default class ShadowRootObserver extends Observer { observe(el: Element) { const shRoot = el.shadowRoot; const hostID = this.app.nodes.getID(el); - if (!shRoot || hostID === undefined) { return } // log + if (!shRoot || hostID === undefined) { + return; + } // log this.observeRoot(shRoot, (rootID) => { if (rootID === undefined) { - console.log("OpenReplay: Shadow Root was not bound") + console.log('OpenReplay: Shadow Root was not bound'); return; } - this.app.send(CreateIFrameDocument(hostID,rootID)); + this.app.send(CreateIFrameDocument(hostID, rootID)); }); } - -} \ No newline at end of file +} diff --git a/tracker/tracker/src/main/app/observer/top_observer.ts b/tracker/tracker/src/main/app/observer/top_observer.ts index 469ad2388..cc921967f 100644 --- a/tracker/tracker/src/main/app/observer/top_observer.ts +++ b/tracker/tracker/src/main/app/observer/top_observer.ts @@ -1,101 +1,112 @@ -import Observer from "./observer.js"; -import { - isElementNode, - hasTag, -} from "../guards.js"; +import Observer from './observer.js'; +import { isElementNode, hasTag } from '../guards.js'; -import IFrameObserver from "./iframe_observer.js"; -import ShadowRootObserver from "./shadow_root_observer.js"; +import IFrameObserver from './iframe_observer.js'; +import ShadowRootObserver from './shadow_root_observer.js'; -import { CreateDocument } from "../../../common/messages.js"; -import App from "../index.js"; -import { IN_BROWSER, hasOpenreplayAttribute } from '../../utils.js' +import { CreateDocument } from '../../../common/messages.js'; +import App from '../index.js'; +import { IN_BROWSER, hasOpenreplayAttribute } from '../../utils.js'; export interface Options { - captureIFrames: boolean + captureIFrames: boolean; } -const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : ()=>new ShadowRoot(); +const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot(); -export default class TopObserver extends Observer { +export default class TopObserver extends Observer { private readonly options: Options; constructor(app: App, options: Partial) { super(app, true); - this.options = Object.assign({ - captureIFrames: true - }, options); + this.options = Object.assign( + { + captureIFrames: true, + }, + options, + ); // IFrames - this.app.nodes.attachNodeCallback(node => { - if (hasTag(node, "IFRAME") && - ((this.options.captureIFrames && !hasOpenreplayAttribute(node, "obscured")) - || hasOpenreplayAttribute(node, "capture")) + this.app.nodes.attachNodeCallback((node) => { + if ( + hasTag(node, 'IFRAME') && + ((this.options.captureIFrames && !hasOpenreplayAttribute(node, 'obscured')) || + hasOpenreplayAttribute(node, 'capture')) ) { - this.handleIframe(node) + this.handleIframe(node); } - }) + }); // ShadowDOM - this.app.nodes.attachNodeCallback(node => { + this.app.nodes.attachNodeCallback((node) => { if (isElementNode(node) && node.shadowRoot !== null) { - this.handleShadowRoot(node.shadowRoot) + this.handleShadowRoot(node.shadowRoot); } - }) + }); } - private iframeObservers: IFrameObserver[] = []; private handleIframe(iframe: HTMLIFrameElement): void { - let doc: Document | null = null + let doc: Document | null = null; const handle = this.app.safe(() => { - const id = this.app.nodes.getID(iframe) - if (id === undefined) { return } //log - if (iframe.contentDocument === doc) { return } // How frequently can it happen? - doc = iframe.contentDocument - if (!doc || !iframe.contentWindow) { return } - const observer = new IFrameObserver(this.app) + const id = this.app.nodes.getID(iframe); + if (id === undefined) { + return; + } //log + if (iframe.contentDocument === doc) { + return; + } // How frequently can it happen? + doc = iframe.contentDocument; + if (!doc || !iframe.contentWindow) { + return; + } + const observer = new IFrameObserver(this.app); - this.iframeObservers.push(observer) - observer.observe(iframe) - }) - iframe.addEventListener("load", handle) // why app.attachEventListener not working? - handle() + this.iframeObservers.push(observer); + observer.observe(iframe); + }); + iframe.addEventListener('load', handle); // why app.attachEventListener not working? + handle(); } - private shadowRootObservers: ShadowRootObserver[] = [] + private shadowRootObservers: ShadowRootObserver[] = []; private handleShadowRoot(shRoot: ShadowRoot) { - const observer = new ShadowRootObserver(this.app) - this.shadowRootObservers.push(observer) - observer.observe(shRoot.host) + const observer = new ShadowRootObserver(this.app); + this.shadowRootObservers.push(observer); + observer.observe(shRoot.host); } observe(): void { // Protection from several subsequent calls? + const observer = this; - Element.prototype.attachShadow = function() { - const shadow = attachShadowNativeFn.apply(this, arguments) - observer.handleShadowRoot(shadow) - return shadow - } + Element.prototype.attachShadow = function () { + // eslint-disable-next-line + const shadow = attachShadowNativeFn.apply(this, arguments); + observer.handleShadowRoot(shadow); + return shadow; + }; // Can observe documentElement () here, because it is not supposed to be changing. // However, it is possible in some exotic cases and may cause an ignorance of the newly created - // In this case context.document have to be observed, but this will cause - // the change in the re-player behaviour caused by CreateDocument message: + // In this case context.document have to be observed, but this will cause + // the change in the re-player behaviour caused by CreateDocument message: // the 0-node ("fRoot") will become #document rather than documentElement as it is now. // Alternatively - observe(#document) then bindNode(documentElement) - this.observeRoot(window.document, () => { - this.app.send(new CreateDocument()) - }, window.document.documentElement); + this.observeRoot( + window.document, + () => { + this.app.send(new CreateDocument()); + }, + window.document.documentElement, + ); } disconnect() { - Element.prototype.attachShadow = attachShadowNativeFn - this.iframeObservers.forEach(o => o.disconnect()) - this.iframeObservers = [] - this.shadowRootObservers.forEach(o => o.disconnect()) - this.shadowRootObservers = [] - super.disconnect() + Element.prototype.attachShadow = attachShadowNativeFn; + this.iframeObservers.forEach((o) => o.disconnect()); + this.iframeObservers = []; + this.shadowRootObservers.forEach((o) => o.disconnect()); + this.shadowRootObservers = []; + super.disconnect(); } - -} \ No newline at end of file +} diff --git a/tracker/tracker/src/main/app/sanitizer.ts b/tracker/tracker/src/main/app/sanitizer.ts index d870cfaca..358913f93 100644 --- a/tracker/tracker/src/main/app/sanitizer.ts +++ b/tracker/tracker/src/main/app/sanitizer.ts @@ -1,6 +1,6 @@ -import type App from "./index.js"; -import { stars, hasOpenreplayAttribute } from "../utils.js"; -import { isElementNode } from "./guards.js"; +import type App from './index.js'; +import { stars, hasOpenreplayAttribute } from '../utils.js'; +import { isElementNode } from './guards.js'; export interface Options { obscureTextEmails: boolean; @@ -13,36 +13,39 @@ export default class Sanitizer { private readonly options: Options; constructor(private readonly app: App, options: Partial) { - this.options = Object.assign({ - obscureTextEmails: true, - obscureTextNumbers: false, - }, options); + this.options = Object.assign( + { + obscureTextEmails: true, + obscureTextNumbers: false, + }, + options, + ); } handleNode(id: number, parentID: number, node: Node) { if ( - this.masked.has(parentID) || - (isElementNode(node) && - hasOpenreplayAttribute(node, 'masked')) - ) { - this.masked.add(id); - } - if ( - this.maskedContainers.has(parentID) || - (isElementNode(node) && - hasOpenreplayAttribute(node, 'htmlmasked')) - ) { - this.maskedContainers.add(id); - } + this.masked.has(parentID) || + (isElementNode(node) && hasOpenreplayAttribute(node, 'masked')) + ) { + this.masked.add(id); + } + if ( + this.maskedContainers.has(parentID) || + (isElementNode(node) && hasOpenreplayAttribute(node, 'htmlmasked')) + ) { + this.maskedContainers.add(id); + } } sanitize(id: number, data: string): string { if (this.masked.has(id)) { // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases? - return data.trim().replace( - /[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, - '█', - ); + return data + .trim() + .replace( + /[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, + '█', + ); } if (this.options.obscureTextNumbers) { data = data.replace(/\d/g, '0'); @@ -50,11 +53,10 @@ export default class Sanitizer { if (this.options.obscureTextEmails) { data = data.replace( /([^\s]+)@([^\s]+)\.([^\s]+)/g, - (...f: Array) => - stars(f[1]) + '@' + stars(f[2]) + '.' + stars(f[3]), + (...f: Array) => stars(f[1]) + '@' + stars(f[2]) + '.' + stars(f[3]), ); } - return data + return data; } isMasked(id: number): boolean { @@ -65,15 +67,15 @@ export default class Sanitizer { } getInnerTextSecure(el: HTMLElement): string { - const id = this.app.nodes.getID(el) - if (!id) { return '' } - return this.sanitize(id, el.innerText) - + const id = this.app.nodes.getID(el); + if (!id) { + return ''; + } + return this.sanitize(id, el.innerText); } clear(): void { this.masked.clear(); this.maskedContainers.clear(); } - } diff --git a/tracker/tracker/src/main/app/session.ts b/tracker/tracker/src/main/app/session.ts index d7815309a..938883359 100644 --- a/tracker/tracker/src/main/app/session.ts +++ b/tracker/tracker/src/main/app/session.ts @@ -1,54 +1,52 @@ -import { UserID, UserAnonymousID, Metadata } from "../../common/messages.js"; - +import { UserID, UserAnonymousID, Metadata } from '../../common/messages.js'; interface SessionInfo { - sessionID: string | null, - metadata: Record, - userID: string | null, + sessionID: string | null; + metadata: Record; + userID: string | null; } -type OnUpdateCallback = (i: Partial) => void - +type OnUpdateCallback = (i: Partial) => void; export default class Session { - private metadata: Record = {} - private userID: string | null = null - private sessionID: string | null = null - private callbacks: OnUpdateCallback[] = [] - + private metadata: Record = {}; + private userID: string | null = null; + private sessionID: string | null = null; + private readonly callbacks: OnUpdateCallback[] = []; attachUpdateCallback(cb: OnUpdateCallback) { - this.callbacks.push(cb) + this.callbacks.push(cb); } private handleUpdate(newInfo: Partial) { if (newInfo.userID == null) { - delete newInfo.userID + delete newInfo.userID; } if (newInfo.sessionID == null) { - delete newInfo.sessionID + delete newInfo.sessionID; } - this.callbacks.forEach(cb => cb(newInfo)) + this.callbacks.forEach((cb) => cb(newInfo)); } update(newInfo: Partial) { - if (newInfo.userID !== undefined) { // TODO clear nullable/undefinable types - this.userID = newInfo.userID + if (newInfo.userID !== undefined) { + // TODO clear nullable/undefinable types + this.userID = newInfo.userID; } if (newInfo.metadata !== undefined) { - Object.entries(newInfo.metadata).forEach(([k,v]) => this.metadata[k] = v) + Object.entries(newInfo.metadata).forEach(([k, v]) => (this.metadata[k] = v)); } if (newInfo.sessionID !== undefined) { - this.sessionID = newInfo.sessionID + this.sessionID = newInfo.sessionID; } - this.handleUpdate(newInfo) + this.handleUpdate(newInfo); } setMetadata(key: string, value: string) { - this.metadata[key] = value - this.handleUpdate({ metadata: { [key]: value } }) + this.metadata[key] = value; + this.handleUpdate({ metadata: { [key]: value } }); } setUserID(userID: string) { - this.userID = userID - this.handleUpdate({ userID }) + this.userID = userID; + this.handleUpdate({ userID }); } getInfo(): SessionInfo { @@ -56,12 +54,12 @@ export default class Session { sessionID: this.sessionID, metadata: this.metadata, userID: this.userID, - } + }; } reset(): void { - this.metadata = {} - this.userID = null - this.sessionID = null + this.metadata = {}; + this.userID = null; + this.sessionID = null; } -} \ No newline at end of file +} diff --git a/tracker/tracker/src/main/app/ticker.ts b/tracker/tracker/src/main/app/ticker.ts index 2a4ee52f7..81bf7b6a8 100644 --- a/tracker/tracker/src/main/app/ticker.ts +++ b/tracker/tracker/src/main/app/ticker.ts @@ -1,4 +1,4 @@ -import App from "./index.js"; +import App from './index.js'; type Callback = () => void; function wrap(callback: Callback, n: number): Callback { diff --git a/tracker/tracker/src/main/index.ts b/tracker/tracker/src/main/index.ts index b5b991bf7..310545dd9 100644 --- a/tracker/tracker/src/main/index.ts +++ b/tracker/tracker/src/main/index.ts @@ -1,30 +1,39 @@ -import App, { DEFAULT_INGEST_POINT } from "./app/index.js"; +import App, { DEFAULT_INGEST_POINT } from './app/index.js'; export { default as App } from './app/index.js'; -import { UserID, UserAnonymousID, Metadata, RawCustomEvent, CustomIssue } from "../common/messages.js"; -import * as _Messages from "../common/messages.js"; +import { + UserID, + UserAnonymousID, + Metadata, + RawCustomEvent, + CustomIssue, +} from '../common/messages.js'; +import * as _Messages from '../common/messages.js'; export const Messages = _Messages; -import Connection from "./modules/connection.js"; -import Console from "./modules/console.js"; -import Exception, { getExceptionMessageFromEvent, getExceptionMessage } from "./modules/exception.js"; -import Img from "./modules/img.js"; -import Input from "./modules/input.js"; -import Mouse from "./modules/mouse.js"; -import Timing from "./modules/timing.js"; -import Performance from "./modules/performance.js"; -import Scroll from "./modules/scroll.js"; -import Viewport from "./modules/viewport.js"; -import CSSRules from "./modules/cssrules.js"; -import { IN_BROWSER, deprecationWarn, DOCS_HOST } from "./utils.js"; +import Connection from './modules/connection.js'; +import Console from './modules/console.js'; +import Exception, { + getExceptionMessageFromEvent, + getExceptionMessage, +} from './modules/exception.js'; +import Img from './modules/img.js'; +import Input from './modules/input.js'; +import Mouse from './modules/mouse.js'; +import Timing from './modules/timing.js'; +import Performance from './modules/performance.js'; +import Scroll from './modules/scroll.js'; +import Viewport from './modules/viewport.js'; +import CSSRules from './modules/cssrules.js'; +import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js'; -import type { Options as AppOptions } from "./app/index.js"; -import type { Options as ConsoleOptions } from "./modules/console.js"; -import type { Options as ExceptionOptions } from "./modules/exception.js"; -import type { Options as InputOptions } from "./modules/input.js"; -import type { Options as PerformanceOptions } from "./modules/performance.js"; -import type { Options as TimingOptions } from "./modules/timing.js"; -import type { StartOptions } from './app/index.js' +import type { Options as AppOptions } from './app/index.js'; +import type { Options as ConsoleOptions } from './modules/console.js'; +import type { Options as ExceptionOptions } from './modules/exception.js'; +import type { Options as InputOptions } from './modules/input.js'; +import type { Options as PerformanceOptions } from './modules/performance.js'; +import type { Options as TimingOptions } from './modules/timing.js'; +import type { StartOptions } from './app/index.js'; //TODO: unique options init import type { StartPromiseReturn } from './app/index.js'; @@ -44,25 +53,32 @@ const DOCS_SETUP = '/installation/setup-or'; function processOptions(obj: any): obj is Options { if (obj == null) { - console.error(`OpenReplay: invalid options argument type. Please, check documentation on ${DOCS_HOST}${DOCS_SETUP}`); + console.error( + `OpenReplay: invalid options argument type. Please, check documentation on ${DOCS_HOST}${DOCS_SETUP}`, + ); return false; } if (typeof obj.projectKey !== 'string') { if (typeof obj.projectKey !== 'number') { - if (typeof obj.projectID !== 'number') { // Back compatability - console.error(`OpenReplay: projectKey is missing or wrong type (string is expected). Please, check ${DOCS_HOST}${DOCS_SETUP} for more information.`) - return false + if (typeof obj.projectID !== 'number') { + // Back compatability + console.error( + `OpenReplay: projectKey is missing or wrong type (string is expected). Please, check ${DOCS_HOST}${DOCS_SETUP} for more information.`, + ); + return false; } else { obj.projectKey = obj.projectID.toString(); - deprecationWarn("`projectID` option", "`projectKey` option", DOCS_SETUP) + deprecationWarn('`projectID` option', '`projectKey` option', DOCS_SETUP); } } else { - console.warn("OpenReplay: projectKey is expected to have a string type.") - obj.projectKey = obj.projectKey.toString() + console.warn('OpenReplay: projectKey is expected to have a string type.'); + obj.projectKey = obj.projectKey.toString(); } } if (typeof obj.sessionToken !== 'string' && obj.sessionToken != null) { - console.warn(`OpenReplay: invalid options argument type. Please, check documentation on ${DOCS_HOST}${DOCS_SETUP}`) + console.warn( + `OpenReplay: invalid options argument type. Please, check documentation on ${DOCS_HOST}${DOCS_SETUP}`, + ); } return true; } @@ -74,18 +90,22 @@ export default class API { return; } if ((window as any).__OPENREPLAY__) { - console.error("OpenReplay: one tracker instance has been initialised already") - return - } - if (!options.__DISABLE_SECURE_MODE && location.protocol !== 'https:') { - console.error("OpenReplay: Your website must be publicly accessible and running on SSL in order for OpenReplay to properly capture and replay the user session. You can disable this check by setting `__DISABLE_SECURE_MODE` option to `true` if you are testing in localhost. Keep in mind, that asset files on a local machine are not available to the outside world. This might affect tracking if you use css files.") + console.error('OpenReplay: one tracker instance has been initialised already'); return; } - const doNotTrack = options.respectDoNotTrack && - (navigator.doNotTrack == '1' + if (!options.__DISABLE_SECURE_MODE && location.protocol !== 'https:') { + console.error( + 'OpenReplay: Your website must be publicly accessible and running on SSL in order for OpenReplay to properly capture and replay the user session. You can disable this check by setting `__DISABLE_SECURE_MODE` option to `true` if you are testing in localhost. Keep in mind, that asset files on a local machine are not available to the outside world. This might affect tracking if you use css files.', + ); + return; + } + const doNotTrack = + options.respectDoNotTrack && + (navigator.doNotTrack == '1' || // @ts-ignore - || window.doNotTrack == '1'); - const app = this.app = doNotTrack || + window.doNotTrack == '1'); + const app = (this.app = + doNotTrack || !('Map' in window) || !('Set' in window) || !('MutationObserver' in window) || @@ -95,7 +115,7 @@ export default class API { !('Blob' in window) || !('Worker' in window) ? null - : new App(options.projectKey, options.sessionToken, options); + : new App(options.projectKey, options.sessionToken, options)); if (app !== null) { Viewport(app); CSSRules(app); @@ -114,29 +134,33 @@ export default class API { const wOpen = window.open; app.attachStartCallback(() => { // @ts-ignore ? - window.open = function(...args) { - app.resetNextPageSession(true) - wOpen.call(window, ...args) - app.resetNextPageSession(false) - } - }) + window.open = function (...args) { + app.resetNextPageSession(true); + wOpen.call(window, ...args); + app.resetNextPageSession(false); + }; + }); app.attachStopCallback(() => { window.open = wOpen; - }) + }); } } else { - console.log("OpenReplay: browser doesn't support API required for tracking or doNotTrack is set to 1.") + console.log( + "OpenReplay: browser doesn't support API required for tracking or doNotTrack is set to 1.", + ); const req = new XMLHttpRequest(); const orig = options.ingestPoint || DEFAULT_INGEST_POINT; - req.open("POST", orig + "/v1/web/not-started"); + req.open('POST', orig + '/v1/web/not-started'); // no-cors issue only with text/plain or not-set Content-Type // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - req.send(JSON.stringify({ - trackerVersion: 'TRACKER_VERSION', - projectKey: options.projectKey, - doNotTrack, - // TODO: add precise reason (an exact API missing) - })); + req.send( + JSON.stringify({ + trackerVersion: 'TRACKER_VERSION', + projectKey: options.projectKey, + doNotTrack, + // TODO: add precise reason (an exact API missing) + }), + ); } } @@ -151,10 +175,12 @@ export default class API { return this.app.active(); } - start(startOpts?: Partial) : Promise { + start(startOpts?: Partial): Promise { if (!IN_BROWSER) { - console.error(`OpenReplay: you are trying to start Tracker on a node.js environment. If you want to use OpenReplay with SSR, please, use componentDidMount or useEffect API for placing the \`tracker.start()\` line. Check documentation on ${DOCS_HOST}${DOCS_SETUP}`) - return Promise.reject("Trying to start not in browser."); + console.error( + `OpenReplay: you are trying to start Tracker on a node.js environment. If you want to use OpenReplay with SSR, please, use componentDidMount or useEffect API for placing the \`tracker.start()\` line. Check documentation on ${DOCS_HOST}${DOCS_SETUP}`, + ); + return Promise.reject('Trying to start not in browser.'); } if (this.app === null) { return Promise.reject("Browser doesn't support required api, or doNotTrack is active."); @@ -182,7 +208,7 @@ export default class API { return this.app.getSessionID(); } sessionID(): string | null | undefined { - deprecationWarn("'sessionID' method", "'getSessionID' method", "/"); + deprecationWarn("'sessionID' method", "'getSessionID' method", '/'); return this.getSessionID(); } @@ -192,7 +218,7 @@ export default class API { } } userID(id: string): void { - deprecationWarn("'userID' method", "'setUserID' method", "/"); + deprecationWarn("'userID' method", "'setUserID' method", '/'); this.setUserID(id); } @@ -202,25 +228,21 @@ export default class API { } } userAnonymousID(id: string): void { - deprecationWarn("'userAnonymousID' method", "'setUserAnonymousID' method", "/") + deprecationWarn("'userAnonymousID' method", "'setUserAnonymousID' method", '/'); this.setUserAnonymousID(id); } setMetadata(key: string, value: string): void { - if ( - typeof key === 'string' && - typeof value === 'string' && - this.app !== null - ) { + if (typeof key === 'string' && typeof value === 'string' && this.app !== null) { this.app.session.setMetadata(key, value); } } metadata(key: string, value: string): void { - deprecationWarn("'metadata' method", "'setMetadata' method", "/") + deprecationWarn("'metadata' method", "'setMetadata' method", '/'); this.setMetadata(key, value); } - event(key: string, payload: any, issue: boolean = false): void { + event(key: string, payload: any, issue = false): void { if (typeof key === 'string' && this.app !== null) { if (issue) { return this.issue(key, payload); @@ -247,10 +269,13 @@ export default class API { } handleError = (e: Error | ErrorEvent | PromiseRejectionEvent) => { - if (this.app === null) { return; } + if (this.app === null) { + return; + } if (e instanceof Error) { this.app.send(getExceptionMessage(e, [])); - } else if (e instanceof ErrorEvent || + } else if ( + e instanceof ErrorEvent || ('PromiseRejectionEvent' in window && e instanceof PromiseRejectionEvent) ) { const msg = getExceptionMessageFromEvent(e); @@ -258,5 +283,5 @@ export default class API { this.app.send(msg); } } - } + }; } diff --git a/tracker/tracker/src/main/modules/connection.ts b/tracker/tracker/src/main/modules/connection.ts index 72ef972f7..ecf9e9fe9 100644 --- a/tracker/tracker/src/main/modules/connection.ts +++ b/tracker/tracker/src/main/modules/connection.ts @@ -1,13 +1,13 @@ -import App from "../app/index.js"; -import { ConnectionInformation } from "../../common/messages.js"; +import App from '../app/index.js'; +import { ConnectionInformation } from '../../common/messages.js'; -export default function(app: App): void { +export default function (app: App): void { const connection: | { - downlink: number; - type?: string; - addEventListener: (type: 'change', cb: () => void) => void; - } + downlink: number; + type?: string; + addEventListener: (type: 'change', cb: () => void) => void; + } | undefined = (navigator as any).connection || (navigator as any).mozConnection || diff --git a/tracker/tracker/src/main/modules/console.ts b/tracker/tracker/src/main/modules/console.ts index 40925c83c..33ad34957 100644 --- a/tracker/tracker/src/main/modules/console.ts +++ b/tracker/tracker/src/main/modules/console.ts @@ -1,7 +1,7 @@ -import type App from "../app/index.js"; -import { hasTag } from "../app/guards.js"; -import { IN_BROWSER } from "../utils.js"; -import { ConsoleLog } from "../../common/messages.js"; +import type App from '../app/index.js'; +import { hasTag } from '../app/guards.js'; +import { IN_BROWSER } from '../utils.js'; +import { ConsoleLog } from '../../common/messages.js'; const printError: (e: Error) => string = IN_BROWSER && 'InstallTrigger' in window // detect Firefox @@ -104,10 +104,7 @@ export default function (app: App, opts: Partial): void { }, opts, ); - if ( - !Array.isArray(options.consoleMethods) || - options.consoleMethods.length === 0 - ) { + if (!Array.isArray(options.consoleMethods) || options.consoleMethods.length === 0) { return; } @@ -139,18 +136,21 @@ export default function (app: App, opts: Partial): void { }); patchConsole(window.console); - app.nodes.attachNodeCallback(app.safe(node => { - if (hasTag(node, "IFRAME")) { // TODO: newContextCallback - let context = node.contentWindow - if (context) { - patchConsole((context as (Window & typeof globalThis)).console) - } - app.attachEventListener(node, "load", () => { - if (node.contentWindow !== context) { - context = node.contentWindow - patchConsole((context as (Window & typeof globalThis)).console) + app.nodes.attachNodeCallback( + app.safe((node) => { + if (hasTag(node, 'IFRAME')) { + // TODO: newContextCallback + let context = node.contentWindow; + if (context) { + patchConsole((context as Window & typeof globalThis).console); } - }) - } - })) + app.attachEventListener(node, 'load', () => { + if (node.contentWindow !== context) { + context = node.contentWindow; + patchConsole((context as Window & typeof globalThis).console); + } + }); + } + }), + ); } diff --git a/tracker/tracker/src/main/modules/cssrules.ts b/tracker/tracker/src/main/modules/cssrules.ts index 5007bbd26..53c805722 100644 --- a/tracker/tracker/src/main/modules/cssrules.ts +++ b/tracker/tracker/src/main/modules/cssrules.ts @@ -1,57 +1,53 @@ -import type App from "../app/index.js"; -import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from "../../common/messages.js"; -import { hasTag } from "../app/guards.js"; +import type App from '../app/index.js'; +import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from '../../common/messages.js'; +import { hasTag } from '../app/guards.js'; - -export default function(app: App | null) { +export default function (app: App | null) { if (app === null) { return; } if (!window.CSSStyleSheet) { - app.send(new TechnicalInfo("no_stylesheet_prototype_in_window", "")) + app.send(new TechnicalInfo('no_stylesheet_prototype_in_window', '')); return; } - const processOperation = app.safe( - (stylesheet: CSSStyleSheet, index: number, rule?: string) => { - const sendMessage = typeof rule === 'string' - ? (nodeID: number) => app.send(new CSSInsertRuleURLBased(nodeID, rule, index, app.getBaseHref())) + const processOperation = app.safe((stylesheet: CSSStyleSheet, index: number, rule?: string) => { + const sendMessage = + typeof rule === 'string' + ? (nodeID: number) => + app.send(new CSSInsertRuleURLBased(nodeID, rule, index, app.getBaseHref())) : (nodeID: number) => app.send(new CSSDeleteRule(nodeID, index)); - // TODO: Extend messages to maintain nested rules (CSSGroupingRule prototype, as well as CSSKeyframesRule) - if (stylesheet.ownerNode == null) { - throw new Error("Owner Node not found"); - } - const nodeID = app.nodes.getID(stylesheet.ownerNode); - if (nodeID !== undefined) { - sendMessage(nodeID); - } // else error? + // TODO: Extend messages to maintain nested rules (CSSGroupingRule prototype, as well as CSSKeyframesRule) + if (stylesheet.ownerNode == null) { + throw new Error('Owner Node not found'); } - ) + const nodeID = app.nodes.getID(stylesheet.ownerNode); + if (nodeID !== undefined) { + sendMessage(nodeID); + } // else error? + }); const { insertRule, deleteRule } = CSSStyleSheet.prototype; - CSSStyleSheet.prototype.insertRule = function( - rule: string, - index: number = 0, - ) { + CSSStyleSheet.prototype.insertRule = function (rule: string, index = 0) { processOperation(this, index, rule); return insertRule.call(this, rule, index); }; - CSSStyleSheet.prototype.deleteRule = function(index: number) { + CSSStyleSheet.prototype.deleteRule = function (index: number) { processOperation(this, index); return deleteRule.call(this, index); }; app.nodes.attachNodeCallback((node: Node): void => { - if (!hasTag(node, "STYLE") || !node.sheet) { + if (!hasTag(node, 'STYLE') || !node.sheet) { return; } if (node.textContent !== null && node.textContent.trim().length > 0) { return; // Only fully virtual sheets maintained so far - } + } const rules = node.sheet.cssRules; for (let i = 0; i < rules.length; i++) { - processOperation(node.sheet, i, rules[i].cssText) + processOperation(node.sheet, i, rules[i].cssText); } }); } diff --git a/tracker/tracker/src/main/modules/exception.ts b/tracker/tracker/src/main/modules/exception.ts index 03dc84d72..2cb66885e 100644 --- a/tracker/tracker/src/main/modules/exception.ts +++ b/tracker/tracker/src/main/modules/exception.ts @@ -1,6 +1,6 @@ -import type App from "../app/index.js"; -import type Message from "../../common/messages.js"; -import { JSException } from "../../common/messages.js"; +import type App from '../app/index.js'; +import type Message from '../../common/messages.js'; +import { JSException } from '../../common/messages.js'; import ErrorStackParser from 'error-stack-parser'; export interface Options { @@ -8,53 +8,56 @@ export interface Options { } interface StackFrame { - columnNumber?: number, - lineNumber?: number, - fileName?: string, - functionName?: string, - source?: string, + columnNumber?: number; + lineNumber?: number; + fileName?: string; + functionName?: string; + source?: string; } function getDefaultStack(e: ErrorEvent): Array { - return [{ - columnNumber: e.colno, - lineNumber: e.lineno, - fileName: e.filename, - functionName: "", - source: "", - }]; + return [ + { + columnNumber: e.colno, + lineNumber: e.lineno, + fileName: e.filename, + functionName: '', + source: '', + }, + ]; } export function getExceptionMessage(error: Error, fallbackStack: Array): Message { let stack = fallbackStack; try { stack = ErrorStackParser.parse(error); - } catch (e) { - } + } catch (e) {} return new JSException(error.name, error.message, JSON.stringify(stack)); } -export function getExceptionMessageFromEvent(e: ErrorEvent | PromiseRejectionEvent): Message | null { +export function getExceptionMessageFromEvent( + e: ErrorEvent | PromiseRejectionEvent, +): Message | null { if (e instanceof ErrorEvent) { if (e.error instanceof Error) { - return getExceptionMessage(e.error, getDefaultStack(e)) + return getExceptionMessage(e.error, getDefaultStack(e)); } else { let [name, message] = e.message.split(':'); if (!message) { name = 'Error'; - message = e.message + message = e.message; } return new JSException(name, message, JSON.stringify(getDefaultStack(e))); } } else if ('PromiseRejectionEvent' in window && e instanceof PromiseRejectionEvent) { if (e.reason instanceof Error) { - return getExceptionMessage(e.reason, []) + return getExceptionMessage(e.reason, []); } else { let message: string; try { - message = JSON.stringify(e.reason) - } catch(_) { - message = String(e.reason) + message = JSON.stringify(e.reason); + } catch (_) { + message = String(e.reason); } return new JSException('Unhandled Promise Rejection', message, '[]'); } @@ -62,7 +65,6 @@ export function getExceptionMessageFromEvent(e: ErrorEvent | PromiseRejectionEve return null; } - export default function (app: App, opts: Partial): void { const options: Options = Object.assign( { @@ -76,17 +78,11 @@ export default function (app: App, opts: Partial): void { if (msg != null) { app.send(msg); } - } + }; - app.attachEventListener( - window, - 'unhandledrejection', - (e: PromiseRejectionEvent): void => handler(e), - ); - app.attachEventListener( - window, - 'error', - (e: ErrorEvent): void => handler(e), + app.attachEventListener(window, 'unhandledrejection', (e: PromiseRejectionEvent): void => + handler(e), ); + app.attachEventListener(window, 'error', (e: ErrorEvent): void => handler(e)); } } diff --git a/tracker/tracker/src/main/modules/img.ts b/tracker/tracker/src/main/modules/img.ts index b462f7705..f34675074 100644 --- a/tracker/tracker/src/main/modules/img.ts +++ b/tracker/tracker/src/main/modules/img.ts @@ -1,34 +1,38 @@ -import type App from "../app/index.js"; -import { timestamp, isURL } from "../utils.js"; -import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from "../../common/messages.js"; -import { hasTag } from "../app/guards.js"; +import type App from '../app/index.js'; +import { timestamp, isURL } from '../utils.js'; +import { + ResourceTiming, + SetNodeAttributeURLBased, + SetNodeAttribute, +} from '../../common/messages.js'; +import { hasTag } from '../app/guards.js'; function resolveURL(url: string, location: Location = document.location) { - url = url.trim() - if (url.startsWith('/')) { - return location.origin + url + url = url.trim(); + if (url.startsWith('/')) { + return location.origin + url; } else if ( url.startsWith('http://') || url.startsWith('https://') || url.startsWith('data:') // any other possible value here? - ){ - return url + ) { + return url; } else { - return location.origin + location.pathname + url + return location.origin + location.pathname + url; } } -const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg"; +const PLACEHOLDER_SRC = 'https://static.openreplay.com/tracker/placeholder.jpeg'; export default function (app: App): void { function sendPlaceholder(id: number, node: HTMLImageElement): void { - app.send(new SetNodeAttribute(id, "src", PLACEHOLDER_SRC)) + app.send(new SetNodeAttribute(id, 'src', PLACEHOLDER_SRC)); const { width, height } = node.getBoundingClientRect(); - if (!node.hasAttribute("width")){ - app.send(new SetNodeAttribute(id, "width", String(width))) + if (!node.hasAttribute('width')) { + app.send(new SetNodeAttribute(id, 'width', String(width))); } - if (!node.hasAttribute("height")){ - app.send(new SetNodeAttribute(id, "height", String(height))) + if (!node.hasAttribute('height')) { + app.send(new SetNodeAttribute(id, 'height', String(height))); } } @@ -41,35 +45,38 @@ export default function (app: App): void { if (!complete) { return; } - const resolvedSrc = resolveURL(src || '') // Src type is null sometimes. - is it true? + const resolvedSrc = resolveURL(src || ''); // Src type is null sometimes. - is it true? if (naturalWidth === 0 && naturalHeight === 0) { if (isURL(resolvedSrc)) { app.send(new ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img')); } } else if (resolvedSrc.length >= 1e5 || app.sanitizer.isMasked(id)) { - sendPlaceholder(id, this) + sendPlaceholder(id, this); } else { - app.send(new SetNodeAttribute(id, 'src', resolvedSrc)) + app.send(new SetNodeAttribute(id, 'src', resolvedSrc)); if (srcset) { - const resolvedSrcset = srcset.split(',').map(str => resolveURL(str)).join(',') - app.send(new SetNodeAttribute(id, 'srcset', resolvedSrcset)) + const resolvedSrcset = srcset + .split(',') + .map((str) => resolveURL(str)) + .join(','); + app.send(new SetNodeAttribute(id, 'srcset', resolvedSrcset)); } } }); const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { - if (mutation.type === "attributes") { - const target = (mutation.target as HTMLImageElement); + if (mutation.type === 'attributes') { + const target = mutation.target as HTMLImageElement; const id = app.nodes.getID(target); if (id === undefined) { return; } - if (mutation.attributeName === "src") { + if (mutation.attributeName === 'src') { const src = target.src; app.send(new SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref())); } - if (mutation.attributeName === "srcset") { + if (mutation.attributeName === 'srcset') { const srcset = target.srcset; app.send(new SetNodeAttribute(id, 'srcset', srcset)); } @@ -78,12 +85,12 @@ export default function (app: App): void { }); app.nodes.attachNodeCallback((node: Node): void => { - if (!hasTag(node, "IMG")) { + if (!hasTag(node, 'IMG')) { return; } app.nodes.attachElementListener('error', node, sendImgSrc); app.nodes.attachElementListener('load', node, sendImgSrc); sendImgSrc.call(node); - observer.observe(node, { attributes: true, attributeFilter: [ "src", "srcset" ] }); + observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] }); }); } diff --git a/tracker/tracker/src/main/modules/input.ts b/tracker/tracker/src/main/modules/input.ts index 496d642a2..26c593c98 100644 --- a/tracker/tracker/src/main/modules/input.ts +++ b/tracker/tracker/src/main/modules/input.ts @@ -1,45 +1,38 @@ -import type App from "../app/index.js"; -import { - normSpaces, - IN_BROWSER, - getLabelAttribute, - hasOpenreplayAttribute, -} from "../utils.js"; -import { hasTag } from "../app/guards.js"; -import { SetInputTarget, SetInputValue, SetInputChecked } from "../../common/messages.js"; +import type App from '../app/index.js'; +import { normSpaces, IN_BROWSER, getLabelAttribute, hasOpenreplayAttribute } from '../utils.js'; +import { hasTag } from '../app/guards.js'; +import { SetInputTarget, SetInputValue, SetInputChecked } from '../../common/messages.js'; -const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date'] +const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date']; // TODO: take into consideration "contenteditable" attribute -type TextEditableElement = HTMLInputElement | HTMLTextAreaElement +type TextEditableElement = HTMLInputElement | HTMLTextAreaElement; function isTextEditable(node: any): node is TextEditableElement { - if (hasTag(node, "TEXTAREA")) { + if (hasTag(node, 'TEXTAREA')) { return true; } - if (!hasTag(node, "INPUT")) { + if (!hasTag(node, 'INPUT')) { return false; } - return INPUT_TYPES.includes(node.type) + return INPUT_TYPES.includes(node.type); } function isCheckable(node: any): node is HTMLInputElement { - if (!hasTag(node, "INPUT")) { + if (!hasTag(node, 'INPUT')) { return false; } const type = node.type; return type === 'checkbox' || type === 'radio'; } -const labelElementFor: ( - element: TextEditableElement, -) => HTMLLabelElement | undefined = +const labelElementFor: (element: TextEditableElement) => HTMLLabelElement | undefined = IN_BROWSER && 'labels' in HTMLInputElement.prototype ? (node) => { let p: Node | null = node; while ((p = p.parentNode) !== null) { - if (hasTag(p, "LABEL")) { - return p + if (hasTag(p, 'LABEL')) { + return p; } } const labels = node.labels; @@ -50,8 +43,8 @@ const labelElementFor: ( : (node) => { let p: Node | null = node; while ((p = p.parentNode) !== null) { - if (hasTag(p, "LABEL")) { - return p as HTMLLabelElement; + if (hasTag(p, 'LABEL')) { + return p; } } const id = node.id; @@ -67,12 +60,13 @@ export function getInputLabel(node: TextEditableElement): string { let label = getLabelAttribute(node); if (label === null) { const labelElement = labelElementFor(node); - label = (labelElement && labelElement.innerText) - || node.placeholder - || node.name - || node.id - || node.className - || node.type + label = + (labelElement && labelElement.innerText) || + node.placeholder || + node.name || + node.id || + node.className || + node.type; } return normSpaces(label).slice(0, 100); } @@ -116,8 +110,7 @@ export default function (app: App, opts: Partial): void { (inputMode === InputMode.Plain && ((options.obscureInputNumbers && node.type !== 'date' && /\d\d\d\d/.test(value)) || (options.obscureInputDates && node.type === 'date') || - (options.obscureInputEmails && - (node.type === 'email' || !!~value.indexOf('@'))))) + (options.obscureInputEmails && (node.type === 'email' || !!~value.indexOf('@'))))) ) { inputMode = InputMode.Obscured; } @@ -149,6 +142,7 @@ export default function (app: App, opts: Partial): void { app.ticker.attach((): void => { inputValues.forEach((value, id) => { const node = app.nodes.getNode(id); + if (!node) return; if (!isTextEditable(node)) { inputValues.delete(id); return; @@ -164,6 +158,7 @@ export default function (app: App, opts: Partial): void { }); checkableValues.forEach((checked, id) => { const node = app.nodes.getNode(id); + if (!node) return; if (!isCheckable(node)) { checkableValues.delete(id); return; @@ -183,11 +178,11 @@ export default function (app: App, opts: Partial): void { return; } // TODO: support multiple select (?): use selectedOptions; Need send target? - if (hasTag(node, "SELECT")) { - sendInputValue(id, node) - app.attachEventListener(node, "change", () => { - sendInputValue(id, node) - }) + if (hasTag(node, 'SELECT')) { + sendInputValue(id, node); + app.attachEventListener(node, 'change', () => { + sendInputValue(id, node); + }); } if (isTextEditable(node)) { inputValues.set(id, node.value); diff --git a/tracker/tracker/src/main/modules/longtasks.ts b/tracker/tracker/src/main/modules/longtasks.ts index 959d748f7..8ac5f789a 100644 --- a/tracker/tracker/src/main/modules/longtasks.ts +++ b/tracker/tracker/src/main/modules/longtasks.ts @@ -1,5 +1,5 @@ -import type App from "../app/index.js"; -import { LongTask } from "../../common/messages.js"; +import type App from '../app/index.js'; +import { LongTask } from '../../common/messages.js'; // https://w3c.github.io/performance-timeline/#the-performanceentry-interface interface TaskAttributionTiming extends PerformanceEntry { @@ -7,22 +7,35 @@ interface TaskAttributionTiming extends PerformanceEntry { readonly containerSrc: string; readonly containerId: string; readonly containerName: string; -}; +} // https://www.w3.org/TR/longtasks/#performancelongtasktiming interface PerformanceLongTaskTiming extends PerformanceEntry { readonly attribution: ReadonlyArray; -}; +} export default function (app: App): void { if (!('PerformanceObserver' in window) || !('PerformanceLongTaskTiming' in window)) { return; } - const contexts: string[] = [ "unknown", "self", "same-origin-ancestor", "same-origin-descendant", "same-origin", "cross-origin-ancestor", "cross-origin-descendant", "cross-origin-unreachable", "multiple-contexts" ]; - const containerTypes: string[] = [ "window", "iframe", "embed", "object" ]; + const contexts: string[] = [ + 'unknown', + 'self', + 'same-origin-ancestor', + 'same-origin-descendant', + 'same-origin', + 'cross-origin-ancestor', + 'cross-origin-descendant', + 'cross-origin-unreachable', + 'multiple-contexts', + ]; + const containerTypes: string[] = ['window', 'iframe', 'embed', 'object']; function longTask(entry: PerformanceLongTaskTiming): void { - let type: string = "", src: string = "", id: string = "", name: string = ""; + let type = '', + src = '', + id = '', + name = ''; const container = entry.attribution[0]; if (container != null) { type = container.containerType; @@ -48,4 +61,4 @@ export default function (app: App): void { list.getEntries().forEach(longTask), ); observer.observe({ entryTypes: ['longtask'] }); -} \ No newline at end of file +} diff --git a/tracker/tracker/src/main/modules/mouse.ts b/tracker/tracker/src/main/modules/mouse.ts index 09a06e0ca..c46ed84ea 100644 --- a/tracker/tracker/src/main/modules/mouse.ts +++ b/tracker/tracker/src/main/modules/mouse.ts @@ -1,44 +1,43 @@ -import type App from "../app/index.js"; -import { hasTag, isSVGElement } from "../app/guards.js"; -import { - normSpaces, - hasOpenreplayAttribute, - getLabelAttribute, -} from "../utils.js"; -import { MouseMove, MouseClick } from "../../common/messages.js"; -import { getInputLabel } from "./input.js"; +import type App from '../app/index.js'; +import { hasTag, isSVGElement } from '../app/guards.js'; +import { normSpaces, hasOpenreplayAttribute, getLabelAttribute } from '../utils.js'; +import { MouseMove, MouseClick } from '../../common/messages.js'; +import { getInputLabel } from './input.js'; function _getSelector(target: Element): string { - let el: Element | null = target - let selector: string | null = null + let el: Element | null = target; + let selector: string | null = null; do { if (el.id) { - return `#${el.id}` + (selector ? ` > ${selector}` : '') + return `#${el.id}` + (selector ? ` > ${selector}` : ''); } selector = - el.className.split(' ') - .map(cn => cn.trim()) - .filter(cn => cn !== '') - .reduce((sel, cn) => `${sel}.${cn}`, el.tagName.toLowerCase()) + - (selector ? ` > ${selector}` : ''); - if (el === document.body) { - return selector - } - el = el.parentElement - } while (el !== document.body && el !== null) - return selector + el.className + .split(' ') + .map((cn) => cn.trim()) + .filter((cn) => cn !== '') + .reduce((sel, cn) => `${sel}.${cn}`, el.tagName.toLowerCase()) + + (selector ? ` > ${selector}` : ''); + if (el === document.body) { + return selector; + } + el = el.parentElement; + } while (el !== document.body && el !== null); + return selector; } function isClickable(element: Element): boolean { - const tag = element.tagName.toUpperCase() - return tag === 'BUTTON' || + const tag = element.tagName.toUpperCase(); + return ( + tag === 'BUTTON' || tag === 'A' || tag === 'LI' || tag === 'SELECT' || (element as HTMLElement).onclick != null || - element.getAttribute('role') === 'button' - //|| element.className.includes("btn") - // MBTODO: intersect addEventListener + element.getAttribute('role') === 'button' + ); + //|| element.className.includes("btn") + // MBTODO: intersect addEventListener } //TODO: fix (typescript doesn't allow work when the guard is inside the function) @@ -73,9 +72,7 @@ function _getTarget(target: Element): Element | null { if (tag === 'INPUT') { return element; } - if (isClickable(element) || - getLabelAttribute(element) !== null - ) { + if (isClickable(element) || getLabelAttribute(element) !== null) { return element; } element = element.parentElement; @@ -84,22 +81,21 @@ function _getTarget(target: Element): Element | null { } export default function (app: App): void { - function getTargetLabel(target: Element): string { const dl = getLabelAttribute(target); if (dl !== null) { return dl; } - if (hasTag(target, "INPUT")) { - return getInputLabel(target) + if (hasTag(target, 'INPUT')) { + return getInputLabel(target); } if (isClickable(target)) { - let label = '' - if (target instanceof HTMLElement) { - label = app.sanitizer.getInnerTextSecure(target) - } - label = label || target.id || target.className - return normSpaces(label).slice(0, 100) + let label = ''; + if (target instanceof HTMLElement) { + label = app.sanitizer.getInnerTextSecure(target); + } + label = label || target.id || target.className; + return normSpaces(label).slice(0, 100); } return ''; } @@ -124,22 +120,18 @@ export default function (app: App): void { } }; - const selectorMap: {[id:number]: string} = {}; + const selectorMap: { [id: number]: string } = {}; function getSelector(id: number, target: Element): string { - return selectorMap[id] = selectorMap[id] || _getSelector(target); + return (selectorMap[id] = selectorMap[id] || _getSelector(target)); } - app.attachEventListener( - document.documentElement, - 'mouseover', - (e: MouseEvent): void => { - const target = getTarget(e.target); - if (target !== mouseTarget) { - mouseTarget = target; - mouseTargetTime = performance.now(); - } - }, - ); + app.attachEventListener(document.documentElement, 'mouseover', (e: MouseEvent): void => { + const target = getTarget(e.target); + if (target !== mouseTarget) { + mouseTarget = target; + mouseTargetTime = performance.now(); + } + }); app.attachEventListener( document, 'mousemove', @@ -158,12 +150,10 @@ export default function (app: App): void { const id = app.nodes.getID(target); if (id !== undefined) { sendMouseMove(); - app.send(new - MouseClick( + app.send( + new MouseClick( id, - mouseTarget === target - ? Math.round(performance.now() - mouseTargetTime) - : 0, + mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0, getTargetLabel(target), getSelector(id, target), ), diff --git a/tracker/tracker/src/main/modules/performance.ts b/tracker/tracker/src/main/modules/performance.ts index c69293225..c921e45eb 100644 --- a/tracker/tracker/src/main/modules/performance.ts +++ b/tracker/tracker/src/main/modules/performance.ts @@ -1,20 +1,19 @@ -import type App from "../app/index.js"; -import { IN_BROWSER } from "../utils.js"; -import { PerformanceTrack } from "../../common/messages.js"; - +import type App from '../app/index.js'; +import { IN_BROWSER } from '../utils.js'; +import { PerformanceTrack } from '../../common/messages.js'; type Perf = { - memory: { - totalJSHeapSize?: number, - usedJSHeapSize?: number, - jsHeapSizeLimit?: number, - } -} - -const perf: Perf = IN_BROWSER && 'performance' in window && 'memory' in performance // works in Chrome only - ? performance as any - : { memory: {} } + memory: { + totalJSHeapSize?: number; + usedJSHeapSize?: number; + jsHeapSizeLimit?: number; + }; +}; +const perf: Perf = + IN_BROWSER && 'performance' in window && 'memory' in performance // works in Chrome only + ? (performance as any) + : { memory: {} }; export const deviceMemory = IN_BROWSER ? ((navigator as any).deviceMemory || 0) * 1024 : 0; export const jsHeapSizeLimit = perf.memory.jsHeapSizeLimit || 0; @@ -30,7 +29,9 @@ export default function (app: App, opts: Partial): void { }, opts, ); - if (!options.capturePerformance) { return; } + if (!options.capturePerformance) { + return; + } let frames: number | undefined; let ticks: number | undefined; @@ -58,8 +59,8 @@ export default function (app: App, opts: Partial): void { if (frames === undefined || ticks === undefined) { return; } - app.send(new - PerformanceTrack( + app.send( + new PerformanceTrack( frames, ticks, perf.memory.totalJSHeapSize || 0, diff --git a/tracker/tracker/src/main/modules/scroll.ts b/tracker/tracker/src/main/modules/scroll.ts index 3515b58e6..656408824 100644 --- a/tracker/tracker/src/main/modules/scroll.ts +++ b/tracker/tracker/src/main/modules/scroll.ts @@ -1,14 +1,14 @@ -import type App from "../app/index.js"; -import { SetViewportScroll, SetNodeScroll } from "../../common/messages.js"; -import { isElementNode } from "../app/guards.js"; +import type App from '../app/index.js'; +import { SetViewportScroll, SetNodeScroll } from '../../common/messages.js'; +import { isElementNode } from '../app/guards.js'; export default function (app: App): void { let documentScroll = false; const nodeScroll: Map = new Map(); const sendSetViewportScroll = app.safe((): void => - app.send(new - SetViewportScroll( + app.send( + new SetViewportScroll( window.pageXOffset || (document.documentElement && document.documentElement.scrollLeft) || (document.body && document.body.scrollLeft) || @@ -39,7 +39,7 @@ export default function (app: App): void { if (isStart && isElementNode(node) && node.scrollLeft + node.scrollTop > 0) { nodeScroll.set(node, [node.scrollLeft, node.scrollTop]); } - }) + }); app.attachEventListener(window, 'scroll', (e: Event): void => { const target = e.target; diff --git a/tracker/tracker/src/main/modules/timing.ts b/tracker/tracker/src/main/modules/timing.ts index 30183dfa6..1ca670562 100644 --- a/tracker/tracker/src/main/modules/timing.ts +++ b/tracker/tracker/src/main/modules/timing.ts @@ -1,8 +1,7 @@ -import type App from "../app/index.js"; -import { hasTag } from "../app/guards.js"; -import { isURL } from "../utils.js"; -import { ResourceTiming, PageLoadTiming, PageRenderTiming } from "../../common/messages.js"; - +import type App from '../app/index.js'; +import { hasTag } from '../app/guards.js'; +import { isURL } from '../utils.js'; +import { ResourceTiming, PageLoadTiming, PageRenderTiming } from '../../common/messages.js'; // Inspired by https://github.com/WPO-Foundation/RUM-SpeedIndex/blob/master/src/rum-speedindex.js @@ -22,13 +21,11 @@ function getPaintBlocks(resources: ResourcesTimeMap): Array { for (let i = 0; i < elements.length; i++) { const element = elements[i]; let src = ''; - if (hasTag(element, "IMG")) { + if (hasTag(element, 'IMG')) { src = element.currentSrc || element.src; } if (!src) { - const backgroundImage = getComputedStyle(element).getPropertyValue( - 'background-image', - ); + const backgroundImage = getComputedStyle(element).getPropertyValue('background-image'); if (backgroundImage) { const matches = styleURL.exec(backgroundImage); if (matches !== null) { @@ -53,9 +50,7 @@ function getPaintBlocks(resources: ResourcesTimeMap): Array { ); const right = Math.min( rect.right, - window.innerWidth || - (document.documentElement && document.documentElement.clientWidth) || - 0, + window.innerWidth || (document.documentElement && document.documentElement.clientWidth) || 0, ); if (bottom <= top || right <= left) continue; const area = (bottom - top) * (right - left); @@ -64,18 +59,14 @@ function getPaintBlocks(resources: ResourcesTimeMap): Array { return paintBlocks; } -function calculateSpeedIndex( - firstContentfulPaint: number, - paintBlocks: Array, -): number { +function calculateSpeedIndex(firstContentfulPaint: number, paintBlocks: Array): number { let a = (Math.max( (document.documentElement && document.documentElement.clientWidth) || 0, window.innerWidth || 0, ) * Math.max( - (document.documentElement && document.documentElement.clientHeight) || - 0, + (document.documentElement && document.documentElement.clientHeight) || 0, window.innerHeight || 0, )) / 10; @@ -106,25 +97,23 @@ export default function (app: App, opts: Partial): void { if (!('PerformanceObserver' in window)) { options.captureResourceTimings = false; } - if (!options.captureResourceTimings) { return } // Resources are necessary for all timings + if (!options.captureResourceTimings) { + return; + } // Resources are necessary for all timings - let resources: ResourcesTimeMap | null = {} + let resources: ResourcesTimeMap | null = {}; function resourceTiming(entry: PerformanceResourceTiming): void { if (entry.duration < 0 || !isURL(entry.name) || app.isServiceURL(entry.name)) return; if (resources !== null) { resources[entry.name] = entry.startTime + entry.duration; } - app.send(new - ResourceTiming( + app.send( + new ResourceTiming( entry.startTime + performance.timing.navigationStart, entry.duration, - entry.responseStart && entry.startTime - ? entry.responseStart - entry.startTime - : 0, - entry.transferSize > entry.encodedBodySize - ? entry.transferSize - entry.encodedBodySize - : 0, + entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0, + entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, entry.decodedBodySize || 0, entry.name, @@ -133,48 +122,45 @@ export default function (app: App, opts: Partial): void { ); } - const observer: PerformanceObserver = new PerformanceObserver( - (list) => list.getEntries().forEach(resourceTiming), - ) + const observer: PerformanceObserver = new PerformanceObserver((list) => + list.getEntries().forEach(resourceTiming), + ); - let prevSessionID: string | undefined - app.attachStartCallback(function({ sessionID }) { - if (sessionID !== prevSessionID) { // Send past page resources on a newly started session - performance.getEntriesByType('resource').forEach(resourceTiming) - prevSessionID = sessionID + let prevSessionID: string | undefined; + app.attachStartCallback(function ({ sessionID }) { + if (sessionID !== prevSessionID) { + // Send past page resources on a newly started session + performance.getEntriesByType('resource').forEach(resourceTiming); + prevSessionID = sessionID; } - observer.observe({ entryTypes: ['resource'] }) - }) - - app.attachStopCallback(function() { - observer.disconnect() - }) + observer.observe({ entryTypes: ['resource'] }); + }); + app.attachStopCallback(function () { + observer.disconnect(); + }); let firstPaint = 0, firstContentfulPaint = 0; if (options.capturePageLoadTimings) { - - let pageLoadTimingSent: boolean = false; + let pageLoadTimingSent = false; app.ticker.attach(() => { if (pageLoadTimingSent) { return; } if (firstPaint === 0 || firstContentfulPaint === 0) { - performance - .getEntriesByType('paint') - .forEach((entry: PerformanceEntry) => { - const { name, startTime } = entry; - switch (name) { - case 'first-paint': - firstPaint = startTime; - break; - case 'first-contentful-paint': - firstContentfulPaint = startTime; - break; - } - }); + performance.getEntriesByType('paint').forEach((entry: PerformanceEntry) => { + const { name, startTime } = entry; + switch (name) { + case 'first-paint': + firstPaint = startTime; + break; + case 'first-contentful-paint': + firstContentfulPaint = startTime; + break; + } + }); } if (performance.timing.loadEventEnd || performance.now() > 30000) { pageLoadTimingSent = true; @@ -188,8 +174,8 @@ export default function (app: App, opts: Partial): void { loadEventStart, loadEventEnd, } = performance.timing; - app.send(new - PageLoadTiming( + app.send( + new PageLoadTiming( requestStart - navigationStart || 0, responseStart - navigationStart || 0, responseEnd - navigationStart || 0, @@ -211,7 +197,7 @@ export default function (app: App, opts: Partial): void { interactiveWindowTickTime: number | null = 0, paintBlocks: Array | null = null; - let pageRenderTimingSent: boolean = false; + let pageRenderTimingSent = false; app.ticker.attach(() => { if (pageRenderTimingSent) { return; @@ -231,37 +217,28 @@ export default function (app: App, opts: Partial): void { if (time - interactiveWindowTickTime > 50) { interactiveWindowStartTime = time; } - interactiveWindowTickTime = - time - interactiveWindowStartTime > 5000 ? null : time; + interactiveWindowTickTime = time - interactiveWindowStartTime > 5000 ? null : time; } - if ( - (paintBlocks !== null && interactiveWindowTickTime === null) || - time > 30000 - ) { + if ((paintBlocks !== null && interactiveWindowTickTime === null) || time > 30000) { pageRenderTimingSent = true; resources = null; const speedIndex = paintBlocks === null ? 0 - : calculateSpeedIndex( - firstContentfulPaint || firstPaint, - paintBlocks, - ); + : calculateSpeedIndex(firstContentfulPaint || firstPaint, paintBlocks); const timeToInteractive = interactiveWindowTickTime === null ? Math.max( interactiveWindowStartTime, firstContentfulPaint, - performance.timing.domContentLoadedEventEnd - - performance.timing.navigationStart || 0, + performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart || + 0, ) : 0; - app.send(new - PageRenderTiming( + app.send( + new PageRenderTiming( speedIndex, - firstContentfulPaint > visuallyComplete - ? firstContentfulPaint - : visuallyComplete, + firstContentfulPaint > visuallyComplete ? firstContentfulPaint : visuallyComplete, timeToInteractive, ), ); diff --git a/tracker/tracker/src/main/modules/viewport.ts b/tracker/tracker/src/main/modules/viewport.ts index 29eac6806..b576b5384 100644 --- a/tracker/tracker/src/main/modules/viewport.ts +++ b/tracker/tracker/src/main/modules/viewport.ts @@ -1,9 +1,5 @@ -import type App from "../app/index.js"; -import { - SetPageLocation, - SetViewportSize, - SetPageVisibility, -} from "../../common/messages.js"; +import type App from '../app/index.js'; +import { SetPageLocation, SetViewportSize, SetPageVisibility } from '../../common/messages.js'; export default function (app: App): void { let url: string, width: number, height: number; diff --git a/tracker/tracker/src/main/utils.ts b/tracker/tracker/src/main/utils.ts index 20dcb3a25..67aa68a94 100644 --- a/tracker/tracker/src/main/utils.ts +++ b/tracker/tracker/src/main/utils.ts @@ -13,48 +13,53 @@ export function normSpaces(str: string): string { // isAbsoluteUrl regexp: /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url) export function isURL(s: string): boolean { - return s.startsWith('https://')|| s.startsWith('http://'); + return s.startsWith('https://') || s.startsWith('http://'); } -export const IN_BROWSER = !(typeof window === "undefined"); - +export const IN_BROWSER = !(typeof window === 'undefined'); // TODO: JOIN IT WITH LOGGER somehow (use logging decorators?); Don't forget about index.js loggin when there is no logger instance. export const DOCS_HOST = 'https://docs.openreplay.com'; -const warnedFeatures: { [key: string]: boolean; } = {}; -export function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath: string = "/"): void { - if (warnedFeatures[ nameOfFeature ]) { - return; - } - console.warn(`OpenReplay: ${ nameOfFeature } is deprecated. ${ useInstead ? `Please, use ${ useInstead } instead.` : "" } Visit ${DOCS_HOST}${docsPath} for more information.`) - warnedFeatures[ nameOfFeature ] = true; +const warnedFeatures: { [key: string]: boolean } = {}; +export function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath = '/'): void { + if (warnedFeatures[nameOfFeature]) { + return; + } + console.warn( + `OpenReplay: ${nameOfFeature} is deprecated. ${ + useInstead ? `Please, use ${useInstead} instead.` : '' + } Visit ${DOCS_HOST}${docsPath} for more information.`, + ); + warnedFeatures[nameOfFeature] = true; } export function getLabelAttribute(e: Element): string | null { - let value = e.getAttribute("data-openreplay-label"); - if (value !== null) { - return value; - } - value = e.getAttribute("data-asayer-label"); - if (value !== null) { - deprecationWarn(`"data-asayer-label" attribute`, `"data-openreplay-label" attribute`, "/"); - } - return value; + let value = e.getAttribute('data-openreplay-label'); + if (value !== null) { + return value; + } + value = e.getAttribute('data-asayer-label'); + if (value !== null) { + deprecationWarn('"data-asayer-label" attribute', '"data-openreplay-label" attribute', '/'); + } + return value; } export function hasOpenreplayAttribute(e: Element, name: string): boolean { - const newName = `data-openreplay-${name}`; - if (e.hasAttribute(newName)) { - return true - } - const oldName = `data-asayer-${name}`; - if (e.hasAttribute(oldName)) { - deprecationWarn(`"${oldName}" attribute`, `"${newName}" attribute`, "/installation/sanitize-data"); - return true; - } - return false; + const newName = `data-openreplay-${name}`; + if (e.hasAttribute(newName)) { + return true; + } + const oldName = `data-asayer-${name}`; + if (e.hasAttribute(oldName)) { + deprecationWarn( + `"${oldName}" attribute`, + `"${newName}" attribute`, + '/installation/sanitize-data', + ); + return true; + } + return false; } - - diff --git a/tracker/tracker/src/main/vendors/finder/finder.ts b/tracker/tracker/src/main/vendors/finder/finder.ts index fc9f64af2..547ed2b08 100644 --- a/tracker/tracker/src/main/vendors/finder/finder.ts +++ b/tracker/tracker/src/main/vendors/finder/finder.ts @@ -1,10 +1,10 @@ type Node = { - name: string - penalty: number - level?: number -} + name: string; + penalty: number; + level?: number; +}; -type Path = Node[] +type Path = Node[]; enum Limit { All, @@ -13,27 +13,27 @@ enum Limit { } export type Options = { - root: Element - idName: (name: string) => boolean - className: (name: string) => boolean - tagName: (name: string) => boolean - attr: (name: string, value: string) => boolean - seedMinLength: number - optimizedMinLength: number - threshold: number - maxNumberOfTries: number -} + root: Element; + idName: (name: string) => boolean; + className: (name: string) => boolean; + tagName: (name: string) => boolean; + attr: (name: string, value: string) => boolean; + seedMinLength: number; + optimizedMinLength: number; + threshold: number; + maxNumberOfTries: number; +}; -let config: Options +let config: Options; -let rootDocument: Document | Element +let rootDocument: Document | Element; export function finder(input: Element, options?: Partial) { if (input.nodeType !== Node.ELEMENT_NODE) { - throw new Error(`Can't generate CSS selector for non-element node type.`) + throw new Error("Can't generate CSS selector for non-element node type."); } - if ("html" === input.tagName.toLowerCase()) { - return "html" + if ('html' === input.tagName.toLowerCase()) { + return 'html'; } const defaults: Options = { @@ -46,356 +46,370 @@ export function finder(input: Element, options?: Partial) { optimizedMinLength: 2, threshold: 1000, maxNumberOfTries: 10000, - } + }; - config = {...defaults, ...options} + config = { ...defaults, ...options }; - rootDocument = findRootDocument(config.root, defaults) + rootDocument = findRootDocument(config.root, defaults); - let path = - bottomUpSearch(input, Limit.All, () => - bottomUpSearch(input, Limit.Two, () => - bottomUpSearch(input, Limit.One))) + let path = bottomUpSearch(input, Limit.All, () => + bottomUpSearch(input, Limit.Two, () => bottomUpSearch(input, Limit.One)), + ); if (path) { - const optimized = sort(optimize(path, input)) + const optimized = sort(optimize(path, input)); if (optimized.length > 0) { - path = optimized[0] + path = optimized[0]; } - return selector(path) + return selector(path); } else { - throw new Error(`Selector was not found.`) + throw new Error('Selector was not found.'); } } function findRootDocument(rootNode: Element | Document, defaults: Options) { if (rootNode.nodeType === Node.DOCUMENT_NODE) { - return rootNode + return rootNode; } if (rootNode === defaults.root) { - return rootNode.ownerDocument as Document + return rootNode.ownerDocument; } - return rootNode + return rootNode; } function bottomUpSearch(input: Element, limit: Limit, fallback?: () => Path | null): Path | null { - let path: Path | null = null - let stack: Node[][] = [] - let current: Element | null = input - let i = 0 + let path: Path | null = null; + const stack: Node[][] = []; + let current: Element | null = input; + let i = 0; while (current && current !== config.root.parentElement) { - let level: Node[] = maybe(id(current)) || maybe(...attr(current)) || maybe(...classNames(current)) || maybe(tagName(current)) || [any()] + let level: Node[] = maybe(id(current)) || + maybe(...attr(current)) || + maybe(...classNames(current)) || + maybe(tagName(current)) || [any()]; - const nth = index(current) + const nth = index(current); if (limit === Limit.All) { if (nth) { - level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))) + level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth))); } } else if (limit === Limit.Two) { - level = level.slice(0, 1) + level = level.slice(0, 1); if (nth) { - level = level.concat(level.filter(dispensableNth).map(node => nthChild(node, nth))) + level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth))); } } else if (limit === Limit.One) { - const [node] = level = level.slice(0, 1) + const [node] = (level = level.slice(0, 1)); if (nth && dispensableNth(node)) { - level = [nthChild(node, nth)] + level = [nthChild(node, nth)]; } } - for (let node of level) { - node.level = i + for (const node of level) { + node.level = i; } - stack.push(level) + stack.push(level); if (stack.length >= config.seedMinLength) { - path = findUniquePath(stack, fallback) + path = findUniquePath(stack, fallback); if (path) { - break + break; } } - current = current.parentElement - i++ + current = current.parentElement; + i++; } if (!path) { - path = findUniquePath(stack, fallback) + path = findUniquePath(stack, fallback); } - return path + return path; } function findUniquePath(stack: Node[][], fallback?: () => Path | null): Path | null { - const paths = sort(combinations(stack)) + const paths = sort(combinations(stack)); if (paths.length > config.threshold) { - return fallback ? fallback() : null + return fallback ? fallback() : null; } - for (let candidate of paths) { + for (const candidate of paths) { if (unique(candidate)) { - return candidate + return candidate; } } - return null + return null; } function selector(path: Path): string { - let node = path[0] - let query = node.name + let node = path[0]; + let query = node.name; for (let i = 1; i < path.length; i++) { - const level = path[i].level || 0 + const level = path[i].level || 0; if (node.level === level - 1) { - query = `${path[i].name} > ${query}` + query = `${path[i].name} > ${query}`; } else { - query = `${path[i].name} ${query}` + query = `${path[i].name} ${query}`; } - node = path[i] + node = path[i]; } - return query + return query; } function penalty(path: Path): number { - return path.map(node => node.penalty).reduce((acc, i) => acc + i, 0) + return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0); } function unique(path: Path) { switch (rootDocument.querySelectorAll(selector(path)).length) { case 0: - throw new Error(`Can't select any node with this selector: ${selector(path)}`) + throw new Error(`Can't select any node with this selector: ${selector(path)}`); case 1: - return true + return true; default: - return false + return false; } } function id(input: Element): Node | null { - const elementId = input.getAttribute("id") + const elementId = input.getAttribute('id'); if (elementId && config.idName(elementId)) { return { - name: "#" + cssesc(elementId, {isIdentifier: true}), + name: '#' + cssesc(elementId, { isIdentifier: true }), penalty: 0, - } + }; } - return null + return null; } function attr(input: Element): Node[] { - const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value)) + const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value)); - return attrs.map((attr): Node => ({ - name: "[" + cssesc(attr.name, {isIdentifier: true}) + "=\"" + cssesc(attr.value) + "\"]", - penalty: 0.5 - })) + return attrs.map( + (attr): Node => ({ + name: '[' + cssesc(attr.name, { isIdentifier: true }) + '="' + cssesc(attr.value) + '"]', + penalty: 0.5, + }), + ); } function classNames(input: Element): Node[] { - const names = Array.from(input.classList) - .filter(config.className) + const names = Array.from(input.classList).filter(config.className); - return names.map((name): Node => ({ - name: "." + cssesc(name, {isIdentifier: true}), - penalty: 1 - })) + return names.map( + (name): Node => ({ + name: '.' + cssesc(name, { isIdentifier: true }), + penalty: 1, + }), + ); } function tagName(input: Element): Node | null { - const name = input.tagName.toLowerCase() + const name = input.tagName.toLowerCase(); if (config.tagName(name)) { return { name, - penalty: 2 - } + penalty: 2, + }; } - return null + return null; } function any(): Node { return { - name: "*", - penalty: 3 - } + name: '*', + penalty: 3, + }; } function index(input: Element): number | null { - const parent = input.parentNode + const parent = input.parentNode; if (!parent) { - return null + return null; } - let child = parent.firstChild + let child = parent.firstChild; if (!child) { - return null + return null; } - let i = 0 + let i = 0; while (child) { if (child.nodeType === Node.ELEMENT_NODE) { - i++ + i++; } if (child === input) { - break + break; } - child = child.nextSibling + child = child.nextSibling; } - return i + return i; } function nthChild(node: Node, i: number): Node { return { name: node.name + `:nth-child(${i})`, - penalty: node.penalty + 1 - } + penalty: node.penalty + 1, + }; } function dispensableNth(node: Node) { - return node.name !== "html" && !node.name.startsWith("#") + return node.name !== 'html' && !node.name.startsWith('#'); } function maybe(...level: (Node | null)[]): Node[] | null { - const list = level.filter(notEmpty) + const list = level.filter(notEmpty); if (list.length > 0) { - return list + return list; } - return null + return null; } function notEmpty(value: T | null | undefined): value is T { - return value !== null && value !== undefined + return value !== null && value !== undefined; } function combinations(stack: Node[][], path: Node[] = []): Node[][] { - const paths: Node[][] = [] + const paths: Node[][] = []; if (stack.length > 0) { - for (let node of stack[0]) { - paths.push(...combinations(stack.slice(1, stack.length), path.concat(node))) + for (const node of stack[0]) { + paths.push(...combinations(stack.slice(1, stack.length), path.concat(node))); } } else { - paths.push(path) + paths.push(path); } - return paths + return paths; } function sort(paths: Iterable): Path[] { - return Array.from(paths).sort((a, b) => penalty(a) - penalty(b)) + return Array.from(paths).sort((a, b) => penalty(a) - penalty(b)); } type Scope = { - counter: number - visited: Map -} + counter: number; + visited: Map; +}; -function optimize(path: Path, input: Element, scope: Scope = { - counter: 0, - visited: new Map() -}): Node[][] { - const paths: Node[][] = [] +function optimize( + path: Path, + input: Element, + scope: Scope = { + counter: 0, + visited: new Map(), + }, +): Node[][] { + const paths: Node[][] = []; if (path.length > 2 && path.length > config.optimizedMinLength) { for (let i = 1; i < path.length - 1; i++) { if (scope.counter > config.maxNumberOfTries) { - return paths // Okay At least I tried! + return paths; // Okay At least I tried! } - scope.counter += 1 - const newPath = [...path] - newPath.splice(i, 1) - const newPathKey = selector(newPath) + scope.counter += 1; + const newPath = [...path]; + newPath.splice(i, 1); + const newPathKey = selector(newPath); if (scope.visited.has(newPathKey)) { - return paths + return paths; } if (unique(newPath) && same(newPath, input)) { - paths.push(newPath) - scope.visited.set(newPathKey, true) - paths.push(...optimize(newPath, input, scope)) + paths.push(newPath); + scope.visited.set(newPathKey, true); + paths.push(...optimize(newPath, input, scope)); } } } - return paths + return paths; } function same(path: Path, input: Element) { - return rootDocument.querySelector(selector(path)) === input + return rootDocument.querySelector(selector(path)) === input; } -const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/ -const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/ -const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g +const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/; +const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/; +const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g; const defaultOptions = { - "escapeEverything": false, - "isIdentifier": false, - "quotes": "single", - "wrap": false -} + escapeEverything: false, + isIdentifier: false, + quotes: 'single', + wrap: false, +}; function cssesc(string: string, opt: Partial = {}) { - const options = {...defaultOptions, ...opt} - if (options.quotes != "single" && options.quotes != "double") { - options.quotes = "single" + const options = { ...defaultOptions, ...opt }; + if (options.quotes != 'single' && options.quotes != 'double') { + options.quotes = 'single'; } - const quote = options.quotes == "double" ? "\"" : "'" - const isIdentifier = options.isIdentifier + const quote = options.quotes == 'double' ? '"' : "'"; + const isIdentifier = options.isIdentifier; - const firstChar = string.charAt(0) - let output = "" - let counter = 0 - const length = string.length + const firstChar = string.charAt(0); + let output = ''; + let counter = 0; + const length = string.length; while (counter < length) { - const character = string.charAt(counter++) - let codePoint = character.charCodeAt(0) - let value: string | undefined = void 0 + const character = string.charAt(counter++); + let codePoint = character.charCodeAt(0); + let value: string | undefined = void 0; // If it’s not a printable ASCII character… - if (codePoint < 0x20 || codePoint > 0x7E) { - if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) { + if (codePoint < 0x20 || codePoint > 0x7e) { + if (codePoint >= 0xd800 && codePoint <= 0xdbff && counter < length) { // It’s a high surrogate, and there is a next character. - const extra = string.charCodeAt(counter++) - if ((extra & 0xFC00) == 0xDC00) { + const extra = string.charCodeAt(counter++); + if ((extra & 0xfc00) == 0xdc00) { // next character is low surrogate - codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000 + codePoint = ((codePoint & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; } else { // It’s an unmatched surrogate; only append this code unit, in case // the next code unit is the high surrogate of a surrogate pair. - counter-- + counter--; } } - value = "\\" + codePoint.toString(16).toUpperCase() + " " + value = '\\' + codePoint.toString(16).toUpperCase() + ' '; } else { if (options.escapeEverything) { if (regexAnySingleEscape.test(character)) { - value = "\\" + character + value = '\\' + character; } else { - value = "\\" + codePoint.toString(16).toUpperCase() + " " + value = '\\' + codePoint.toString(16).toUpperCase() + ' '; } } else if (/[\t\n\f\r\x0B]/.test(character)) { - value = "\\" + codePoint.toString(16).toUpperCase() + " " - } else if (character == "\\" || !isIdentifier && (character == "\"" && quote == character || character == "'" && quote == character) || isIdentifier && regexSingleEscape.test(character)) { - value = "\\" + character + value = '\\' + codePoint.toString(16).toUpperCase() + ' '; + } else if ( + character == '\\' || + (!isIdentifier && + ((character == '"' && quote == character) || (character == "'" && quote == character))) || + (isIdentifier && regexSingleEscape.test(character)) + ) { + value = '\\' + character; } else { - value = character + value = character; } } - output += value + output += value; } if (isIdentifier) { if (/^-[-\d]/.test(output)) { - output = "\\-" + output.slice(1) + output = '\\-' + output.slice(1); } else if (/\d/.test(firstChar)) { - output = "\\3" + firstChar + " " + output.slice(1) + output = '\\3' + firstChar + ' ' + output.slice(1); } } @@ -405,14 +419,14 @@ function cssesc(string: string, opt: Partial = {}) { output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) { if ($1 && $1.length % 2) { // It’s not safe to remove the space, so don’t. - return $0 + return $0; } // Strip the space. - return ($1 || "") + $2 - }) + return ($1 || '') + $2; + }); if (!isIdentifier && options.wrap) { - return quote + output + quote + return quote + output + quote; } - return output + return output; } diff --git a/tracker/tracker/src/webworker/BatchWriter.ts b/tracker/tracker/src/webworker/BatchWriter.ts index 181663300..be0aed933 100644 --- a/tracker/tracker/src/webworker/BatchWriter.ts +++ b/tracker/tracker/src/webworker/BatchWriter.ts @@ -1,76 +1,74 @@ -import type Message from "../common/messages.js"; -import PrimitiveWriter from "./PrimitiveWriter.js"; -import { - BatchMeta, - Timestamp, -} from "../common/messages.js"; +import type Message from '../common/messages.js'; +import PrimitiveWriter from './PrimitiveWriter.js'; +import { BatchMeta, Timestamp } from '../common/messages.js'; export default class BatchWriter { - private nextIndex = 0 - private beaconSize = 2 * 1e5 // Default 200kB - private writer = new PrimitiveWriter(this.beaconSize) - private isEmpty = true + private nextIndex = 0; + private beaconSize = 2 * 1e5; // Default 200kB + private writer = new PrimitiveWriter(this.beaconSize); + private isEmpty = true; constructor( - private readonly pageNo: number, + private readonly pageNo: number, private timestamp: number, - private onBatch: (batch: Uint8Array) => void + private readonly onBatch: (batch: Uint8Array) => void, ) { - this.prepare() + this.prepare(); } private prepare(): void { if (!this.writer.isEmpty()) { - return + return; } - new BatchMeta(this.pageNo, this.nextIndex, this.timestamp).encode(this.writer) + new BatchMeta(this.pageNo, this.nextIndex, this.timestamp).encode(this.writer); } private write(message: Message): boolean { - const wasWritten = message.encode(this.writer) + const wasWritten = message.encode(this.writer); if (wasWritten) { - this.isEmpty = false - this.writer.checkpoint() - this.nextIndex++ + this.isEmpty = false; + this.writer.checkpoint(); + this.nextIndex++; } - return wasWritten + return wasWritten; } - private beaconSizeLimit = 1e6 + private beaconSizeLimit = 1e6; setBeaconSizeLimit(limit: number) { - this.beaconSizeLimit = limit + this.beaconSizeLimit = limit; } writeMessage(message: Message) { if (message instanceof Timestamp) { - this.timestamp = (message).timestamp + this.timestamp = (message).timestamp; } while (!this.write(message)) { - this.finaliseBatch() + this.finaliseBatch(); if (this.beaconSize === this.beaconSizeLimit) { - console.warn("OpenReplay: beacon size overflow. Skipping large message."); - this.writer.reset() - this.prepare() - this.isEmpty = true - return + console.warn('OpenReplay: beacon size overflow. Skipping large message.'); + this.writer.reset(); + this.prepare(); + this.isEmpty = true; + return; } // MBTODO: tempWriter for one message? - this.beaconSize = Math.min(this.beaconSize*2, this.beaconSizeLimit) - this.writer = new PrimitiveWriter(this.beaconSize) - this.prepare() - this.isEmpty = true + this.beaconSize = Math.min(this.beaconSize * 2, this.beaconSizeLimit); + this.writer = new PrimitiveWriter(this.beaconSize); + this.prepare(); + this.isEmpty = true; } } finaliseBatch() { - if (this.isEmpty) { return } - this.onBatch(this.writer.flush()) - this.prepare() - this.isEmpty = true + if (this.isEmpty) { + return; + } + this.onBatch(this.writer.flush()); + this.prepare(); + this.isEmpty = true; } clean() { - this.writer.reset() + this.writer.reset(); } - } diff --git a/tracker/tracker/src/webworker/PrimitiveWriter.ts b/tracker/tracker/src/webworker/PrimitiveWriter.ts index 587291bea..87924bf75 100644 --- a/tracker/tracker/src/webworker/PrimitiveWriter.ts +++ b/tracker/tracker/src/webworker/PrimitiveWriter.ts @@ -8,13 +8,13 @@ const textEncoder: { encode(str: string): Uint8Array } = const Len = str.length, resArr = new Uint8Array(Len * 3); let resPos = -1; - for (var point = 0, nextcode = 0, i = 0; i !== Len; ) { + for (let point = 0, nextcode = 0, i = 0; i !== Len; ) { (point = str.charCodeAt(i)), (i += 1); if (point >= 0xd800 && point <= 0xdbff) { if (i === Len) { - resArr[(resPos += 1)] = 0xef /*0b11101111*/; - resArr[(resPos += 1)] = 0xbf /*0b10111111*/; - resArr[(resPos += 1)] = 0xbd /*0b10111101*/; + resArr[(resPos += 1)] = 0xef; /*0b11101111*/ + resArr[(resPos += 1)] = 0xbf; /*0b10111111*/ + resArr[(resPos += 1)] = 0xbd; /*0b10111101*/ break; } // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae @@ -23,21 +23,18 @@ const textEncoder: { encode(str: string): Uint8Array } = point = (point - 0xd800) * 0x400 + nextcode - 0xdc00 + 0x10000; i += 1; if (point > 0xffff) { + resArr[(resPos += 1)] = (0x1e /*0b11110*/ << 3) | (point >>> 18); resArr[(resPos += 1)] = - (0x1e /*0b11110*/ << 3) | (point >>> 18); + (0x2 /*0b10*/ << 6) | ((point >>> 12) & 0x3f); /*0b00111111*/ resArr[(resPos += 1)] = - (0x2 /*0b10*/ << 6) | - ((point >>> 12) & 0x3f) /*0b00111111*/; - resArr[(resPos += 1)] = - (0x2 /*0b10*/ << 6) | ((point >>> 6) & 0x3f) /*0b00111111*/; - resArr[(resPos += 1)] = - (0x2 /*0b10*/ << 6) | (point & 0x3f) /*0b00111111*/; + (0x2 /*0b10*/ << 6) | ((point >>> 6) & 0x3f); /*0b00111111*/ + resArr[(resPos += 1)] = (0x2 /*0b10*/ << 6) | (point & 0x3f); /*0b00111111*/ continue; } } else { - resArr[(resPos += 1)] = 0xef /*0b11101111*/; - resArr[(resPos += 1)] = 0xbf /*0b10111111*/; - resArr[(resPos += 1)] = 0xbd /*0b10111101*/; + resArr[(resPos += 1)] = 0xef; /*0b11101111*/ + resArr[(resPos += 1)] = 0xbf; /*0b10111111*/ + resArr[(resPos += 1)] = 0xbd; /*0b10111101*/ continue; } } @@ -45,14 +42,11 @@ const textEncoder: { encode(str: string): Uint8Array } = resArr[(resPos += 1)] = (0x0 /*0b0*/ << 7) | point; } else if (point <= 0x07ff) { resArr[(resPos += 1)] = (0x6 /*0b110*/ << 5) | (point >>> 6); - resArr[(resPos += 1)] = - (0x2 /*0b10*/ << 6) | (point & 0x3f) /*0b00111111*/; + resArr[(resPos += 1)] = (0x2 /*0b10*/ << 6) | (point & 0x3f); /*0b00111111*/ } else { resArr[(resPos += 1)] = (0xe /*0b1110*/ << 4) | (point >>> 12); - resArr[(resPos += 1)] = - (0x2 /*0b10*/ << 6) | ((point >>> 6) & 0x3f) /*0b00111111*/; - resArr[(resPos += 1)] = - (0x2 /*0b10*/ << 6) | (point & 0x3f) /*0b00111111*/; + resArr[(resPos += 1)] = (0x2 /*0b10*/ << 6) | ((point >>> 6) & 0x3f); /*0b00111111*/ + resArr[(resPos += 1)] = (0x2 /*0b10*/ << 6) | (point & 0x3f); /*0b00111111*/ } } return resArr.subarray(0, resPos + 1); @@ -60,13 +54,13 @@ const textEncoder: { encode(str: string): Uint8Array } = }; export default class PrimitiveWriter { - private offset: number = 0; - private checkpointOffset: number = 0; + private offset = 0; + private checkpointOffset = 0; private readonly data: Uint8Array; constructor(private readonly size: number) { this.data = new Uint8Array(size); } - checkpoint(): void { + checkpoint() { this.checkpointOffset = this.offset; } isEmpty(): boolean { @@ -78,7 +72,7 @@ export default class PrimitiveWriter { } uint(value: number): boolean { if (value < 0 || value > Number.MAX_SAFE_INTEGER) { - value = 0 + value = 0; } while (value >= 0x80) { this.data[this.offset++] = value % 0x100 | 0x80; @@ -95,7 +89,7 @@ export default class PrimitiveWriter { const encoded = textEncoder.encode(value); const length = encoded.byteLength; if (!this.uint(length) || this.offset + length > this.size) { - return false + return false; } this.data.set(encoded, this.offset); this.offset += length; diff --git a/tracker/tracker/src/webworker/QueueSender.ts b/tracker/tracker/src/webworker/QueueSender.ts index d4686539d..b6aa3ed98 100644 --- a/tracker/tracker/src/webworker/QueueSender.ts +++ b/tracker/tracker/src/webworker/QueueSender.ts @@ -1,6 +1,6 @@ -const INGEST_PATH = "/v1/web/i" +const INGEST_PATH = '/v1/web/i'; -const KEEPALIVE_SIZE_LIMIT = 64 << 10 // 64 kB +const KEEPALIVE_SIZE_LIMIT = 64 << 10; // 64 kB // function sendXHR(url: string, token: string, batch: Uint8Array): Promise { // const req = new XMLHttpRequest() @@ -20,88 +20,83 @@ const KEEPALIVE_SIZE_LIMIT = 64 << 10 // 64 kB // }) // } - export default class QueueSender { - private attemptsCount = 0 - private busy = false - private readonly queue: Array = [] - private readonly ingestURL - private token: string | null = null + private attemptsCount = 0; + private busy = false; + private readonly queue: Array = []; + private readonly ingestURL; + private token: string | null = null; constructor( - ingestBaseURL: string, - private readonly onUnauthorised: Function, - private readonly onFailure: Function, + ingestBaseURL: string, + private readonly onUnauthorised: () => any, + private readonly onFailure: () => any, private readonly MAX_ATTEMPTS_COUNT = 10, private readonly ATTEMPT_TIMEOUT = 1000, ) { - this.ingestURL = ingestBaseURL + INGEST_PATH + this.ingestURL = ingestBaseURL + INGEST_PATH; } - authorise(token: string) { - this.token = token + authorise(token: string): void { + this.token = token; } - push(batch: Uint8Array) { + push(batch: Uint8Array): void { if (this.busy || !this.token) { - this.queue.push(batch) + this.queue.push(batch); } else { - this.sendBatch(batch) + this.sendBatch(batch); } } - private retry(batch: Uint8Array) { + private retry(batch: Uint8Array): void { if (this.attemptsCount >= this.MAX_ATTEMPTS_COUNT) { - this.onFailure() - return + this.onFailure(); + return; } - this.attemptsCount++ - setTimeout(() => this.sendBatch(batch), this.ATTEMPT_TIMEOUT * this.attemptsCount) + this.attemptsCount++; + setTimeout(() => this.sendBatch(batch), this.ATTEMPT_TIMEOUT * this.attemptsCount); } // would be nice to use Beacon API, but it is not available in WebWorker - private sendBatch(batch: Uint8Array):void { - this.busy = true + private sendBatch(batch: Uint8Array): void { + this.busy = true; fetch(this.ingestURL, { body: batch, method: 'POST', headers: { - "Authorization": "Bearer " + this.token, + Authorization: 'Bearer ' + this.token, //"Content-Type": "", }, keepalive: batch.length < KEEPALIVE_SIZE_LIMIT, }) - .then(r => { - if (r.status === 401) { // TODO: continuous session ? - this.busy = false - this.onUnauthorised() - return - } else if (r.status >= 400) { - this.retry(batch) - return - } - - // Success - this.attemptsCount = 0 - const nextBatch = this.queue.shift() - if (nextBatch) { - this.sendBatch(nextBatch) - } else { - this.busy = false - } - }) - .catch(e => { - console.warn("OpenReplay:", e) - this.retry(batch) - }) + .then((r) => { + if (r.status === 401) { + // TODO: continuous session ? + this.busy = false; + this.onUnauthorised(); + return; + } else if (r.status >= 400) { + this.retry(batch); + return; + } + // Success + this.attemptsCount = 0; + const nextBatch = this.queue.shift(); + if (nextBatch) { + this.sendBatch(nextBatch); + } else { + this.busy = false; + } + }) + .catch((e) => { + console.warn('OpenReplay:', e); + this.retry(batch); + }); } clean() { - this.queue.length = 0 + this.queue.length = 0; } - } - - - diff --git a/tracker/tracker/src/webworker/index.ts b/tracker/tracker/src/webworker/index.ts index 7d1cb0393..a22e75303 100644 --- a/tracker/tracker/src/webworker/index.ts +++ b/tracker/tracker/src/webworker/index.ts @@ -1,126 +1,125 @@ -import type Message from "../common/messages.js"; -import { WorkerMessageData } from "../common/webworker.js"; +import type Message from '../common/messages.js'; +import { WorkerMessageData } from '../common/webworker.js'; -import { - classes, - SetPageVisibility, - MouseMove, -} from "../common/messages.js"; -import QueueSender from "./QueueSender.js"; -import BatchWriter from "./BatchWriter.js"; +import { classes, SetPageVisibility } from '../common/messages.js'; +import QueueSender from './QueueSender.js'; +import BatchWriter from './BatchWriter.js'; enum WorkerStatus { NotActive, Starting, Stopping, - Active + Active, } -const AUTO_SEND_INTERVAL = 10 * 1000 +const AUTO_SEND_INTERVAL = 10 * 1000; -let sender: QueueSender | null = null -let writer: BatchWriter | null = null +let sender: QueueSender | null = null; +let writer: BatchWriter | null = null; let workerStatus: WorkerStatus = WorkerStatus.NotActive; function send(): void { if (!writer) { - return + return; } - writer.finaliseBatch() + writer.finaliseBatch(); } - -function reset() { - workerStatus = WorkerStatus.Stopping +function reset(): void { + workerStatus = WorkerStatus.Stopping; if (sendIntervalID !== null) { clearInterval(sendIntervalID); sendIntervalID = null; } if (writer) { - writer.clean() - writer = null + writer.clean(); + writer = null; } - workerStatus = WorkerStatus.NotActive + workerStatus = WorkerStatus.NotActive; } -function resetCleanQueue() { +function resetCleanQueue(): void { if (sender) { - sender.clean() - sender = null - } - reset() + sender.clean(); + sender = null; + } + reset(); } -let sendIntervalID: ReturnType | null = null -let restartTimeoutID: ReturnType +let sendIntervalID: ReturnType | null = null; +let restartTimeoutID: ReturnType; -self.onmessage = ({ data }: MessageEvent) => { +self.onmessage = ({ data }: MessageEvent): any => { if (data == null) { - send() // TODO: sendAll? - return + send(); // TODO: sendAll? + return; } - if (data === "stop") { - send() - reset() - return + if (data === 'stop') { + send(); + reset(); + return; } if (Array.isArray(data)) { if (!writer) { - throw new Error("WebWorker: writer not initialised. Service Should be Started.") + throw new Error('WebWorker: writer not initialised. Service Should be Started.'); } - const w = writer + const w = writer; // Message[] data.forEach((data) => { - const message: Message = new (classes.get(data._id))(); - Object.assign(message, data) + // @ts-ignore + const message: Message = new (classes.get(data._id))(); + Object.assign(message, data); if (message instanceof SetPageVisibility) { - if ( (message).hidden) { - restartTimeoutID = setTimeout(() => self.postMessage("restart"), 30*60*1000) + // @ts-ignore + if ((message).hidden) { + restartTimeoutID = setTimeout(() => self.postMessage('restart'), 30 * 60 * 1000); } else { - clearTimeout(restartTimeoutID) + clearTimeout(restartTimeoutID); } - } - w.writeMessage(message) - }) - return + } + w.writeMessage(message); + }); + return; } if (data.type === 'start') { - workerStatus = WorkerStatus.Starting + workerStatus = WorkerStatus.Starting; sender = new QueueSender( data.ingestPoint, - () => { // onUnauthorised - self.postMessage("restart") + () => { + // onUnauthorised + self.postMessage('restart'); }, - () => { // onFailure - resetCleanQueue() - self.postMessage("failed") + () => { + // onFailure + resetCleanQueue(); + self.postMessage('failed'); }, data.connAttemptCount, data.connAttemptGap, - ) + ); writer = new BatchWriter( data.pageNo, data.timestamp, // onBatch - batch => sender && sender.push(batch) - ) + (batch) => sender && sender.push(batch), + ); if (sendIntervalID === null) { - sendIntervalID = setInterval(send, AUTO_SEND_INTERVAL) + sendIntervalID = setInterval(send, AUTO_SEND_INTERVAL); } - return workerStatus = WorkerStatus.Active + return (workerStatus = WorkerStatus.Active); } - if (data.type === "auth") { + if (data.type === 'auth') { if (!sender) { - throw new Error("WebWorker: sender not initialised. Received auth.") + throw new Error('WebWorker: sender not initialised. Received auth.'); } if (!writer) { - throw new Error("WebWorker: writer not initialised. Received auth.") + throw new Error('WebWorker: writer not initialised. Received auth.'); } - sender.authorise(data.token) - data.beaconSizeLimit && writer.setBeaconSizeLimit(data.beaconSizeLimit) - return + sender.authorise(data.token); + data.beaconSizeLimit && writer.setBeaconSizeLimit(data.beaconSizeLimit); + return; } }; diff --git a/tracker/tracker/src/webworker/tsconfig.json b/tracker/tracker/src/webworker/tsconfig.json index 6794ea55c..52228f9af 100644 --- a/tracker/tracker/src/webworker/tsconfig.json +++ b/tracker/tracker/src/webworker/tsconfig.json @@ -3,7 +3,5 @@ "compilerOptions": { "lib": ["es6", "webworker"] }, - "references": [ - { "path": "../common" } - ] + "references": [{ "path": "../common" }] } diff --git a/utilities/package-lock.json b/utilities/package-lock.json index 8e90703f3..f4eb45944 100644 --- a/utilities/package-lock.json +++ b/utilities/package-lock.json @@ -42,9 +42,9 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, "node_modules/@types/node": { - "version": "17.0.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.42.tgz", - "integrity": "sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==" + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", + "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" }, "node_modules/accepts": { "version": "1.3.8", @@ -61,12 +61,12 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "engines": { "node": ">=0.8" } @@ -175,9 +175,9 @@ } }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -185,12 +185,12 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/cors": { "version": "2.8.5", @@ -270,6 +270,14 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/engine.io/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -345,18 +353,10 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "engines": [ "node >=0.6.0" ] @@ -504,7 +504,7 @@ "node_modules/lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" }, "node_modules/map-obj": { "version": "4.3.0", @@ -541,12 +541,12 @@ "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "engines": { "node": ">= 0.6" } @@ -641,7 +641,7 @@ "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -808,9 +808,9 @@ "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" }, "node_modules/socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", + "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", "dependencies": { "@types/component-emitter": "^1.2.10", "component-emitter": "~1.3.0", @@ -938,7 +938,7 @@ "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "engines": { "node": ">= 0.4.0" } @@ -946,7 +946,7 @@ "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "engines": { "node": ">= 0.8" } @@ -954,7 +954,7 @@ "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "engines": [ "node >=0.6.0" ], @@ -1013,9 +1013,9 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, "@types/node": { - "version": "17.0.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.42.tgz", - "integrity": "sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==" + "version": "18.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", + "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" }, "accepts": { "version": "1.3.8", @@ -1029,12 +1029,12 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" }, "base64id": { "version": "2.0.0", @@ -1109,19 +1109,19 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "cors": { "version": "2.8.5", @@ -1177,6 +1177,11 @@ "ws": "~8.2.3" }, "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1243,19 +1248,12 @@ "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" - }, - "dependencies": { - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - } } }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" }, "finalhandler": { "version": "1.2.0", @@ -1367,7 +1365,7 @@ "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" }, "map-obj": { "version": "4.3.0", @@ -1391,12 +1389,12 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "mime": { "version": "1.6.0", @@ -1457,7 +1455,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, "proxy-addr": { "version": "2.0.7", @@ -1594,9 +1592,9 @@ "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" }, "socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", + "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", "requires": { "@types/component-emitter": "^1.2.10", "component-emitter": "~1.3.0", @@ -1660,17 +1658,17 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", diff --git a/utilities/utils/helper.js b/utilities/utils/helper.js index 7088d2b77..b012ccf6c 100644 --- a/utilities/utils/helper.js +++ b/utilities/utils/helper.js @@ -40,19 +40,20 @@ const extractSessionIdFromRequest = function (req) { } const isValidSession = function (sessionInfo, filters) { let foundAll = true; - for (const [key, values] of Object.entries(filters)) { + for (const [key, body] of Object.entries(filters)) { let found = false; - if (values !== undefined && values !== null) { + if (body.values !== undefined && body.values !== null) { for (const [skey, svalue] of Object.entries(sessionInfo)) { if (svalue !== undefined && svalue !== null) { if (typeof (svalue) === "object") { - if (isValidSession(svalue, {[key]: values})) { + if (isValidSession(svalue, {[key]: body})) { found = true; break; } } else if (skey.toLowerCase() === key.toLowerCase()) { - for (let v of values) { - if (String(svalue).toLowerCase().indexOf(v.toLowerCase()) >= 0) { + for (let v of body["values"]) { + if (body.operator === "is" && String(svalue).toLowerCase() === v.toLowerCase() + || body.operator !== "is" && String(svalue).toLowerCase().indexOf(v.toLowerCase()) >= 0) { found = true; break; } @@ -97,21 +98,37 @@ const objectToObjectOfArrays = function (obj) { for (let k of Object.keys(obj)) { if (obj[k] !== undefined && obj[k] !== null) { _obj[k] = obj[k]; - if (!Array.isArray(_obj[k])) { + if (!Array.isArray(_obj[k].values)) { _obj[k] = [_obj[k]]; } - for (let i = 0; i < _obj[k].length; i++) { - _obj[k][i] = String(_obj[k][i]); + for (let i = 0; i < _obj[k].values.length; i++) { + _obj[k].values[i] = String(_obj[k].values[i]); } } } } return _obj; } +const transformFilters = function (filter) { + for (let key of Object.keys(filter)) { + //To support old v1.7.0 payload + if (Array.isArray(filter[key]) || filter[key] === undefined || filter[key] === null) { + debug && console.log(`[WS]old format for key=${key}`); + filter[key] = {"values": filter[key]}; + } + if (filter[key].operator) { + debug && console.log(`[WS]where operator=${filter[key].operator}`); + } else { + debug && console.log(`[WS]where operator=DEFAULT-contains`); + filter[key].operator = "contains"; + } + } + return filter; +} const extractPayloadFromRequest = function (req) { let filters = { - "query": {}, - "filter": {}, + "query": {}, // for autocomplete + "filter": {}, // for sessions search "sort": { "key": req.body.sort && req.body.sort.key ? req.body.sort.key : undefined, "order": req.body.sort && req.body.sort.order === "DESC" @@ -135,6 +152,7 @@ const extractPayloadFromRequest = function (req) { } filters.filter = objectToObjectOfArrays(filters.filter); filters.filter = {...filters.filter, ...(req.body.filter || {})}; + filters.filter = transformFilters(filters.filter); debug && console.log("payload/filters:" + JSON.stringify(filters)) return filters; } @@ -194,6 +212,7 @@ const uniqueAutocomplete = function (list) { return _list; } module.exports = { + transformFilters, extractPeerId, request_logger, getValidAttributes,