Skip to content

Commit a5193c9

Browse files
authored
Merge pull request #230 from Instabug/release/10.13.0
[MOB-8617] Release/10.13.0
2 parents 614f593 + c40a92c commit a5193c9

20 files changed

+711
-997
lines changed

.circleci/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ jobs:
6868
- run: bundle exec danger
6969
- run: flutter doctor
7070
- run: flutter packages get
71+
- run: flutter pub run build_runner build --delete-conflicting-outputs
7172
- run: flutter test --coverage
7273
- run: bash <(curl -s https://codecov.io/bash)
7374
- run: dartanalyzer --options analysis_options.yaml --fatal-warnings lib
@@ -83,6 +84,7 @@ jobs:
8384
- run: bundle exec danger
8485
- run: flutter doctor
8586
- run: flutter packages get
87+
- run: flutter pub run build_runner build --delete-conflicting-outputs
8688
- run: flutter test --coverage
8789

8890
ios_tests:

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Generated files
2+
*.mocks.dart
3+
14
# Miscellaneous
25
*.class
36
*.log

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## v10.13.0 (2022-03-31)
2+
3+
* Adds support for uploading debug symbols on Android to be used for crash deobfuscation
4+
* Adds Instabug Experiments APIs
5+
* Bumps Instabug native SDKs to v10.13
6+
* Fixes iOS platform calls not completing with `void` return type
7+
18
## v10.11.0 (2022-01-04)
29

310
* Adds support for APM.endAppLaunch API

analysis_options.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ analyzer:
3838
# see https://github.com/dart-lang/sdk/issues/28463
3939
- 'lib/i18n/stock_messages_*.dart'
4040
- 'lib/src/http/**'
41+
- 'test/*.mocks.dart'
4142

4243
linter:
4344
rules:
@@ -155,7 +156,7 @@ linter:
155156
- throw_in_finally
156157
# - type_annotate_public_apis # subset of always_specify_types
157158
- type_init_formals
158-
# - unawaited_futures # too many false positives
159+
- unawaited_futures
159160
# - unnecessary_await_in_return # not yet tested
160161
- unnecessary_brace_in_string_interps
161162
- unnecessary_const

android/build.gradle

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ android {
3434
}
3535
}
3636
dependencies {
37-
implementation 'com.instabug.library:instabug:10.11.1'
37+
implementation 'com.instabug.library:instabug:10.13.0'
3838
testImplementation 'junit:junit:4.12'
3939
}
40+
41+
// add upload_symbols task
42+
apply from: './upload_symbols.gradle'
43+
tasks.whenTaskAdded { task ->
44+
if (task.name == 'assembleRelease') {
45+
task.finalizedBy upload_symbols_task
46+
}
47+
}

android/src/main/java/com/instabug/instabugflutter/InstabugFlutterPlugin.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,31 @@ public ArrayList<String> getTags() {
303303
return Instabug.getTags();
304304
}
305305

306+
/**
307+
* Adds experiments to the next report.
308+
*
309+
* @param experiments An array of experiments to add.
310+
*/
311+
public void addExperiments(ArrayList<String> experiments) {
312+
Instabug.addExperiments(experiments);
313+
}
314+
315+
/**
316+
* Removes certain experiments from the next report.
317+
*
318+
* @param experiments An array of experiments to remove.
319+
*/
320+
public void removeExperiments(ArrayList<String> experiments) {
321+
Instabug.removeExperiments(experiments);
322+
}
323+
324+
/**
325+
* Clears all experiments from the next report.
326+
*/
327+
public void clearAllExperiments() {
328+
Instabug.clearAllExperiments();
329+
}
330+
306331
/**
307332
* Set custom user attributes that are going to be sent with each feedback, bug
308333
* or crash.

android/upload_symbols.gradle

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import java.util.zip.ZipOutputStream
2+
import java.util.zip.ZipEntry
3+
4+
// Custom Exception for undefined variable
5+
public class VariableNotFoundException extends Exception {
6+
public VariableNotFoundException(String errorMessage) {
7+
super(errorMessage);
8+
}
9+
}
10+
// Constants
11+
final INSTABUG_APP_TOKEN = 'INSTABUG_APP_TOKEN'
12+
final INSTABUG_API_KEY = 'INSTABUG_API_KEY'
13+
final UPLOAD_URL = 'https://api.instabug.com/api/web/public/flutter-symbol-files/android'
14+
final INSTABUG_AUTO_UPLOAD_ENABLE = 'INSTABUG_AUTO_UPLOAD_ENABLE'
15+
ext.boundary = 'abcd' + Long.toString(System.currentTimeMillis()) * 2 + 'dcba'
16+
ext.lineEnd = '\r\n'
17+
18+
/**
19+
* task to upload symbols to Instabug endpoint
20+
*/
21+
task upload_symbols_task {
22+
doLast {
23+
try {
24+
// If obfuscate flag not provided, exit the task
25+
if (project['dart-obfuscation'] != 'true') {
26+
println "Instabug: obfuscate flag not used — skipping debug symbols upload"
27+
return
28+
}
29+
// Load local properties
30+
final localProperties = load_local_properties()
31+
32+
// If auto upload is disabled, exit the task
33+
final uploadFlag = get_variable(INSTABUG_AUTO_UPLOAD_ENABLE, localProperties)
34+
println "$INSTABUG_AUTO_UPLOAD_ENABLE: $uploadFlag"
35+
if (!uploadFlag?.toBoolean()) {
36+
println "Instabug: skipping debug symbols upload"
37+
return
38+
}
39+
40+
// Get upload configs
41+
final applicationToken = get_variable(INSTABUG_APP_TOKEN, localProperties);
42+
final apiKey = get_variable(INSTABUG_API_KEY, localProperties);
43+
final symbolsPath = get_symbols_path()
44+
45+
// Prepare zip file with symbols
46+
final zipFile = prepare_zip_file(symbolsPath)
47+
48+
upload_symbols(zipFile, applicationToken, apiKey, UPLOAD_URL)
49+
} catch (VariableNotFoundException e) {
50+
println "Instabug: uploading symbols: ${e.message}"
51+
} catch (Exception e) {
52+
println "Instabug err: uploading symbols: ${e.message}"
53+
}
54+
}
55+
}
56+
/**
57+
* load local properties into a global variable localProperties
58+
*/
59+
def load_local_properties() {
60+
// Get local properties
61+
final properties = new Properties()
62+
final propertiesFile = project.rootProject.file('local.properties')
63+
if (propertiesFile.exists()) {
64+
properties.load(propertiesFile.newDataInputStream())
65+
}
66+
return properties
67+
}
68+
/**
69+
* get environment variable
70+
* @param name variable name
71+
* @param localProperties local properties object
72+
* @return variable environment variable for the given name
73+
*/
74+
String get_variable(name, localProperties) {
75+
// Look for the variable in local properties, gradle properties and env variables
76+
final variable = localProperties.getProperty(name) ?: project.findProperty(name) ?: System.getenv(name)
77+
if (!variable) {
78+
throw new VariableNotFoundException("$name not found. Make sure you've added the environment variable $name")
79+
}
80+
println "Instabug: ${name} found"
81+
return variable;
82+
}
83+
84+
/**
85+
* get symbols path
86+
* @return symbolsPath the path of the symbols directory
87+
*/
88+
String get_symbols_path() {
89+
// Get root host directory path
90+
final rootFile = new File(project['rootDir'].toString())
91+
92+
// Get the path of the symbols directory
93+
final obfuscationPath = project['split-debug-info']
94+
final obfuscationFile = new File(obfuscationPath)
95+
96+
// If the provided path is relative
97+
// append the symbols directory to the root project path to get the full path
98+
if (obfuscationFile.absolute) {
99+
return obfuscationPath
100+
} else {
101+
return new File(rootFile.parent, obfuscationPath)
102+
}
103+
}
104+
105+
/**
106+
* search for the symbols files in the symbols directory
107+
* and create a zip file with the symbols files
108+
* @param symbolsPath the path of the symbols directory
109+
* @return zipFile the zip file created
110+
*/
111+
112+
File prepare_zip_file(symbolsPath) {
113+
// Check if the symbols directory provided exists
114+
final symbolsDir = new File(symbolsPath)
115+
if (!symbolsDir.exists()) {
116+
throw new Exception('Symbols directory not found')
117+
}
118+
119+
// Search in symbolsPath for files with extension .symbols and add them to a list
120+
final symbolsFiles = symbolsDir.listFiles().findAll { file -> file.path.endsWith('.symbols') }
121+
if (symbolsFiles.empty) {
122+
throw new Exception('No symbols files found')
123+
}
124+
println "Instabug: ${symbolsFiles.size()} Symbols files found:"
125+
126+
// Print list of symbols files
127+
symbolsFiles.forEach { file ->
128+
println "${file.path}"
129+
}
130+
131+
// Create a zip file with the symbols files
132+
final zipFile = zip_files(symbolsPath, symbolsFiles)
133+
134+
return zipFile
135+
}
136+
/**
137+
* create a zip file with the symbols files
138+
*
139+
* @param symbolsPath the path of the symbols directory
140+
* @param symbolsFiles the list of symbols files
141+
* @return zipFile the zip file created
142+
*/
143+
144+
File zip_files(symbolsPath, symbolsFiles) {
145+
final zipFile = new File(symbolsPath + '.zip')
146+
final zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile))
147+
symbolsFiles.forEach { file ->
148+
final fileName = file.path.substring(symbolsPath.length() + 1)
149+
final zipEntry = new ZipEntry(fileName)
150+
zipOutputStream.putNextEntry(zipEntry)
151+
FileInputStream fileInputStream = new FileInputStream(file)
152+
final buffer = new byte[1024]
153+
int length = fileInputStream.read(buffer)
154+
while (length > 0) {
155+
zipOutputStream.write(buffer, 0, length)
156+
length = fileInputStream.read(buffer)
157+
}
158+
fileInputStream.close()
159+
zipOutputStream.closeEntry()
160+
}
161+
zipOutputStream.close()
162+
println "Instabug: Zip file created at ${zipFile.path}"
163+
return zipFile
164+
}
165+
/**
166+
* upload symbol files to Instabug endpoint
167+
*
168+
* @param zipFile the zip file containing the symbols files
169+
* @param applicationToken the app token
170+
* @param apiKey the api key
171+
* @return void
172+
*/
173+
void upload_symbols(zipFile, applicationToken, apiKey, uploadUrl) {
174+
// Initialize connection
175+
final connection = new URL(uploadUrl).openConnection() as HttpURLConnection
176+
177+
// Set connection headers and request method
178+
connection.setRequestMethod('POST')
179+
connection.setRequestProperty('Connection', 'Keep-Alive')
180+
connection.setRequestProperty('Content-Type', "multipart/form-data;boundary=${boundary}")
181+
connection.setRequestProperty('Accept-Encoding', 'gzip')
182+
connection.setRequestProperty('Content-Length', "${zipFile.length()}")
183+
connection.setDoOutput(true)
184+
185+
// Initialize output stream and writer
186+
final outputStream = connection.getOutputStream()
187+
final charset = 'ISO-8859-1'
188+
final writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), true)
189+
190+
// Write application_token in the request body
191+
writeField(writer, 'application_token', applicationToken)
192+
193+
// Write api_key in the request body
194+
writeField(writer, 'api_key', apiKey)
195+
196+
// Write zip file in the request body
197+
final fileName = zipFile.getName()
198+
writeFile(writer, fileName, zipFile, outputStream)
199+
200+
// Send the request
201+
writer.close()
202+
203+
// Get the response
204+
final responseCode = connection.getResponseCode()
205+
println "Instabug: Response Code: ${responseCode}"
206+
println "Instabug: Response Message: ${connection.getResponseMessage()}"
207+
if (responseCode == 200) {
208+
println 'Instabug: Upload successful'
209+
} else {
210+
println 'Instabug: Upload failed'
211+
}
212+
213+
// Close the connection
214+
connection.disconnect()
215+
// Delete the zip file
216+
zipFile.delete()
217+
}
218+
/**
219+
* write a field in the request body
220+
* @param writer the writer
221+
* @param field the name of the field
222+
* @param value the value of the field
223+
* @return void
224+
*/
225+
void writeField(writer, field, value) {
226+
writer.append('--' + boundary).append(lineEnd)
227+
writer.append("Content-Disposition: form-data; name=\"" + field + "\"").append(lineEnd)
228+
writer.append(lineEnd)
229+
writer.append(value).append(lineEnd)
230+
}
231+
/**
232+
* write a field in the request body
233+
* @param writer the writer
234+
* @param fileName the name of the file
235+
* @param file the file to be uploaded
236+
* @param outputStream the output stream
237+
* @return void
238+
*/
239+
void writeFile(writer, fileName, file, outputStream) {
240+
writer.append('--' + boundary).append(lineEnd)
241+
writer.append("Content-Disposition: form-data; name=\"" + 'file' + "\"; filename=\"" + fileName + "\"").append(lineEnd);
242+
writer.append('Content-Type: ' + URLConnection.guessContentTypeFromName(fileName)).append(lineEnd)
243+
writer.append(lineEnd)
244+
writer.flush()
245+
246+
final inputStream = new FileInputStream(file)
247+
byte[] buffer = new byte[4096]
248+
int bytesRead = -1
249+
while ((bytesRead = inputStream.read(buffer)) != -1) {
250+
outputStream.write(buffer, 0, bytesRead)
251+
}
252+
outputStream.flush()
253+
inputStream.close()
254+
255+
writer.append('--' + boundary).append(lineEnd)
256+
writer.flush()
257+
}

example/android/app/src/main/kotlin/com/example/InstabugSample/OnMethodCallTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,37 @@ public void testAppendTags() {
7777
verify(instabugMock).appendTags(tags);
7878
}
7979

80+
public void testAddExperiments() {
81+
String methodName = "addExperiments";
82+
ArrayList<Object> argsList = new ArrayList<>();
83+
ArrayList<String> experiments = new ArrayList<>();
84+
experiments.add("exp1");
85+
experiments.add("exp2");
86+
argsList.add(experiments);
87+
Mockito.doNothing().when(instabugMock).addExperiments(any(ArrayList.class));
88+
testMethodCall(methodName, argsList);
89+
verify(instabugMock).addExperiments(experiments);
90+
}
91+
92+
public void testRemoveExperiments() {
93+
String methodName = "removeExperiments";
94+
ArrayList<Object> argsList = new ArrayList<>();
95+
ArrayList<String> experiments = new ArrayList<>();
96+
experiments.add("exp1");
97+
experiments.add("exp2");
98+
argsList.add(experiments);
99+
Mockito.doNothing().when(instabugMock).removeExperiments(any(ArrayList.class));
100+
testMethodCall(methodName, argsList);
101+
verify(instabugMock).removeExperiments(experiments);
102+
}
103+
104+
public void testClearAllExperiments() {
105+
String methodName = "clearAllExperiments";
106+
Mockito.doNothing().when(instabugMock).clearAllExperiments();
107+
testMethodCall(methodName, null);
108+
verify(instabugMock).clearAllExperiments();
109+
}
110+
80111
public void testShowBugReportingWithReportTypeAndOptions() {
81112
String methodName = "showBugReportingWithReportTypeAndOptions";
82113
ArrayList<Object> argsList = new ArrayList<>();

0 commit comments

Comments
 (0)