Skip to content

Commit 6cd558b

Browse files
authored
feat: add defineAction method for defining custom templates (#138)
* feat: allow defining of new IAM templates * fix: use strong type for argument parameters * feat: allow definition of action-only templates * test: socket response to getActions * style: lint * test: trim * feat: allow specifying default colors in rgba/hex when defining options * chore: rebuild * fix: do not change absolute URLs * fix: resolve file URLs when previewing messages * fix: expose types for showMessage handlers * chore: remove unused type
1 parent 9b4bc29 commit 6cd558b

19 files changed

+471
-56
lines changed

dist/leanplum.d.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export default class Leanplum {
100100
* inconsistent state or user experience.
101101
*/
102102
static clearUserContent(): void;
103+
static defineAction(options: MessageTemplateOptions): void;
103104
static applyQueue(queue: Array<{
104105
name: string;
105106
args: Array<any>;
@@ -156,4 +157,35 @@ export interface WebPushOptions {
156157
serviceWorkerUrl?: string;
157158
scope?: string;
158159
}
160+
export enum ActionParameterType {
161+
Int = "int",
162+
Integer = "integer",
163+
Color = "color",
164+
Float = "float",
165+
Decimal = "decimal",
166+
Number = "number",
167+
Boolean = "bool",
168+
String = "string",
169+
Text = "text",
170+
HTML = "html",
171+
File = "file",
172+
List = "list",
173+
Group = "group",
174+
Action = "action",
175+
Unknown = ""
176+
}
177+
export enum MessageKind {
178+
Action = 2,
179+
Template = 3
180+
}
181+
export type ActionParameter = {
182+
name: string;
183+
type: ActionParameterType;
184+
value: string | boolean | number | Array<ActionParameter> | Record<string, ActionParameter>;
185+
};
186+
export type MessageTemplateOptions = {
187+
name: string;
188+
kind?: MessageKind;
189+
args: Array<ActionParameter>;
190+
};
159191

dist/leanplum.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/leanplum.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Constants.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export default {
7777
HASH: 'hash',
7878
EMAIL: 'email',
7979
VARIABLES: 'vars',
80+
ACTION_DEFINITIONS: 'actionDefinitions',
8081
PARAMS: 'params',
8182
INCLUDE_DEFAULTS: 'includeDefaults',
8283
INCLUDE_VARIANT_DEBUG_INFO: 'includeVariantDebugInfo',
@@ -93,7 +94,7 @@ export default {
9394
VARS: 'vars',
9495
VARIANTS: 'variants',
9596
VARIANT_DEBUG_INFO: 'variantDebugInfo',
96-
ACTION_METADATA: 'actionMetadata',
97+
ACTION_DEFINITIONS: 'actionDefinitions',
9798
TOKEN: 'token',
9899
},
99100

@@ -103,7 +104,7 @@ export default {
103104
VARIABLES: '__leanplum_variables',
104105
VARIANTS: '__leanplum_variants',
105106
VARIANT_DEBUG_INFO: '__leanplum_variant_debug_info',
106-
ACTION_METADATA: '__leanplum_action_metadata',
107+
ACTION_DEFINITIONS: '__leanplum_action_definitions',
107108
INBOX_MESSAGES: '__leanplum_inbox_messages',
108109
TOKEN: '__leanplum_token',
109110
DEVICE_ID: '__leanplum_device_id',

src/Leanplum.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import LeanplumInternal from './LeanplumInternal'
1919
import {
2020
EventType,
2121
Inbox,
22+
MessageTemplateOptions,
2223
SimpleHandler,
2324
StatusHandler,
2425
WebPushOptions,
@@ -266,6 +267,10 @@ export default class Leanplum {
266267
Leanplum._lp.clearUserContent()
267268
}
268269

270+
static defineAction(options: MessageTemplateOptions): void {
271+
Leanplum._lp.defineAction(options)
272+
}
273+
269274
static applyQueue(queue: Array<{ name: string; args: Array<any> }>): void {
270275
Leanplum._lp.applyQueue(queue)
271276
}

src/LeanplumInternal.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import {
2929
Action,
3030
EventType,
3131
Inbox,
32+
MessageKind,
33+
MessageTemplateOptions,
3234
SimpleHandler,
3335
StatusHandler,
3436
WebPushOptions,
@@ -205,6 +207,13 @@ export default class LeanplumInternal {
205207
)
206208
}
207209

210+
defineAction(options: MessageTemplateOptions): void {
211+
this._varCache.registerActionDefinition({
212+
kind: MessageKind.Template,
213+
...options,
214+
})
215+
}
216+
208217
// TODO(breaking change): replace with events and remove stateful handlers
209218
addStartResponseHandler(handler: StatusHandler): void {
210219
this._internalState.addStartResponseHandler(handler)
@@ -241,7 +250,7 @@ export default class LeanplumInternal {
241250
this._varCache.applyDiffs(
242251
getVarsResponse[Constants.KEYS.VARS],
243252
getVarsResponse[Constants.KEYS.VARIANTS],
244-
getVarsResponse[Constants.KEYS.ACTION_METADATA])
253+
getVarsResponse[Constants.KEYS.ACTION_DEFINITIONS])
245254
this._varCache.setVariantDebugInfo(getVarsResponse[Constants.KEYS.VARIANT_DEBUG_INFO])
246255

247256
this._events.emit('messagesReceived', getVarsResponse[Constants.KEYS.MESSAGES])
@@ -342,7 +351,7 @@ Use "npm update leanplum-sdk" or go to https://docs.leanplum.com/reference#javas
342351
this._varCache.applyDiffs(
343352
startResponse[Constants.KEYS.VARS],
344353
startResponse[Constants.KEYS.VARIANTS],
345-
startResponse[Constants.KEYS.ACTION_METADATA])
354+
startResponse[Constants.KEYS.ACTION_DEFINITIONS])
346355
this._varCache.setVariantDebugInfo(startResponse[Constants.KEYS.VARIANT_DEBUG_INFO])
347356
this._varCache.token = startResponse[Constants.KEYS.TOKEN]
348357
} else {

src/LeanplumRequest.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ export default class LeanplumRequest {
153153
return ''
154154
}
155155

156+
if (/^https?:/.test(filename)) {
157+
return filename
158+
}
159+
156160
const args = new ArgsBuilder()
157161
.attachApiKeys(this.appId, this.clientKey)
158162
.add(Constants.PARAMS.SDK_VERSION, Constants.SDK_VERSION)

src/LeanplumSocket.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ export default class LeanplumSocket {
107107
const getVarsResponse = this.getLastResponse(response)
108108
const values = getVarsResponse[Constants.KEYS.VARS]
109109
const variants = getVarsResponse[Constants.KEYS.VARIANTS]
110-
const actionMetadata = getVarsResponse[Constants.KEYS.ACTION_METADATA]
110+
const actionDefinitions = getVarsResponse[Constants.KEYS.ACTION_DEFINITIONS]
111111
if (!isEqual(values, this.cache.diffs)) {
112-
this.cache.applyDiffs(values, variants, actionMetadata)
112+
this.cache.applyDiffs(values, variants, actionDefinitions)
113113
}
114114
},
115115
})
@@ -119,9 +119,9 @@ export default class LeanplumSocket {
119119
'updated': true,
120120
})
121121
} else if (event === 'getActions') {
122-
// Unsupported in JavaScript SDK.
122+
const updated = this.cache.sendActions()
123123
this.socketClient.send('getContentResponse', {
124-
'updated': false,
124+
updated,
125125
})
126126
} else if (event === 'registerDevice') {
127127
const message = args[0] as RegisterMessage

src/Messages.ts

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
1-
import { Action, UserAttributes } from './types/public'
1+
import { Action, UserAttributes, ActionContext, RenderOptions } from './types/public'
22
import Constants from './Constants'
33
import ArgsBuilder from './ArgsBuilder'
44
import { CreateRequestFunction, Message, MessageVariables } from './types/internal'
55
import EventEmitter from './EventEmitter'
66
import Network from './Network'
77
import isEqual from 'lodash.isequal'
88
import LocalStorageManager from './LocalStorageManager'
9+
import ValueTransforms from './ValueTransforms'
910

1011
/* eslint-disable @typescript-eslint/ban-types */
1112

1213
type MessageId = string
1314
type Timestamp = number
1415
type MessageHash = { [key: string]: Message }
15-
type ActionContext = {
16-
// matches the ActionContext API in Android/iOS
17-
// https://docs.leanplum.com/reference#section-android-custom-templates
18-
track: (event?: string, value?: number, info?: string, params?: Object) => void;
19-
runActionNamed: (actionName: string) => void;
20-
runTrackedActionNamed: (actionName: string) => void;
21-
}
2216
type TriggerContext =
17+
// TODO: 'install' trigger for first session
2318
{ trigger: 'start' } |
2419
{ trigger: 'resume' } |
2520
{ trigger: 'userAttribute'; attributes: UserAttributes } |
@@ -34,11 +29,6 @@ type FilterConfig = {
3429
objects?: Array<string | number>;
3530
}>;
3631
}
37-
type RenderOptions = {
38-
isPreview?: boolean;
39-
context: ActionContext;
40-
message: MessageVariables;
41-
}
4232
type TrackOptions = {
4333
event?: string;
4434
value?: number;
@@ -123,7 +113,6 @@ const verbToInterval = (verb: string): number => {
123113
}
124114

125115
export default class Messages {
126-
private _files: { [key: string]: string } = {}
127116
private _messageCache: MessageHash = {}
128117
private occurrenceTracker = new OccurrenceTracker()
129118

@@ -200,10 +189,10 @@ export default class Messages {
200189
this.handleMessage({
201190
isPreview: true,
202191

203-
message: {
192+
message: this.addDefaults({
204193
messageId: message.messageId,
205194
...vars,
206-
},
195+
}),
207196

208197
context,
209198
})
@@ -452,35 +441,36 @@ export default class Messages {
452441
return this._messageCache || {}
453442
}
454443

455-
private colorToHex(color: number): string {
456-
const b = color & 0xff; color >>= 8
457-
const g = color & 0xff; color >>= 8
458-
const r = color & 0xff; color >>= 8
459-
const a = (color & 0xff) / 255
460-
return `rgba(${r},${g},${b},${a})`
461-
}
462-
463444
private addDefaults(vars: MessageVariables): MessageVariables {
464-
const kinds = this.getMessages().actionDefinitions || {}
465-
const defaults = kinds[vars.__name__]
445+
const definitions = this.getMessages().actionDefinitions || {}
446+
const definition = definitions[vars.__name__]
447+
const kinds = definition?.kinds
466448

467-
if (!defaults) {
449+
if (!definition) {
468450
return vars
469451
}
470452

471-
function useDefaults(obj: MessageVariables, defaultValues: MessageVariables): MessageVariables {
453+
const useDefaults = (
454+
obj: MessageVariables,
455+
defaultValues: MessageVariables,
456+
path = ''
457+
): MessageVariables => {
472458
for (const key of Object.keys(defaultValues)) {
473459
const value = defaultValues[key]
474460
if (typeof value === 'object') {
475-
obj[key] = useDefaults(obj[key] || {}, value)
461+
obj[key] = useDefaults(obj[key] || {}, value, `${path}${key}.`)
476462
} else if (typeof obj[key] === 'undefined') {
477463
obj[key] = value
478464
}
465+
466+
if (kinds[`${path}${key}`] === 'FILE') {
467+
obj[key] = this.getFileUrl(obj[key])
468+
}
479469
}
480470
return obj
481471
}
482472

483-
return useDefaults({ ...vars }, defaults.values)
473+
return useDefaults({ ...vars }, definition.values)
484474
}
485475

486476
private resolveFiles(vars: MessageVariables): MessageVariables {
@@ -505,7 +495,7 @@ export default class Messages {
505495
const name = key.replace(filePrefix, '')
506496
vars[name + ' URL'] = this.getFileUrl(vars[key])
507497
} else if (colorSuffix.test(key)) {
508-
vars[key] = this.colorToHex(vars[key])
498+
vars[key] = ValueTransforms.decodeColor(vars[key])
509499
} else if (typeof vars[key] === 'object') {
510500
vars[key] = this.resolveFields(vars[key])
511501
}

src/ValueTransforms.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export default class ValueTransforms {
2+
public static decodeColor(color: number): string {
3+
const b = color & 0xff; color >>= 8
4+
const g = color & 0xff; color >>= 8
5+
const r = color & 0xff; color >>= 8
6+
const a = (color & 0xff) / 255
7+
return `rgba(${r},${g},${b},${a})`
8+
}
9+
10+
public static encodeColor(color: string | number): number {
11+
if (typeof color === 'number') {
12+
return color
13+
}
14+
15+
// rgba -> number
16+
const rgbaRe = /^rgba\((\d+),(\d+),(\d+),(\d+(\.\d+)?)\)$/
17+
const rgba = rgbaRe.exec(color)
18+
if (rgba) {
19+
const a = parseInt(rgba[4], 10) * 255
20+
const r = parseInt(rgba[1], 10) & 0xff
21+
const g = parseInt(rgba[2], 10) & 0xff
22+
const b = parseInt(rgba[3], 10) & 0xff
23+
24+
return (a << 24) + (r << 16) + (g << 8) + b
25+
}
26+
27+
// hex -> number
28+
const hexRe = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i
29+
const hex = hexRe.exec(color)
30+
if (hex) {
31+
const r = parseInt(hex[1], 16) & 0xff
32+
const g = parseInt(hex[2], 16) & 0xff
33+
const b = parseInt(hex[3], 16) & 0xff
34+
35+
return (255 << 24) + (r << 16) + (g << 8) + b
36+
}
37+
38+
throw new Error(`Could not parse color "${color}"`)
39+
}
40+
}

0 commit comments

Comments
 (0)