feat(tracker): add zustand support

This commit is contained in:
sylenien 2022-09-08 15:06:18 +02:00
parent 3d82a6558b
commit f360d8416d
29 changed files with 690 additions and 34 deletions

View file

@ -2,7 +2,7 @@
package messages
func IsReplayerType(id int) bool {
return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 22 == id || 37 == id || 38 == id || 39 == id || 40 == id || 41 == id || 44 == id || 45 == id || 46 == id || 47 == id || 48 == id || 49 == id || 54 == id || 55 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id
return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 22 == id || 37 == id || 38 == id || 39 == id || 40 == id || 41 == id || 44 == id || 45 == id || 46 == id || 47 == id || 48 == id || 49 == id || 54 == id || 55 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 79 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id
}
func IsIOSType(id int) bool {

View file

@ -156,6 +156,8 @@ const (
MsgAdoptedSSRemoveOwner = 77
MsgZustand = 79
MsgIOSBatchMeta = 107
MsgIOSSessionStart = 90
@ -3038,6 +3040,40 @@ func (msg *AdoptedSSRemoveOwner) TypeID() int {
return 77
}
type Zustand struct {
message
Mutation string
State string
}
func (msg *Zustand) Encode() []byte {
buf := make([]byte, 21+len(msg.Mutation)+len(msg.State))
buf[0] = 79
p := 1
p = WriteString(msg.Mutation, buf, p)
p = WriteString(msg.State, buf, p)
return buf[:p]
}
func (msg *Zustand) EncodeWithIndex() []byte {
encoded := msg.Encode()
if IsIOSType(msg.TypeID()) {
return encoded
}
data := make([]byte, len(encoded)+8)
copy(data[8:], encoded[:])
binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index)
return data
}
func (msg *Zustand) Decode() Message {
return msg
}
func (msg *Zustand) TypeID() int {
return 79
}
type IOSBatchMeta struct {
message
Timestamp uint64

View file

@ -1306,6 +1306,18 @@ func DecodeAdoptedSSRemoveOwner(reader io.Reader) (Message, error) {
return msg, err
}
func DecodeZustand(reader io.Reader) (Message, error) {
var err error = nil
msg := &Zustand{}
if msg.Mutation, err = ReadString(reader); err != nil {
return nil, err
}
if msg.State, err = ReadString(reader); err != nil {
return nil, err
}
return msg, err
}
func DecodeIOSBatchMeta(reader io.Reader) (Message, error) {
var err error = nil
msg := &IOSBatchMeta{}
@ -1939,6 +1951,9 @@ func ReadMessage(t uint64, reader io.Reader) (Message, error) {
case 77:
return DecodeAdoptedSSRemoveOwner(reader)
case 79:
return DecodeZustand(reader)
case 107:
return DecodeIOSBatchMeta(reader)

View file

@ -63,13 +63,6 @@ class SessionStart(Message):
self.user_id = user_id
class SessionDisconnect(Message):
__id__ = 2
def __init__(self, timestamp):
self.timestamp = timestamp
class SessionEnd(Message):
__id__ = 3
@ -106,7 +99,6 @@ class CreateDocument(Message):
__id__ = 7
def __init__(self, ):
pass
@ -752,6 +744,14 @@ class AdoptedSSRemoveOwner(Message):
self.id = id
class Zustand(Message):
__id__ = 79
def __init__(self, mutation, state):
self.mutation = mutation
self.state = state
class IOSBatchMeta(Message):
__id__ = 107

View file

@ -2,6 +2,7 @@
from msgcodec.codec import Codec
from msgcodec.messages import *
from typing import List
import io
class MessageCodec(Codec):
@ -42,7 +43,7 @@ class MessageCodec(Codec):
raise UnicodeDecodeError(f"Error while decoding message key (SessionID) from {b}\n{e}")
return decoded
def decode_detailed(self, b: bytes):
def decode_detailed(self, b: bytes) -> List[Message]:
reader = io.BytesIO(b)
messages_list = list()
messages_list.append(self.handler(reader, 0))
@ -61,7 +62,7 @@ class MessageCodec(Codec):
break
return messages_list
def handler(self, reader: io.BytesIO, mode=0):
def handler(self, reader: io.BytesIO, mode=0) -> Message:
message_id = self.read_message_id(reader)
if mode == 1:
# We skip the three bytes representing the length of message. It can be used to skip unwanted messages
@ -71,9 +72,10 @@ class MessageCodec(Codec):
# Old format with no bytes for message length
return self.read_head_message(reader, message_id)
else:
raise IOError()
raise IOError()
def read_head_message(self, reader: io.BytesIO, message_id) -> Message:
def read_head_message(self, reader: io.BytesIO, message_id: int):
if message_id == 80:
return BatchMeta(
page_no=self.read_uint(reader),
@ -121,11 +123,6 @@ class MessageCodec(Codec):
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)
@ -665,6 +662,12 @@ class MessageCodec(Codec):
id=self.read_uint(reader)
)
if message_id == 79:
return Zustand(
mutation=self.read_string(reader),
state=self.read_string(reader)
)
if message_id == 107:
return IOSBatchMeta(
timestamp=self.read_uint(reader),

View file

@ -527,6 +527,16 @@ export default class RawMessageReader extends PrimitiveReader {
};
}
case 79: {
const mutation = this.readString(); if (mutation === null) { return resetPointer() }
const state = this.readString(); if (state === null) { return resetPointer() }
return {
tp: "zustand",
mutation,
state,
};
}
case 90: {
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
const projectID = this.readUint(); if (projectID === null) { return resetPointer() }

View file

@ -47,6 +47,7 @@ import type {
RawAdoptedSsDeleteRule,
RawAdoptedSsAddOwner,
RawAdoptedSsRemoveOwner,
RawZustand,
RawIosSessionStart,
RawIosCustomEvent,
RawIosScreenChanges,
@ -147,6 +148,8 @@ export type AdoptedSsAddOwner = RawAdoptedSsAddOwner & Timed
export type AdoptedSsRemoveOwner = RawAdoptedSsRemoveOwner & Timed
export type Zustand = RawZustand & Timed
export type IosSessionStart = RawIosSessionStart & Timed
export type IosCustomEvent = RawIosCustomEvent & Timed

View file

@ -300,6 +300,12 @@ export interface RawAdoptedSsRemoveOwner {
id: number,
}
export interface RawZustand {
tp: "zustand",
mutation: string,
state: string,
}
export interface RawIosSessionStart {
tp: "ios_session_start",
timestamp: number,
@ -371,4 +377,4 @@ export interface RawIosNetworkCall {
}
export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawConnectionInformation | RawSetPageVisibility | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall;
export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawConnectionInformation | RawSetPageVisibility | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawIosSessionStart | RawIosCustomEvent | RawIosScreenChanges | RawIosClickEvent | RawIosPerformanceEvent | RawIosLog | RawIosNetworkCall;

View file

@ -60,6 +60,7 @@ export const TP_MAP = {
75: "adopted_ss_delete_rule",
76: "adopted_ss_add_owner",
77: "adopted_ss_remove_owner",
79: "zustand",
90: "ios_session_start",
93: "ios_custom_event",
96: "ios_screen_changes",

View file

@ -382,8 +382,14 @@ type TrAdoptedSSRemoveOwner = [
id: number,
]
type TrZustand = [
type: 79,
mutation: string,
state: string,
]
export type TrackerMessage = TrBatchMetadata | TrPartitionedMessage | TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrJSException | TrRawCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner
export type TrackerMessage = TrBatchMetadata | TrPartitionedMessage | TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrJSException | TrRawCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrResourceTiming | TrConnectionInformation | TrSetPageVisibility | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrZustand
export default function translate(tMsg: TrackerMessage): RawMessage | null {
switch(tMsg[0]) {
@ -750,6 +756,14 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
}
}
case 79: {
return {
tp: "zustand",
mutation: tMsg[1],
state: tMsg[2],
}
}
default:
return null
}

View file

@ -1,7 +1,7 @@
# Special one for Batch Metadata. Message id could define the version
# Special one for Batch Metadata. Message id could define the version
# Depricated since tracker 3.6.0 in favor of BatchMetadata
message 80, 'BatchMeta', :replayer => false, :tracker => false do
message 80, 'BatchMeta', :replayer => false, :tracker => false do
uint 'PageNo'
uint 'FirstIndex'
int 'Timestamp'
@ -421,7 +421,7 @@ message 70, 'CreateIFrameDocument' do
uint 'FrameID'
uint 'ID'
end
#Since 3.6.0 AdoptedStyleSheets
message 71, 'AdoptedSSReplaceURLBased' do
uint 'SheetID'
@ -432,26 +432,31 @@ message 72, 'AdoptedSSReplace', :tracker => false do
uint 'SheetID'
string 'Text'
end
message 73, 'AdoptedSSInsertRuleURLBased' do
message 73, 'AdoptedSSInsertRuleURLBased' do
uint 'SheetID'
string 'Rule'
uint 'Index'
string 'BaseURL'
end
message 74, 'AdoptedSSInsertRule', :tracker => false do
message 74, 'AdoptedSSInsertRule', :tracker => false do
uint 'SheetID'
string 'Rule'
uint 'Index'
end
message 75, 'AdoptedSSDeleteRule' do
message 75, 'AdoptedSSDeleteRule' do
uint 'SheetID'
uint 'Index'
end
message 76, 'AdoptedSSAddOwner' do
message 76, 'AdoptedSSAddOwner' do
uint 'SheetID'
uint 'ID'
end
message 77, 'AdoptedSSRemoveOwner' do
message 77, 'AdoptedSSRemoveOwner' do
uint 'SheetID'
uint 'ID'
end
message 79, 'Zustand' do
string 'Mutation'
string 'State'
end

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-vuex",
"description": "Tracker plugin for Vuex state recording",
"version": "4.0.0",
"version": "4.0.1",
"keywords": [
"vuex",
"logging",
@ -23,8 +23,7 @@
},
"dependencies": {},
"peerDependencies": {
"@openreplay/tracker": "^3.4.8",
"@ngrx/store": ">=4"
"@openreplay/tracker": "^3.4.8"
},
"devDependencies": {
"@openreplay/tracker": "^3.4.8",

View file

@ -52,7 +52,7 @@ export default function(opts: Partial<Options> = {}) {
const randomId = Math.random().toString(36).substring(2, 9)
store.subscribe((mutation, storeState) => {
state[storeName || randomId] = state
processMutationAndState(app, options, encoder, mutation, storeState);
processMutationAndState(app, options, encoder, mutation, state);
});
}

6
tracker/tracker-zustand/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
node_modules
npm-debug.log
lib
cjs
.cache
*.DS_Store

View file

@ -0,0 +1,5 @@
src
tsconfig-cjs.json
tsconfig.json
.prettierrc.json
.cache

View file

@ -0,0 +1,19 @@
Copyright (c) 2022 Asayer, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,48 @@
# OpenReplay Tracker Vuex plugin
A Vuex plugin for OpenReplay Tracker. This plugin allows you to see the application state during session replay.
## Installation
```bash
npm i @openreplay/tracker-vuex
```
## Usage
Initialize the `@openreplay/tracker` package as usual and load the plugin into it.
Then put the generated plugin into your `plugins` field of your store.
```js
import Vuex from 'vuex'
import Tracker from '@openreplay/tracker';
import trackerVuex from '@openreplay/tracker-vuex';
const tracker = new Tracker({
projectKey: YOUR_PROJECT_KEY,
});
const store = new Vuex.Store({
// ...
plugins: [tracker.plugin(trackerVuex())],
});
```
You can customize the plugin behavior with options to sanitize your data. They are similar to the ones from the standard `createLogger` plugin.
```js
trackerVuex({
filter (mutation, state) {
// returns `true` if a mutation should be logged
// `mutation` is a `{ type, payload }`
return mutation.type !== "aBlacklistedMutation";
},
transformer (state) {
// transform the state before logging it.
// for example return only a specific sub-tree
return state.subTree;
},
mutationTransformer (mutation) {
// mutations are logged in the format of `{ type, payload }`
// we can format it any way we want.
return mutation.type;
},
})
```

View file

@ -0,0 +1,35 @@
{
"name": "@openreplay/tracker-zustand",
"description": "Tracker plugin for Zustand state recording",
"version": "1.0.0",
"keywords": [
"zustand",
"state",
"logging",
"replay"
],
"author": "Nikita Melnikov <nikita@openreplay.com>",
"contributors": [
"Aleksandr K <alex@openreplay.com>"
],
"license": "MIT",
"type": "module",
"main": "./lib/index.js",
"scripts": {
"lint": "prettier --write 'src/**/*.ts' && tsc --noEmit",
"build": "npm run build-es && npm run build-cjs",
"build-es": "rm -Rf lib && tsc",
"build-cjs": "rm -Rf cjs && tsc --project tsconfig-cjs.json && echo '{ \"type\": \"commonjs\" }' > cjs/package.json && replace-in-files cjs/* --string='@openreplay/tracker' --replacement='@openreplay/tracker/cjs'",
"prepublishOnly": "npm run build"
},
"dependencies": {},
"peerDependencies": {
"@openreplay/tracker": "^4.0.0"
},
"devDependencies": {
"@openreplay/tracker": "file:../tracker",
"prettier": "^1.18.2",
"replace-in-files-cli": "^1.0.0",
"typescript": "^4.6.0-dev.20211126"
}
}

View file

@ -0,0 +1,62 @@
import { App, Messages } from "@openreplay/tracker";
import { Encoder, sha1 } from "./syncod/index.js";
export interface Options {
filter: (mutation: any, state: any) => boolean;
transformer: (state: any) => any;
mutationTransformer: (mutation: any) => any;
}
function processMutationAndState(
app: App,
options: Options,
encoder: Encoder,
mutation: string[],
state: Record<string, any>
) {
if (options.filter(mutation, state)) {
try {
const _mutation = encoder.encode(options.mutationTransformer(mutation));
const _state = encoder.encode(options.transformer(state));
const _table = encoder.commit();
for (let key in _table) app.send(Messages.OTable(key, _table[key]));
app.send(Messages.Zustand(_mutation, _state));
} catch (e) {
encoder.clear();
app.debug.error(e)
}
}
}
export default function(opts: Partial<Options> = {}) {
const options: Options = Object.assign(
{
filter: () => true,
transformer: state => state,
mutationTransformer: mutation => mutation,
},
opts
);
return (app: App | null) => {
if (app === null) {
return Function.prototype;
}
const encoder = new Encoder(sha1, 50);
const state = {};
return (storeName: string = Math.random().toString(36).substring(2, 9)) =>
(config: Function) =>
(set: (...args: any) => void, get: () => Record<string, any>, api: any) =>
config(
(...args) => {
set(...args)
const newState = get();
state[storeName] = newState
const triggeredActions = args.map(action => action.toString?.())
processMutationAndState(app, options, encoder, triggeredActions, state)
},
get,
api
)
};
}

View file

@ -0,0 +1,18 @@
const chars = {};
[
"DEL",
"UNDEF",
"TRUE",
"FALSE",
"NUMBER",
"BIGINT",
"FUNCTION",
"STRING",
"SYMBOL",
"NULL",
"OBJECT",
"ARRAY"
].forEach((k, i) => (chars[k] = String.fromCharCode(i + 0xe000)));
export default chars;

View file

@ -0,0 +1,220 @@
import _ from "./chars.js";
// @ts-ignore
// @ts-ignore
export default class Encoder {
// @ts-ignore
constructor(hash, slen = Infinity) {
// @ts-ignore
this._hash = hash;
// @ts-ignore
this._slen = slen;
// @ts-ignore
this._refmap = new Map();
// @ts-ignore
this._refset = new Set();
// @ts-ignore
}
// @ts-ignore
// @ts-ignore
_ref_str(str) {
// @ts-ignore
if (str.length < this._slen && str.indexOf(_.DEL) === -1) {
// @ts-ignore
return str;
// @ts-ignore
}
// @ts-ignore
let ref = this._refmap.get(str);
// @ts-ignore
if (ref === undefined) {
// @ts-ignore
ref = this._hash(str);
// @ts-ignore
this._refmap.set(str, ref);
// @ts-ignore
}
// @ts-ignore
return ref;
// @ts-ignore
}
// @ts-ignore
// @ts-ignore
_encode_prim(obj) {
// @ts-ignore
switch (typeof obj) {
// @ts-ignore
case "undefined":
// @ts-ignore
return _.UNDEF;
// @ts-ignore
case "boolean":
// @ts-ignore
return obj ? _.TRUE : _.FALSE;
// @ts-ignore
case "number":
// @ts-ignore
return _.NUMBER + obj.toString();
// @ts-ignore
case "bigint":
// @ts-ignore
return _.BIGINT + obj.toString();
// @ts-ignore
case "function":
// @ts-ignore
return _.FUNCTION;
// @ts-ignore
case "string":
// @ts-ignore
return _.STRING + this._ref_str(obj);
// @ts-ignore
case "symbol":
// @ts-ignore
return _.SYMBOL + this._ref_str(obj.toString().slice(7, -1));
// @ts-ignore
}
// @ts-ignore
if (obj === null) {
// @ts-ignore
return _.NULL;
// @ts-ignore
}
// @ts-ignore
}
// @ts-ignore
// @ts-ignore
_encode_obj(obj, ref = this._refmap.get(obj)) {
// @ts-ignore
return (Array.isArray(obj) ? _.ARRAY : _.OBJECT) + ref;
// @ts-ignore
}
// @ts-ignore
// @ts-ignore
_encode_term(obj) {
// @ts-ignore
return this._encode_prim(obj) || this._encode_obj(obj);
// @ts-ignore
}
// @ts-ignore
// @ts-ignore
_encode_deep(obj, depth) {
// @ts-ignore
const enc = this._encode_prim(obj);
// @ts-ignore
if (enc !== undefined) {
// @ts-ignore
return enc;
// @ts-ignore
}
// @ts-ignore
const ref = this._refmap.get(obj);
// @ts-ignore
switch (typeof ref) {
// @ts-ignore
case "number":
// @ts-ignore
return (depth - ref).toString();
// @ts-ignore
case "string":
// @ts-ignore
return this._encode_obj(obj, ref);
// @ts-ignore
}
// @ts-ignore
this._refmap.set(obj, depth);
// @ts-ignore
const hash = this._hash(
// @ts-ignore
(Array.isArray(obj)
// @ts-ignore
? obj.map(v => this._encode_deep(v, depth + 1))
// @ts-ignore
: Object.keys(obj)
// @ts-ignore
.sort()
// @ts-ignore
.map(
// @ts-ignore
k =>
// @ts-ignore
this._ref_str(k) + _.DEL + this._encode_deep(obj[k], depth + 1)
// @ts-ignore
)
// @ts-ignore
).join(_.DEL)
// @ts-ignore
);
// @ts-ignore
this._refmap.set(obj, hash);
// @ts-ignore
return this._encode_obj(obj, hash);
// @ts-ignore
}
// @ts-ignore
// @ts-ignore
encode(obj) {
// @ts-ignore
return this._encode_deep(obj, 0);
// @ts-ignore
}
// @ts-ignore
// @ts-ignore
commit() {
// @ts-ignore
const dict = {};
// @ts-ignore
this._refmap.forEach((ref, obj) => {
// @ts-ignore
if (this._refset.has(ref)) {
// @ts-ignore
return;
// @ts-ignore
}
// @ts-ignore
this._refset.add(ref);
// @ts-ignore
if (typeof obj !== "string") {
// @ts-ignore
obj = (Array.isArray(obj)
// @ts-ignore
? obj.map(v => this._encode_term(v))
// @ts-ignore
: Object.keys(obj).map(
// @ts-ignore
k => this._ref_str(k) + _.DEL + this._encode_term(obj[k])
// @ts-ignore
)
// @ts-ignore
).join(_.DEL);
// @ts-ignore
}
// @ts-ignore
dict[ref] = obj;
// @ts-ignore
});
// @ts-ignore
this._refmap.clear();
// @ts-ignore
return dict;
// @ts-ignore
}
// @ts-ignore
// @ts-ignore
clear() {
// @ts-ignore
this._refmap.clear();
// @ts-ignore
this._refset.clear();
// @ts-ignore
}
// @ts-ignore
}
// @ts-ignore

View file

@ -0,0 +1,5 @@
// TODO: SSR solution for all asayer libraries
import Encoder from "./encoder.js";
import sha1 from "./sha1.js";
export { Encoder, sha1 };

View file

@ -0,0 +1,104 @@
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
*/
function core_sha1(x, len) {
x[len >> 5] |= 0x80 << (24 - (len % 32));
x[(((len + 64) >> 9) << 4) + 15] = len;
var w = Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for (var i = 0; i < x.length; i += 16) {
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for (var j = 0; j < 80; j++) {
if (j < 16) w[j] = x[i + j];
else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
var t = safe_add(
safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
safe_add(safe_add(e, w[j]), sha1_kt(j))
);
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
function sha1_ft(t, b, c, d) {
if (t < 20) return (b & c) | (~b & d);
if (t < 40) return b ^ c ^ d;
if (t < 60) return (b & c) | (b & d) | (c & d);
return b ^ c ^ d;
}
function sha1_kt(t) {
return t < 20
? 1518500249
: t < 40
? 1859775393
: t < 60
? -1894007588
: -899497514;
}
function safe_add(x, y) {
var lsw = (x & 0xffff) + (y & 0xffff);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
function rol(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
function str2binb(str) {
var bin = Array();
var mask = (1 << 16) - 1;
for (var i = 0; i < str.length * 16; i += 16)
bin[i >> 5] |= (str.charCodeAt(i / 16) & mask) << (32 - 16 - (i % 32));
return bin;
}
function binb2b64(binarray) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for (var i = 0; i < binarray.length * 4; i += 3) {
var triplet =
(((binarray[i >> 2] >> (8 * (3 - (i % 4)))) & 0xff) << 16) |
(((binarray[(i + 1) >> 2] >> (8 * (3 - ((i + 1) % 4)))) & 0xff) << 8) |
((binarray[(i + 2) >> 2] >> (8 * (3 - ((i + 2) % 4)))) & 0xff);
for (var j = 0; j < 4; j++) {
if (i * 8 + j * 6 <= binarray.length * 32)
str += tab.charAt((triplet >> (6 * (3 - j))) & 0x3f);
}
}
return str;
}
export default function(s) {
return binb2b64(core_sha1(str2binb(s), s.length * 16));
}

View file

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"outDir": "./cjs",
"declaration": false
},
}

View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"noImplicitThis": true,
"strictNullChecks": true,
"alwaysStrict": true,
"target": "es6",
"module": "es6",
"moduleResolution": "nodenext",
"declaration": true,
"outDir": "./lib"
}
}

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker",
"description": "The OpenReplay tracker main package",
"version": "4.0.0",
"version": "4.0.1",
"keywords": [
"logging",
"replay"

View file

@ -56,6 +56,7 @@ export declare const enum Type {
AdoptedSSDeleteRule = 75,
AdoptedSSAddOwner = 76,
AdoptedSSRemoveOwner = 77,
Zustand = 79,
}
@ -438,6 +439,12 @@ export type AdoptedSSRemoveOwner = [
/*id:*/ number,
]
export type Zustand = [
/*type:*/ Type.Zustand,
/*mutation:*/ string,
/*state:*/ string,
]
type Message = BatchMetadata | PartitionedMessage | Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | PageLoadTiming | PageRenderTiming | JSException | RawCustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | ResourceTiming | ConnectionInformation | SetPageVisibility | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner
type Message = BatchMetadata | PartitionedMessage | Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | PageLoadTiming | PageRenderTiming | JSException | RawCustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | ResourceTiming | ConnectionInformation | SetPageVisibility | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | Zustand
export default Message

View file

@ -707,3 +707,14 @@ export function AdoptedSSRemoveOwner(
]
}
export function Zustand(
mutation: string,
state: string,
): Messages.Zustand {
return [
Messages.Type.Zustand,
mutation,
state,
]
}

View file

@ -229,6 +229,10 @@ export default class MessageEncoder extends PrimitiveEncoder {
return this.uint(msg[1]) && this.uint(msg[2])
break
case Messages.Type.Zustand:
return this.string(msg[1]) && this.string(msg[2])
break
}
}